KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_allegro_import.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
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but 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
24
25#include "allegro_test_utils.h"
29
31
32#include <board.h>
34#include <footprint.h>
35#include <pad.h>
36#include <pcb_shape.h>
37#include <pcb_text.h>
38#include <pcb_track.h>
39#include <zone.h>
40#include <netinfo.h>
41#include <netclass.h>
44#include <reporter.h>
45
46#include <filesystem>
47#include <fstream>
48#include <map>
49#include <set>
50
51using namespace KI_TEST;
52
53
55{
57
58 std::unique_ptr<BOARD> LoadAllegroBoard( const std::string& aFileName )
59 {
60 std::string dataPath = KI_TEST::AllegroBoardFile( aFileName );
61
62 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
63 m_allegroPlugin.LoadBoard( dataPath, board.get(), nullptr, nullptr );
64
65 return board;
66 }
67
69};
70
71
72BOOST_FIXTURE_TEST_SUITE( AllegroImport, ALLEGRO_IMPORT_FIXTURE )
73
74
75
78BOOST_AUTO_TEST_CASE( FootprintRefDes )
79{
80 std::unique_ptr<BOARD> board = LoadAllegroBoard( "TRS80_POWER/TRS80_POWER.brd" );
81
82 BOOST_REQUIRE( board != nullptr );
83
84 int emptyRefDesCount = 0;
85 int validRefDesCount = 0;
86
87 for( FOOTPRINT* fp : board->Footprints() )
88 {
89 wxString refdes = fp->GetReference();
90
91 if( refdes.IsEmpty() )
92 emptyRefDesCount++;
93 else
94 validRefDesCount++;
95 }
96
97 BOOST_TEST_MESSAGE( "Valid RefDes: " << validRefDesCount << ", Empty: " << emptyRefDesCount );
98
99 // Most footprints should have valid reference designators
100 BOOST_CHECK_GT( validRefDesCount, emptyRefDesCount );
101}
102
103
108{
109 std::unique_ptr<BOARD> board = LoadAllegroBoard( "TRS80_POWER/TRS80_POWER.brd" );
110
111 BOOST_REQUIRE( board != nullptr );
112
113 int validPadCount = 0;
114 int zeroPadCount = 0;
115 int hugePadCount = 0;
116
117 for( FOOTPRINT* fp : board->Footprints() )
118 {
119 for( PAD* pad : fp->Pads() )
120 {
121 VECTOR2I size = pad->GetSize( F_Cu );
122
123 if( size.x == 0 || size.y == 0 )
124 {
125 zeroPadCount++;
126 BOOST_TEST_MESSAGE( "Zero-size pad in " << fp->GetReference() << " pad "
127 << pad->GetNumber() );
128 }
129 else if( size.x > 50000000 || size.y > 50000000 ) // > 50mm is suspicious
130 {
131 hugePadCount++;
132 BOOST_TEST_MESSAGE( "Huge pad in " << fp->GetReference() << " pad " << pad->GetNumber()
133 << ": " << size.x / 1000000.0 << "mm x "
134 << size.y / 1000000.0 << "mm" );
135 }
136 else
137 {
138 validPadCount++;
139 }
140 }
141 }
142
143 BOOST_TEST_MESSAGE( "Valid pads: " << validPadCount << ", Zero: " << zeroPadCount
144 << ", Huge: " << hugePadCount );
145
146 // No pads should be zero-size (this catches hardcoded fallbacks)
147 BOOST_CHECK_EQUAL( zeroPadCount, 0 );
148}
149
150
155{
156 std::unique_ptr<BOARD> board = LoadAllegroBoard( "TRS80_POWER/TRS80_POWER.brd" );
157
158 BOOST_REQUIRE( board != nullptr );
159
160 int validViaCount = 0;
161 int suspiciousViaCount = 0;
162
163 // Count vias with the hardcoded size (1000000 = 1mm exactly)
164 const int HARDCODED_SIZE = 1000000;
165
166 for( PCB_TRACK* track : board->Tracks() )
167 {
168 if( track->Type() == PCB_VIA_T )
169 {
170 PCB_VIA* via = static_cast<PCB_VIA*>( track );
171 int width = via->GetWidth( F_Cu );
172
173 if( width == HARDCODED_SIZE )
174 {
175 suspiciousViaCount++;
176 }
177 else if( width > 0 && width < 10000000 ) // 0 < size < 10mm is reasonable
178 {
179 validViaCount++;
180 }
181 }
182 }
183
184 BOOST_TEST_MESSAGE( "Valid vias: " << validViaCount << ", Hardcoded-size vias: " << suspiciousViaCount );
185
186 // This test will fail until via size is properly extracted from padstack
187 if( suspiciousViaCount > 0 )
188 {
189 BOOST_WARN_MESSAGE( false, "Found " << suspiciousViaCount << " vias with hardcoded 1mm size" );
190 }
191}
192
193
198{
199 std::unique_ptr<BOARD> board = LoadAllegroBoard( "TRS80_POWER/TRS80_POWER.brd" );
200
201 BOOST_REQUIRE( board != nullptr );
202
203 int validTrackCount = 0;
204 int zeroTrackCount = 0;
205
206 for( PCB_TRACK* track : board->Tracks() )
207 {
208 if( track->Type() == PCB_TRACE_T )
209 {
210 int width = track->GetWidth();
211
212 if( width == 0 )
213 zeroTrackCount++;
214 else if( width > 0 && width < 10000000 ) // 0 < width < 10mm
215 validTrackCount++;
216 }
217 }
218
219 BOOST_TEST_MESSAGE( "Valid tracks: " << validTrackCount << ", Zero-width: " << zeroTrackCount );
220
221 BOOST_CHECK_EQUAL( zeroTrackCount, 0 );
222}
223
224
229{
230 std::unique_ptr<BOARD> board = LoadAllegroBoard( "TRS80_POWER/TRS80_POWER.brd" );
231
232 BOOST_REQUIRE( board != nullptr );
233
234 int numberedPads = 0;
235 int unnumberedPads = 0;
236
237 for( FOOTPRINT* fp : board->Footprints() )
238 {
239 for( PAD* pad : fp->Pads() )
240 {
241 if( pad->GetNumber().IsEmpty() )
242 unnumberedPads++;
243 else
244 numberedPads++;
245 }
246 }
247
248 BOOST_TEST_MESSAGE( "Numbered pads: " << numberedPads << ", Unnumbered: " << unnumberedPads );
249
250 // All pads should have numbers for proper netlist generation
251 // This test will fail until pad numbers are properly set
252 if( unnumberedPads > 0 )
253 {
254 BOOST_WARN_MESSAGE( false, "Found " << unnumberedPads << " pads without numbers" );
255 }
256}
257
258
259static unsigned CountOutlineElements( const BOARD& board )
260{
261 unsigned count = 0;
262 for( const BOARD_ITEM* item : board.Drawings() )
263 {
264 if( item->Type() == PCB_SHAPE_T && item->GetLayer() == Edge_Cuts )
265 {
266 count++;
267 }
268 }
269 return count;
270}
271
272
273static void AssertOutlineValid( const BOARD& aBoard )
274{
275 // Verify outline forms a closed contour by checking that all segments connect
276 std::vector<SEG> outlineSegs;
277
278 for( BOARD_ITEM* item : aBoard.Drawings() )
279 {
280 if( item->Type() == PCB_SHAPE_T && item->GetLayer() == Edge_Cuts )
281 {
282 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
283 switch( shape->GetShape() )
284 {
285 case SHAPE_T::SEGMENT:
286 case SHAPE_T::ARC:
287 case SHAPE_T::BEZIER:
288 {
289 outlineSegs.push_back( SEG( shape->GetStart(), shape->GetEnd() ) );
290 break;
291 }
293 {
294 // Rectangles are stored as a single item but represent 4 segments
295 VECTOR2I start = shape->GetStart();
296 VECTOR2I end = shape->GetEnd();
297
298 for( const auto& seg : KIGEOM::BoxToSegs( BOX2I( start, end ) ) )
299 {
300 outlineSegs.push_back( seg );
301 }
302 break;
303 }
304 case SHAPE_T::POLY:
305 {
306 std::vector<VECTOR2I> polyPoints = shape->GetPolyPoints();
307
308 for( size_t i = 0; i < polyPoints.size() - 1; i++ )
309 {
310 VECTOR2I start = polyPoints[i];
311 VECTOR2I end = polyPoints[( i + 1 ) % polyPoints.size()];
312 outlineSegs.emplace_back( start, end );
313 }
314 break;
315 }
316 case SHAPE_T::CIRCLE:
317 // Not really sure what we can do here? Zero-length seg?
318 outlineSegs.push_back( SEG( shape->GetStart(), shape->GetStart() ) );
319 break;
320 default:
321 BOOST_WARN_MESSAGE(
322 false, "Unexpected shape type in board outline: " << static_cast<int>( shape->GetShape() ) );
323 }
324 }
325 }
326
327 if( !outlineSegs.empty() )
328 {
329 // For a valid closed outline, the sum of all segment lengths should equal the perimeter
330 // and each endpoint should connect to another endpoint
331 int connectedCount = 0;
332
333 for( const SEG& seg : outlineSegs )
334 {
335 for( const SEG& other : outlineSegs )
336 {
337 if( &other == &seg )
338 continue;
339
340 // Check if this shape's start connects to another shape's start or end
341 if( seg.A == other.A || seg.A == other.B )
342 connectedCount++;
343
344 // Check if this shape's end connects to another shape's start or end
345 if( seg.B == other.A || seg.B == other.B )
346 connectedCount++;
347 }
348 }
349
350 // Each segment should connect at both ends for a closed outline
351 // For 4 segments, we expect 8 connections (2 per segment)
352 BOOST_TEST_MESSAGE( "Connected endpoints: " << connectedCount );
353 BOOST_CHECK_GE( connectedCount, outlineSegs.size() * 2 );
354 }
355}
356
357
361BOOST_AUTO_TEST_CASE( BoardOutline )
362{
363 std::unique_ptr<BOARD> board = LoadAllegroBoard( "TRS80_POWER/TRS80_POWER.brd" );
364
365 BOOST_REQUIRE( board != nullptr );
366
367 // Count shapes on Edge_Cuts layer
368 int outlineSegmentCount = CountOutlineElements( *board );
369
370 BOOST_TEST_MESSAGE( "Board outline elements: " << outlineSegmentCount );
371
372 // Board should have an outline - TRS80_POWER.brd has a rectangular outline (4 segments)
373 BOOST_CHECK_GE( outlineSegmentCount, 1 );
374
375 AssertOutlineValid( *board );
376}
377
378
382BOOST_AUTO_TEST_CASE( PadsInsideOutline )
383{
384 std::unique_ptr<BOARD> board = LoadAllegroBoard( "TRS80_POWER/TRS80_POWER.brd" );
385
386 BOOST_REQUIRE( board != nullptr );
387
388 // Get board bounding box from outline
389 BOX2I boardBbox;
390 bool hasBbox = false;
391
392 for( BOARD_ITEM* item : board->Drawings() )
393 {
394 if( item->Type() == PCB_SHAPE_T && item->GetLayer() == Edge_Cuts )
395 {
396 if( !hasBbox )
397 {
398 boardBbox = item->GetBoundingBox();
399 hasBbox = true;
400 }
401 else
402 {
403 boardBbox.Merge( item->GetBoundingBox() );
404 }
405 }
406 }
407
408 BOOST_REQUIRE_MESSAGE( hasBbox, "Board should have an outline" );
409
410 int padsInside = 0;
411 int padsOutside = 0;
412
413 // Inflate bbox slightly to account for edge cases
414 BOX2I testBbox = boardBbox;
415 testBbox.Inflate( 1000 ); // 1mm tolerance
416
417 for( FOOTPRINT* fp : board->Footprints() )
418 {
419 for( PAD* pad : fp->Pads() )
420 {
421 VECTOR2I padCenter = pad->GetPosition();
422
423 if( testBbox.Contains( padCenter ) )
424 {
425 padsInside++;
426 }
427 else
428 {
429 padsOutside++;
430 BOOST_TEST_MESSAGE( "Pad outside outline: " << fp->GetReference() << " pad "
431 << pad->GetNumber() << " at ("
432 << padCenter.x / 1000000.0 << ", "
433 << padCenter.y / 1000000.0 << ") mm" );
434 }
435 }
436 }
437
438 BOOST_TEST_MESSAGE( "Pads inside outline: " << padsInside << ", outside: " << padsOutside );
439
440 // Most pads should be inside the board outline
441 // Some boards may have off-board test points or fiducials
442 if( padsOutside > 0 )
443 {
444 BOOST_WARN_MESSAGE( false, "Found " << padsOutside << " pads outside board outline" );
445 }
446
447 // At minimum, most pads should be inside
448 BOOST_CHECK_GT( padsInside, padsOutside );
449}
450
451
456BOOST_AUTO_TEST_CASE( PreV16FileRejection )
457{
458 BOOST_CHECK_EXCEPTION(
459 LoadAllegroBoard( "v13_header/v13_header.brd" ), IO_ERROR,
460 []( const IO_ERROR& e )
461 {
462 wxString msg = e.What();
463
464 return msg.Contains( wxS( "predates Allegro 16.0" ) )
465 && msg.Contains( wxS( "Allegro PCB Design" ) );
466 } );
467}
468
469
475BOOST_AUTO_TEST_CASE( RectsZoneVsCopperPolygon )
476{
477 std::unique_ptr<BOARD> board = LoadAllegroBoard( "rects/rects.brd" );
478 BOOST_REQUIRE( board );
479
480 // Should have exactly one zone (the left rectangle as a zone fill)
481 BOOST_CHECK_EQUAL( board->Zones().size(), 1 );
482
483 ZONE* zone = board->Zones().front();
484 BOOST_CHECK( zone->GetNetCode() > 0 );
485 BOOST_CHECK( IsCopperLayer( zone->GetFirstLayer() ) );
486 BOOST_CHECK( zone->IsFilled() );
487
488 // Should have exactly one standalone copper polygon (the right rectangle)
489 int copperPolyCount = 0;
490 int copperPolyWithNet = 0;
491
492 for( BOARD_ITEM* item : board->Drawings() )
493 {
494 if( item->Type() == PCB_SHAPE_T )
495 {
496 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
497
498 if( IsCopperLayer( shape->GetLayer() ) && shape->GetShape() == SHAPE_T::POLY )
499 {
500 copperPolyCount++;
501
502 if( shape->GetNetCode() > 0 )
503 copperPolyWithNet++;
504 }
505 }
506 }
507
508 BOOST_CHECK_EQUAL( copperPolyCount, 1 );
509 BOOST_CHECK_EQUAL( copperPolyWithNet, 1 );
510}
511
512
517{
518 std::unique_ptr<BOARD> board = LoadAllegroBoard( "copper_text/copper_text.brd" );
519 BOOST_REQUIRE( board );
520
521 int copperTextCount = 0;
522 bool foundTestingText = false;
523
524 for( BOARD_ITEM* item : board->Drawings() )
525 {
526 if( item->Type() == PCB_TEXT_T )
527 {
528 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
529
530 if( IsCopperLayer( text->GetLayer() ) )
531 {
532 copperTextCount++;
533
534 if( text->GetText() == wxS( "TESTING" ) )
535 {
536 foundTestingText = true;
537 BOOST_CHECK_EQUAL( text->GetLayer(), F_Cu );
538 }
539 }
540 }
541 }
542
543 BOOST_CHECK_MESSAGE( foundTestingText, "Board should contain 'TESTING' text on F.Cu" );
544 BOOST_CHECK_EQUAL( copperTextCount, 1 );
545}
546
547
549
550
551
555{
556 std::string filename;
557 bool expected_to_load; // Set false for known-broken boards
558};
559
560
565{
567
573 BOARD* GetCachedBoard( const std::string& aFilePath )
574 {
576 }
577
581 static std::vector<std::string> GetAllBoardFiles()
582 {
583 std::vector<std::string> boards;
584 std::string dataPath = KI_TEST::AllegroBoardDataDir( "" );
585
586 // For each board dir, look for .brd files and add them to the list of test cases
587 try
588 {
589 for( const auto& boardDir : std::filesystem::directory_iterator( dataPath ) )
590 {
591 if( !boardDir.is_directory() )
592 continue;
593
594 for( const auto& entry : std::filesystem::directory_iterator( boardDir ) )
595 {
596 if( entry.is_regular_file() && entry.path().extension() == ".brd" && entry.file_size() > 0 )
597 {
598 std::string name = entry.path().filename().string();
599
600 // v13_header.brd is intentionally pre-v16 and tested separately
601 if( name != "v13_header.brd" )
602 {
603 boards.push_back( boardDir.path().string() + "/" + name );
604 }
605 }
606 }
607 }
608 }
609 catch( const std::filesystem::filesystem_error& e )
610 {
611 BOOST_TEST_MESSAGE( "Failed to enumerate board files: " << e.what() );
612 }
613
614 std::sort( boards.begin(), boards.end() );
615 return boards;
616 }
617
619};
620
621BOOST_FIXTURE_TEST_SUITE( AllegroComprehensive, ALLEGRO_COMPREHENSIVE_FIXTURE )
622
623
624
629BOOST_AUTO_TEST_CASE( BeagleBone_OutermostZoneNets )
630{
631 std::string dataPath = KI_TEST::AllegroBoardFile( "BeagleBone_Black_RevC/BeagleBone_Black_RevC.brd" );
632
633 BOARD* board = GetCachedBoard( dataPath );
634 BOOST_REQUIRE( board );
635
636 const std::vector<wxString> expectedLayers = { wxS( "TOP" ), wxS( "LYR2_GND" ),
637 wxS( "LYR5_PWR" ), wxS( "BOTTOM" ) };
638
639 for( const wxString& layerName : expectedLayers )
640 {
641 BOOST_TEST_CONTEXT( "Outermost zone on " << layerName )
642 {
643 PCB_LAYER_ID layerId = board->GetLayerID( layerName );
644
645 BOOST_REQUIRE_MESSAGE( layerId != UNDEFINED_LAYER,
646 "Layer " << layerName << " should exist" );
647
648 const ZONE* largest = nullptr;
649 double largestArea = 0;
650
651 for( const ZONE* zone : board->Zones() )
652 {
653 if( zone->GetIsRuleArea() )
654 continue;
655
656 if( zone->GetNetCode() == 0 )
657 continue;
658
659 if( !zone->GetLayerSet().Contains( layerId ) )
660 continue;
661
662 BOX2I bbox = zone->GetBoundingBox();
663 double area = static_cast<double>( bbox.GetWidth() )
664 * static_cast<double>( bbox.GetHeight() );
665
666 if( area > largestArea )
667 {
668 largestArea = area;
669 largest = zone;
670 }
671 }
672
673 BOOST_REQUIRE_MESSAGE( largest != nullptr,
674 "Should find a netted copper zone on " << layerName );
675 BOOST_CHECK_EQUAL( largest->GetNetname(), wxString( wxS( "GND_EARTH" ) ) );
676 }
677 }
678}
679
680
684BOOST_AUTO_TEST_CASE( PadSizesPositive )
685{
686 std::vector<std::string> boards = GetAllBoardFiles();
687
688 for( const std::string& boardPath : boards )
689 {
690 std::string boardName = std::filesystem::path( boardPath ).filename().string();
691 BOARD* board = GetCachedBoard( boardPath );
692
693 if( !board )
694 continue;
695
696 BOOST_TEST_CONTEXT( "Testing board: " << boardName )
697 {
698 int negativePadCount = 0;
699
700 for( FOOTPRINT* fp : board->Footprints() )
701 {
702 for( PAD* pad : fp->Pads() )
703 {
704 VECTOR2I size = pad->GetSize( F_Cu );
705
706 if( size.x < 0 || size.y < 0 )
707 {
708 negativePadCount++;
709 BOOST_TEST_MESSAGE( boardName << ": Negative pad size in " << fp->GetReference()
710 << " pad " << pad->GetNumber() << ": " << size.x << " x "
711 << size.y );
712 }
713 }
714 }
715
716 BOOST_CHECK_EQUAL( negativePadCount, 0 );
717 }
718 }
719}
720
721
726BOOST_AUTO_TEST_CASE( ViaDrillNotLargerThanSize )
727{
728 std::vector<std::string> boards = GetAllBoardFiles();
729
730 for( const std::string& boardPath : boards )
731 {
732 std::string boardName = std::filesystem::path( boardPath ).filename().string();
733 BOARD* board = GetCachedBoard( boardPath );
734
735 if( !board )
736 continue;
737
738 BOOST_TEST_CONTEXT( "Testing board: " << boardName )
739 {
740 int invalidViaCount = 0;
741
742 for( PCB_TRACK* track : board->Tracks() )
743 {
744 if( track->Type() == PCB_VIA_T )
745 {
746 PCB_VIA* via = static_cast<PCB_VIA*>( track );
747 int drill = via->GetDrill();
748 int width = via->GetWidth( F_Cu );
749
750 if( drill > width )
751 {
752 invalidViaCount++;
753 BOOST_TEST_MESSAGE( boardName << ": Via at ("
754 << via->GetPosition().x / 1000000.0 << ", "
755 << via->GetPosition().y / 1000000.0
756 << ") has drill " << drill / 1000000.0
757 << "mm > width " << width / 1000000.0 << "mm" );
758 }
759 }
760 }
761
762 BOOST_CHECK_EQUAL( invalidViaCount, 0 );
763 }
764 }
765}
766
767
772BOOST_AUTO_TEST_CASE( SmdPadDetection )
773{
774 std::vector<std::string> boards = GetAllBoardFiles();
775
776 for( const std::string& boardPath : boards )
777 {
778 std::string boardName = std::filesystem::path( boardPath ).filename().string();
779 BOARD* board = GetCachedBoard( boardPath );
780
781 if( !board )
782 continue;
783
784 BOOST_TEST_CONTEXT( "Testing board: " << boardName )
785 {
786 int misclassifiedSmdCount = 0;
787 int correctSmdCount = 0;
788 int correctThCount = 0;
789
790 for( FOOTPRINT* fp : board->Footprints() )
791 {
792 for( PAD* pad : fp->Pads() )
793 {
794 bool hasDrill = pad->GetDrillSizeX() > 0 && pad->GetDrillSizeY() > 0;
795 PAD_ATTRIB attr = pad->GetAttribute();
796
797 if( !hasDrill && attr == PAD_ATTRIB::PTH )
798 {
799 misclassifiedSmdCount++;
800 BOOST_TEST_MESSAGE( boardName << ": Pad " << fp->GetReference()
801 << "." << pad->GetNumber()
802 << " has no drill but is marked as PTH (should be SMD)" );
803 }
804 else if( !hasDrill && attr == PAD_ATTRIB::SMD )
805 {
806 correctSmdCount++;
807 }
808 else if( hasDrill && ( attr == PAD_ATTRIB::PTH || attr == PAD_ATTRIB::NPTH ) )
809 {
810 correctThCount++;
811 }
812 }
813 }
814
815 BOOST_TEST_MESSAGE( boardName << ": Correct SMD=" << correctSmdCount
816 << ", Correct TH=" << correctThCount
817 << ", Misclassified=" << misclassifiedSmdCount );
818
819 BOOST_CHECK_EQUAL( misclassifiedSmdCount, 0 );
820 }
821 }
822}
823
824
829BOOST_AUTO_TEST_CASE( QuadPackagePadRotation )
830{
831 std::vector<std::string> boards = GetAllBoardFiles();
832
833 for( const std::string& boardPath : boards )
834 {
835 std::string boardName = std::filesystem::path( boardPath ).filename().string();
836 BOARD* board = GetCachedBoard( boardPath );
837
838 if( !board )
839 continue;
840
841 BOOST_TEST_CONTEXT( "Testing board: " << boardName )
842 {
843 int quadPackageCount = 0;
844 int packagesWithRotatedPads = 0;
845 int packagesWithUnrotatedPads = 0;
846
847 for( FOOTPRINT* fp : board->Footprints() )
848 {
849 wxString refdes = fp->GetReference().Upper();
850
851 // Look for ICs (typically U* prefix) that might be quad packages
852 if( !refdes.StartsWith( "U" ) )
853 continue;
854
855 // Must have at least 16 pads to be a quad package
856 if( fp->Pads().size() < 16 )
857 continue;
858
859 // Find bounding box of all pad centers to estimate package shape
860 BOX2I padBounds;
861 bool first = true;
862
863 for( PAD* pad : fp->Pads() )
864 {
865 VECTOR2I pos = pad->GetPosition();
866
867 if( first )
868 {
869 padBounds = BOX2I( pos, VECTOR2I( 0, 0 ) );
870 first = false;
871 }
872 else
873 {
874 padBounds.Merge( pos );
875 }
876 }
877
878 // Must be roughly square to be a quad package
879 int width = padBounds.GetWidth();
880 int height = padBounds.GetHeight();
881
882 if( width == 0 || height == 0 )
883 continue;
884
885 double aspectRatio = static_cast<double>( std::max( width, height ) ) /
886 static_cast<double>( std::min( width, height ) );
887
888 if( aspectRatio > 2.0 )
889 continue;
890
891 quadPackageCount++;
892
893 // Check if pads have varying orientations
894 std::set<int> uniqueAngles;
895
896 for( PAD* pad : fp->Pads() )
897 {
898 EDA_ANGLE angle = pad->GetOrientation();
899 angle.Normalize();
900 int degrees = static_cast<int>( angle.AsDegrees() + 0.5 ) % 360;
901 uniqueAngles.insert( degrees );
902 }
903
904 // A properly imported quad package should have at least 2 different pad orientations
905 // (for 2-sided packages) or 4 (for 4-sided packages like QFP)
906 if( uniqueAngles.size() >= 2 )
907 {
908 packagesWithRotatedPads++;
909 BOOST_TEST_MESSAGE( boardName << ": " << fp->GetReference()
910 << " has " << uniqueAngles.size() << " unique pad orientations" );
911 }
912 else
913 {
914 packagesWithUnrotatedPads++;
915 BOOST_TEST_MESSAGE( boardName << ": " << fp->GetReference()
916 << " has only " << uniqueAngles.size()
917 << " unique pad orientation (may be missing rotation)" );
918 }
919 }
920
921 if( quadPackageCount > 0 )
922 {
923 BOOST_TEST_MESSAGE( boardName << ": Found " << quadPackageCount
924 << " potential quad packages, "
925 << packagesWithRotatedPads << " with rotated pads, "
926 << packagesWithUnrotatedPads << " without" );
927
928 // At least some packages should have rotated pads to confirm rotation parsing works.
929 // Many packages may legitimately have all pads at the same orientation (BGAs, single-row).
930 if( packagesWithRotatedPads == 0 && quadPackageCount > 0 )
931 {
932 BOOST_WARN_MESSAGE( false, boardName << " has no packages with rotated pads" );
933 }
934 }
935 }
936 }
937}
938
939
945BOOST_AUTO_TEST_CASE( FootprintLayerPlacement )
946{
947 std::string dataPath = KI_TEST::AllegroBoardFile( "BeagleBone_Black_RevC/BeagleBone_Black_RevC.brd" );
948
949 BOARD* board = GetCachedBoard( dataPath );
950 BOOST_REQUIRE_MESSAGE( board != nullptr, "BeagleBone_Black_RevC.brd should load successfully" );
951
952 // Look for C78 which should be on the bottom layer
953 FOOTPRINT* c78 = nullptr;
954
955 for( FOOTPRINT* fp : board->Footprints() )
956 {
957 if( fp->GetReference() == "C78" )
958 {
959 c78 = fp;
960 break;
961 }
962 }
963
964 BOOST_REQUIRE_MESSAGE( c78 != nullptr, "Footprint C78 should exist in BeagleBone Black" );
965
966 PCB_LAYER_ID fpLayer = c78->GetLayer();
967
968 BOOST_TEST_MESSAGE( "C78 layer: " << board->GetLayerName( fpLayer ) << " (ID: " << fpLayer << ")" );
969 BOOST_TEST_MESSAGE( "C78 is flipped: " << ( c78->IsFlipped() ? "yes" : "no" ) );
970
971 BOOST_CHECK_MESSAGE( fpLayer == B_Cu, "C78 should be on the bottom copper layer (B_Cu), got "
972 << board->GetLayerName( fpLayer ) );
973 BOOST_CHECK_MESSAGE( c78->IsFlipped(), "C78 should be flipped (IsFlipped() == true)" );
974
975 // Count footprints on top vs bottom to ensure we're parsing layer correctly
976 int topCount = 0;
977 int bottomCount = 0;
978
979 for( FOOTPRINT* fp : board->Footprints() )
980 {
981 if( fp->GetLayer() == F_Cu )
982 topCount++;
983 else if( fp->GetLayer() == B_Cu )
984 bottomCount++;
985 }
986
987 BOOST_TEST_MESSAGE( "Footprints on top: " << topCount << ", on bottom: " << bottomCount );
988
989 // BeagleBone should have components on both sides
990 BOOST_CHECK_GT( topCount, 0 );
991 BOOST_CHECK_GT( bottomCount, 0 );
992}
993
994
999BOOST_AUTO_TEST_CASE( ArcConnectivity )
1000{
1001 std::vector<std::string> boards = GetAllBoardFiles();
1002
1003 for( const std::string& boardPath : boards )
1004 {
1005 std::string boardName = std::filesystem::path( boardPath ).filename().string();
1006 BOARD* board = GetCachedBoard( boardPath );
1007
1008 if( !board )
1009 continue;
1010
1011 BOOST_TEST_CONTEXT( "Testing board: " << boardName )
1012 {
1013 int arcCount = 0;
1014 int disconnectedArcs = 0;
1015
1016 // Build a map of track endpoints per net for quick lookup
1017 std::map<int, std::vector<VECTOR2I>> netEndpoints;
1018
1019 for( PCB_TRACK* track : board->Tracks() )
1020 {
1021 int netCode = track->GetNetCode();
1022
1023 if( track->Type() == PCB_TRACE_T )
1024 {
1025 netEndpoints[netCode].push_back( track->GetStart() );
1026 netEndpoints[netCode].push_back( track->GetEnd() );
1027 }
1028 else if( track->Type() == PCB_VIA_T )
1029 {
1030 netEndpoints[netCode].push_back( track->GetPosition() );
1031 }
1032 }
1033
1034 // Also include pad positions
1035 for( FOOTPRINT* fp : board->Footprints() )
1036 {
1037 for( PAD* pad : fp->Pads() )
1038 {
1039 int netCode = pad->GetNetCode();
1040
1041 if( netCode > 0 )
1042 netEndpoints[netCode].push_back( pad->GetPosition() );
1043 }
1044 }
1045
1046 // Now check each arc
1047 for( PCB_TRACK* track : board->Tracks() )
1048 {
1049 if( track->Type() != PCB_ARC_T )
1050 continue;
1051
1052 arcCount++;
1053 PCB_ARC* arc = static_cast<PCB_ARC*>( track );
1054 int netCode = arc->GetNetCode();
1055
1056 VECTOR2I arcStart = arc->GetStart();
1057 VECTOR2I arcEnd = arc->GetEnd();
1058
1059 // Check if arc endpoints connect to something
1060 bool startConnected = false;
1061 bool endConnected = false;
1062 const int tolerance = 1000; // 1um tolerance
1063
1064 for( const VECTOR2I& pt : netEndpoints[netCode] )
1065 {
1066 if( ( pt - arcStart ).EuclideanNorm() < tolerance )
1067 startConnected = true;
1068
1069 if( ( pt - arcEnd ).EuclideanNorm() < tolerance )
1070 endConnected = true;
1071 }
1072
1073 // Arc should connect to at least one other track/pad at each end
1074 // (unless it's an isolated arc, which is unusual but possible)
1075 if( !startConnected && !endConnected && netEndpoints[netCode].size() > 2 )
1076 {
1077 disconnectedArcs++;
1078 BOOST_TEST_MESSAGE( boardName << ": Arc at ("
1079 << arcStart.x / 1000000.0 << ", "
1080 << arcStart.y / 1000000.0 << ") to ("
1081 << arcEnd.x / 1000000.0 << ", "
1082 << arcEnd.y / 1000000.0
1083 << ") appears disconnected from net " << netCode );
1084 }
1085 }
1086
1087 if( arcCount > 0 )
1088 {
1089 BOOST_TEST_MESSAGE( boardName << ": Found " << arcCount << " arcs, "
1090 << disconnectedArcs << " disconnected" );
1091 }
1092
1093 // Allow some disconnected arcs as they may be legitimate isolated features
1094 // but flag if more than 20% are disconnected
1095 if( arcCount > 5 )
1096 {
1097 BOOST_CHECK_LE( disconnectedArcs, arcCount / 5 );
1098 }
1099 }
1100 }
1101}
1102
1103
1120{
1121 wxString layer;
1122 int recordId = 0;
1123 wxString netName;
1124 double minX = 1e18, minY = 1e18, maxX = -1e18, maxY = -1e18;
1126
1127 void AddPoint( double aX, double aY )
1128 {
1129 minX = std::min( minX, aX );
1130 minY = std::min( minY, aY );
1131 maxX = std::max( maxX, aX );
1132 maxY = std::max( maxY, aY );
1133 segmentCount++;
1134 }
1135};
1136
1137
1139{
1140 std::set<wxString> netNames;
1141 std::set<wxString> refDes;
1142 std::map<wxString, wxString> refDesToSymName;
1143 std::map<wxString, std::set<wxString>> netToRefDes;
1144
1145 std::vector<ALG_ZONE_POLYGON> zonePolygons;
1146
1147 static std::vector<wxString> SplitAlgLine( const wxString& aLine )
1148 {
1149 std::vector<wxString> fields;
1150 wxString current;
1151
1152 for( size_t i = 0; i < aLine.size(); ++i )
1153 {
1154 if( aLine[i] == '!' )
1155 {
1156 fields.push_back( current );
1157 current.clear();
1158 }
1159 else
1160 {
1161 current += aLine[i];
1162 }
1163 }
1164
1165 if( !current.empty() )
1166 fields.push_back( current );
1167
1168 return fields;
1169 }
1170
1175 static int ParseRecordId( const wxString& aTag )
1176 {
1177 long val = -1;
1178 wxString tag = aTag.BeforeFirst( ' ' );
1179 tag.ToLong( &val );
1180 return static_cast<int>( val );
1181 }
1182
1183 static ALG_REFERENCE_DATA ParseAlgFile( const std::string& aPath )
1184 {
1185 ALG_REFERENCE_DATA data;
1186 std::ifstream file( aPath );
1187
1188 if( !file.is_open() )
1189 return data;
1190
1191 enum class SECTION
1192 {
1193 UNKNOWN,
1194 NET_NODES,
1195 SYM_PLACEMENT,
1196 GRAPHICS,
1197 };
1198
1199 SECTION currentSection = SECTION::UNKNOWN;
1200 std::string line;
1201
1202 // Accumulate zone segments grouped by (layer, recordId)
1203 std::map<std::pair<wxString, int>, ALG_ZONE_POLYGON> zoneMap;
1204
1205 while( std::getline( file, line ) )
1206 {
1207 if( line.empty() || line[0] == 'J' )
1208 continue;
1209
1210 if( line[0] == 'A' )
1211 {
1212 if( line.find( "NET_NAME_SORT!NODE_SORT!NET_NAME!REFDES!" ) != std::string::npos )
1213 currentSection = SECTION::NET_NODES;
1214 else if( line.find( "SYM_TYPE!SYM_NAME!REFDES!SYM_MIRROR!" ) != std::string::npos )
1215 currentSection = SECTION::SYM_PLACEMENT;
1216 else if( line.find( "CLASS!SUBCLASS!RECORD_TAG!GRAPHIC_DATA_NAME!" ) != std::string::npos )
1217 currentSection = SECTION::GRAPHICS;
1218 else
1219 currentSection = SECTION::UNKNOWN;
1220
1221 continue;
1222 }
1223
1224 if( line[0] != 'S' )
1225 continue;
1226
1227 auto fields = SplitAlgLine( wxString::FromUTF8( line ) );
1228
1229 switch( currentSection )
1230 {
1231 case SECTION::NET_NODES:
1232 {
1233 // S!sort!nodeSort!NET_NAME!REFDES!PIN!PIN_NAME!SUBCLASS!
1234 if( fields.size() >= 5 )
1235 {
1236 wxString netName = fields[3];
1237 wxString refdes = fields[4];
1238
1239 if( !netName.empty() )
1240 {
1241 data.netNames.insert( netName );
1242
1243 if( !refdes.empty() )
1244 data.netToRefDes[netName].insert( refdes );
1245 }
1246 }
1247
1248 break;
1249 }
1250 case SECTION::SYM_PLACEMENT:
1251 {
1252 // S!SYM_TYPE!SYM_NAME!REFDES!MIRROR!ROTATE!X!Y!CX!CY!LIB_PATH!
1253 if( fields.size() >= 4 )
1254 {
1255 wxString symType = fields[1];
1256 wxString symName = fields[2];
1257 wxString refdes = fields[3];
1258
1259 if( symType == wxT( "PACKAGE" ) && !refdes.empty() )
1260 {
1261 data.refDes.insert( refdes );
1262 data.refDesToSymName[refdes] = symName;
1263 }
1264 }
1265
1266 break;
1267 }
1268 case SECTION::GRAPHICS:
1269 {
1270 // Field layout (0-indexed after splitting on '!'):
1271 // 0=S, 1=CLASS, 2=SUBCLASS, 3=RECORD_TAG, 4=GRAPHIC_DATA_NAME,
1272 // 5=GRAPHIC_DATA_NUMBER, 6..15=GRAPHIC_DATA_1..10,
1273 // 16=PIN_NUMBER, ..., 23=NET_NAME
1274 if( fields.size() < 16 || fields[1] != wxT( "BOUNDARY" ) )
1275 break;
1276
1277 wxString closureType = fields[15];
1278
1279 if( closureType != wxT( "SHAPE" ) )
1280 break;
1281
1282 wxString layer = fields[2];
1283 int recordId = ParseRecordId( fields[3] );
1284
1285 if( recordId < 0 )
1286 break;
1287
1288 wxString netName;
1289
1290 if( fields.size() > 23 )
1291 netName = fields[23];
1292
1293 auto key = std::make_pair( layer, recordId );
1294 auto& zone = zoneMap[key];
1295 zone.layer = layer;
1296 zone.recordId = recordId;
1297
1298 if( !netName.empty() )
1299 zone.netName = netName;
1300
1301 double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
1302
1303 if( fields.size() > 9 )
1304 {
1305 fields[6].ToDouble( &x1 );
1306 fields[7].ToDouble( &y1 );
1307 fields[8].ToDouble( &x2 );
1308 fields[9].ToDouble( &y2 );
1309 zone.AddPoint( x1, y1 );
1310 zone.AddPoint( x2, y2 );
1311 }
1312
1313 break;
1314 }
1315 default:
1316 break;
1317 }
1318 }
1319
1320 for( auto& [key, zone] : zoneMap )
1321 data.zonePolygons.push_back( std::move( zone ) );
1322
1323 return data;
1324 }
1325};
1326
1327
1329{
1330 std::string brdFile;
1331 std::string algFile;
1332};
1333
1334
1339static std::vector<BRD_ALG_PAIR> getBoardsWithAlg()
1340{
1341 std::string dataPath = KI_TEST::AllegroBoardDataDir( "" );
1342 std::vector<BRD_ALG_PAIR> boardsWithAlg;
1343
1344 for( const auto& boardDir : std::filesystem::directory_iterator( dataPath ) )
1345 {
1346 if( !boardDir.is_directory() )
1347 continue;
1348
1349 std::filesystem::path boardPath;
1350 std::filesystem::path algPath;
1351
1352 for( const auto& entry : std::filesystem::directory_iterator( boardDir ) )
1353 {
1354 if( !entry.is_regular_file() )
1355 continue;
1356
1357 if( entry.path().extension() == ".brd" )
1358 boardPath = entry.path();
1359 else if( entry.path().extension() == ".alg" )
1360 algPath = entry.path();
1361
1362 if( !boardPath.empty() && !algPath.empty() )
1363 {
1364 boardsWithAlg.push_back( { boardPath.string(), algPath.string() } );
1365 break;
1366 }
1367 }
1368 }
1369
1370 return boardsWithAlg;
1371}
1372
1373
1377BOOST_AUTO_TEST_CASE( AlgReferenceNetNames )
1378{
1379 std::vector<BRD_ALG_PAIR> boardsWithAlg = getBoardsWithAlg();
1380
1381 BOOST_REQUIRE_GT( boardsWithAlg.size(), 0u );
1382
1383 for( const auto& [brdFile, algFile] : boardsWithAlg )
1384 {
1385 BOOST_TEST_MESSAGE( "Validating net names: " << brdFile );
1386
1388 BOOST_REQUIRE_GT( algData.netNames.size(), 0u );
1389
1390 BOARD* board = GetCachedBoard( brdFile );
1391 BOOST_REQUIRE( board );
1392
1393 std::set<wxString> boardNets;
1394
1395 for( const NETINFO_ITEM* net : board->GetNetInfo() )
1396 {
1397 if( net->GetNetCode() > 0 )
1398 boardNets.insert( net->GetNetname() );
1399 }
1400
1401 int missingNets = 0;
1402
1403 for( const wxString& algNet : algData.netNames )
1404 {
1405 if( boardNets.find( algNet ) == boardNets.end() )
1406 {
1407 missingNets++;
1408
1409 if( missingNets <= 10 )
1410 BOOST_TEST_MESSAGE( " Missing net: " << algNet );
1411 }
1412 }
1413
1414 BOOST_TEST_MESSAGE( brdFile << ": .alg has " << algData.netNames.size() << " nets, board has "
1415 << boardNets.size() << ", missing " << missingNets );
1416
1417 BOOST_CHECK_EQUAL( missingNets, 0 );
1418 }
1419}
1420
1421
1425BOOST_AUTO_TEST_CASE( AlgReferenceComponentPlacement )
1426{
1427 std::vector<BRD_ALG_PAIR> boardsWithAlg = getBoardsWithAlg();
1428
1429 BOOST_REQUIRE_GT( boardsWithAlg.size(), 0u );
1430
1431 for( const auto& [brdFile, algFile] : boardsWithAlg )
1432 {
1433 BOOST_TEST_MESSAGE( "Validating components: " << brdFile );
1434
1436 BOOST_REQUIRE_GT( algData.refDes.size(), 0u );
1437
1438 BOARD* board = GetCachedBoard( brdFile );
1439 BOOST_REQUIRE( board );
1440
1441 std::set<wxString> boardRefDes;
1442
1443 for( const FOOTPRINT* fp : board->Footprints() )
1444 boardRefDes.insert( fp->GetReference() );
1445
1446 int missingRefDes = 0;
1447 int extraRefDes = 0;
1448
1449 for( const wxString& algRef : algData.refDes )
1450 {
1451 if( boardRefDes.find( algRef ) == boardRefDes.end() )
1452 {
1453 missingRefDes++;
1454
1455 if( missingRefDes <= 10 )
1456 BOOST_TEST_MESSAGE( " Missing refdes: " << algRef );
1457 }
1458 }
1459
1460 for( const wxString& boardRef : boardRefDes )
1461 {
1462 if( algData.refDes.find( boardRef ) == algData.refDes.end() )
1463 {
1464 extraRefDes++;
1465
1466 if( extraRefDes <= 10 )
1467 BOOST_TEST_MESSAGE( " Extra refdes in board: " << boardRef );
1468 }
1469 }
1470
1471 BOOST_TEST_MESSAGE( brdFile << ": .alg has " << algData.refDes.size()
1472 << " components, board has " << boardRefDes.size()
1473 << ", missing " << missingRefDes
1474 << ", extra " << extraRefDes );
1475
1476 BOOST_CHECK_EQUAL( missingRefDes, 0 );
1477 }
1478}
1479
1480
1484BOOST_AUTO_TEST_CASE( AllTracksPositiveWidth )
1485{
1486 std::vector<std::string> boards = GetAllBoardFiles();
1487
1488 for( const std::string& boardPath : boards )
1489 {
1490 std::string boardName = std::filesystem::path( boardPath ).filename().string();
1491 BOARD* board = GetCachedBoard( boardPath );
1492
1493 if( !board )
1494 continue;
1495
1496 BOOST_TEST_CONTEXT( "Testing board: " << boardName )
1497 {
1498 int zeroWidthCount = 0;
1499 int totalCount = 0;
1500
1501 for( PCB_TRACK* track : board->Tracks() )
1502 {
1503 if( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T )
1504 {
1505 totalCount++;
1506
1507 if( track->GetWidth() <= 0 )
1508 {
1509 zeroWidthCount++;
1510
1511 if( zeroWidthCount <= 5 )
1512 {
1513 BOOST_TEST_MESSAGE( boardName << ": Zero-width track at ("
1514 << track->GetStart().x / 1000000.0 << ", "
1515 << track->GetStart().y / 1000000.0 << ")" );
1516 }
1517 }
1518 }
1519 }
1520
1521 BOOST_CHECK_EQUAL( zeroWidthCount, 0 );
1522 }
1523 }
1524}
1525
1526
1534{
1535 enum class SEGMENT_TYPE
1536 {
1540 };
1541
1543 {
1545 double x1, y1, x2, y2;
1548 };
1549
1552 std::vector<OUTLINE_SEGMENT> designOutlineSegments;
1553 std::vector<OUTLINE_SEGMENT> outlineSegments;
1554
1555 double minX = std::numeric_limits<double>::max();
1556 double minY = std::numeric_limits<double>::max();
1557 double maxX = std::numeric_limits<double>::lowest();
1558 double maxY = std::numeric_limits<double>::lowest();
1559
1560 void updateBounds( double aX, double aY )
1561 {
1562 minX = std::min( minX, aX );
1563 minY = std::min( minY, aY );
1564 maxX = std::max( maxX, aX );
1565 maxY = std::max( maxY, aY );
1566 }
1567
1568 static ALG_OUTLINE_DATA ParseAlgOutlines( const std::string& aPath )
1569 {
1570 ALG_OUTLINE_DATA data;
1571 std::ifstream file( aPath );
1572
1573 if( !file.is_open() )
1574 return data;
1575
1576 std::string line;
1577
1578 while( std::getline( file, line ) )
1579 {
1580 if( line.empty() || line[0] != 'S' )
1581 continue;
1582
1583 auto fields = ALG_REFERENCE_DATA::SplitAlgLine( wxString::FromUTF8( line ) );
1584
1585 if( fields.size() < 10 )
1586 continue;
1587
1588 bool isDesignOutline = ( fields[1] == wxT( "BOARD GEOMETRY" )
1589 && fields[2] == wxT( "DESIGN_OUTLINE" ) );
1590
1591 bool isOutline = ( fields[1] == wxT( "BOARD GEOMETRY" )
1592 && fields[2] == wxT( "OUTLINE" ) );
1593
1594 if( !isDesignOutline && !isOutline )
1595 continue;
1596
1597 wxString shapeType = fields[4];
1598 OUTLINE_SEGMENT seg = {};
1599
1600 if( shapeType == wxT( "LINE" ) && fields.size() >= 10 )
1601 {
1603 fields[6].ToCDouble( &seg.x1 );
1604 fields[7].ToCDouble( &seg.y1 );
1605 fields[8].ToCDouble( &seg.x2 );
1606 fields[9].ToCDouble( &seg.y2 );
1607
1608 data.updateBounds( seg.x1, seg.y1 );
1609 data.updateBounds( seg.x2, seg.y2 );
1610 }
1611 else if( shapeType == wxT( "ARC" ) && fields.size() >= 15 )
1612 {
1613 seg.type = SEGMENT_TYPE::ARC;
1614 fields[6].ToCDouble( &seg.x1 );
1615 fields[7].ToCDouble( &seg.y1 );
1616 fields[8].ToCDouble( &seg.x2 );
1617 fields[9].ToCDouble( &seg.y2 );
1618 fields[10].ToCDouble( &seg.centerX );
1619 fields[11].ToCDouble( &seg.centerY );
1620 fields[12].ToCDouble( &seg.radius );
1621 seg.clockwise = ( fields[14] == wxT( "CLOCKWISE" ) );
1622
1623 data.updateBounds( seg.x1, seg.y1 );
1624 data.updateBounds( seg.x2, seg.y2 );
1625 }
1626 else if( shapeType == wxT( "RECTANGLE" ) && fields.size() >= 10 )
1627 {
1629 fields[6].ToCDouble( &seg.x1 );
1630 fields[7].ToCDouble( &seg.y1 );
1631 fields[8].ToCDouble( &seg.x2 );
1632 fields[9].ToCDouble( &seg.y2 );
1633
1634 data.updateBounds( seg.x1, seg.y1 );
1635 data.updateBounds( seg.x2, seg.y2 );
1636 }
1637 else
1638 {
1639 continue;
1640 }
1641
1642 if( isDesignOutline )
1643 {
1644 data.designOutlineCount++;
1645 data.designOutlineSegments.push_back( seg );
1646 }
1647 else
1648 {
1649 data.outlineCount++;
1650 data.outlineSegments.push_back( seg );
1651 }
1652 }
1653
1654 return data;
1655 }
1656
1662 {
1663 int count = 0;
1664
1665 for( const auto& seg : designOutlineSegments )
1666 {
1667 if( seg.type == SEGMENT_TYPE::RECTANGLE )
1668 count += 4;
1669 else
1670 count += 1;
1671 }
1672
1673 return count;
1674 }
1675};
1676
1677
1682BOOST_AUTO_TEST_CASE( OutlineSegmentCount )
1683{
1684 std::vector<BRD_ALG_PAIR> testBoards = getBoardsWithAlg();
1685
1686 BOOST_REQUIRE_GT( testBoards.size(), 0u );
1687
1688 for( const auto& [brdFile, algFile] : testBoards )
1689 {
1690 BOOST_TEST_CONTEXT( "Board: " << brdFile )
1691 {
1693
1694 if( algOutlines.designOutlineCount == 0 && algOutlines.outlineCount == 0 )
1695 {
1696 BOOST_TEST_MESSAGE( " No outline records in .alg, skipping" );
1697 continue;
1698 }
1699
1700 BOARD* board = GetCachedBoard( brdFile );
1701 BOOST_REQUIRE( board );
1702
1703 int edgeCutsCount = 0;
1704
1705 for( BOARD_ITEM* item : board->Drawings() )
1706 {
1707 if( item->Type() == PCB_SHAPE_T && item->GetLayer() == Edge_Cuts )
1708 edgeCutsCount++;
1709 }
1710
1711 int expectedCount = algOutlines.expectedEdgeCutsSegments();
1712
1713 BOOST_TEST_MESSAGE( " .alg DESIGN_OUTLINE records: " << algOutlines.designOutlineCount
1714 << " -> expected Edge_Cuts segments: " << expectedCount );
1715 BOOST_TEST_MESSAGE( " .alg OUTLINE records: " << algOutlines.outlineCount );
1716 BOOST_TEST_MESSAGE( " Binary import Edge_Cuts segments: " << edgeCutsCount );
1717
1718 BOOST_CHECK_EQUAL( edgeCutsCount, expectedCount );
1719 }
1720 }
1721}
1722
1723
1728BOOST_AUTO_TEST_CASE( OutlineBoundingBox )
1729{
1730 std::vector<BRD_ALG_PAIR> testBoards = getBoardsWithAlg();
1731
1732 BOOST_REQUIRE_GT( testBoards.size(), 0u );
1733
1734 // 1 mil = 25400 nm
1735 const double milToNm = 25400.0;
1736
1737 // Allow 2 mil tolerance for coordinate rounding across formats
1738 const int toleranceNm = static_cast<int>( 2.0 * milToNm );
1739
1740 for( const auto& [brdFile, algFile] : testBoards )
1741 {
1742 BOOST_TEST_CONTEXT( "Board: " << brdFile )
1743 {
1745
1746 if( algOutlines.designOutlineCount == 0 )
1747 {
1748 BOOST_TEST_MESSAGE( " No DESIGN_OUTLINE records in .alg, skipping" );
1749 continue;
1750 }
1751
1752 BOARD* board = GetCachedBoard( brdFile );
1753 BOOST_REQUIRE( board );
1754
1755 BOX2I boardBbox;
1756 bool hasBbox = false;
1757
1758 for( BOARD_ITEM* item : board->Drawings() )
1759 {
1760 if( item->Type() == PCB_SHAPE_T && item->GetLayer() == Edge_Cuts )
1761 {
1762 if( !hasBbox )
1763 {
1764 boardBbox = item->GetBoundingBox();
1765 hasBbox = true;
1766 }
1767 else
1768 {
1769 boardBbox.Merge( item->GetBoundingBox() );
1770 }
1771 }
1772 }
1773
1774 BOOST_REQUIRE_MESSAGE( hasBbox, "Board should have Edge_Cuts outline" );
1775
1776 // Convert .alg bounding box from mils to nm
1777 int algMinXnm = static_cast<int>( algOutlines.minX * milToNm );
1778 int algMinYnm = static_cast<int>( algOutlines.minY * milToNm );
1779 int algMaxXnm = static_cast<int>( algOutlines.maxX * milToNm );
1780 int algMaxYnm = static_cast<int>( algOutlines.maxY * milToNm );
1781 int algWidthNm = algMaxXnm - algMinXnm;
1782 int algHeightNm = algMaxYnm - algMinYnm;
1783
1784 int boardWidth = boardBbox.GetWidth();
1785 int boardHeight = boardBbox.GetHeight();
1786
1787 BOOST_TEST_MESSAGE( " .alg extent (mils): "
1788 << algOutlines.minX << "," << algOutlines.minY << " to "
1789 << algOutlines.maxX << "," << algOutlines.maxY
1790 << " = " << ( algOutlines.maxX - algOutlines.minX ) << " x "
1791 << ( algOutlines.maxY - algOutlines.minY ) );
1792 BOOST_TEST_MESSAGE( " Board bbox (nm): "
1793 << boardBbox.GetLeft() << "," << boardBbox.GetTop() << " to "
1794 << boardBbox.GetRight() << "," << boardBbox.GetBottom()
1795 << " = " << boardWidth << " x " << boardHeight );
1796 BOOST_TEST_MESSAGE( " .alg (nm): " << algWidthNm << " x " << algHeightNm );
1797
1798 // KiCad bounding boxes include line width so allow 3% tolerance
1799 BOOST_CHECK_CLOSE( static_cast<double>( boardWidth ),
1800 static_cast<double>( algWidthNm ), 3.0 );
1801 BOOST_CHECK_CLOSE( static_cast<double>( boardHeight ),
1802 static_cast<double>( algHeightNm ), 3.0 );
1803 }
1804 }
1805}
1806
1807
1815BOOST_AUTO_TEST_CASE( OutlineEndpoints )
1816{
1817 std::vector<BRD_ALG_PAIR> testBoards = getBoardsWithAlg();
1818
1819 BOOST_REQUIRE_GT( testBoards.size(), 0u );
1820
1821 const double milToNm = 25400.0;
1822 const int toleranceNm = static_cast<int>( 2.0 * milToNm );
1823
1824 for( const auto& [brdFile, algFile] : testBoards )
1825 {
1826 BOOST_TEST_CONTEXT( "Board: " << brdFile )
1827 {
1829
1830 if( algOutlines.designOutlineCount == 0 )
1831 continue;
1832
1833 // Only validate endpoint-by-endpoint for pure-LINE outlines
1834 bool allLines = true;
1835
1836 for( const auto& seg : algOutlines.designOutlineSegments )
1837 {
1840 {
1841 allLines = false;
1842 break;
1843 }
1844 }
1845
1846 if( !allLines )
1847 {
1848 BOOST_TEST_MESSAGE( " Outline has arcs, skipping endpoint-level validation" );
1849 continue;
1850 }
1851
1852 BOARD* board = GetCachedBoard( brdFile );
1853 BOOST_REQUIRE( board );
1854
1855 // Collect all Edge_Cuts segment endpoints
1856 struct ENDPOINT_PAIR
1857 {
1858 VECTOR2I start;
1859 VECTOR2I end;
1860 };
1861
1862 std::vector<ENDPOINT_PAIR> boardSegments;
1863
1864 for( BOARD_ITEM* item : board->Drawings() )
1865 {
1866 if( item->Type() != PCB_SHAPE_T || item->GetLayer() != Edge_Cuts )
1867 continue;
1868
1869 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
1870
1871 if( shape->GetShape() == SHAPE_T::SEGMENT )
1872 boardSegments.push_back( { shape->GetStart(), shape->GetEnd() } );
1873 }
1874
1875 // Build expected segments from .alg, expanding RECTANGLEs into 4 segments
1876 std::vector<ENDPOINT_PAIR> algSegments;
1877
1878 for( const auto& seg : algOutlines.designOutlineSegments )
1879 {
1880 if( seg.type == ALG_OUTLINE_DATA::SEGMENT_TYPE::LINE )
1881 {
1882 VECTOR2I start( static_cast<int>( seg.x1 * milToNm ),
1883 static_cast<int>( seg.y1 * milToNm ) );
1884 VECTOR2I end( static_cast<int>( seg.x2 * milToNm ),
1885 static_cast<int>( seg.y2 * milToNm ) );
1886 algSegments.push_back( { start, end } );
1887 }
1888 else if( seg.type == ALG_OUTLINE_DATA::SEGMENT_TYPE::RECTANGLE )
1889 {
1890 int x1 = static_cast<int>( seg.x1 * milToNm );
1891 int y1 = static_cast<int>( seg.y1 * milToNm );
1892 int x2 = static_cast<int>( seg.x2 * milToNm );
1893 int y2 = static_cast<int>( seg.y2 * milToNm );
1894
1895 algSegments.push_back( { { x1, y1 }, { x2, y1 } } );
1896 algSegments.push_back( { { x2, y1 }, { x2, y2 } } );
1897 algSegments.push_back( { { x2, y2 }, { x1, y2 } } );
1898 algSegments.push_back( { { x1, y2 }, { x1, y1 } } );
1899 }
1900 }
1901
1902 BOOST_CHECK_EQUAL( boardSegments.size(), algSegments.size() );
1903
1904 if( boardSegments.size() != algSegments.size() )
1905 continue;
1906
1907 // Match each .alg segment to a board segment by finding closest start/end pair.
1908 // Allegro and KiCad may have opposite Y axis, so we compare using absolute
1909 // coordinate deltas.
1910 int matchedCount = 0;
1911
1912 std::vector<bool> used( boardSegments.size(), false );
1913
1914 for( size_t ai = 0; ai < algSegments.size(); ++ai )
1915 {
1916 const auto& algSeg = algSegments[ai];
1917 int bestIdx = -1;
1918 int64_t bestDist = std::numeric_limits<int64_t>::max();
1919
1920 for( size_t bi = 0; bi < boardSegments.size(); ++bi )
1921 {
1922 if( used[bi] )
1923 continue;
1924
1925 const auto& bSeg = boardSegments[bi];
1926
1927 // Try both orientations (start-start or start-end swap)
1928 auto dist = [&]( const VECTOR2I& aAlgPt, const VECTOR2I& aBoardPt ) -> int64_t
1929 {
1930 int64_t dx = std::abs( static_cast<int64_t>( aAlgPt.x )
1931 - static_cast<int64_t>( aBoardPt.x ) );
1932 int64_t dy = std::abs( static_cast<int64_t>( aAlgPt.y )
1933 - static_cast<int64_t>( aBoardPt.y ) );
1934 return dx + dy;
1935 };
1936
1937 int64_t d1 = dist( algSeg.start, bSeg.start ) + dist( algSeg.end, bSeg.end );
1938 int64_t d2 = dist( algSeg.start, bSeg.end ) + dist( algSeg.end, bSeg.start );
1939 int64_t d = std::min( d1, d2 );
1940
1941 if( d < bestDist )
1942 {
1943 bestDist = d;
1944 bestIdx = static_cast<int>( bi );
1945 }
1946 }
1947
1948 if( bestIdx >= 0 && bestDist < 2LL * toleranceNm )
1949 {
1950 used[bestIdx] = true;
1951 matchedCount++;
1952 }
1953 else
1954 {
1955 BOOST_TEST_MESSAGE( " Unmatched .alg segment " << ai << ": ("
1956 << algSeg.start.x / 1000000.0 << ", "
1957 << algSeg.start.y / 1000000.0 << ") -> ("
1958 << algSeg.end.x / 1000000.0 << ", "
1959 << algSeg.end.y / 1000000.0 << ") mm"
1960 << " bestDist=" << bestDist );
1961 }
1962 }
1963
1964 BOOST_TEST_MESSAGE( " Matched " << matchedCount << " / " << algSegments.size()
1965 << " outline segments" );
1966
1967 BOOST_CHECK_EQUAL( matchedCount, static_cast<int>( algSegments.size() ) );
1968 }
1969 }
1970}
1971
1972
1977BOOST_AUTO_TEST_CASE( PadDrillConsistency )
1978{
1979 std::vector<std::string> boards = GetAllBoardFiles();
1980
1981 for( const std::string& boardPath : boards )
1982 {
1983 std::string boardName = std::filesystem::path( boardPath ).filename().string();
1984 BOARD* board = GetCachedBoard( boardPath );
1985
1986 if( !board )
1987 continue;
1988
1989 BOOST_TEST_CONTEXT( "Testing board: " << boardName )
1990 {
1991 int pthNoDrill = 0;
1992 int smdWithDrill = 0;
1993
1994 for( FOOTPRINT* fp : board->Footprints() )
1995 {
1996 for( PAD* pad : fp->Pads() )
1997 {
1998 PAD_ATTRIB attr = pad->GetAttribute();
1999 bool hasDrill = pad->GetDrillSizeX() > 0;
2000
2001 if( attr == PAD_ATTRIB::PTH && !hasDrill )
2002 {
2003 pthNoDrill++;
2004
2005 if( pthNoDrill <= 5 )
2006 {
2007 BOOST_TEST_MESSAGE( boardName << ": PTH pad without drill: "
2008 << fp->GetReference() << "."
2009 << pad->GetNumber() );
2010 }
2011 }
2012
2013 if( attr == PAD_ATTRIB::SMD && hasDrill )
2014 {
2015 smdWithDrill++;
2016
2017 if( smdWithDrill <= 5 )
2018 {
2019 BOOST_TEST_MESSAGE( boardName << ": SMD pad with drill: "
2020 << fp->GetReference() << "."
2021 << pad->GetNumber() );
2022 }
2023 }
2024 }
2025 }
2026
2027 BOOST_CHECK_EQUAL( pthNoDrill, 0 );
2028 BOOST_CHECK_EQUAL( smdWithDrill, 0 );
2029 }
2030 }
2031}
2032
2033
2037BOOST_AUTO_TEST_CASE( ZoneCountMatchesAlg )
2038{
2039 std::vector<BRD_ALG_PAIR> boardsWithAlg = getBoardsWithAlg();
2040
2041 BOOST_REQUIRE_GT( boardsWithAlg.size(), 0u );
2042
2043 for( const auto& [brdFile, algFile] : boardsWithAlg )
2044 {
2045 BOOST_TEST_CONTEXT( "Zone count: " << brdFile )
2046 {
2048
2049 BOARD* board = GetCachedBoard( brdFile );
2050 BOOST_REQUIRE( board );
2051
2052 size_t boardCopperZoneLayers = 0;
2053
2054 for( const ZONE* zone : board->Zones() )
2055 {
2056 if( !zone->GetIsRuleArea() )
2057 boardCopperZoneLayers += ( zone->GetLayerSet() & LSET::AllCuMask() ).count();
2058 }
2059
2060 size_t algZoneCount = algData.zonePolygons.size();
2061
2062 BOOST_TEST_MESSAGE( brdFile << ": .alg has " << algZoneCount
2063 << " zone polygons, board has " << boardCopperZoneLayers
2064 << " copper zone-layers" );
2065
2066 BOOST_CHECK_EQUAL( static_cast<size_t>( boardCopperZoneLayers ), algZoneCount );
2067 }
2068 }
2069}
2070
2071
2075BOOST_AUTO_TEST_CASE( ZoneLayerDistribution )
2076{
2077 std::vector<BRD_ALG_PAIR> boardsWithAlg = getBoardsWithAlg();
2078
2079 BOOST_REQUIRE_GT( boardsWithAlg.size(), 0u );
2080
2081 for( const auto& [brdFile, algFile] : boardsWithAlg )
2082 {
2083 BOOST_TEST_CONTEXT( "Zone layers: " << brdFile )
2084 {
2086
2087 BOARD* board = GetCachedBoard( brdFile );
2088 BOOST_REQUIRE( board );
2089
2090 std::map<wxString, int> algLayerCounts;
2091
2092 for( const ALG_ZONE_POLYGON& zone : algData.zonePolygons )
2093 algLayerCounts[zone.layer]++;
2094
2095 std::map<wxString, int> boardLayerCounts;
2096
2097 for( const ZONE* zone : board->Zones() )
2098 {
2099 if( zone->GetIsRuleArea() )
2100 continue;
2101
2102 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
2103 {
2104 if( IsCopperLayer( layer ) )
2105 boardLayerCounts[board->GetLayerName( layer )]++;
2106 }
2107 }
2108
2109 BOOST_TEST_MESSAGE( brdFile << " layer distribution:" );
2110
2111 for( const auto& [layer, count] : algLayerCounts )
2112 {
2113 auto it = boardLayerCounts.find( layer );
2114 int boardCount = ( it != boardLayerCounts.end() ) ? it->second : 0;
2115
2116 BOOST_TEST_MESSAGE( " " << layer << ": .alg=" << count << " board=" << boardCount );
2117 BOOST_CHECK_EQUAL( boardCount, count );
2118 }
2119 }
2120 }
2121}
2122
2123
2128BOOST_AUTO_TEST_CASE( ZoneBoundingBoxes )
2129{
2130 std::vector<BRD_ALG_PAIR> boardsWithAlg = getBoardsWithAlg();
2131
2132 BOOST_REQUIRE_GT( boardsWithAlg.size(), 0u );
2133
2134 for( const auto& [brdFile, algFile] : boardsWithAlg )
2135 {
2136 BOOST_TEST_CONTEXT( "Zone bboxes: " << brdFile )
2137 {
2139
2140 BOARD* board = GetCachedBoard( brdFile );
2141 BOOST_REQUIRE( board );
2142
2143 // Collect sorted areas per layer from .alg and board, then compare distributions
2144 const double milsToNm = 25400.0;
2145
2146 std::map<wxString, std::vector<double>> algAreas;
2147
2148 for( const ALG_ZONE_POLYGON& zone : algData.zonePolygons )
2149 {
2150 double w = ( zone.maxX - zone.minX ) * milsToNm;
2151 double h = ( zone.maxY - zone.minY ) * milsToNm;
2152 algAreas[zone.layer].push_back( w * h );
2153 }
2154
2155 std::map<wxString, std::vector<double>> boardAreas;
2156
2157 for( const ZONE* zone : board->Zones() )
2158 {
2159 if( zone->GetIsRuleArea() )
2160 continue;
2161
2162 BOX2I bbox = zone->GetBoundingBox();
2163 double area = static_cast<double>( bbox.GetWidth() )
2164 * static_cast<double>( bbox.GetHeight() );
2165
2166 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
2167 {
2168 if( IsCopperLayer( layer ) )
2169 boardAreas[board->GetLayerName( layer )].push_back( area );
2170 }
2171 }
2172
2173 int matched = 0;
2174 int mismatched = 0;
2175
2176 for( auto& [layer, algList] : algAreas )
2177 {
2178 std::sort( algList.begin(), algList.end() );
2179 auto it = boardAreas.find( layer );
2180
2181 if( it == boardAreas.end() || it->second.size() != algList.size() )
2182 continue;
2183
2184 std::sort( it->second.begin(), it->second.end() );
2185
2186 for( size_t i = 0; i < algList.size(); ++i )
2187 {
2188 double ref = std::max( algList[i], 1.0 );
2189 double err = std::abs( it->second[i] - algList[i] ) / ref;
2190
2191 if( err < 0.10 )
2192 {
2193 matched++;
2194 }
2195 else
2196 {
2197 mismatched++;
2198
2199 if( mismatched <= 5 )
2200 {
2201 BOOST_TEST_MESSAGE( " " << layer << " index " << i
2202 << ": alg area " << algList[i] / ( milsToNm * milsToNm )
2203 << " sq mils vs board area "
2204 << it->second[i] / ( milsToNm * milsToNm )
2205 << " sq mils" );
2206 }
2207 }
2208 }
2209 }
2210
2211 BOOST_TEST_MESSAGE( brdFile << ": " << matched << " zone areas matched, "
2212 << mismatched << " mismatched" );
2213
2214 int total = matched + mismatched;
2215
2216 if( total > 0 )
2217 {
2218 BOOST_CHECK_GT( matched, total * 8 / 10 );
2219 }
2220 }
2221 }
2222}
2223
2224
2231BOOST_AUTO_TEST_CASE( PadContainedInFabOutline )
2232{
2233 std::string dataPath = KI_TEST::AllegroBoardFile( "BeagleBone_Black_RevC/BeagleBone_Black_RevC.brd" );
2234
2235 BOARD* board = GetCachedBoard( dataPath );
2236 BOOST_REQUIRE( board );
2237
2238 // Footprints known to have pads and fab outlines that enclose them.
2239 // P6 and P10 are excluded: they are bottom-side connectors whose assembly outlines
2240 // only cover the housing, not the full pin field.
2241 const std::set<wxString> targetRefs = { wxS( "J1" ), wxS( "P5" ), wxS( "U5" ),
2242 wxS( "U13" ), wxS( "C78" ) };
2243
2244 int testedCount = 0;
2245 int failedCount = 0;
2246
2247 for( FOOTPRINT* fp : board->Footprints() )
2248 {
2249 if( targetRefs.find( fp->GetReference() ) == targetRefs.end() )
2250 continue;
2251
2252 PCB_LAYER_ID fabLayer = fp->IsFlipped() ? B_Fab : F_Fab;
2253
2254 BOX2I fabBbox;
2255 bool hasFab = false;
2256
2257 for( BOARD_ITEM* item : fp->GraphicalItems() )
2258 {
2259 if( item->GetLayer() == fabLayer )
2260 {
2261 if( !hasFab )
2262 {
2263 fabBbox = item->GetBoundingBox();
2264 hasFab = true;
2265 }
2266 else
2267 {
2268 fabBbox.Merge( item->GetBoundingBox() );
2269 }
2270 }
2271 }
2272
2273 if( !hasFab || fp->Pads().empty() )
2274 continue;
2275
2276 // Allow generous tolerance: pad centers can extend slightly beyond the fab outline
2277 // (e.g. edge-mount connectors, thermal pads). 3mm handles most cases.
2278 BOX2I testBbox = fabBbox;
2279 testBbox.Inflate( 3000000 );
2280
2281 BOOST_TEST_CONTEXT( "Footprint " << fp->GetReference() )
2282 {
2283 testedCount++;
2284
2285 for( PAD* pad : fp->Pads() )
2286 {
2287 VECTOR2I padCenter = pad->GetPosition();
2288
2289 if( !testBbox.Contains( padCenter ) )
2290 {
2291 failedCount++;
2292 BOOST_TEST_MESSAGE( fp->GetReference() << " pad " << pad->GetNumber()
2293 << " at (" << padCenter.x / 1e6 << ", "
2294 << padCenter.y / 1e6 << ") mm is outside F.Fab bbox" );
2295 }
2296 }
2297 }
2298 }
2299
2300 BOOST_TEST_MESSAGE( "Tested " << testedCount << " footprints for pad containment" );
2301 BOOST_CHECK_GE( testedCount, 4 );
2302 BOOST_CHECK_EQUAL( failedCount, 0 );
2303}
2304
2305
2311BOOST_AUTO_TEST_CASE( PadOrientationP6P10 )
2312{
2313 std::string dataPath = KI_TEST::AllegroBoardFile( "BeagleBone_Black_RevC/BeagleBone_Black_RevC.brd" );
2314
2315 BOARD* board = GetCachedBoard( dataPath );
2316 BOOST_REQUIRE( board );
2317
2318 for( FOOTPRINT* fp : board->Footprints() )
2319 {
2320 wxString ref = fp->GetReference();
2321
2322 if( ref != wxS( "P6" ) && ref != wxS( "P10" ) )
2323 continue;
2324
2325 BOOST_TEST_CONTEXT( "Footprint " << ref )
2326 {
2327 for( PAD* pad : fp->Pads() )
2328 {
2329 long padNum = 0;
2330
2331 if( !pad->GetNumber().ToLong( &padNum ) )
2332 continue;
2333
2334 // Pads 1-19 on P6/P10 are rectangular SMD pads that should be wider than tall
2335 if( padNum < 1 || padNum > 19 )
2336 continue;
2337
2338 // GetBoundingBox accounts for rotation, giving visual dimensions
2339 BOX2I bbox = pad->GetBoundingBox();
2340 auto bboxW = bbox.GetWidth();
2341 auto bboxH = bbox.GetHeight();
2342
2343 // Skip square/circular pads where orientation doesn't affect shape
2344 if( bboxW == bboxH )
2345 continue;
2346
2347 BOOST_TEST_CONTEXT( "Pad " << pad->GetNumber() )
2348 {
2349 BOOST_CHECK_MESSAGE( bboxW > bboxH,
2350 ref << " pad " << pad->GetNumber()
2351 << " should be visually wider than tall: "
2352 << bboxW / 1e6 << " x " << bboxH / 1e6 << " mm" );
2353 }
2354 }
2355 }
2356 }
2357}
2358
2359
2368{
2369 std::string dataPath = KI_TEST::AllegroBoardFile( "BeagleBone_Black_RevC/BeagleBone_Black_RevC.brd" );
2370
2371 BOARD* board = GetCachedBoard( dataPath );
2372 BOOST_REQUIRE( board );
2373
2374 int oblongCount = 0;
2375
2376 for( FOOTPRINT* fp : board->Footprints() )
2377 {
2378 for( PAD* pad : fp->Pads() )
2379 {
2380 VECTOR2I drillSize = pad->GetDrillSize();
2381
2382 if( drillSize.x <= 0 || drillSize.y <= 0 )
2383 continue;
2384
2385 if( drillSize.x == drillSize.y )
2386 continue;
2387
2388 oblongCount++;
2389
2390 BOOST_TEST_CONTEXT( fp->GetReference() << " pad " << pad->GetNumber() )
2391 {
2392 BOOST_CHECK( pad->GetDrillShape() == PAD_DRILL_SHAPE::OBLONG );
2393
2394 BOOST_TEST_MESSAGE( fp->GetReference() << " pad " << pad->GetNumber()
2395 << " slot: " << drillSize.x / 1e6 << " x "
2396 << drillSize.y / 1e6 << " mm"
2397 << " attr=" << static_cast<int>( pad->GetAttribute() ) );
2398 }
2399 }
2400 }
2401
2402 BOOST_TEST_MESSAGE( "Found " << oblongCount << " oblong drill holes" );
2403 BOOST_CHECK_EQUAL( oblongCount, 7 );
2404}
2405
2406
2412BOOST_AUTO_TEST_CASE( FootprintOrientation )
2413{
2414 std::string dataPath = KI_TEST::AllegroBoardFile( "BeagleBone_Black_RevC/BeagleBone_Black_RevC.brd" );
2415
2416 BOARD* board = GetCachedBoard( dataPath );
2417
2418 BOOST_REQUIRE( board );
2419
2420 FOOTPRINT* j1 = nullptr;
2421
2422 for( FOOTPRINT* fp : board->Footprints() )
2423 {
2424 if( fp->GetReference() == wxT( "J1" ) )
2425 {
2426 j1 = fp;
2427 break;
2428 }
2429 }
2430
2431 BOOST_REQUIRE_MESSAGE( j1 != nullptr, "Footprint J1 must exist in BeagleBone_Black_RevC" );
2432
2433 EDA_ANGLE orientation = j1->GetOrientation();
2434 BOOST_TEST_MESSAGE( "J1 orientation: " << orientation.AsDegrees() << " degrees" );
2435 BOOST_CHECK_CLOSE( orientation.AsDegrees(), 90.0, 0.1 );
2436}
2437
2438
2443BOOST_AUTO_TEST_CASE( UIImportPath_NullBoard )
2444{
2445 std::string dataPath = KI_TEST::AllegroBoardFile( "ProiectBoard/ProiectBoard.brd" );
2446
2447 PCB_IO_ALLEGRO plugin;
2449 plugin.SetReporter( &reporter );
2450
2451 BOARD* rawBoard = nullptr;
2452
2453 try
2454 {
2455 rawBoard = plugin.LoadBoard( dataPath, nullptr, nullptr, nullptr );
2456 }
2457 catch( const IO_ERROR& e )
2458 {
2459 BOOST_TEST_MESSAGE( "IO_ERROR: " << e.What() );
2460 }
2461 catch( const std::exception& e )
2462 {
2463 BOOST_TEST_MESSAGE( "Exception: " << e.what() );
2464 }
2465
2466 reporter.PrintAllMessages( "UIImportPath_NullBoard" );
2467
2468 BOOST_REQUIRE_MESSAGE( rawBoard != nullptr, "LoadBoard with nullptr aAppendToMe must return a valid board" );
2469
2470 std::unique_ptr<BOARD> board( rawBoard );
2471
2472 BOOST_CHECK_GT( board->GetNetCount(), 0 );
2473 BOOST_CHECK_GT( board->Footprints().size(), 0 );
2474 BOOST_CHECK_GT( board->Tracks().size(), 0 );
2475 BOOST_CHECK_EQUAL( reporter.GetErrorCount(), 0 );
2476
2477 PrintBoardStats( board.get(), "ProiectBoard (UI path)" );
2478}
2479
2480
2489BOOST_AUTO_TEST_CASE( SmdPadLayerConsistency )
2490{
2491 std::vector<std::string> boards = GetAllBoardFiles();
2492
2493 for( const std::string& boardPath : boards )
2494 {
2495 std::string boardName = std::filesystem::path( boardPath ).filename().string();
2496 BOARD* board = GetCachedBoard( boardPath );
2497
2498 if( !board )
2499 continue;
2500
2501 BOOST_TEST_CONTEXT( "Testing board: " << boardName )
2502 {
2503 int inconsistentCount = 0;
2504
2505 for( FOOTPRINT* fp : board->Footprints() )
2506 {
2507 const bool onBottom = fp->IsFlipped();
2508
2509 for( PAD* pad : fp->Pads() )
2510 {
2511 if( pad->GetAttribute() != PAD_ATTRIB::SMD )
2512 continue;
2513
2514 LSET layers = pad->GetLayerSet();
2515 bool hasTopCopper = layers.Contains( F_Cu );
2516 bool hasBotCopper = layers.Contains( B_Cu );
2517
2518 if( onBottom && hasTopCopper && !hasBotCopper )
2519 {
2520 inconsistentCount++;
2521 BOOST_TEST_MESSAGE( boardName << ": " << fp->GetReference() << " pad "
2522 << pad->GetNumber() << " is on bottom footprint but SMD "
2523 << "pad has F.Cu without B.Cu" );
2524 }
2525 else if( !onBottom && hasBotCopper && !hasTopCopper )
2526 {
2527 inconsistentCount++;
2528 BOOST_TEST_MESSAGE( boardName << ": " << fp->GetReference() << " pad "
2529 << pad->GetNumber() << " is on top footprint but SMD "
2530 << "pad has B.Cu without F.Cu" );
2531 }
2532 }
2533 }
2534
2535 BOOST_CHECK_EQUAL( inconsistentCount, 0 );
2536 }
2537 }
2538}
2539
2540
2549BOOST_AUTO_TEST_CASE( SmdFootprintTechLayers )
2550{
2551 // Note that this test is NOT true for all boards - some boards have SMD FPs with
2552 // back-layer items.
2553 std::vector<std::string> boards = {
2554 KI_TEST::AllegroBoardFile( "EVK_BaseBoard/EVK_BaseBoard.brd" ),
2555 };
2556
2557 for( const std::string& boardPath : boards )
2558 {
2559 std::string boardName = std::filesystem::path( boardPath ).filename().string();
2560 BOARD* board = GetCachedBoard( boardPath );
2561
2562 BOOST_REQUIRE( board );
2563
2564 BOOST_TEST_CONTEXT( "Testing board: " << boardName )
2565 {
2566 int inconsistentCount = 0;
2567 int checkedFootprints = 0;
2568
2569 for( FOOTPRINT* fp : board->Footprints() )
2570 {
2571 // Only check SMD-only footprints (no through-hole pads)
2572 bool hasSmd = false;
2573 bool hasTH = false;
2574
2575 for( PAD* pad : fp->Pads() )
2576 {
2577 if( pad->GetAttribute() == PAD_ATTRIB::SMD )
2578 hasSmd = true;
2579 else if( pad->GetAttribute() == PAD_ATTRIB::PTH )
2580 hasTH = true;
2581 }
2582
2583 if( !hasSmd || hasTH )
2584 continue;
2585
2586 checkedFootprints++;
2587
2588 const bool onBottom = fp->IsFlipped();
2589
2590 for( BOARD_ITEM* item : fp->GraphicalItems() )
2591 {
2592 PCB_LAYER_ID layer = item->GetLayer();
2593
2594 if( !IsFrontLayer( layer ) && !IsBackLayer( layer ) )
2595 continue;
2596
2597 bool wrongSide = false;
2598
2599 if( onBottom && IsFrontLayer( layer ) )
2600 wrongSide = true;
2601 else if( !onBottom && IsBackLayer( layer ) )
2602 wrongSide = true;
2603
2604 if( wrongSide )
2605 {
2606 inconsistentCount++;
2608 boardName << ": " << fp->GetReference() << " is "
2609 << ( onBottom ? "bottom" : "top" ) << "-side SMD but has "
2610 << item->GetClass() << " on "
2611 << board->GetLayerName( layer ) );
2612 }
2613 }
2614 }
2615
2616 BOOST_TEST_MESSAGE( "Checked " << checkedFootprints
2617 << " SMD-only footprints for tech layer consistency" );
2618 BOOST_CHECK_EQUAL( inconsistentCount, 0 );
2619 }
2620 }
2621}
2622
2623
2630BOOST_AUTO_TEST_CASE( BeagleBone_DrillSlotOrientation )
2631{
2632 std::string dataPath = KI_TEST::AllegroBoardFile( "BeagleBone_Black_RevC/BeagleBone_Black_RevC.brd" );
2633
2634 BOARD* board = GetCachedBoard( dataPath );
2635 BOOST_REQUIRE( board );
2636
2637 struct SLOT_CHECK
2638 {
2639 wxString fpRef;
2640 wxString padNum;
2641 };
2642
2643 // These pads have vertical oblong copper shapes and should have taller-than-wide drill slots
2644 std::vector<SLOT_CHECK> checks = {
2645 { wxS( "P6" ), wxS( "20" ) },
2646 { wxS( "P6" ), wxS( "21" ) },
2647 { wxS( "P3" ), wxS( "6" ) },
2648 { wxS( "P1" ), wxS( "1" ) },
2649 { wxS( "P1" ), wxS( "2" ) },
2650 { wxS( "P1" ), wxS( "3" ) },
2651 };
2652
2653 for( const auto& check : checks )
2654 {
2655 BOOST_TEST_CONTEXT( check.fpRef << " pad " << check.padNum )
2656 {
2657 FOOTPRINT* fp = nullptr;
2658
2659 for( FOOTPRINT* candidate : board->Footprints() )
2660 {
2661 if( candidate->GetReference() == check.fpRef )
2662 {
2663 fp = candidate;
2664 break;
2665 }
2666 }
2667
2668 BOOST_REQUIRE_MESSAGE( fp != nullptr, "Footprint " << check.fpRef << " should exist" );
2669
2670 PAD* pad = nullptr;
2671
2672 for( PAD* candidate : fp->Pads() )
2673 {
2674 if( candidate->GetNumber() == check.padNum )
2675 {
2676 pad = candidate;
2677 break;
2678 }
2679 }
2680
2681 BOOST_REQUIRE_MESSAGE( pad != nullptr,
2682 "Pad " << check.padNum << " should exist on " << check.fpRef );
2683
2684 VECTOR2I padSize = pad->GetSize( F_Cu );
2685 VECTOR2I drillSize = pad->GetDrillSize();
2686
2687 BOOST_TEST_MESSAGE( check.fpRef << " pad " << check.padNum
2688 << ": pad=" << padSize.x << "x" << padSize.y
2689 << " drill=" << drillSize.x << "x" << drillSize.y );
2690
2691 // If the pad is oblong, the drill should match the pad's aspect ratio
2692 if( drillSize.x != drillSize.y )
2693 {
2694 bool padIsTaller = ( padSize.y > padSize.x );
2695 bool drillIsTaller = ( drillSize.y > drillSize.x );
2696
2697 BOOST_CHECK_MESSAGE( padIsTaller == drillIsTaller,
2698 "Drill slot should match pad orientation" );
2699 }
2700 }
2701 }
2702}
2703
2704
2709BOOST_AUTO_TEST_CASE( BeagleBone_ZoneFills )
2710{
2711 std::string dataPath = KI_TEST::AllegroBoardFile( "BeagleBone_Black_RevC/BeagleBone_Black_RevC.brd" );
2712
2713 BOARD* board = GetCachedBoard( dataPath );
2714 BOOST_REQUIRE( board );
2715
2716 int filledZoneCount = 0;
2717 int totalCopperZones = 0;
2718
2719 for( const ZONE* zone : board->Zones() )
2720 {
2721 if( zone->GetIsRuleArea() || zone->GetNetCode() == 0 )
2722 continue;
2723
2724 totalCopperZones++;
2725
2726 if( zone->IsFilled() )
2727 {
2728 filledZoneCount++;
2729
2730 BOOST_TEST_MESSAGE( "Filled zone: net=" << zone->GetNetname()
2731 << " layers=" << zone->GetLayerSet().count() );
2732 }
2733 }
2734
2735 BOOST_TEST_MESSAGE( "Total copper zones: " << totalCopperZones
2736 << ", filled: " << filledZoneCount );
2737
2738 BOOST_CHECK_GT( totalCopperZones, 0 );
2739 BOOST_CHECK_GT( filledZoneCount, 0 );
2740}
2741
2742
2747BOOST_AUTO_TEST_CASE( BeagleBone_Teardrops )
2748{
2749 std::string dataPath = KI_TEST::AllegroBoardFile( "BeagleBone_Black_RevC/BeagleBone_Black_RevC.brd" );
2750
2751 BOARD* board = GetCachedBoard( dataPath );
2752 BOOST_REQUIRE( board );
2753
2754 int teardropZones = 0;
2755
2756 for( const ZONE* zone : board->Zones() )
2757 {
2758 if( zone->IsTeardropArea() )
2759 teardropZones++;
2760 }
2761
2762 BOOST_CHECK_GT( teardropZones, 1000 );
2763 BOOST_TEST_MESSAGE( "Teardrop zones: " << teardropZones );
2764
2765 // Pads and vias anchoring teardrops must have teardrops enabled
2766 int padsWithTeardrops = 0;
2767 int totalPads = 0;
2768
2769 for( const FOOTPRINT* fp : board->Footprints() )
2770 {
2771 for( const PAD* pad : fp->Pads() )
2772 {
2773 totalPads++;
2774
2775 if( pad->GetTeardropsEnabled() )
2776 padsWithTeardrops++;
2777 }
2778 }
2779
2780 BOOST_CHECK_GT( padsWithTeardrops, 0 );
2781 BOOST_TEST_MESSAGE( "Pads with teardrops enabled: " << padsWithTeardrops << " / " << totalPads );
2782
2783 int viasWithTeardrops = 0;
2784 int totalVias = 0;
2785
2786 for( const PCB_TRACK* track : board->Tracks() )
2787 {
2788 if( track->Type() != PCB_VIA_T )
2789 continue;
2790
2791 totalVias++;
2792
2793 if( static_cast<const PCB_VIA*>( track )->GetTeardropsEnabled() )
2794 viasWithTeardrops++;
2795 }
2796
2797 BOOST_CHECK_GT( viasWithTeardrops, 0 );
2798 BOOST_TEST_MESSAGE( "Vias with teardrops enabled: " << viasWithTeardrops << " / " << totalVias );
2799}
2800
2801
2806BOOST_AUTO_TEST_CASE( PreV172_NoTeardrops )
2807{
2808 std::string dataPath = KI_TEST::AllegroBoardFile( "ProiectBoard/ProiectBoard.brd" );
2809
2810 BOARD* board = GetCachedBoard( dataPath );
2811 BOOST_REQUIRE( board );
2812
2813 int teardropZones = 0;
2814
2815 for( const ZONE* zone : board->Zones() )
2816 {
2817 if( zone->IsTeardropArea() )
2818 teardropZones++;
2819 }
2820
2821 BOOST_CHECK_EQUAL( teardropZones, 0 );
2822
2823 int padsWithTeardrops = 0;
2824
2825 for( const FOOTPRINT* fp : board->Footprints() )
2826 {
2827 for( const PAD* pad : fp->Pads() )
2828 {
2829 if( pad->GetTeardropsEnabled() )
2830 padsWithTeardrops++;
2831 }
2832 }
2833
2834 BOOST_CHECK_EQUAL( padsWithTeardrops, 0 );
2835}
2836
2837
2843BOOST_AUTO_TEST_CASE( LegacyNetclassFlags )
2844{
2845 std::string dataPath = KI_TEST::AllegroBoardFile( "TRS80_POWER/TRS80_POWER.brd" );
2846
2847 PCB_IO_ALLEGRO plugin;
2849 plugin.SetReporter( &reporter );
2850
2851 BOARD* rawBoard = plugin.LoadBoard( dataPath, nullptr, nullptr, nullptr );
2852
2853 BOOST_REQUIRE( rawBoard );
2854
2855 std::unique_ptr<BOARD> board( rawBoard );
2856
2857 BOOST_CHECK_MESSAGE( board->m_LegacyNetclassesLoaded,
2858 "m_LegacyNetclassesLoaded must be true after Allegro import" );
2859 BOOST_CHECK_MESSAGE( board->m_LegacyDesignSettingsLoaded,
2860 "m_LegacyDesignSettingsLoaded must be true after Allegro import" );
2861}
2862
2863
2868BOOST_AUTO_TEST_CASE( NetclassesCreatedForAllBoards )
2869{
2870 std::vector<std::string> boards = GetAllBoardFiles();
2871
2872 for( const std::string& boardPath : boards )
2873 {
2874 std::string boardName = std::filesystem::path( boardPath ).filename().string();
2875 BOARD* board = GetCachedBoard( boardPath );
2876
2877 if( !board )
2878 continue;
2879
2880 BOOST_TEST_CONTEXT( "Testing board: " << boardName )
2881 {
2882 std::shared_ptr<NET_SETTINGS> netSettings = board->GetDesignSettings().m_NetSettings;
2883 const auto& netclasses = netSettings->GetNetclasses();
2884
2885 BOOST_TEST_MESSAGE( boardName << ": " << netclasses.size() << " netclasses" );
2886
2887 for( const auto& [name, nc] : netclasses )
2888 {
2889 BOOST_TEST_MESSAGE( " " << name << ": track="
2890 << nc->GetTrackWidth() << " clearance="
2891 << nc->GetClearance() );
2892
2893 // Constraint set netclasses should have positive track width.
2894 // Skip generated netclasses (DP_, MG_, W*mil) which may not set track width.
2895 if( !name.StartsWith( wxS( "DP_" ) )
2896 && !name.StartsWith( wxS( "MG_" ) )
2897 && !name.StartsWith( wxS( "W" ) ) )
2898 {
2899 BOOST_CHECK_MESSAGE( nc->HasTrackWidth(),
2900 name << " should have a track width" );
2901 BOOST_CHECK_MESSAGE( nc->GetTrackWidth() > 0,
2902 name << " track width should be positive" );
2903 }
2904 }
2905 }
2906 }
2907}
2908
2909
const char * name
General utilities for PCB file IO for QA programs.
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
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:81
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1086
const ZONES & Zones() const
Definition board.h:424
PCB_LAYER_ID GetLayerID(const wxString &aLayerName) const
Return the ID of a layer.
Definition board.cpp:773
const FOOTPRINTS & Footprints() const
Definition board.h:420
const TRACKS & Tracks() const
Definition board.h:418
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:793
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1149
const DRAWINGS & Drawings() const
Definition board.h:422
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:554
constexpr size_type GetWidth() const
Definition box2.h:210
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:654
constexpr size_type GetHeight() const
Definition box2.h:211
constexpr coord_type GetLeft() const
Definition box2.h:224
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:164
constexpr coord_type GetRight() const
Definition box2.h:213
constexpr coord_type GetTop() const
Definition box2.h:225
constexpr coord_type GetBottom() const
Definition box2.h:218
EDA_ANGLE Normalize()
Definition eda_angle.h:229
double AsDegrees() const
Definition eda_angle.h:116
std::vector< VECTOR2I > GetPolyPoints() const
Duplicate the polygon outlines into a flat list of VECTOR2I points.
SHAPE_T GetShape() const
Definition eda_shape.h:185
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:240
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:190
EDA_ANGLE GetOrientation() const
Definition footprint.h:406
std::deque< PAD * > & Pads()
Definition footprint.h:375
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:417
bool IsFlipped() const
Definition footprint.h:614
virtual void SetReporter(REPORTER *aReporter)
Set an optional reporter for warnings/errors.
Definition io_base.h:89
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
virtual const char * what() const override
std::exception interface, returned as UTF-8
static ALLEGRO_CACHED_LOADER & GetInstance()
Get the singleton instance of the Allegro board cache loader.
BOARD * GetCachedBoard(const std::string &aFilePath)
Get a cached board for the given file path, or load it if not already cached, without forcing a reloa...
Custom REPORTER that captures all messages for later analysis in the unit test framework.
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
Handle the data for a net.
Definition netinfo.h:46
Definition pad.h:61
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const std::map< std::string, UTF8 > *aProperties, PROJECT *aProject) override
Load information from some input file format that this PCB_IO implementation knows about into either ...
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition pcb_shape.h:68
const VECTOR2I & GetStart() const
Definition pcb_track.h:93
const VECTOR2I & GetEnd() const
Definition pcb_track.h:90
Definition seg.h:38
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool IsFilled() const
Definition zone.h:306
PCB_LAYER_ID GetFirstLayer() const
Definition zone.cpp:554
@ SEGMENT
Definition eda_shape.h:46
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:47
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:778
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:801
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:675
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Edge_Cuts
Definition layer_ids.h:108
@ B_Cu
Definition layer_ids.h:61
@ F_Fab
Definition layer_ids.h:115
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ F_Cu
Definition layer_ids.h:60
@ B_Fab
Definition layer_ids.h:114
std::array< SEG, 4 > BoxToSegs(const BOX2I &aBox)
Decompose a BOX2 into four segments.
std::string AllegroBoardDataDir(const std::string &aBoardName)
void PrintBoardStats(const BOARD *aBoard, const std::string &aBoardName)
Print detailed board statistics for debugging using test-framework logging.
std::string AllegroBoardFile(const std::string &aFileName)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
PAD_ATTRIB
The set of pad shapes, used with PAD::{Set,Get}Attribute().
Definition padstack.h:97
@ 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
Utility functions for working with shapes.
Parse board outline geometry from a .alg ASCII reference file.
void updateBounds(double aX, double aY)
static ALG_OUTLINE_DATA ParseAlgOutlines(const std::string &aPath)
std::vector< OUTLINE_SEGMENT > designOutlineSegments
std::vector< OUTLINE_SEGMENT > outlineSegments
int expectedEdgeCutsSegments() const
Expected number of Edge_Cuts segments when translating to KiCad.
std::set< wxString > netNames
static ALG_REFERENCE_DATA ParseAlgFile(const std::string &aPath)
std::map< wxString, wxString > refDesToSymName
std::set< wxString > refDes
static std::vector< wxString > SplitAlgLine(const wxString &aLine)
std::vector< ALG_ZONE_POLYGON > zonePolygons
static int ParseRecordId(const wxString &aTag)
Extract the integer record ID from a RECORD_TAG field like "36 1 0".
std::map< wxString, std::set< wxString > > netToRefDes
Parse a FabMaster .alg file and extract reference data for cross-validation.
void AddPoint(double aX, double aY)
Data for parameterized all-boards test.
Fixture for comprehensive board import tests with error capturing.
BOARD * GetCachedBoard(const std::string &aFilePath)
Get a cached board, loading it on first access.
static std::vector< std::string > GetAllBoardFiles()
Get list of all .brd files in the Allegro test data directory.
std::unique_ptr< BOARD > LoadAllegroBoard(const std::string &aFileName)
BOOST_AUTO_TEST_CASE(FootprintRefDes)
Test that footprints have valid reference designators.
static void AssertOutlineValid(const BOARD &aBoard)
static std::vector< BRD_ALG_PAIR > getBoardsWithAlg()
Get a list of all board files in the test data that have a corresponding .alg reference file.
static unsigned CountOutlineElements(const BOARD &board)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
IbisParser parser & reporter
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
std::vector< BOARD_BEST > boards
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
VECTOR2I end
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:81
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:90
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:85
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:91
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:89
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683