KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_diptrace_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
28
31
32#include <board.h>
34#include <footprint.h>
35#include <netclass.h>
37#include <pad.h>
38#include <pcb_track.h>
39#include <pcb_shape.h>
40
41#include <wx/ffile.h>
42#include <wx/filefn.h>
43#include <wx/filename.h>
44
45
47{
49
51
52 std::string GetTestDataDir()
53 {
54 return KI_TEST::GetPcbnewTestDataDir() + "plugins/diptrace/";
55 }
56};
57
58
59BOOST_FIXTURE_TEST_SUITE( DipTracePcbImport, DIPTRACE_PCB_IMPORT_FIXTURE )
60
61
62
66BOOST_AUTO_TEST_CASE( CanReadBoard )
67{
68 BOOST_CHECK( m_plugin.CanReadBoard( GetTestDataDir() + "keyboard.dip" ) );
69 BOOST_CHECK( m_plugin.CanReadBoard( GetTestDataDir() + "156bus_narrow.dip" ) );
70 BOOST_CHECK( m_plugin.CanReadBoard( GetTestDataDir() + "z80_board.dip" ) );
71 BOOST_CHECK( m_plugin.CanReadBoard( GetTestDataDir() + "logic_probe.dip" ) );
72 BOOST_CHECK( m_plugin.CanReadBoard( GetTestDataDir() + "project4.dip" ) );
73
74 wxString tempBase = wxFileName::CreateTempFileName( wxS( "kicad_diptrace_legacy_" ) );
75 wxRemoveFile( tempBase );
76 wxString legacyPath = tempBase + wxS( ".dip" );
77
78 {
79 const uint8_t legacyHeader[] = {
80 0x0B, 'D', 'T', 'B', 'O', 'A', 'R', 'D', '2', '.', '2', '1'
81 };
82
83 wxFFile file( legacyPath, wxS( "wb" ) );
84 BOOST_REQUIRE( file.IsOpened() );
85 BOOST_REQUIRE_EQUAL( file.Write( legacyHeader, sizeof( legacyHeader ) ),
86 sizeof( legacyHeader ) );
87 }
88
89 BOOST_CHECK( m_plugin.CanReadBoard( legacyPath ) );
90 wxRemoveFile( legacyPath );
91}
92
93
94BOOST_AUTO_TEST_CASE( InvalidComponentHeaderFailsDeterministically )
95{
96 const std::string sourcePath = GetTestDataDir() + "z80_board.dip";
97 wxString tempBase = wxFileName::CreateTempFileName( wxS( "kicad_diptrace_bad_pcb_comp_" ) );
98 wxRemoveFile( tempBase );
99 wxString tempPath = tempBase + wxS( ".dip" );
100
101 BOOST_REQUIRE( wxCopyFile( sourcePath, tempPath ) );
102
103 {
104 static constexpr wxFileOffset FIRST_COMPONENT_FLAGS_OFFSET = 0x491;
105 const uint8_t invalidFlags[] = { 0xFF, 0x00, 0x00, 0x00 };
106
107 wxFFile file( tempPath, wxS( "r+b" ) );
108 BOOST_REQUIRE( file.IsOpened() );
109 BOOST_REQUIRE( file.Seek( FIRST_COMPONENT_FLAGS_OFFSET ) );
110 BOOST_REQUIRE_EQUAL( file.Write( invalidFlags, sizeof( invalidFlags ) ),
111 sizeof( invalidFlags ) );
112 }
113
114 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
115
116 BOOST_CHECK_THROW( m_plugin.LoadBoard( tempPath.ToStdString(), board.get() ), IO_ERROR );
117
118 wxRemoveFile( tempPath );
119}
120
121
122BOOST_AUTO_TEST_CASE( InvalidRouteChainNodeCountFailsDeterministically )
123{
124 const std::string sourcePath = GetTestDataDir() + "z80_board.dip";
125 wxString tempBase = wxFileName::CreateTempFileName( wxS( "kicad_diptrace_bad_route_chain_" ) );
126 wxRemoveFile( tempBase );
127 wxString tempPath = tempBase + wxS( ".dip" );
128
129 BOOST_REQUIRE( wxCopyFile( sourcePath, tempPath ) );
130
131 {
132 static constexpr wxFileOffset FIRST_ROUTE_CHAIN_NODE_COUNT_OFFSET = 0x1CEA7;
133 const uint8_t invalidNodeCount[] = { 0x0F, 0x69, 0x51 }; // int3 value 10001
134
135 wxFFile file( tempPath, wxS( "r+b" ) );
136 BOOST_REQUIRE( file.IsOpened() );
137 BOOST_REQUIRE( file.Seek( FIRST_ROUTE_CHAIN_NODE_COUNT_OFFSET ) );
138 BOOST_REQUIRE_EQUAL( file.Write( invalidNodeCount, sizeof( invalidNodeCount ) ),
139 sizeof( invalidNodeCount ) );
140 }
141
142 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
143
144 BOOST_CHECK_THROW( m_plugin.LoadBoard( tempPath.ToStdString(), board.get() ), IO_ERROR );
145
146 wxRemoveFile( tempPath );
147}
148
149
150BOOST_AUTO_TEST_CASE( InvalidNetNameLengthFailsDeterministically )
151{
152 const std::string sourcePath = GetTestDataDir() + "z80_board.dip";
153 wxString tempBase = wxFileName::CreateTempFileName( wxS( "kicad_diptrace_bad_net_name_" ) );
154 wxRemoveFile( tempBase );
155 wxString tempPath = tempBase + wxS( ".dip" );
156
157 BOOST_REQUIRE( wxCopyFile( sourcePath, tempPath ) );
158
159 {
160 static constexpr wxFileOffset FIRST_NET_NAME_LENGTH_OFFSET = 0x1CE2D;
161 const uint8_t invalidNameLength[] = { 0x01, 0xF5 }; // UTF-16 char count 501
162
163 wxFFile file( tempPath, wxS( "r+b" ) );
164 BOOST_REQUIRE( file.IsOpened() );
165 BOOST_REQUIRE( file.Seek( FIRST_NET_NAME_LENGTH_OFFSET ) );
166 BOOST_REQUIRE_EQUAL( file.Write( invalidNameLength, sizeof( invalidNameLength ) ),
167 sizeof( invalidNameLength ) );
168 }
169
170 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
171
172 BOOST_CHECK_THROW( m_plugin.LoadBoard( tempPath.ToStdString(), board.get() ), IO_ERROR );
173
174 wxRemoveFile( tempPath );
175}
176
177
178BOOST_AUTO_TEST_CASE( InvalidZoneMinWidthFailsDeterministically )
179{
180 const std::string sourcePath = GetTestDataDir() + "z80_board.dip";
181 wxString tempBase = wxFileName::CreateTempFileName( wxS( "kicad_diptrace_bad_zone_width_" ) );
182 wxRemoveFile( tempBase );
183 wxString tempPath = tempBase + wxS( ".dip" );
184
185 BOOST_REQUIRE( wxCopyFile( sourcePath, tempPath ) );
186
187 {
188 static constexpr wxFileOffset FIRST_ZONE_MIN_WIDTH_OFFSET = 0x382CB;
189 const uint8_t zeroMinWidth[] = { 0x3B, 0x9A, 0xCA, 0x00 }; // int4 value 0
190
191 wxFFile file( tempPath, wxS( "r+b" ) );
192 BOOST_REQUIRE( file.IsOpened() );
193 BOOST_REQUIRE( file.Seek( FIRST_ZONE_MIN_WIDTH_OFFSET ) );
194 BOOST_REQUIRE_EQUAL( file.Write( zeroMinWidth, sizeof( zeroMinWidth ) ),
195 sizeof( zeroMinWidth ) );
196 }
197
198 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
199
200 BOOST_CHECK_THROW( m_plugin.LoadBoard( tempPath.ToStdString(), board.get() ), IO_ERROR );
201
202 wxRemoveFile( tempPath );
203}
204
205
211BOOST_AUTO_TEST_CASE( LoadKeyboard )
212{
213 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
214
215 m_plugin.LoadBoard( GetTestDataDir() + "keyboard.dip", board.get() );
216
217 BOOST_REQUIRE( board );
218
219 // keyboard.dip has approximately 103 components
220 BOOST_CHECK_GT( board->Footprints().size(), 50 );
221
222 // keyboard.dip has ~70 nets (key matrix: col1-6, row1-8, diode nets, etc.)
223 BOOST_CHECK_GT( board->GetNetCount(), 20 );
224}
225
226
240BOOST_AUTO_TEST_CASE( ObjectsAreFieldLocatedNotScanned )
241{
242 const std::vector<std::string> files = {
243 "keyboard.dip", "156bus_narrow.dip", "logic_probe.dip", "project4.dip", "z80_board.dip"
244 };
245
246 for( const std::string& name : files )
247 {
248 const std::string path = GetTestDataDir() + name;
249 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
250 board->SetFileName( wxString::FromUTF8( path ) );
251
252 DIPTRACE::PCB_PARSER parser( wxString::FromUTF8( path ), board.get() );
253 parser.Parse();
254
255 // Per-category regression guards for the categories already converted to a
256 // deterministic field-walk. Pad records are field-located from the component
257 // header; mount holes carry no scan in this corpus.
259 name + ": pad located by scan (" + std::to_string( parser.PadLocatorScans() )
260 + ")" );
262 name + ": mount hole located by scan ("
263 + std::to_string( parser.MountHoleLocatorScans() ) + ")" );
265 name + ": shape located by scan ("
266 + std::to_string( parser.ShapeLocatorScans() ) + ")" );
268 name + ": component located by scan ("
269 + std::to_string( parser.ComponentLocatorScans() ) + ")" );
271 name + ": section located by scan ("
272 + std::to_string( parser.SectionLocatorScans() ) + ")" );
273
275 parser.ScanLocatorUseCount() == 0,
276 name + ": object/section located by byte-pattern scan (total="
277 + std::to_string( parser.ScanLocatorUseCount() )
278 + " components=" + std::to_string( parser.ComponentLocatorScans() )
279 + " pads=" + std::to_string( parser.PadLocatorScans() )
280 + " shapes=" + std::to_string( parser.ShapeLocatorScans() )
281 + " holes=" + std::to_string( parser.MountHoleLocatorScans() )
282 + " sections=" + std::to_string( parser.SectionLocatorScans() ) + ")" );
283 }
284}
285
286
292{
293 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
294
295 m_plugin.LoadBoard( GetTestDataDir() + "156bus_narrow.dip", board.get() );
296
297 BOOST_REQUIRE( board );
298 BOOST_CHECK_GT( board->Footprints().size(), 0 );
299
300 // 156bus_narrow has a board outline with 12 vertices including arcs
301 bool hasOutline = false;
302
303 for( BOARD_ITEM* drawing : board->Drawings() )
304 {
305 if( drawing->GetLayer() == Edge_Cuts )
306 {
307 hasOutline = true;
308 break;
309 }
310 }
311
312 BOOST_CHECK( hasOutline );
313}
314
315
321{
322 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
323
324 m_plugin.LoadBoard( GetTestDataDir() + "project4.dip", board.get() );
325
326 BOOST_REQUIRE( board );
327 BOOST_CHECK_GT( board->Footprints().size(), 0 );
328}
329
330
334BOOST_AUTO_TEST_CASE( LoadLogicProbe )
335{
336 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
337
338 m_plugin.LoadBoard( GetTestDataDir() + "logic_probe.dip", board.get() );
339
340 BOOST_REQUIRE( board );
341 BOOST_CHECK_GT( board->Footprints().size(), 0 );
342}
343
344
349BOOST_AUTO_TEST_CASE( LoadZ80Board )
350{
351 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
352
353 m_plugin.LoadBoard( GetTestDataDir() + "z80_board.dip", board.get() );
354
355 BOOST_REQUIRE( board );
356
357 // Z80 computer board should have a healthy number of components
358 BOOST_CHECK_GT( board->Footprints().size(), 10 );
359
360 // Verify reference designators are present on imported footprints
361 int refsFound = 0;
362 int totalPads = 0;
363
364 for( const FOOTPRINT* fp : board->Footprints() )
365 {
366 if( !fp->GetReference().IsEmpty() )
367 refsFound++;
368
369 totalPads += static_cast<int>( fp->Pads().size() );
370 }
371
372 BOOST_CHECK_GT( refsFound, 10 );
373 BOOST_CHECK_GT( totalPads, 0 );
374
375 // Z80 board has ~97 nets (address bus A0-A15, data bus D0-D7, control, etc.)
376 BOOST_CHECK_GT( board->GetNetCount(), 20 );
377}
378
379
384{
385 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
386
387 m_plugin.LoadBoard( GetTestDataDir() + "project4.dip", board.get() );
388
389 BOOST_REQUIRE( board );
390
391 int totalPads = 0;
392
393 for( const FOOTPRINT* fp : board->Footprints() )
394 totalPads += static_cast<int>( fp->Pads().size() );
395
396 // project4.dip has 27 components, mostly 2-pin through-hole parts
397 BOOST_CHECK_GT( totalPads, 0 );
398}
399
400
404BOOST_AUTO_TEST_CASE( LoadKeyboardPads )
405{
406 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
407
408 m_plugin.LoadBoard( GetTestDataDir() + "keyboard.dip", board.get() );
409
410 BOOST_REQUIRE( board );
411
412 int totalPads = 0;
413
414 for( const FOOTPRINT* fp : board->Footprints() )
415 totalPads += static_cast<int>( fp->Pads().size() );
416
417 BOOST_CHECK_GT( totalPads, 0 );
418}
419
420
427BOOST_AUTO_TEST_CASE( KeyboardFootprintGraphics )
428{
429 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
430
431 m_plugin.LoadBoard( GetTestDataDir() + "keyboard.dip", board.get() );
432
433 BOOST_REQUIRE( board );
434
435 int totalGraphics = 0;
436
437 for( const FOOTPRINT* fp : board->Footprints() )
438 {
439 for( const BOARD_ITEM* item : fp->GraphicalItems() )
440 {
441 if( item->Type() == PCB_SHAPE_T )
442 totalGraphics++;
443 }
444 }
445
446 // keyboard.dip has 123 components; keyboard switches have 20 line segments each,
447 // capacitors have 14 each. A healthy import should produce many hundreds of shapes.
448 BOOST_CHECK_GT( totalGraphics, 100 );
449}
450
451
456BOOST_AUTO_TEST_CASE( LogicProbeFootprintGraphics )
457{
458 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
459
460 m_plugin.LoadBoard( GetTestDataDir() + "logic_probe.dip", board.get() );
461
462 BOOST_REQUIRE( board );
463
464 int totalGraphics = 0;
465
466 for( const FOOTPRINT* fp : board->Footprints() )
467 {
468 for( const BOARD_ITEM* item : fp->GraphicalItems() )
469 {
470 if( item->Type() == PCB_SHAPE_T )
471 totalGraphics++;
472 }
473 }
474
475 BOOST_CHECK_GT( totalGraphics, 50 );
476}
477
478
483BOOST_AUTO_TEST_CASE( Z80FootprintGraphics )
484{
485 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
486
487 m_plugin.LoadBoard( GetTestDataDir() + "z80_board.dip", board.get() );
488
489 BOOST_REQUIRE( board );
490
491 int totalGraphics = 0;
492
493 for( const FOOTPRINT* fp : board->Footprints() )
494 {
495 for( const BOARD_ITEM* item : fp->GraphicalItems() )
496 {
497 if( item->Type() == PCB_SHAPE_T )
498 totalGraphics++;
499 }
500 }
501
502 // v45 uses fixed-record shapes; the Z80 board has fewer shape-bearing footprints
503 // than the keyboard/logic_probe boards since many components are simple 2-pin DIP.
504 BOOST_CHECK_GT( totalGraphics, 10 );
505}
506
507
513BOOST_AUTO_TEST_CASE( LogicProbeTextPositioning )
514{
515 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
516
517 m_plugin.LoadBoard( GetTestDataDir() + "logic_probe.dip", board.get() );
518
519 BOOST_REQUIRE( board );
520
521 int nonZeroRefY = 0;
522 int nonZeroValY = 0;
523
524 for( const FOOTPRINT* fp : board->Footprints() )
525 {
526 VECTOR2I refPos = fp->Reference().GetPosition();
527 VECTOR2I valPos = fp->Value().GetPosition();
528 VECTOR2I fpPos = fp->GetPosition();
529
530 if( refPos.y != fpPos.y )
531 nonZeroRefY++;
532
533 if( valPos.y != fpPos.y )
534 nonZeroValY++;
535 }
536
537 // logic_probe.dip has 113 components; the component tail parser finds text
538 // offsets for components where the 37-byte tail is intact at the expected position.
539 BOOST_CHECK_GT( nonZeroRefY, 30 );
540 BOOST_CHECK_GT( nonZeroValY, 3 );
541}
542
543
547BOOST_AUTO_TEST_CASE( Z80TextPositioning )
548{
549 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
550
551 m_plugin.LoadBoard( GetTestDataDir() + "z80_board.dip", board.get() );
552
553 BOOST_REQUIRE( board );
554
555 int nonZeroRefY = 0;
556 int nonZeroValY = 0;
557
558 for( const FOOTPRINT* fp : board->Footprints() )
559 {
560 VECTOR2I refPos = fp->Reference().GetPosition();
561 VECTOR2I valPos = fp->Value().GetPosition();
562 VECTOR2I fpPos = fp->GetPosition();
563
564 if( refPos.y != fpPos.y )
565 nonZeroRefY++;
566
567 if( valPos.y != fpPos.y )
568 nonZeroValY++;
569 }
570
571 BOOST_CHECK_GT( nonZeroRefY, 1 );
572 BOOST_CHECK_GT( nonZeroValY, 1 );
573}
574
575
579BOOST_AUTO_TEST_CASE( KeyboardTextPositioning )
580{
581 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
582
583 m_plugin.LoadBoard( GetTestDataDir() + "keyboard.dip", board.get() );
584
585 BOOST_REQUIRE( board );
586
587 int nonZeroRefY = 0;
588
589 for( const FOOTPRINT* fp : board->Footprints() )
590 {
591 VECTOR2I refPos = fp->Reference().GetPosition();
592 VECTOR2I fpPos = fp->GetPosition();
593
594 if( refPos.y != fpPos.y )
595 nonZeroRefY++;
596 }
597
598 BOOST_CHECK_GT( nonZeroRefY, 30 );
599}
600
601
605BOOST_AUTO_TEST_CASE( V37TextPositioning )
606{
607 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
608
609 m_plugin.LoadBoard( GetTestDataDir() + "project4.dip", board.get() );
610
611 BOOST_REQUIRE( board );
612
613 int nonZeroValY = 0;
614
615 for( const FOOTPRINT* fp : board->Footprints() )
616 {
617 VECTOR2I valPos = fp->Value().GetPosition();
618 VECTOR2I fpPos = fp->GetPosition();
619
620 if( valPos.y != fpPos.y )
621 nonZeroValY++;
622 }
623
624 // project4.dip v37 has 27 components; at least some should have value Y offsets
625 BOOST_CHECK_GT( nonZeroValY, 5 );
626}
627
628
633BOOST_AUTO_TEST_CASE( FootprintGraphicShapeTypes )
634{
635 std::vector<std::string> files = { "logic_probe.dip", "keyboard.dip" };
636
637 int totalSegments = 0;
638 int totalCircles = 0;
639 int totalArcs = 0;
640
641 for( const std::string& file : files )
642 {
643 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
644
645 m_plugin.LoadBoard( GetTestDataDir() + file, board.get() );
646
647 BOOST_REQUIRE( board );
648
649 int segments = 0;
650 int circles = 0;
651 int arcs = 0;
652
653 for( const FOOTPRINT* fp : board->Footprints() )
654 {
655 for( const BOARD_ITEM* item : fp->GraphicalItems() )
656 {
657 if( item->Type() != PCB_SHAPE_T )
658 continue;
659
660 const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( item );
661
662 switch( shape->GetShape() )
663 {
664 case SHAPE_T::SEGMENT: segments++; break;
665 case SHAPE_T::CIRCLE: circles++; break;
666 case SHAPE_T::ARC: arcs++; break;
667 default: break;
668 }
669 }
670 }
671
672 BOOST_TEST_MESSAGE( file << ": SEGMENT=" << segments
673 << " CIRCLE=" << circles << " ARC=" << arcs );
674
675 BOOST_CHECK_GT( segments + circles + arcs, 0 );
676
677 totalSegments += segments;
678 totalCircles += circles;
679 totalArcs += arcs;
680 }
681
682 // Across both boards, verify we see a healthy mix of shape types and not just segments
683 BOOST_CHECK_GT( totalSegments, 200 );
684 BOOST_CHECK_GT( totalCircles + totalArcs, 0 );
685}
686
687
693BOOST_AUTO_TEST_CASE( Z80PolygonPads )
694{
695 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
696
697 m_plugin.LoadBoard( GetTestDataDir() + "z80_board.dip", board.get() );
698
699 BOOST_REQUIRE( board );
700
701 int customPads = 0;
702
703 for( const FOOTPRINT* fp : board->Footprints() )
704 {
705 for( const PAD* pad : fp->Pads() )
706 {
707 if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM )
708 customPads++;
709 }
710 }
711
712 BOOST_CHECK_GT( customPads, 0 );
713}
714
715
720BOOST_AUTO_TEST_CASE( Z80RectangularPads )
721{
722 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
723
724 m_plugin.LoadBoard( GetTestDataDir() + "z80_board.dip", board.get() );
725
726 BOOST_REQUIRE( board );
727
728 int rectPads = 0;
729
730 for( const FOOTPRINT* fp : board->Footprints() )
731 {
732 for( const PAD* pad : fp->Pads() )
733 {
734 if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::RECTANGLE )
735 rectPads++;
736 }
737 }
738
739 // The Z80 board has SMD components with rectangular pads (padStyleC=2)
740 BOOST_CHECK_GT( rectPads, 0 );
741}
742
743
748BOOST_AUTO_TEST_CASE( KeyboardPadShapes )
749{
750 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
751
752 m_plugin.LoadBoard( GetTestDataDir() + "keyboard.dip", board.get() );
753
754 BOOST_REQUIRE( board );
755
756 int circlePads = 0;
757 int ovalPads = 0;
758 int rectPads = 0;
759
760 for( const FOOTPRINT* fp : board->Footprints() )
761 {
762 for( const PAD* pad : fp->Pads() )
763 {
764 switch( pad->GetShape( PADSTACK::ALL_LAYERS ) )
765 {
766 case PAD_SHAPE::CIRCLE: circlePads++; break;
767 case PAD_SHAPE::OVAL: ovalPads++; break;
768 case PAD_SHAPE::RECTANGLE: rectPads++; break;
769 default: break;
770 }
771 }
772 }
773
774 BOOST_TEST_MESSAGE( "keyboard.dip pads: CIRCLE=" << circlePads
775 << " OVAL=" << ovalPads << " RECT=" << rectPads );
776
777 // Should have at least some circular pads (THT switch pins)
778 BOOST_CHECK_GT( circlePads, 0 );
779}
780
781
786BOOST_AUTO_TEST_CASE( LogicProbeBoardSettings )
787{
788 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
789
790 m_plugin.LoadBoard( GetTestDataDir() + "logic_probe.dip", board.get() );
791
792 BOOST_REQUIRE( board );
793
794 BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
795
796 // Board should have at least 2 copper layers
797 BOOST_CHECK_GE( board->GetCopperLayerCount(), 2 );
798
799 // Default track width and clearance should be non-zero (from design rules)
800 std::shared_ptr<NETCLASS> defNc = bds.m_NetSettings->GetDefaultNetclass();
801 BOOST_CHECK_GT( defNc->GetTrackWidth(), 0 );
802 BOOST_CHECK_GT( defNc->GetClearance(), 0 );
803
804 // logic_probe.dip has ViaStyles, so via diameter should be set
805 BOOST_CHECK_GT( defNc->GetViaDiameter(), 0 );
806 BOOST_CHECK_GT( defNc->GetViaDrill(), 0 );
807}
808
809
813BOOST_AUTO_TEST_CASE( Z80BoardSettings )
814{
815 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
816
817 m_plugin.LoadBoard( GetTestDataDir() + "z80_board.dip", board.get() );
818
819 BOOST_REQUIRE( board );
820
821 // Z80 board is a 2-layer board
822 BOOST_CHECK_EQUAL( board->GetCopperLayerCount(), 2 );
823
824 BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
825 std::shared_ptr<NETCLASS> defNc = bds.m_NetSettings->GetDefaultNetclass();
826
827 // Default track width should have been set from design rules
828 BOOST_CHECK_GT( defNc->GetTrackWidth(), 0 );
829}
830
831
842BOOST_AUTO_TEST_CASE( ReArmPlacementRotationsMatchDipTrace )
843{
844 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
845 m_plugin.LoadBoard( GetTestDataDir() + "re-arm_pub.dip", board.get() );
846
847 std::map<wxString, int> orient;
848
849 for( FOOTPRINT* fp : board->Footprints() )
850 orient[fp->GetReference()] = KiROUND( fp->GetOrientation().Normalize().AsDegrees() ) % 360;
851
852 const std::vector<std::pair<wxString, int>> expected = {
853 { wxT( "A1" ), 0 }, { wxT( "C1" ), 270 }, { wxT( "C2" ), 180 }, { wxT( "C12" ), 180 },
854 { wxT( "C25" ), 90 }, { wxT( "C32" ), 270 }, { wxT( "D2" ), 90 }, { wxT( "J3" ), 180 },
855 { wxT( "J5" ), 90 }, { wxT( "J7" ), 270 }, { wxT( "J12" ), 180 }, { wxT( "R2" ), 270 },
856 { wxT( "R11" ), 90 }, { wxT( "U1" ), 270 }, { wxT( "U2" ), 180 }, { wxT( "X1" ), 90 },
857 };
858
859 for( const auto& [ref, deg] : expected )
860 {
861 auto it = orient.find( ref );
862 BOOST_REQUIRE_MESSAGE( it != orient.end(), "missing footprint " << ref.ToStdString() );
863 BOOST_CHECK_MESSAGE( it->second == deg, ref.ToStdString() << " orientation " << it->second
864 << " != expected " << deg );
865 }
866
867 // The board is heavily rotated; the old defect left almost every component at 0 degrees.
868 int rotated = 0;
869
870 for( const auto& [ref, deg] : orient )
871 {
872 if( deg != 0 )
873 rotated++;
874 }
875
876 BOOST_CHECK_GT( rotated, 50 );
877}
878
const char * name
General utilities for PCB file IO for QA programs.
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
Container for design settings for a BOARD object.
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
Parses a DipTrace .dip binary board file and populates a KiCad BOARD.
int ScanLocatorUseCount() const
Number of objects or sections that were located by byte-pattern scanning rather than a deterministic ...
void Parse()
Parse the file and populate the board. Throws IO_ERROR on failure.
SHAPE_T GetShape() const
Definition eda_shape.h:185
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
std::shared_ptr< NETCLASS > GetDefaultNetclass() const
Gets the default netclass for the project.
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:61
Parser for DipTrace binary .dip board files.
@ SEGMENT
Definition eda_shape.h:46
@ Edge_Cuts
Definition layer_ids.h:108
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
@ RECTANGLE
Definition padstack.h:54
Pcbnew PCB_IO for DipTrace binary .dip board files.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_CASE(CanReadBoard)
Test that CanReadBoard correctly identifies DipTrace .dip files by their magic header bytes (0x07 "DT...
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
VECTOR3I expected(15, 30, 45)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:81
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683