KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_pads_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 (C) 2025 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
23
25#include <layer_ids.h>
26#include <padstack.h>
27#include <board.h>
28#include <pcb_text.h>
29#include <pcb_shape.h>
30#include <pcb_field.h>
31#include <pad.h>
32#include <pcb_track.h>
33#include <footprint.h>
34#include <zone.h>
36#include <pcb_dimension.h>
39#include <set>
40
41
43{
44 std::string dir;
45 std::string file;
46};
47
48
49static const PADS_BOARD_INFO PADS_BOARDS[] = {
50 { "ClaySight_MK1", "ClaySight_MK1.asc" }, // V10.0 BASIC
51 { "TMS1mmX19", "TMS1mmX19.asc" }, // V9.5 BASIC MILS
52 { "MC4_PLUS_CSHAPE", "MC4_PLUS_CSHAPE.asc" }, // V9.5 MILS
53 { "MC2_PLUS_REV1", "MC2_PLUS_REV1.asc" }, // V9.4 METRIC
54 { "Ems4_Rev2", "Ems4_Rev2.asc" }, // V9.4 MILS
55 { "LCORE_4", "LCORE_4.asc" }, // V9.0 METRIC
56 { "LCORE_2", "LCORE_2.asc" }, // V2005.0 METRIC
57 { "Dexter_MotorCtrl", "Dexter_MotorCtrl.asc" }, // V2007.0 MILS
58 { "MAIS_FC", "MAIS_FC.asc" }, // V5.0 METRIC
59 { "ClaySight_MK2", "ClaySight_MK2.asc" }, // V10.0 BASIC (copper lines)
60};
61
62
63static wxString GetBoardPath( const PADS_BOARD_INFO& aBoard )
64{
65 return KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/" + aBoard.dir + "/" + aBoard.file;
66}
67
68
72static std::unique_ptr<BOARD> LoadAndVerify( const PADS_BOARD_INFO& aBoard )
73{
74 PCB_IO_PADS plugin;
75
76 wxString filename = GetBoardPath( aBoard );
77
78 BOOST_CHECK_MESSAGE( plugin.CanReadBoard( filename ),
79 aBoard.dir << " should be a readable PADS file" );
80
81 std::unique_ptr<BOARD> board;
82
83 try
84 {
85 board.reset( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
86 }
87 catch( const std::exception& e )
88 {
89 BOOST_WARN_MESSAGE( false,
90 aBoard.dir << " threw exception during load: " << e.what() );
91 return board;
92 }
93
94 BOOST_REQUIRE_MESSAGE( board != nullptr, aBoard.dir << " failed to load" );
95 BOOST_CHECK_MESSAGE( board->Footprints().size() > 0,
96 aBoard.dir << " should have footprints" );
97
98 return board;
99}
100
101
109static void RunStructuralChecks( const PADS_BOARD_INFO& aBoard )
110{
111 std::unique_ptr<BOARD> board = LoadAndVerify( aBoard );
112
113 if( !board )
114 return;
115
116 BOOST_WARN_MESSAGE( board->Tracks().size() > 0,
117 aBoard.dir << " has no tracks (parser may not support this format version)" );
118
119 if( board->Tracks().size() > 0 && board->Footprints().size() > 0 )
120 {
121 BOX2I fpBbox;
122 fpBbox.SetMaximum();
123
124 for( FOOTPRINT* fp : board->Footprints() )
125 fpBbox.Merge( fp->GetBoundingBox() );
126
127 BOX2I trackBbox;
128 trackBbox.SetMaximum();
129
130 for( PCB_TRACK* trk : board->Tracks() )
131 trackBbox.Merge( trk->GetBoundingBox() );
132
133 BOOST_CHECK_MESSAGE( fpBbox.Intersects( trackBbox ),
134 aBoard.dir << " footprint and track bounding boxes should overlap" );
135 }
136
137 // No duplicate through-hole vias at the same position
138 std::set<std::pair<int, int>> viaPositions;
139 bool hasDuplicate = false;
140
141 for( PCB_TRACK* trk : board->Tracks() )
142 {
143 PCB_VIA* via = dynamic_cast<PCB_VIA*>( trk );
144
145 if( !via || via->GetViaType() != VIATYPE::THROUGH )
146 continue;
147
148 auto key = std::make_pair( via->GetPosition().x, via->GetPosition().y );
149
150 if( viaPositions.count( key ) )
151 {
152 hasDuplicate = true;
153 break;
154 }
155
156 viaPositions.insert( key );
157 }
158
159 BOOST_CHECK_MESSAGE( !hasDuplicate,
160 aBoard.dir << " should have no duplicate through-hole vias" );
161
162 // All imported tracks must be on copper layers
163 for( PCB_TRACK* trk : board->Tracks() )
164 {
165 if( trk->Type() == PCB_TRACE_T || trk->Type() == PCB_ARC_T )
166 {
167 BOOST_CHECK_MESSAGE( IsCopperLayer( trk->GetLayer() ),
168 aBoard.dir << " track on non-copper layer " << trk->GetLayer() );
169 }
170 }
171
172 // Pad size check uses WARN since some boards have pads the parser doesn't handle yet
173 for( FOOTPRINT* fp : board->Footprints() )
174 {
175 for( PAD* pad : fp->Pads() )
176 {
177 BOOST_WARN_MESSAGE( pad->GetSize( PADSTACK::ALL_LAYERS ).x > 0
178 && pad->GetSize( PADSTACK::ALL_LAYERS ).y > 0,
179 aBoard.dir << " " << fp->GetReference() << " pad has zero size" );
180 }
181 }
182
183 // Every zone outline must have non-empty contours
184 for( ZONE* zone : board->Zones() )
185 {
186 const SHAPE_POLY_SET* outline = zone->Outline();
187 BOOST_REQUIRE_MESSAGE( outline != nullptr,
188 aBoard.dir << " zone has null outline" );
189
190 for( int ii = 0; ii < outline->OutlineCount(); ++ii )
191 {
192 BOOST_CHECK_MESSAGE( outline->COutline( ii ).PointCount() >= 3,
193 aBoard.dir << " zone outline " << ii << " has "
194 << outline->COutline( ii ).PointCount() << " points" );
195 }
196 }
197}
198
199
200BOOST_AUTO_TEST_SUITE( PADS_IMPORT )
201
202
203BOOST_AUTO_TEST_CASE( ImportClaySight_MK1 )
204{
206}
207
208
216BOOST_AUTO_TEST_CASE( ClaySight_MK1_ElementCounts )
217{
218 std::unique_ptr<BOARD> board = LoadAndVerify( PADS_BOARDS[0] );
219
220 BOOST_REQUIRE( board != nullptr );
221
222 // Footprints: 36 parts in the *PART* section
223 BOOST_CHECK_EQUAL( board->Footprints().size(), 36 );
224
225 // Total pads across all footprints
226 int totalPads = 0;
227
228 for( FOOTPRINT* fp : board->Footprints() )
229 totalPads += fp->Pads().size();
230
231 BOOST_CHECK_EQUAL( totalPads, 140 );
232
233 // Tracks: routed signal segments from 32 *SIGNAL* sections
234 int traceCount = 0;
235 int viaCount = 0;
236
237 for( PCB_TRACK* trk : board->Tracks() )
238 {
239 if( trk->Type() == PCB_TRACE_T || trk->Type() == PCB_ARC_T )
240 traceCount++;
241 else if( trk->Type() == PCB_VIA_T )
242 viaCount++;
243 }
244
245 BOOST_CHECK_EQUAL( traceCount, 247 );
246
247 // No vias on this 2-layer board (empty *VIA* section)
248 BOOST_CHECK_EQUAL( viaCount, 0 );
249
250 // No zones (empty *POUR* section)
251 BOOST_CHECK_EQUAL( board->Zones().size(), 0 );
252
253 // Board outline on Edge.Cuts
254 int edgeCutsCount = 0;
255
256 for( BOARD_ITEM* item : board->Drawings() )
257 {
258 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
259 {
260 if( shape->GetLayer() == Edge_Cuts )
261 edgeCutsCount++;
262 }
263 }
264
265 BOOST_CHECK_EQUAL( edgeCutsCount, 6 );
266
267 // Free text items from the *TEXT* section
268 int textCount = 0;
269
270 for( BOARD_ITEM* item : board->Drawings() )
271 {
272 if( dynamic_cast<PCB_TEXT*>( item ) )
273 textCount++;
274 }
275
276 BOOST_CHECK_EQUAL( textCount, 17 );
277
278 // Net assignments: tracks and pads should reference named nets
279 std::set<wxString> trackNets;
280
281 for( PCB_TRACK* trk : board->Tracks() )
282 {
283 NETINFO_ITEM* net = trk->GetNet();
284
285 if( net && !net->GetNetname().IsEmpty() )
286 trackNets.insert( net->GetNetname() );
287 }
288
289 BOOST_CHECK_EQUAL( trackNets.size(), 32 );
290
291 // All traces on copper layers (F.Cu or B.Cu for this 2-layer board)
292 for( PCB_TRACK* trk : board->Tracks() )
293 {
294 if( trk->Type() == PCB_TRACE_T )
295 {
296 PCB_LAYER_ID layer = trk->GetLayer();
297 BOOST_CHECK_MESSAGE( layer == F_Cu || layer == B_Cu,
298 "trace on unexpected layer " << layer );
299 }
300 }
301}
302
303
304BOOST_AUTO_TEST_CASE( ImportTMS1mmX19 )
305{
307}
308
309
310BOOST_AUTO_TEST_CASE( ImportMC4_PLUS_CSHAPE )
311{
313}
314
315
316BOOST_AUTO_TEST_CASE( ImportMC2_PLUS_REV1 )
317{
319}
320
321
322BOOST_AUTO_TEST_CASE( ImportEms4_Rev2 )
323{
325}
326
327
328BOOST_AUTO_TEST_CASE( ImportLCORE_4 )
329{
331}
332
333
334BOOST_AUTO_TEST_CASE( ImportLCORE_2 )
335{
337}
338
339
340BOOST_AUTO_TEST_CASE( ImportDexter_MotorCtrl )
341{
343}
344
345
346BOOST_AUTO_TEST_CASE( ImportMAIS_FC )
347{
349}
350
351
352BOOST_AUTO_TEST_CASE( ImportNonCopperTrackSkipped )
353{
354 // Test that tracks on non-copper layers are skipped without crashing
355 PCB_IO_PADS plugin;
356
357 wxString filename = KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/synthetic_noncopper_track.asc";
358
359 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
360
361 BOOST_REQUIRE( board != nullptr );
362
363 int track_count = 0;
364
365 for( PCB_TRACK* track : board->Tracks() )
366 {
367 if( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T )
368 {
369 track_count++;
370 BOOST_CHECK( IsCopperLayer( track->GetLayer() ) );
371 }
372 }
373
374 BOOST_CHECK( track_count > 0 );
375}
376
377
378BOOST_AUTO_TEST_CASE( ImportTextOnUnmappedLayer )
379{
380 // Test that text on unmapped layers is assigned to Comments layer without crashing
381 PCB_IO_PADS plugin;
382
383 wxString filename = KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/synthetic_unmapped_text_layer.asc";
384
385 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
386
387 BOOST_REQUIRE( board != nullptr );
388
389 int silkscreen_count = 0;
390 int comments_count = 0;
391 int copper_count = 0;
392
393 for( BOARD_ITEM* item : board->Drawings() )
394 {
395 if( PCB_TEXT* text = dynamic_cast<PCB_TEXT*>( item ) )
396 {
397 PCB_LAYER_ID layer = text->GetLayer();
398
399 BOOST_CHECK( layer != UNDEFINED_LAYER );
400
401 if( layer == F_SilkS )
402 silkscreen_count++;
403 else if( layer == Cmts_User )
404 comments_count++;
405 else if( layer == F_Cu )
406 copper_count++;
407 }
408 }
409
410 BOOST_CHECK_EQUAL( silkscreen_count, 1 );
411 BOOST_CHECK_EQUAL( comments_count, 1 );
412 BOOST_CHECK_EQUAL( copper_count, 1 );
413}
414
415
416BOOST_AUTO_TEST_CASE( ImportClaySight_MK2 )
417{
419}
420
421
429BOOST_AUTO_TEST_CASE( ClaySight_MK2_ElementCounts )
430{
431 std::unique_ptr<BOARD> board = LoadAndVerify( PADS_BOARDS[9] );
432
433 BOOST_REQUIRE( board != nullptr );
434
435 // 10 parts: U1 (RPi Pico), SU1-SU8 (TO-92), U2 (ULN2003A)
436 BOOST_CHECK_EQUAL( board->Footprints().size(), 10 );
437
438 // U1=40 pads, SU1-SU8=3 each (24), U2=16 pads = 80 total
439 int totalPads = 0;
440
441 for( FOOTPRINT* fp : board->Footprints() )
442 totalPads += fp->Pads().size();
443
444 BOOST_CHECK_EQUAL( totalPads, 80 );
445
446 int traceCount = 0;
447 int viaCount = 0;
448
449 for( PCB_TRACK* trk : board->Tracks() )
450 {
451 if( trk->Type() == PCB_TRACE_T || trk->Type() == PCB_ARC_T )
452 traceCount++;
453 else if( trk->Type() == PCB_VIA_T )
454 viaCount++;
455 }
456
457 // 138 track segments from 2 *SIGNAL* route sections only
458 BOOST_CHECK_EQUAL( traceCount, 138 );
459 BOOST_CHECK_EQUAL( viaCount, 0 );
460
461 // 2 nets from *SIGNAL* routes: N$12982 and N$12975
462 std::set<wxString> trackNets;
463
464 for( PCB_TRACK* trk : board->Tracks() )
465 {
466 NETINFO_ITEM* net = trk->GetNet();
467
468 if( net && !net->GetNetname().IsEmpty() )
469 trackNets.insert( net->GetNetname() );
470 }
471
472 BOOST_CHECK_EQUAL( trackNets.size(), 2 );
473
474 // All traces on copper layers
475 for( PCB_TRACK* trk : board->Tracks() )
476 {
477 if( trk->Type() == PCB_TRACE_T )
478 {
479 BOOST_CHECK_MESSAGE( IsCopperLayer( trk->GetLayer() ),
480 "trace on non-copper layer " << trk->GetLayer() );
481 }
482 }
483
484 // 72 COPPER items on layer 126 become silkscreen graphics. 64 of these
485 // (16 groups of 4 axis-aligned segments) are detected as rectangles. The
486 // remaining 8 are individual segments. Plus 44 segments from LINES items.
487 int silkCount = 0;
488 int rectCount = 0;
489
490 for( BOARD_ITEM* item : board->Drawings() )
491 {
492 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
493 {
494 if( shape->GetLayer() == F_SilkS )
495 {
496 silkCount++;
497
498 if( shape->GetShape() == SHAPE_T::RECTANGLE )
499 rectCount++;
500 }
501 }
502 }
503
504 BOOST_CHECK_EQUAL( silkCount, 68 );
505 BOOST_CHECK_EQUAL( rectCount, 16 );
506
507 // Default via size from JMPVIA_1 definition (drill=457505, size=915010 BASIC)
508 const BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
509 std::shared_ptr<NETCLASS> defaultNc = bds.m_NetSettings->GetDefaultNetclass();
510 BOOST_CHECK( defaultNc->GetViaDiameter() > 0 );
511 BOOST_CHECK( defaultNc->GetViaDrill() > 0 );
512 BOOST_CHECK( defaultNc->GetViaDiameter() > defaultNc->GetViaDrill() );
514
515 // Copper-to-edge clearance from OUTLINE_TO_* rules (227990 BASIC)
516 BOOST_CHECK( bds.m_CopperEdgeClearance > 0 );
517
518 // Board outline on Edge.Cuts (rectangular outline = 4 segments)
519 int edgeCutsCount = 0;
520
521 for( BOARD_ITEM* item : board->Drawings() )
522 {
523 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
524 {
525 if( shape->GetLayer() == Edge_Cuts )
526 edgeCutsCount++;
527 }
528 }
529
530 BOOST_CHECK_EQUAL( edgeCutsCount, 4 );
531
532 // 1 board-level text item on silkscreen with multi-line content
533 int textCount = 0;
534 wxString textContent;
535
536 for( BOARD_ITEM* item : board->Drawings() )
537 {
538 if( item->Type() == PCB_TEXT_T )
539 {
540 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
541 textContent = text->GetText();
542 textCount++;
543 }
544 }
545
546 BOOST_CHECK_EQUAL( textCount, 1 );
547
548 // EasyEDA exports encode newlines as underscores in text content.
549 // The parser converts them back to newlines for proper multi-line display.
550 BOOST_CHECK( textContent.Contains( wxT( "\n" ) ) );
551 BOOST_CHECK( !textContent.Contains( wxT( "_" ) ) );
552 BOOST_CHECK( textContent.Contains( wxT( "CLAYSIGHT MCU V.2" ) ) );
553 BOOST_CHECK( textContent.Contains( wxT( "The Ohio State University" ) ) );
554}
555
556
563BOOST_AUTO_TEST_CASE( MAIS_FC_Stackup )
564{
565 std::unique_ptr<BOARD> board = LoadAndVerify( PADS_BOARDS[8] );
566
567 BOOST_REQUIRE( board != nullptr );
568
569 const BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
570 BOOST_CHECK( bds.m_HasStackup );
571
572 const BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
573
574 bool foundCopperThickness = false;
575 bool foundDielectric = false;
576
577 for( BOARD_STACKUP_ITEM* item : stackup.GetList() )
578 {
579 if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_COPPER )
580 {
581 if( item->GetThickness() > 0 )
582 foundCopperThickness = true;
583 }
584 else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_DIELECTRIC )
585 {
586 if( item->GetEpsilonR() > 3.0 )
587 foundDielectric = true;
588 }
589 }
590
591 BOOST_CHECK_MESSAGE( foundCopperThickness, "stackup should have non-zero copper thickness" );
592 BOOST_CHECK_MESSAGE( foundDielectric, "stackup should have dielectric constant > 3.0" );
593 BOOST_CHECK( bds.GetBoardThickness() > 0 );
594}
595
596
605BOOST_AUTO_TEST_CASE( ImportDegeneratePourSkipped )
606{
607 PCB_IO_PADS plugin;
608
609 wxString filename = KI_TEST::GetPcbnewTestDataDir()
610 + "plugins/pads/synthetic_degenerate_pour.asc";
611
612 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
613
614 BOOST_REQUIRE( board != nullptr );
615
616 // Only the valid 4-point pour should produce a zone.
617 // The PADTHERM (2 SEG pieces with 2 points each) and
618 // VIATHERM (1 SEG piece with 2 points) must be skipped.
619 BOOST_CHECK_EQUAL( board->Zones().size(), 1 );
620
621 // The single valid zone must have a non-degenerate outline
622 if( board->Zones().size() == 1 )
623 {
624 ZONE* zone = board->Zones()[0];
625 BOOST_CHECK( zone->Outline()->OutlineCount() == 1 );
626 BOOST_CHECK( zone->Outline()->COutline( 0 ).PointCount() >= 3 );
627 }
628}
629
630
639BOOST_AUTO_TEST_CASE( ImportFilledCopperSingleOutline )
640{
641 PCB_IO_PADS plugin;
642
643 wxString filename = KI_TEST::GetPcbnewTestDataDir()
644 + "plugins/pads/synthetic_filled_copper.asc";
645
646 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
647
648 BOOST_REQUIRE( board != nullptr );
649 BOOST_REQUIRE_EQUAL( board->Zones().size(), 1 );
650
651 ZONE* zone = board->Zones()[0];
652 BOOST_CHECK_EQUAL( zone->Outline()->OutlineCount(), 1 );
653 BOOST_CHECK( zone->Outline()->COutline( 0 ).PointCount() >= 3 );
654}
655
656
666BOOST_AUTO_TEST_CASE( Importer_SpecificFixes )
667{
668 PCB_IO_PADS plugin;
669
670 wxString filename = KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/Importer.asc";
671
672 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
673
674 BOOST_REQUIRE( board != nullptr );
675
676 // Bug 1: Graphics on layers 18/19/20 must be imported.
677 // The LAYERSTACK_6L_35U block has 556 pieces mostly on layer 18, plus layer 20.
678 // The DRW59706864 block has a BOARD outline on layer 0 (Edge.Cuts).
679 // Count graphics on Dwgs_User and Cmts_User to verify documentation layers imported.
680 int dwgsUserCount = 0;
681
682 for( BOARD_ITEM* item : board->Drawings() )
683 {
684 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
685 {
686 if( shape->GetLayer() == Dwgs_User )
687 dwgsUserCount++;
688 }
689 }
690
691 BOOST_CHECK_MESSAGE( dwgsUserCount > 0,
692 "graphics on PADS layer 18 (drill drawing) should map to Dwgs_User" );
693
694 // Bug 3: U1 pads should be oval (OF shape), not circular (RT thermal).
695 // U1 has part type DIO_RECT_3PH_1600V_100A with DIOB_D100JHT160V decal.
696 // PAD 0 stack has OF 0.000 11550000 on layer -2 (4.8mm height, 11.55mm width).
697 FOOTPRINT* u1 = nullptr;
698
699 for( FOOTPRINT* fp : board->Footprints() )
700 {
701 if( fp->GetReference() == wxT( "U1" ) )
702 {
703 u1 = fp;
704 break;
705 }
706 }
707
708 BOOST_REQUIRE_MESSAGE( u1 != nullptr, "U1 footprint should exist" );
709
710 bool foundOvalPad = false;
711
712 for( PAD* pad : u1->Pads() )
713 {
714 VECTOR2I padSize = pad->GetSize( PADSTACK::ALL_LAYERS );
715
716 if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::OVAL && padSize.x != padSize.y )
717 {
718 foundOvalPad = true;
719 break;
720 }
721 }
722
723 BOOST_CHECK_MESSAGE( foundOvalPad, "U1 should have oval pads (OF shape, not RT thermal)" );
724
725 // Bug 2: Copper pours should not have duplicates from HATOUT records.
726 // The file has 13 POUROUT records. HATOUT records should become fills, not zones.
727 // Count zones that are NOT rule areas (actual copper pours).
728 int pourZoneCount = 0;
729 int filledZoneCount = 0;
730
731 for( ZONE* zone : board->Zones() )
732 {
733 if( !zone->GetIsRuleArea() )
734 {
735 pourZoneCount++;
736
737 if( zone->IsFilled() )
738 filledZoneCount++;
739 }
740 }
741
742 BOOST_CHECK_MESSAGE( pourZoneCount <= 13,
743 "should not have duplicate zones from HATOUT; got " << pourZoneCount );
744
745 BOOST_CHECK_MESSAGE( filledZoneCount > 0, "HATOUT records should produce filled zones" );
746
747 // Bug 4: Dimension line should not be skewed.
748 // DIM92271615 measures 110.00mm horizontal. Start=(0,9000000) end=(165000000,1500000).
749 // After fix, both endpoints should have the same Y for horizontal measurement.
750 int dimCount = 0;
751
752 for( BOARD_ITEM* item : board->Drawings() )
753 {
754 if( PCB_DIM_ALIGNED* dim = dynamic_cast<PCB_DIM_ALIGNED*>( item ) )
755 {
756 dimCount++;
757
758 VECTOR2I start = dim->GetStart();
759 VECTOR2I end = dim->GetEnd();
760
761 BOOST_CHECK_MESSAGE( start.y == end.y,
762 "horizontal dimension endpoints should have equal Y coordinates; "
763 "start.y=" << start.y << " end.y=" << end.y );
764 }
765 }
766
767 BOOST_CHECK_MESSAGE( dimCount > 0, "should have at least one dimension" );
768}
769
770
783BOOST_AUTO_TEST_CASE( Peka_ViaImport )
784{
785 PCB_IO_PADS plugin;
786
787 wxString filename = KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/peka.asc";
788
789 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
790
791 BOOST_REQUIRE( board != nullptr );
792
793 // Collect all vias and check for duplicates
794 std::map<std::pair<int, int>, int> viaPositionCount;
795 int blindCount = 0;
796 int throughCount = 0;
797
798 // Copper pad size from the STANDARDVIA definition is 1371600 BASIC units.
799 // At BASIC_TO_NM = 25400/38100, that converts to ~914,400 nm (36 mil).
800 // The soldermask opening is 2400300 BASIC = ~1,600,200 nm (63 mil).
801 // Via size must use the copper value, not the mask opening.
802 const int maxExpectedViaWidth = 1200000; // 1.2mm, well above 36 mil copper pad
803
804 int oversizedViaCount = 0;
805
806 for( PCB_TRACK* track : board->Tracks() )
807 {
808 PCB_VIA* via = dynamic_cast<PCB_VIA*>( track );
809
810 if( !via )
811 continue;
812
813 VECTOR2I pos = via->GetPosition();
814 auto key = std::make_pair( pos.x, pos.y );
815 viaPositionCount[key]++;
816
817 if( via->GetViaType() == VIATYPE::BLIND )
818 blindCount++;
819 else if( via->GetViaType() == VIATYPE::THROUGH )
820 throughCount++;
821
822 if( via->GetWidth( F_Cu ) > maxExpectedViaWidth )
823 oversizedViaCount++;
824 }
825
826 // All vias in this 4-layer board span top-to-bottom, so none should be blind
827 BOOST_CHECK_MESSAGE( blindCount == 0,
828 "no vias should be blind; STANDARDVIA spans all copper layers; got "
829 << blindCount << " blind vias" );
830
831 BOOST_CHECK_MESSAGE( throughCount > 0, "should have through-hole vias" );
832
833 // No via should use the soldermask opening as its pad size
834 BOOST_CHECK_MESSAGE( oversizedViaCount == 0,
835 "via size should use copper pad, not soldermask opening; got "
836 << oversizedViaCount << " oversized vias" );
837
838 // No duplicate vias at the same position
839 int duplicateCount = 0;
840
841 for( const auto& [pos, count] : viaPositionCount )
842 {
843 if( count > 1 )
844 duplicateCount++;
845 }
846
847 BOOST_CHECK_MESSAGE( duplicateCount == 0,
848 "should not have duplicate vias at the same position; got "
849 << duplicateCount << " positions with duplicates" );
850
851 // STANDARDVIA has a layer 25 (front mask) entry but no layer 28 (back mask),
852 // so the back should be tented. JMPVIA has no mask layers at all.
853 // At minimum, every via should have the back tented.
854 int backTentedCount = 0;
855 int totalVias = 0;
856
857 for( PCB_TRACK* track : board->Tracks() )
858 {
859 PCB_VIA* via = dynamic_cast<PCB_VIA*>( track );
860
861 if( !via )
862 continue;
863
864 totalVias++;
865
866 if( via->GetBackTentingMode() == TENTING_MODE::TENTED )
867 backTentedCount++;
868 }
869
870 BOOST_CHECK_MESSAGE( backTentedCount == totalVias,
871 "vias without soldermask opening should be tented; "
872 << backTentedCount << " of " << totalVias << " back-tented" );
873}
874
875
884BOOST_AUTO_TEST_CASE( Importer_OvalDrillHits )
885{
886 PCB_IO_PADS plugin;
887
888 wxString filename = KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/Importer.asc";
889
890 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
891
892 BOOST_REQUIRE( board != nullptr );
893
894 FOOTPRINT* u1 = nullptr;
895
896 for( FOOTPRINT* fp : board->Footprints() )
897 {
898 if( fp->GetReference() == "U1" )
899 {
900 u1 = fp;
901 break;
902 }
903 }
904
905 BOOST_REQUIRE_MESSAGE( u1, "U1 not found on board" );
906
907 // BASIC-to-nm: value * 25400 / 38100 = value * 2/3
908 // drill = 2250000 BASIC -> 1500000 nm (1.5mm)
909 // slot_length = 9000000 BASIC -> 6000000 nm (6.0mm)
910 const int expectedMajor = 6000000;
911 const int expectedMinor = 1500000;
912 const int tolerance = 10000; // 10um
913
914 int oblongCount = 0;
915
916 for( PAD* pad : u1->Pads() )
917 {
918 wxString padNum = pad->GetNumber();
919
920 if( padNum == "1" || padNum == "2" || padNum == "3"
921 || padNum == "4" || padNum == "5" )
922 {
924 "pad " << padNum << " should have oblong drill" );
925
926 VECTOR2I drillSize = pad->GetDrillSize();
927 int major = std::max( drillSize.x, drillSize.y );
928 int minor = std::min( drillSize.x, drillSize.y );
929
930 BOOST_CHECK_MESSAGE( std::abs( major - expectedMajor ) < tolerance,
931 "pad " << padNum << " drill major axis " << major
932 << " should be ~" << expectedMajor );
933
934 BOOST_CHECK_MESSAGE( std::abs( minor - expectedMinor ) < tolerance,
935 "pad " << padNum << " drill minor axis " << minor
936 << " should be ~" << expectedMinor );
937
938 if( pad->GetDrillShape() == PAD_DRILL_SHAPE::OBLONG )
939 oblongCount++;
940 }
941 }
942
943 BOOST_CHECK_MESSAGE( oblongCount == 5,
944 "expected 5 pads with oblong drill, got " << oblongCount );
945}
946
947
956BOOST_AUTO_TEST_CASE( Peka_AlternateDecalDrill )
957{
958 PCB_IO_PADS plugin;
959
960 wxString filename = KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/peka.asc";
961
962 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
963
964 BOOST_REQUIRE( board != nullptr );
965
966 FOOTPRINT* m4 = nullptr;
967
968 for( FOOTPRINT* fp : board->Footprints() )
969 {
970 if( fp->GetReference() == "M4" )
971 {
972 m4 = fp;
973 break;
974 }
975 }
976
977 BOOST_REQUIRE_MESSAGE( m4, "M4 not found on board" );
978
979 // MTHOLEAAAB: pad = 9525000 BASIC * 2/3 = 6350000 nm (250 mil)
980 // drill = 4762500 BASIC * 2/3 = 3175000 nm (125 mil)
981 const int expectedPadSize = 6350000;
982 const int expectedDrill = 3175000;
983 const int tolerance = 10000;
984
985 BOOST_REQUIRE_MESSAGE( m4->Pads().size() == 1,
986 "MTHOLEAAAB has 1 terminal; got " << m4->Pads().size() );
987
988 PAD* pad = m4->Pads().front();
989
991 "M4 pad 1 drill should be circular" );
992
993 VECTOR2I padSize = pad->GetSize( F_Cu );
994 int padDim = std::max( padSize.x, padSize.y );
995
996 BOOST_CHECK_MESSAGE( std::abs( padDim - expectedPadSize ) < tolerance,
997 "M4 pad size " << padDim << " should be ~" << expectedPadSize
998 << " (250 mil)" );
999
1000 VECTOR2I drillSize = pad->GetDrillSize();
1001 int drillDim = std::max( drillSize.x, drillSize.y );
1002
1003 BOOST_CHECK_MESSAGE( std::abs( drillDim - expectedDrill ) < tolerance,
1004 "M4 drill size " << drillDim << " should be ~" << expectedDrill
1005 << " (125 mil)" );
1006}
1007
1008
1017BOOST_AUTO_TEST_CASE( Peka_ZoneFillNoSelfIntersection )
1018{
1019 PCB_IO_PADS plugin;
1020
1021 wxString filename = KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/peka.asc";
1022
1023 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
1024
1025 BOOST_REQUIRE( board != nullptr );
1026
1027 // Check whether any two non-adjacent segments in the polygon truly
1028 // cross. Clipper2 BooleanSubtract can produce bridge edges where
1029 // non-adjacent segments share endpoints at T-junctions. These are
1030 // valid geometry, not real crossings.
1031 auto hasTrueCrossing = []( const SHAPE_POLY_SET& aPoly, int aIdx ) -> bool
1032 {
1033 std::vector<SEG> segs;
1034
1035 for( auto it = aPoly.CIterateSegmentsWithHoles( aIdx ); it; it++ )
1036 segs.emplace_back( *it );
1037
1038 for( size_t i = 0; i < segs.size(); i++ )
1039 {
1040 for( size_t j = i + 1; j < segs.size(); j++ )
1041 {
1042 // Segments sharing any endpoint are either adjacent in the
1043 // contour or bridge junctions from Clipper2.
1044 if( segs[i].A == segs[j].A || segs[i].A == segs[j].B
1045 || segs[i].B == segs[j].A || segs[i].B == segs[j].B )
1046 {
1047 continue;
1048 }
1049
1050 if( segs[i].Intersects( segs[j] ) )
1051 return true;
1052 }
1053 }
1054
1055 return false;
1056 };
1057
1058 int zonesChecked = 0;
1059
1060 for( ZONE* zone : board->Zones() )
1061 {
1062 if( !zone->IsFilled() )
1063 continue;
1064
1065 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
1066 {
1067 if( !zone->HasFilledPolysForLayer( layer ) )
1068 continue;
1069
1070 std::shared_ptr<SHAPE_POLY_SET> fill = zone->GetFilledPolysList( layer );
1071
1072 if( !fill || fill->OutlineCount() == 0 )
1073 continue;
1074
1075 zonesChecked++;
1076
1077 for( int pi = 0; pi < fill->OutlineCount(); pi++ )
1078 {
1079 if( fill->Outline( pi ).PointCount() < 3 )
1080 continue;
1081
1083 !hasTrueCrossing( *fill, pi ),
1084 "zone \"" << zone->GetNetname() << "\" on "
1085 << board->GetLayerName( layer )
1086 << " outline " << pi
1087 << " has self-intersecting fill polygon" );
1088 }
1089 }
1090 }
1091
1092 BOOST_CHECK_MESSAGE( zonesChecked > 0, "no filled zones found to check" );
1093}
1094
1095
1110BOOST_AUTO_TEST_CASE( ImportMaskPasteLayers )
1111{
1112 PCB_IO_PADS plugin;
1113
1114 wxString filename =
1115 KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/synthetic_mask_paste.asc";
1116
1117 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
1118
1119 BOOST_REQUIRE( board != nullptr );
1120 BOOST_REQUIRE_EQUAL( board->Footprints().size(), 5 );
1121
1122 auto findFP = [&]( const wxString& aRef ) -> FOOTPRINT*
1123 {
1124 for( FOOTPRINT* fp : board->Footprints() )
1125 if( fp->GetReference() == aRef )
1126 return fp;
1127 return nullptr;
1128 };
1129
1130 // U1: explicit F.Mask (layer 21) and F.Paste (layer 23) in pad stack
1131 {
1132 FOOTPRINT* u1 = findFP( "U1" );
1133 BOOST_REQUIRE_MESSAGE( u1, "U1 should exist" );
1134 BOOST_REQUIRE_EQUAL( u1->Pads().size(), 1 );
1135
1136 PAD* pad = u1->Pads().front();
1137 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Cu ), "U1 pad should be on F.Cu" );
1138 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Mask ), "U1 pad should have F.Mask (explicit in stack)" );
1139 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Paste ), "U1 pad should have F.Paste (explicit in stack)" );
1140 BOOST_CHECK_MESSAGE( !pad->IsOnLayer( B_Cu ), "U1 SMD pad should not be on B.Cu" );
1141 }
1142
1143 // U2: explicit F.Mask (layer 21) only; F.Paste added by fallback
1144 {
1145 FOOTPRINT* u2 = findFP( "U2" );
1146 BOOST_REQUIRE_MESSAGE( u2, "U2 should exist" );
1147 BOOST_REQUIRE_EQUAL( u2->Pads().size(), 1 );
1148
1149 PAD* pad = u2->Pads().front();
1150 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Cu ), "U2 pad should be on F.Cu" );
1151 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Mask ), "U2 pad should have F.Mask (explicit in stack)" );
1152 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Paste ), "U2 pad should have F.Paste (fallback for SMD)" );
1153 BOOST_CHECK_MESSAGE( !pad->IsOnLayer( B_Cu ), "U2 SMD pad should not be on B.Cu" );
1154 }
1155
1156 // U3: no mask entries; both F.Mask and F.Paste added by fallback
1157 {
1158 FOOTPRINT* u3 = findFP( "U3" );
1159 BOOST_REQUIRE_MESSAGE( u3, "U3 should exist" );
1160 BOOST_REQUIRE_EQUAL( u3->Pads().size(), 1 );
1161
1162 PAD* pad = u3->Pads().front();
1163 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Cu ), "U3 pad should be on F.Cu" );
1164 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Mask ), "U3 pad should have F.Mask (SMD fallback)" );
1165 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Paste ), "U3 pad should have F.Paste (SMD fallback)" );
1166 BOOST_CHECK_MESSAGE( !pad->IsOnLayer( B_Cu ), "U3 SMD pad should not be on B.Cu" );
1167 }
1168
1169 // U4: PTH pad with explicit F.Mask (layer 21) and B.Mask (layer 28).
1170 // No paste layers are present in the stack, so F.Paste/B.Paste must not be set.
1171 {
1172 FOOTPRINT* u4 = findFP( "U4" );
1173 BOOST_REQUIRE_MESSAGE( u4, "U4 should exist" );
1174 BOOST_REQUIRE_EQUAL( u4->Pads().size(), 1 );
1175
1176 PAD* pad = u4->Pads().front();
1177 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Cu ), "U4 PTH pad should be on F.Cu" );
1178 BOOST_CHECK_MESSAGE( pad->IsOnLayer( B_Cu ), "U4 PTH pad should be on B.Cu" );
1179 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Mask ), "U4 pad should have F.Mask (explicit in stack)" );
1180 BOOST_CHECK_MESSAGE( pad->IsOnLayer( B_Mask ), "U4 pad should have B.Mask (explicit in stack)" );
1181 BOOST_CHECK_MESSAGE( !pad->IsOnLayer( F_Paste ), "U4 PTH pad should not have F.Paste" );
1182 }
1183
1184 // U5: SMD pad with explicit zero-size F.Paste entry (layer 23 size 0).
1185 // A zero-size entry means "intentionally no paste on this layer".
1186 // The SMD fallback must not re-enable F.Paste for this pad.
1187 {
1188 FOOTPRINT* u5 = findFP( "U5" );
1189 BOOST_REQUIRE_MESSAGE( u5, "U5 should exist" );
1190 BOOST_REQUIRE_EQUAL( u5->Pads().size(), 1 );
1191
1192 PAD* pad = u5->Pads().front();
1193 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Cu ), "U5 pad should be on F.Cu" );
1194 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Mask ), "U5 pad should have F.Mask (SMD fallback)" );
1195 BOOST_CHECK_MESSAGE( !pad->IsOnLayer( F_Paste ), "U5 pad should NOT have F.Paste (explicitly zero-size)" );
1196 BOOST_CHECK_MESSAGE( !pad->IsOnLayer( B_Cu ), "U5 SMD pad should not be on B.Cu" );
1197 }
1198}
1199
1200
1205BOOST_AUTO_TEST_CASE( ImportMaskPasteLayersIssue23254 )
1206{
1207 PCB_IO_PADS plugin;
1208
1209 wxString filename =
1210 KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/issue23254/issue23254.asc";
1211
1212 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
1213
1214 BOOST_REQUIRE( board != nullptr );
1215
1216 bool foundSmdWithMask = false;
1217
1218 for( FOOTPRINT* fp : board->Footprints() )
1219 {
1220 for( PAD* pad : fp->Pads() )
1221 {
1222 if( pad->GetAttribute() == PAD_ATTRIB::SMD && pad->IsOnLayer( F_Mask )
1223 && pad->IsOnLayer( F_Paste ) )
1224 {
1225 foundSmdWithMask = true;
1226 break;
1227 }
1228 }
1229
1230 if( foundSmdWithMask )
1231 break;
1232 }
1233
1234 BOOST_CHECK_MESSAGE( foundSmdWithMask,
1235 "At least one SMD pad in issue23254.asc should have F.Mask and F.Paste" );
1236}
1237
1238
1243BOOST_AUTO_TEST_CASE( ImportIssue23352 )
1244{
1245 PCB_IO_PADS plugin;
1246 wxString filename = KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/issue23352.asc";
1247
1248 std::unique_ptr<BOARD> board;
1249 board.reset( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
1250 BOOST_REQUIRE( board != nullptr );
1251
1252 // Issue 1: Square pads should be imported as RECTANGLE, not CIRCLE.
1253 // The CON_2X1M part has PAD 1 with shape "S" (square) on F_Cu.
1254 bool foundSquarePad = false;
1255
1256 for( FOOTPRINT* fp : board->Footprints() )
1257 {
1258 for( PAD* pad : fp->Pads() )
1259 {
1260 if( pad->GetShape( F_Cu ) == PAD_SHAPE::RECTANGLE )
1261 {
1262 foundSquarePad = true;
1263 break;
1264 }
1265 }
1266
1267 if( foundSquarePad )
1268 break;
1269 }
1270
1271 BOOST_CHECK_MESSAGE( foundSquarePad,
1272 "At least one pad should have RECTANGLE shape (square pad import)" );
1273
1274 // Issue 2: Zone connection should default to FULL (solid), not THERMAL.
1275 // Pads with RT/ST entries should have per-pad THERMAL override.
1276 bool foundZoneWithFull = false;
1277 bool foundPadWithThermal = false;
1278 bool foundPadWithoutThermal = false;
1279
1280 for( ZONE* zone : board->Zones() )
1281 {
1282 if( zone->GetPadConnection() == ZONE_CONNECTION::FULL )
1283 {
1284 foundZoneWithFull = true;
1285 break;
1286 }
1287 }
1288
1289 BOOST_CHECK_MESSAGE( foundZoneWithFull,
1290 "Zones should default to FULL (solid) connection" );
1291
1292 for( FOOTPRINT* fp : board->Footprints() )
1293 {
1294 for( PAD* pad : fp->Pads() )
1295 {
1296 if( pad->GetLocalZoneConnection() == ZONE_CONNECTION::THERMAL )
1297 foundPadWithThermal = true;
1298 else
1299 foundPadWithoutThermal = true;
1300 }
1301 }
1302
1303 BOOST_CHECK_MESSAGE( foundPadWithThermal,
1304 "Pads with RT/ST entries should have per-pad THERMAL connection" );
1305 BOOST_CHECK_MESSAGE( foundPadWithoutThermal,
1306 "Pads without RT/ST entries should not have per-pad THERMAL override" );
1307
1308 // Issue 3: Netclasses should be imported with their rules.
1309 const BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
1310 const auto& netclasses = bds.m_NetSettings->GetNetclasses();
1311
1312 auto nc1It = netclasses.find( wxT( "NETTCLASS1" ) );
1313 auto nc2It = netclasses.find( wxT( "NETTCLASS2" ) );
1314
1315 BOOST_CHECK_MESSAGE( nc1It != netclasses.end(), "NETTCLASS1 should exist" );
1316 BOOST_CHECK_MESSAGE( nc2It != netclasses.end(), "NETTCLASS2 should exist" );
1317
1318 if( nc1It != netclasses.end() )
1319 {
1320 BOOST_CHECK_MESSAGE( nc1It->second->HasTrackWidth(),
1321 "NETTCLASS1 should have a track width rule" );
1322 }
1323
1324 if( nc2It != netclasses.end() )
1325 {
1326 BOOST_CHECK_MESSAGE( nc2It->second->HasTrackWidth(),
1327 "NETTCLASS2 should have a track width rule" );
1328 BOOST_CHECK_MESSAGE( nc2It->second->HasClearance(),
1329 "NETTCLASS2 should have a clearance rule" );
1330 }
1331
1332 // Verify net-to-class assignments from the NET_CLASS DATA block
1333 const auto& patterns = bds.m_NetSettings->GetNetclassPatternAssignments();
1334 std::map<wxString, wxString> netAssignments;
1335
1336 for( const auto& [matcher, ncName] : patterns )
1337 netAssignments[matcher->GetPattern()] = ncName;
1338
1339 BOOST_CHECK_MESSAGE( netAssignments.count( wxT( "+24V0" ) ),
1340 "+24V0 should be assigned to a net class" );
1341 BOOST_CHECK_MESSAGE( netAssignments.count( wxT( "+24V0_FILTER" ) ),
1342 "+24V0_FILTER should be assigned to a net class" );
1343 BOOST_CHECK_MESSAGE( netAssignments.count( wxT( "+24V0_FILTER_RTN" ) ),
1344 "+24V0_FILTER_RTN should be assigned to a net class" );
1345
1346 if( netAssignments.count( wxT( "+24V0" ) ) )
1347 {
1348 BOOST_CHECK_EQUAL( netAssignments[wxT( "+24V0" )], wxT( "NETTCLASS1" ) );
1349 }
1350
1351 if( netAssignments.count( wxT( "+24V0_FILTER_RTN" ) ) )
1352 {
1353 BOOST_CHECK_EQUAL( netAssignments[wxT( "+24V0_FILTER_RTN" )], wxT( "NETTCLASS2" ) );
1354 }
1355}
1356
1357
1366BOOST_AUTO_TEST_CASE( Issue23393_NetClassImport )
1367{
1368 PCB_IO_PADS plugin;
1369 wxString filename = KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/issue23393/demo.asc";
1370
1371 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
1372 BOOST_REQUIRE( board != nullptr );
1373
1374 const BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
1375 const auto& netclasses = bds.m_NetSettings->GetNetclasses();
1376
1377 BOOST_CHECK_MESSAGE( netclasses.find( wxT( "NETTCLASS1" ) ) != netclasses.end(),
1378 "NETTCLASS1 should be imported" );
1379 BOOST_CHECK_MESSAGE( netclasses.find( wxT( "NETTCLASS2" ) ) != netclasses.end(),
1380 "NETTCLASS2 should be imported" );
1381
1382 // Verify net-to-class assignments
1383 const auto& patterns = bds.m_NetSettings->GetNetclassPatternAssignments();
1384 std::map<wxString, wxString> netAssignments;
1385
1386 for( const auto& [matcher, ncName] : patterns )
1387 netAssignments[matcher->GetPattern()] = ncName;
1388
1389 // NETTCLASS1 should contain +24V0 and +24V0_FILTER
1390 BOOST_CHECK_EQUAL( netAssignments[wxT( "+24V0" )], wxT( "NETTCLASS1" ) );
1391 BOOST_CHECK_EQUAL( netAssignments[wxT( "+24V0_FILTER" )], wxT( "NETTCLASS1" ) );
1392
1393 // NETTCLASS2 should contain +24V0_FILTER_RTN, +24V0_RTN, GND_CHASSIS
1394 BOOST_CHECK_EQUAL( netAssignments[wxT( "+24V0_FILTER_RTN" )], wxT( "NETTCLASS2" ) );
1395 BOOST_CHECK_EQUAL( netAssignments[wxT( "+24V0_RTN" )], wxT( "NETTCLASS2" ) );
1396 BOOST_CHECK_EQUAL( netAssignments[wxT( "GND_CHASSIS" )], wxT( "NETTCLASS2" ) );
1397
1398 // NETTCLASS2 RULE_SET has TRACK_TO_TRACK 4500000 BASIC
1399 auto nc2It = netclasses.find( wxT( "NETTCLASS2" ) );
1400
1401 if( nc2It != netclasses.end() )
1402 {
1403 BOOST_CHECK_MESSAGE( nc2It->second->HasClearance(),
1404 "NETTCLASS2 should have clearance from RULE_SET" );
1405 BOOST_CHECK_MESSAGE( nc2It->second->HasTrackWidth(),
1406 "NETTCLASS2 should have track width from RULE_SET" );
1407 }
1408}
1409
1410
1418BOOST_AUTO_TEST_CASE( Issue23540_RouteArcSemicircle )
1419{
1420 PCB_IO_PADS plugin;
1421
1422 wxString filename = KI_TEST::GetPcbnewTestDataDir()
1423 + "plugins/pads/issue23540/test_import.asc";
1424
1425 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
1426
1427 BOOST_REQUIRE( board != nullptr );
1428
1429 int arcCount = 0;
1430
1431 for( PCB_TRACK* trk : board->Tracks() )
1432 {
1433 if( trk->Type() != PCB_ARC_T )
1434 continue;
1435
1436 PCB_ARC* arc = static_cast<PCB_ARC*>( trk );
1437 arcCount++;
1438
1439 EDA_ANGLE angle = arc->GetAngle();
1440 double absDeg = std::abs( angle.AsDegrees() );
1441
1442 BOOST_CHECK_MESSAGE( absDeg > 170.0 && absDeg < 190.0,
1443 "route arc angle " << absDeg << " should be ~180 degrees (semicircle)" );
1444
1445 VECTOR2I mid = arc->GetMid();
1446 VECTOR2I start = arc->GetStart();
1447 VECTOR2I end = arc->GetEnd();
1448
1449 // In PADS the CW arc from left to right goes upward. After the Y-axis
1450 // flip to KiCad coordinates, "upward on screen" means smaller Y values.
1451 // The arc midpoint Y must be less than both endpoint Y values.
1452 int chordY = ( start.y + end.y ) / 2;
1453
1454 BOOST_CHECK_MESSAGE( mid.y < chordY,
1455 "arc midpoint Y=" << mid.y << " should be above (less than) "
1456 "chord center Y=" << chordY );
1457 }
1458
1459 BOOST_CHECK_MESSAGE( arcCount >= 1,
1460 "expected at least 1 PCB_ARC from route CW/CCW arc, got " << arcCount );
1461}
1462
1463
1479BOOST_AUTO_TEST_CASE( ImportFingerPadOffsetIssue23425 )
1480{
1481 PCB_IO_PADS plugin;
1482
1483 wxString filename = KI_TEST::GetPcbnewTestDataDir()
1484 + "plugins/pads/issue23425/controlCARDDockingStation.asc";
1485
1486 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
1487
1488 BOOST_REQUIRE( board != nullptr );
1489
1490 FOOTPRINT* j3 = nullptr;
1491
1492 for( FOOTPRINT* fp : board->Footprints() )
1493 {
1494 if( fp->GetReference() == wxT( "J3" ) )
1495 {
1496 j3 = fp;
1497 break;
1498 }
1499 }
1500
1501 BOOST_REQUIRE_MESSAGE( j3, "J3 (HSEC8 edge connector) not found on board" );
1502
1503 // FINOFFSET 1143000 BASIC units * (25400 / 38100) = 762000 nm (30 mil)
1504 const int expectedOffset = 762000;
1505 const int tolerance = 1000; // 1um
1506
1507 int offsetPadCount = 0;
1508
1509 for( PAD* pad : j3->Pads() )
1510 {
1511 VECTOR2I offset = pad->GetOffset( F_Cu );
1512
1513 if( offset == VECTOR2I( 0, 0 ) )
1514 continue;
1515
1516 offsetPadCount++;
1517
1518 // Stored unrotated in pad-local space: all magnitude on X, none on Y.
1519 BOOST_CHECK_MESSAGE( offset.y == 0,
1520 "J3 pad " << pad->GetNumber()
1521 << " offset Y should be 0 (unrotated pad-local), got " << offset.y );
1522
1523 BOOST_CHECK_MESSAGE( std::abs( std::abs( offset.x ) - expectedOffset ) < tolerance,
1524 "J3 pad " << pad->GetNumber()
1525 << " offset X magnitude " << std::abs( offset.x )
1526 << " should be ~" << expectedOffset );
1527 }
1528
1529 // The connector's signal fingers all carry the offset; before the fix none of
1530 // them satisfied the checks above. Require a substantial number so a parser
1531 // change that stops applying the offset entirely cannot pass silently.
1532 BOOST_CHECK_MESSAGE( offsetPadCount >= 90,
1533 "expected the HSEC8 finger pads to carry a finger offset; got "
1534 << offsetPadCount );
1535}
1536
1537
1549BOOST_AUTO_TEST_CASE( ImportIssue23391 )
1550{
1551 PCB_IO_PADS plugin;
1552
1553 wxString filename = KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/issue23391.asc";
1554
1555 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
1556
1557 BOOST_REQUIRE( board != nullptr );
1558
1559 // Collect mounting-hole footprints (MTG_HOLE_TYPE: E1/E2 top, E3/E4 bottom)
1560 // and connector footprints (SQ_PIN1_TYPE: X1 top, X2 bottom).
1561 PAD* mtg_top_pad = nullptr;
1562 PAD* mtg_bot_pad = nullptr;
1563 PAD* conn_top_pin1 = nullptr;
1564 PAD* conn_bot_pin1 = nullptr;
1565
1566 for( FOOTPRINT* fp : board->Footprints() )
1567 {
1568 wxString ref = fp->GetReference();
1569
1570 for( PAD* pad : fp->Pads() )
1571 {
1572 if( ref == "E1" )
1573 mtg_top_pad = pad;
1574 else if( ref == "E3" )
1575 mtg_bot_pad = pad;
1576 else if( ref == "X1" && pad->GetNumber() == "1" )
1577 conn_top_pin1 = pad;
1578 else if( ref == "X2" && pad->GetNumber() == "1" )
1579 conn_bot_pin1 = pad;
1580 }
1581 }
1582
1583 BOOST_REQUIRE_MESSAGE( mtg_top_pad, "E1 (top mounting hole) not found" );
1584 BOOST_REQUIRE_MESSAGE( mtg_bot_pad, "E3 (bottom mounting hole) not found" );
1585 BOOST_REQUIRE_MESSAGE( conn_top_pin1, "X1 pin 1 (top connector square pad) not found" );
1586 BOOST_REQUIRE_MESSAGE( conn_bot_pin1, "X2 pin 1 (bottom connector square pad) not found" );
1587
1588 // Same-shape / different-size padstack must remain in NORMAL mode.
1590 mtg_top_pad->Padstack().Mode() == PADSTACK::MODE::NORMAL,
1591 "Mounting hole with same shape but different sizes should use NORMAL padstack mode" );
1592
1594 mtg_bot_pad->Padstack().Mode() == PADSTACK::MODE::NORMAL,
1595 "Bottom-placed mounting hole should also use NORMAL padstack mode" );
1596
1597 // Both instances of the same footprint must have identical pad sizes.
1598 VECTOR2I top_size = mtg_top_pad->GetSize( F_Cu );
1599 VECTOR2I bot_size = mtg_bot_pad->GetSize( F_Cu );
1600
1602 top_size == bot_size,
1603 "Top and bottom mounting holes should have equal pad size on F_Cu; "
1604 "top=" << top_size.x << " bot=" << bot_size.x );
1605
1606 // The primary (layer -2) size must be used, not the secondary (layer -1) size.
1607 // In the test file layer -2 = 200 mils and layer -1 = 150 mils.
1608 // At 1 mil = 25400 nm, 200 mils = 5080000 nm.
1610 top_size.x > 0,
1611 "Mounting hole pad size must be non-zero" );
1612
1613 // Different-shape padstack (square vs round) must still use FRONT_INNER_BACK.
1615 conn_top_pin1->Padstack().Mode() == PADSTACK::MODE::FRONT_INNER_BACK,
1616 "Connector pin-1 with square-on-top / round-on-bottom must use FRONT_INNER_BACK" );
1617
1618 // Square (RECTANGLE) shape must appear on F_Cu for the top-placed connector.
1620 conn_top_pin1->GetShape( F_Cu ) == PAD_SHAPE::RECTANGLE,
1621 "Top connector pin-1 must have RECTANGLE shape on F_Cu" );
1622
1624 conn_top_pin1->GetShape( B_Cu ) == PAD_SHAPE::CIRCLE,
1625 "Top connector pin-1 must have CIRCLE shape on B_Cu" );
1626
1627 // After Flip, the bottom-placed connector pin-1 must have the shapes swapped.
1629 conn_bot_pin1->GetShape( F_Cu ) == PAD_SHAPE::CIRCLE,
1630 "Bottom connector pin-1 must have CIRCLE shape on F_Cu after flip" );
1631
1633 conn_bot_pin1->GetShape( B_Cu ) == PAD_SHAPE::RECTANGLE,
1634 "Bottom connector pin-1 must have RECTANGLE shape on B_Cu after flip" );
1635}
1636
1637
1654BOOST_AUTO_TEST_CASE( InCircuitTestPointImport )
1655{
1656 PCB_IO_PADS plugin;
1657
1658 wxString filename =
1659 KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/synthetic_testpoint.asc";
1660
1661 std::unique_ptr<BOARD> board( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
1662
1663 BOOST_REQUIRE( board != nullptr );
1664
1665 // R1 (1 part) + 2 test point footprints = 3 total
1666 BOOST_CHECK_EQUAL( board->Footprints().size(), 3u );
1667
1668 FOOTPRINT* tpBottom = nullptr;
1669 FOOTPRINT* tpTop = nullptr;
1670
1671 for( FOOTPRINT* fp : board->Footprints() )
1672 {
1673 if( fp->GetValue() == wxT( "TP_BOTTOM_SMD" ) )
1674 tpBottom = fp;
1675 else if( fp->GetValue() == wxT( "TP_TOP_SMD" ) )
1676 tpTop = fp;
1677 }
1678
1679 // TP_BOTTOM_SMD: stack has soldermask bottom (layer 28) -> must land on B.Cu
1680 BOOST_REQUIRE_MESSAGE( tpBottom, "TP_BOTTOM_SMD test point footprint should exist" );
1681 BOOST_CHECK_EQUAL( tpBottom->Pads().size(), 1u );
1682
1683 if( tpBottom->Pads().size() == 1 )
1684 {
1685 PAD* pad = tpBottom->Pads().front();
1686 BOOST_CHECK_MESSAGE( pad->IsOnLayer( B_Cu ),
1687 "TP_BOTTOM_SMD pad should be on B.Cu" );
1688 BOOST_CHECK_MESSAGE( !pad->IsOnLayer( F_Cu ),
1689 "TP_BOTTOM_SMD pad should not be on F.Cu" );
1690
1691 // Pad size: 1200000 BASIC * 2/3 = 800000 nm (0.8mm). Allow 5% tolerance.
1692 int padSize = pad->GetSize( PADSTACK::ALL_LAYERS ).x;
1693 BOOST_CHECK_MESSAGE( padSize > 700000 && padSize < 900000,
1694 "TP_BOTTOM_SMD pad size " << padSize << " should be ~800000 nm" );
1695 }
1696
1697 // TP_TOP_SMD: explicit top copper pad (layer -2) -> must land on F.Cu
1698 BOOST_REQUIRE_MESSAGE( tpTop, "TP_TOP_SMD test point footprint should exist" );
1699 BOOST_CHECK_EQUAL( tpTop->Pads().size(), 1u );
1700
1701 if( tpTop->Pads().size() == 1 )
1702 {
1703 PAD* pad = tpTop->Pads().front();
1704 BOOST_CHECK_MESSAGE( pad->IsOnLayer( F_Cu ),
1705 "TP_TOP_SMD pad should be on F.Cu" );
1706 BOOST_CHECK_MESSAGE( !pad->IsOnLayer( B_Cu ),
1707 "TP_TOP_SMD pad should not be on B.Cu" );
1708
1709 int padSize = pad->GetSize( PADSTACK::ALL_LAYERS ).x;
1710 BOOST_CHECK_MESSAGE( padSize > 700000 && padSize < 900000,
1711 "TP_TOP_SMD pad size " << padSize << " should be ~800000 nm" );
1712 }
1713
1714 // Test point positions must NOT also appear as bare PCB_VIA objects.
1715 // Before the fix, loadTracksAndVias() placed a PCB_VIA at each test point
1716 // position, creating a duplicate and causing DRC open-connection errors.
1717 VECTOR2I tpBottomPos( 0, 0 );
1718 VECTOR2I tpTopPos( 0, 0 );
1719
1720 if( tpBottom )
1721 tpBottomPos = tpBottom->GetPosition();
1722
1723 if( tpTop )
1724 tpTopPos = tpTop->GetPosition();
1725
1726 for( PCB_TRACK* trk : board->Tracks() )
1727 {
1728 PCB_VIA* via = dynamic_cast<PCB_VIA*>( trk );
1729
1730 if( !via )
1731 continue;
1732
1733 VECTOR2I pos = via->GetPosition();
1734
1735 BOOST_CHECK_MESSAGE( pos != tpBottomPos,
1736 "TP_BOTTOM_SMD position should not have a bare PCB_VIA" );
1737 BOOST_CHECK_MESSAGE( pos != tpTopPos,
1738 "TP_TOP_SMD position should not have a bare PCB_VIA" );
1739 }
1740}
1741
1742
1753BOOST_AUTO_TEST_CASE( Issue23856_TextAndPadOrientation )
1754{
1755 PCB_IO_PADS plugin;
1756 wxString filename = KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/issue23856.asc";
1757
1758 std::unique_ptr<BOARD> board;
1759 board.reset( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
1760 BOOST_REQUIRE( board != nullptr );
1761
1762 // Issue 1 + 3: free text with the copyright character must survive import,
1763 // and back-side text must be mirrored.
1764 int copyrightCount = 0;
1765 bool frontCopyrightNotMirrored = false;
1766 bool backCopyrightMirrored = false;
1767
1768 for( BOARD_ITEM* item : board->Drawings() )
1769 {
1770 PCB_TEXT* t = dynamic_cast<PCB_TEXT*>( item );
1771
1772 if( !t )
1773 continue;
1774
1775 // No imported free text should be empty (empty == dropped on decode).
1776 BOOST_CHECK_MESSAGE( !t->GetText().IsEmpty(),
1777 "imported free text should not be empty" );
1778
1779 if( t->GetText().Contains( wxT( "TEXMATE" ) ) )
1780 {
1781 copyrightCount++;
1782
1783 // Copyright sign previously broke the UTF-8 decode.
1784 BOOST_CHECK_MESSAGE( t->GetText().Contains( wxString::FromUTF8( "©" ) ),
1785 "copyright text should retain the (c) character" );
1786
1787 if( t->GetLayer() == F_SilkS )
1788 {
1790 "front silkscreen text should not be mirrored" );
1791 frontCopyrightNotMirrored = true;
1792 }
1793 else if( IsBackLayer( t->GetLayer() ) )
1794 {
1796 "back-side text should be mirrored" );
1797 backCopyrightMirrored = true;
1798 }
1799 }
1800 }
1801
1802 BOOST_CHECK_MESSAGE( copyrightCount >= 2,
1803 "expected the copyright text on both front and back, got " << copyrightCount );
1804 BOOST_CHECK( frontCopyrightNotMirrored );
1805 BOOST_CHECK( backCopyrightMirrored );
1806
1807 // Issue 2: CN1 finger pads use FINORI 90, which must not be reset to zero by
1808 // the back-side round entry of the through-hole stack.
1809 bool foundCN1 = false;
1810
1811 for( FOOTPRINT* fp : board->Footprints() )
1812 {
1813 if( fp->GetReference() != wxT( "CN1" ) )
1814 continue;
1815
1816 foundCN1 = true;
1817
1818 BOOST_CHECK_MESSAGE( fp->Pads().size() >= 10,
1819 "CN1 should have at least 10 pads, got " << fp->Pads().size() );
1820
1821 for( PAD* pad : fp->Pads() )
1822 {
1823 // Oval/rectangle finger pads must carry the 90 degree finger rotation.
1824 if( pad->GetShape( F_Cu ) == PAD_SHAPE::OVAL
1825 || pad->GetShape( F_Cu ) == PAD_SHAPE::RECTANGLE )
1826 {
1828 pad->GetOrientation() == EDA_ANGLE( 90, DEGREES_T ),
1829 "CN1 finger pad " << pad->GetNumber().ToStdString()
1830 << " should be oriented 90 degrees, got "
1831 << pad->GetOrientation().AsDegrees() );
1832 }
1833 }
1834 }
1835
1836 BOOST_CHECK_MESSAGE( foundCN1, "CN1 footprint should be imported" );
1837}
1838
1839
General utilities for PCB file IO for QA programs.
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_DIELECTRIC
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
int GetBoardThickness() const
The full thickness of the board including copper and masks.
BOARD_STACKUP & GetStackupDescriptor()
std::vector< VIA_DIMENSION > m_ViasDimensionsList
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:265
Manage one layer needed to make a physical board.
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
constexpr void SetMaximum()
Definition box2.h:76
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 bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:307
double AsDegrees() const
Definition eda_angle.h:116
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:110
bool IsMirrored() const
Definition eda_text.h:211
std::deque< PAD * > & Pads()
Definition footprint.h:375
VECTOR2I GetPosition() const override
Definition footprint.h:403
Handle the data for a net.
Definition netinfo.h:46
const wxString & GetNetname() const
Definition netinfo.h:100
const std::map< wxString, std::shared_ptr< NETCLASS > > & GetNetclasses() const
Gets all netclasses.
std::shared_ptr< NETCLASS > GetDefaultNetclass() const
Gets the default netclass for the project.
std::vector< std::pair< std::unique_ptr< EDA_COMBINED_MATCHER >, wxString > > & GetNetclassPatternAssignments()
Gets the netclass pattern assignments.
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:171
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:172
MODE Mode() const
Definition padstack.h:335
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
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition pad.h:202
VECTOR2I GetSize(PCB_LAYER_ID aLayer) const
Definition pad.cpp:287
const PADSTACK & Padstack() const
Definition pad.h:326
EDA_ANGLE GetAngle() const
const VECTOR2I & GetMid() const
Definition pcb_track.h:286
For better understanding of the points that make a dimension:
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 ...
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PCB_IO can read the specified board file.
const VECTOR2I & GetStart() const
Definition pcb_track.h:93
const VECTOR2I & GetEnd() const
Definition pcb_track.h:90
int PointCount() const
Return the number of points (vertices) in this line chain.
Represent a set of closed polygons.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
CONST_SEGMENT_ITERATOR CIterateSegmentsWithHoles() const
Return an iterator object, for the aOutline-th outline in the set (with holes).
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
Handle a list of polygons defining a copper zone.
Definition zone.h:70
SHAPE_POLY_SET * Outline()
Definition zone.h:418
@ DEGREES_T
Definition eda_angle.h:31
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:47
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
@ Dwgs_User
Definition layer_ids.h:103
@ F_Paste
Definition layer_ids.h:100
@ Cmts_User
Definition layer_ids.h:104
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ F_SilkS
Definition layer_ids.h:96
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ F_Cu
Definition layer_ids.h:60
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:99
@ RECTANGLE
Definition padstack.h:54
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
static void RunStructuralChecks(const PADS_BOARD_INFO &aBoard)
Run structural integrity checks on a successfully loaded board.
static wxString GetBoardPath(const PADS_BOARD_INFO &aBoard)
static const PADS_BOARD_INFO PADS_BOARDS[]
static std::unique_ptr< BOARD > LoadAndVerify(const PADS_BOARD_INFO &aBoard)
Verify that the PADS file is recognized and loads without crashing.
BOOST_AUTO_TEST_CASE(ImportClaySight_MK1)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
VECTOR2I end
BOOST_CHECK_EQUAL(result, "25.4")
@ 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
@ THERMAL
Use thermal relief for pads.
Definition zones.h:46
@ FULL
pads are covered by copper
Definition zones.h:47