KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_ipc2581_export.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
28
32
35
36#include <board.h>
39#include <footprint.h>
40#include <pad.h>
41#include <pcb_shape.h>
42#include <pcb_track.h>
43#include <base_units.h>
44
45#include <wx/dir.h>
46#include <wx/file.h>
47#include <wx/filename.h>
48#include <wx/process.h>
49#include <wx/txtstrm.h>
50
51#include <cmath>
52#include <fstream>
53#include <sstream>
54
55
56namespace
57{
58
62bool IsXmllintAvailable()
63{
64 wxArrayString output;
65 wxArrayString errors;
66 int result = wxExecute( "xmllint --version", output, errors, wxEXEC_SYNC );
67 return result == 0;
68}
69
70
75wxString ValidateXmlWithXsd( const wxString& aXmlPath, const wxString& aXsdPath )
76{
77 wxString cmd = wxString::Format( "xmllint --noout --schema \"%s\" \"%s\"",
78 aXsdPath, aXmlPath );
79
80 wxArrayString output;
81 wxArrayString errors;
82 int result = wxExecute( cmd, output, errors, wxEXEC_SYNC );
83
84 if( result != 0 )
85 {
86 wxString errorMsg;
87
88 for( const wxString& line : errors )
89 errorMsg += line + "\n";
90
91 return errorMsg;
92 }
93
94 return wxEmptyString;
95}
96
97
101bool FileContainsPattern( const wxString& aFilePath, const wxString& aPattern )
102{
103 std::ifstream file( aFilePath.ToStdString() );
104
105 if( !file.is_open() )
106 return false;
107
108 std::stringstream buffer;
109 buffer << file.rdbuf();
110 std::string content = buffer.str();
111
112 return content.find( aPattern.ToStdString() ) != std::string::npos;
113}
114
115
120static const std::vector<std::string> VALIDATION_TEST_BOARDS = {
121 "custom_pads.kicad_pcb",
122 "notched_zones.kicad_pcb",
123 "sliver.kicad_pcb",
124 "tracks_arcs_vias.kicad_pcb",
125 "issue7241.kicad_pcb",
126 "issue10906.kicad_pcb",
127 "issue22798.kicad_pcb",
128 "padstacks_complex.kicad_pcb",
129 "issue12609.kicad_pcb",
130 "issue22794.kicad_pcb",
131};
132
133} // anonymous namespace
134
135
137{
139 m_xmllintAvailable( IsXmllintAvailable() )
140 {
141 }
142
144 {
145 // Clean up temporary files
146 for( const wxString& path : m_tempFiles )
147 {
148 if( wxFileExists( path ) )
149 wxRemoveFile( path );
150 }
151 }
152
153 wxString CreateTempFile( const wxString& aSuffix = wxT( "" ) )
154 {
155 wxString path = wxFileName::CreateTempFileName( wxT( "kicad_ipc2581_test" ) );
156
157 if( !aSuffix.IsEmpty() )
158 path += aSuffix;
159 else
160 path += wxT( ".xml" );
161
162 m_tempFiles.push_back( path );
163 return path;
164 }
165
166 wxString GetXsdPath( char aVersion )
167 {
168 wxString filename = ( aVersion == 'C' ) ? wxT( "IPC-2581C.xsd" ) : wxT( "IPC-2581B1.xsd" );
169 return KI_TEST::GetPcbnewTestDataDir() + "ipc2581/" + filename;
170 }
171
172 std::unique_ptr<BOARD> LoadBoard( const std::string& aRelativePath )
173 {
174 std::string fullPath = KI_TEST::GetPcbnewTestDataDir() + aRelativePath;
175 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
176
177 m_kicadPlugin.LoadBoard( fullPath, board.get(), nullptr, nullptr );
178
179 return board;
180 }
181
182 bool ExportAndValidate( BOARD* aBoard, char aVersion, wxString& aErrorMsg )
183 {
184 wxString tempPath = CreateTempFile();
185
186 std::map<std::string, UTF8> props;
187 props["units"] = "mm";
188 props["version"] = std::string( 1, aVersion );
189 props["sigfig"] = "3";
190
191 try
192 {
193 m_ipc2581Plugin.SaveBoard( tempPath, aBoard, &props );
194 }
195 catch( const std::exception& e )
196 {
197 aErrorMsg = wxString::Format( "Export failed: %s", e.what() );
198 return false;
199 }
200
201 if( !wxFileExists( tempPath ) )
202 {
203 aErrorMsg = "Export file was not created";
204 return false;
205 }
206
208 {
209 wxString xsdPath = GetXsdPath( aVersion );
210
211 if( wxFileExists( xsdPath ) )
212 {
213 aErrorMsg = ValidateXmlWithXsd( tempPath, xsdPath );
214 return aErrorMsg.IsEmpty();
215 }
216 }
217
218 // If xmllint not available, just check that export succeeded
219 return true;
220 }
221
223 std::vector<wxString> m_tempFiles;
226};
227
228
229BOOST_FIXTURE_TEST_SUITE( Ipc2581Export, IPC2581_EXPORT_FIXTURE )
230
231
232
240BOOST_AUTO_TEST_CASE( SurfaceFinishExport )
241{
242 // Load a board with ENIG surface finish (issue3812.kicad_pcb has ENIG)
243 std::unique_ptr<BOARD> board = LoadBoard( "issue3812.kicad_pcb" );
244
245 BOOST_REQUIRE( board );
246
247 // Verify the board has ENIG finish
248 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
249 BOOST_CHECK_EQUAL( stackup.m_FinishType, wxT( "ENIG" ) );
250
251 // Export to IPC-2581 version C
252 wxString tempPath = CreateTempFile();
253
254 std::map<std::string, UTF8> props;
255 props["units"] = "mm";
256 props["version"] = "C";
257 props["sigfig"] = "3";
258
259 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
260
261 BOOST_REQUIRE( wxFileExists( tempPath ) );
262
263 // Verify SurfaceFinish element is present with correct type attribute
264 // Schema requires: <SurfaceFinish type="ENIG-N"/>
265 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "<SurfaceFinish" ) ),
266 "SurfaceFinish element should be present" );
267 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "type=\"ENIG-N\"" ) ),
268 "SurfaceFinish type should be ENIG-N" );
269
270 // Verify coating layers are present
271 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "COATING_TOP" ) ),
272 "COATING_TOP layer should be present" );
273 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "COATING_BOTTOM" ) ),
274 "COATING_BOTTOM layer should be present" );
275 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "layerFunction=\"COATINGCOND\"" ) ),
276 "Coating layers should have layerFunction=COATINGCOND" );
277
278 // Note: XSD validation is done separately in SchemaValidation tests.
279 // This test focuses on verifying the surface finish elements are present.
280}
281
282
286BOOST_AUTO_TEST_CASE( NoSurfaceFinishExport )
287{
288 // Load a board without surface finish (vme-wren.kicad_pcb has "None")
289 std::unique_ptr<BOARD> board = LoadBoard( "vme-wren.kicad_pcb" );
290
291 BOOST_REQUIRE( board );
292
293 // Verify the board has no finish
294 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
295 BOOST_CHECK( stackup.m_FinishType == wxT( "None" ) || stackup.m_FinishType.IsEmpty() );
296
297 // Export to IPC-2581 version C
298 wxString tempPath = CreateTempFile();
299
300 std::map<std::string, UTF8> props;
301 props["units"] = "mm";
302 props["version"] = "C";
303 props["sigfig"] = "3";
304
305 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
306
307 BOOST_REQUIRE( wxFileExists( tempPath ) );
308
309 // Verify SurfaceFinish element is NOT present
310 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "<SurfaceFinish" ) ),
311 "SurfaceFinish element should not be present for 'None' finish" );
312
313 // Verify coating layers are NOT present
314 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "COATING_TOP" ) ),
315 "COATING_TOP layer should not be present" );
316 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "COATING_BOTTOM" ) ),
317 "COATING_BOTTOM layer should not be present" );
318
319 // Note: XSD validation is done separately in SchemaValidation tests.
320 // This test focuses on verifying coating layers are NOT present for "None" finish.
321}
322
323
330BOOST_AUTO_TEST_CASE( SchemaValidationVersionB )
331{
332 if( !m_xmllintAvailable )
333 {
334 BOOST_WARN_MESSAGE( false, "xmllint not available, skipping schema validation tests" );
335 return;
336 }
337
338 wxString xsdPath = GetXsdPath( 'B' );
339
340 if( !wxFileExists( xsdPath ) )
341 {
342 BOOST_WARN_MESSAGE( false, "IPC-2581B1.xsd not found, skipping schema validation" );
343 return;
344 }
345
346 for( const std::string& boardFile : VALIDATION_TEST_BOARDS )
347 {
348 BOOST_TEST_CONTEXT( "Board: " << boardFile << " (Version B)" )
349 {
350 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
351
352 if( !board )
353 {
354 BOOST_WARN_MESSAGE( false, "Could not load board: " + boardFile );
355 continue;
356 }
357
358 wxString errorMsg;
359 bool valid = ExportAndValidate( board.get(), 'B', errorMsg );
360
361 BOOST_CHECK_MESSAGE( valid, "IPC-2581B validation failed for " + boardFile + ": " + errorMsg );
362 }
363 }
364}
365
366
373BOOST_AUTO_TEST_CASE( SchemaValidationVersionC )
374{
375 if( !m_xmllintAvailable )
376 {
377 BOOST_WARN_MESSAGE( false, "xmllint not available, skipping schema validation tests" );
378 return;
379 }
380
381 wxString xsdPath = GetXsdPath( 'C' );
382
383 if( !wxFileExists( xsdPath ) )
384 {
385 BOOST_WARN_MESSAGE( false, "IPC-2581C.xsd not found, skipping schema validation" );
386 return;
387 }
388
389 for( const std::string& boardFile : VALIDATION_TEST_BOARDS )
390 {
391 BOOST_TEST_CONTEXT( "Board: " << boardFile << " (Version C)" )
392 {
393 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
394
395 if( !board )
396 {
397 BOOST_WARN_MESSAGE( false, "Could not load board: " + boardFile );
398 continue;
399 }
400
401 wxString errorMsg;
402 bool valid = ExportAndValidate( board.get(), 'C', errorMsg );
403
404 BOOST_CHECK_MESSAGE( valid, "IPC-2581C validation failed for " + boardFile + ": " + errorMsg );
405 }
406 }
407}
408
409
416BOOST_AUTO_TEST_CASE( ComplexBoardExport )
417{
418 // Test boards with specific complex features
419 static const std::vector<std::string> complexBoards = {
420 "intersectingzones.kicad_pcb",
421 "custom_pads.kicad_pcb",
422 };
423
424 for( const std::string& boardFile : complexBoards )
425 {
426 BOOST_TEST_CONTEXT( "Complex board: " << boardFile )
427 {
428 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
429
430 if( !board )
431 {
432 BOOST_WARN_MESSAGE( false, "Could not load board: " + boardFile );
433 continue;
434 }
435
436 // Test both versions
437 for( char version : { 'B', 'C' } )
438 {
439 BOOST_TEST_CONTEXT( "Version " << version )
440 {
441 wxString errorMsg;
442 bool valid = ExportAndValidate( board.get(), version, errorMsg );
443
444 BOOST_CHECK_MESSAGE( valid,
445 wxString::Format( "Export/validation failed for %s version %c: %s",
446 boardFile, version, errorMsg ) );
447 }
448 }
449 }
450 }
451}
452
453
461BOOST_AUTO_TEST_CASE( SmdPadSolderMaskExport_Issue16658 )
462{
463 // Load a board with standard SMD components (capacitors using SMD footprints)
464 std::unique_ptr<BOARD> board = LoadBoard( "issue16658/issue16658.kicad_pcb" );
465
466 BOOST_REQUIRE( board );
467
468 // Verify the board has SMD pads with implicit mask openings
469 bool hasSmtPad = false;
470
471 for( FOOTPRINT* fp : board->Footprints() )
472 {
473 for( PAD* pad : fp->Pads() )
474 {
475 if( pad->GetAttribute() == PAD_ATTRIB::SMD )
476 {
477 hasSmtPad = true;
478
479 // Verify pad is on copper but NOT explicitly on mask layer
480 bool isOnCopperOnly = pad->IsOnLayer( F_Cu ) && !pad->IsOnLayer( F_Mask );
481
482 if( isOnCopperOnly )
483 {
484 // This is the condition we're testing
485 break;
486 }
487 }
488 }
489
490 if( hasSmtPad )
491 break;
492 }
493
494 BOOST_REQUIRE_MESSAGE( hasSmtPad, "Test board should have SMD pads" );
495
496 // Export to IPC-2581 version C
497 wxString tempPath = CreateTempFile();
498
499 std::map<std::string, UTF8> props;
500 props["units"] = "mm";
501 props["version"] = "C";
502 props["sigfig"] = "3";
503
504 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
505
506 BOOST_REQUIRE( wxFileExists( tempPath ) );
507
508 // Verify that F_Mask layer features are present in the export
509 // (this was the bug - mask layers were empty for SMD pads)
510 bool hasFMaskLayer = FileContainsPattern( tempPath, wxT( "layerRef=\"F.Mask\"" ) )
511 || FileContainsPattern( tempPath, wxT( "layerRef=\"TSM\"" ) );
512
513 BOOST_CHECK_MESSAGE( hasFMaskLayer,
514 "IPC-2581 export should contain F.Mask layer features for SMD pads" );
515
516 // Also check for LayerFeature element with mask layer reference
517 bool hasLayerFeature = FileContainsPattern( tempPath, wxT( "<LayerFeature" ) );
518 BOOST_CHECK_MESSAGE( hasLayerFeature, "IPC-2581 export should contain LayerFeature elements" );
519}
520
521
529BOOST_AUTO_TEST_CASE( EmptyRefDesProducesValidXml )
530{
531 std::unique_ptr<BOARD> board = LoadBoard( "padstacks_complex.kicad_pcb" );
532 BOOST_REQUIRE( board );
533
534 for( char version : { 'B', 'C' } )
535 {
536 BOOST_TEST_CONTEXT( "Version " << version )
537 {
538 wxString tempPath = CreateTempFile();
539
540 std::map<std::string, UTF8> props;
541 props["units"] = "mm";
542 props["version"] = std::string( 1, version );
543 props["sigfig"] = "3";
544
545 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
546 BOOST_REQUIRE( wxFileExists( tempPath ) );
547
548 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "refDes=\"\"" ) ),
549 "Empty refDes attribute found" );
550 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "<RefDes name=\"\"" ) ),
551 "Empty RefDes/@name attribute found" );
552 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "componentRef=\"\"" ) ),
553 "Empty PinRef/@componentRef attribute found" );
554 }
555
556 m_ipc2581Plugin = PCB_IO_IPC2581();
557 }
558}
559
560
569BOOST_AUTO_TEST_CASE( FlippedComponentRotation )
570{
571 std::unique_ptr<BOARD> board = LoadBoard( "issue12609.kicad_pcb" );
572 BOOST_REQUIRE( board );
573
574 wxString tempPath = CreateTempFile();
575
576 std::map<std::string, UTF8> props;
577 props["units"] = "mm";
578 props["version"] = "C";
579 props["sigfig"] = "3";
580
581 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
582 BOOST_REQUIRE( wxFileExists( tempPath ) );
583
584 // C5 is on B.Cu at 90 degrees. Its Component Xform must be rotation="90.0",
585 // not the inverted "270.0". Check by finding Component refDes="C5" and verifying
586 // its Xform has rotation="90.0".
587 std::ifstream xmlFile( tempPath.ToStdString() );
588 BOOST_REQUIRE( xmlFile.is_open() );
589
590 std::string xmlContent( ( std::istreambuf_iterator<char>( xmlFile ) ),
591 std::istreambuf_iterator<char>() );
592
593 // Find the C5 component and check its rotation
594 size_t c5Pos = xmlContent.find( "refDes=\"C5\"" );
595
596 if( c5Pos == std::string::npos )
597 c5Pos = xmlContent.find( "refDes=\"NOREF_" );
598
599 BOOST_REQUIRE_MESSAGE( c5Pos != std::string::npos,
600 "C5 component should exist in export" );
601
602 // Look for the Xform within the next 200 chars after refDes="C5"
603 std::string c5Region = xmlContent.substr( c5Pos, 200 );
604 BOOST_CHECK_MESSAGE( c5Region.find( "rotation=\"90.0\"" ) != std::string::npos
605 || c5Region.find( "rotation=\"90.00\"" ) != std::string::npos,
606 "C5 component rotation should be 90, not inverted. Region: "
607 + c5Region );
608}
609
610
614BOOST_AUTO_TEST_CASE( ContentBomRef )
615{
616 std::unique_ptr<BOARD> board = LoadBoard( "issue12609.kicad_pcb" );
617 BOOST_REQUIRE( board );
618
619 wxString tempPath = CreateTempFile();
620
621 std::map<std::string, UTF8> props;
622 props["units"] = "mm";
623 props["version"] = "C";
624 props["sigfig"] = "3";
625
626 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
627 BOOST_REQUIRE( wxFileExists( tempPath ) );
628
629 bool hasBom = FileContainsPattern( tempPath, wxT( "<Bom " ) );
630 bool hasBomRef = FileContainsPattern( tempPath, wxT( "<BomRef " ) );
631
632 if( hasBom )
633 {
634 BOOST_CHECK_MESSAGE( hasBomRef,
635 "Content should have BomRef when Bom section is present" );
636 }
637}
638
639
653BOOST_AUTO_TEST_CASE( KnockoutTextMultiContour_Issue23968 )
654{
655 std::unique_ptr<BOARD> board = LoadBoard( "test_copper_graphics.kicad_pcb" );
656 BOOST_REQUIRE( board );
657
658 for( char version : { 'B', 'C' } )
659 {
660 BOOST_TEST_CONTEXT( "Version " << version )
661 {
662 wxString errorMsg;
663 bool valid = ExportAndValidate( board.get(), version, errorMsg );
664
665 BOOST_CHECK_MESSAGE( valid,
666 wxString::Format( "Knockout text export should be schema-valid "
667 "(version %c): %s", version, errorMsg ) );
668 }
669
670 m_ipc2581Plugin = PCB_IO_IPC2581();
671 }
672}
673
674
686BOOST_AUTO_TEST_CASE( BackdrillSpecEncoding )
687{
688 // Build a minimal 6-layer synthetic board so we can place a via whose
689 // backdrill targets a specific must-cut layer.
690 BOARD board;
691 board.SetCopperLayerCount( 6 );
692
695
696 // Front-side backdrill: drill from F_Cu, must cut through In3_Cu. The
697 // must-not-cut layer should therefore resolve to In4_Cu (the next signal
698 // layer past must-cut going inward from the start surface).
699 auto* via = new PCB_VIA( &board );
700 via->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) ) );
701 via->SetLayerPair( F_Cu, B_Cu );
702 via->SetDrill( pcbIUScale.mmToIU( 0.30 ) );
703 via->SetWidth( pcbIUScale.mmToIU( 0.60 ) );
704 via->SetSecondaryDrillSize( pcbIUScale.mmToIU( 0.40 ) );
705 via->SetSecondaryDrillStartLayer( F_Cu );
706 via->SetSecondaryDrillEndLayer( In3_Cu );
707 via->SetFrontPostMachiningMode( PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK );
708 board.Add( via );
709
710 wxString tempPath = CreateTempFile();
711 std::map<std::string, UTF8> props;
712 props["units"] = "mm";
713 props["version"] = "C";
714 props["sigfig"] = "4";
715
716 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, &board, &props ) );
717 BOOST_REQUIRE( wxFileExists( tempPath ) );
718
720 FileContainsPattern( tempPath, wxT( "<Backdrill type=\"START_LAYER\"" ) ),
721 "Backdrill spec should declare a START_LAYER child" );
723 FileContainsPattern( tempPath, wxT( "<Backdrill type=\"MUST_NOT_CUT_LAYER\"" ) ),
724 "Backdrill spec should declare a MUST_NOT_CUT_LAYER child" );
726 FileContainsPattern( tempPath, wxT( "<Backdrill type=\"MAX_STUB_LENGTH\"" ) ),
727 "Backdrill spec should declare a MAX_STUB_LENGTH child" );
728
729 // Schema requires layer references via Property layerOrGroupRef, not as
730 // Backdrill attributes.
731 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "layerOrGroupRef=" ) ),
732 "Backdrill must convey layers through Property layerOrGroupRef" );
733 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "startLayerRef=" ) ),
734 "Backdrill should not use schema-invalid startLayerRef attribute" );
735 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "mustNotCutLayerRef=" ) ),
736 "Backdrill should not use schema-invalid mustNotCutLayerRef attribute" );
737 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "maxStubLength=" ) ),
738 "Backdrill should not use schema-invalid maxStubLength attribute" );
739 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "postMachining=" ) ),
740 "Backdrill should not use the non-standard postMachining attribute" );
741
742 // The must-not-cut layer for a backdrill from F_Cu through In3_Cu in a
743 // 6-layer stack must resolve to In4_Cu, not the must-cut layer itself.
745 FileContainsPattern( tempPath, wxT( "layerOrGroupRef=\"In4.Cu\"" ) ),
746 "Front backdrill must-not-cut layer should be In4.Cu" );
747
748 // The third (primary) backdrill spec slot has been removed; through-drills
749 // must not be exported as backdrill specs.
750 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "BD_1C" ) ),
751 "Exporter should not emit a primary backdrill spec slot" );
752
753 // Counterbore/countersink encoded as a Backdrill type=OTHER child with
754 // a comment, never as a non-standard postMachining attribute.
756 FileContainsPattern( tempPath, wxT( "<Backdrill type=\"OTHER\"" ) ),
757 "Post-machining hint should produce a Backdrill type=OTHER child" );
759 FileContainsPattern( tempPath, wxT( "comment=\"post-machining=COUNTERSINK\"" ) ),
760 "OTHER Backdrill should carry the post-machining comment" );
761}
762
763
775BOOST_AUTO_TEST_CASE( ExposedPadPasteRespected_Issue24318 )
776{
777 BOARD board;
778
779 FOOTPRINT* fp = new FOOTPRINT( &board );
780 fp->SetReference( wxT( "U1" ) );
781 fp->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50 ), pcbIUScale.mmToIU( 50 ) ) );
782 board.Add( fp );
783
784 // Copper-only thermal pad: F.Cu + F.Mask, deliberately NOT on F.Paste.
785 PAD* thermalPad = new PAD( fp );
786 thermalPad->SetNumber( wxT( "33" ) );
787 thermalPad->SetAttribute( PAD_ATTRIB::SMD );
788 thermalPad->SetProperty( PAD_PROP::HEATSINK );
790 thermalPad->SetSize( PADSTACK::ALL_LAYERS,
791 VECTOR2I( pcbIUScale.mmToIU( 3.45 ), pcbIUScale.mmToIU( 3.45 ) ) );
792 thermalPad->SetLayerSet( LSET( { F_Cu, F_Mask } ) );
793 fp->Add( thermalPad );
794
795 // Paste-only aperture pad, models a stencil opening for the thermal pad.
796 PAD* pasteAperture = new PAD( fp );
797 pasteAperture->SetNumber( wxEmptyString );
798 pasteAperture->SetAttribute( PAD_ATTRIB::SMD );
800 pasteAperture->SetSize( PADSTACK::ALL_LAYERS,
801 VECTOR2I( pcbIUScale.mmToIU( 0.93 ),
802 pcbIUScale.mmToIU( 0.93 ) ) );
803 pasteAperture->SetPosition( fp->GetPosition()
804 + VECTOR2I( pcbIUScale.mmToIU( 1.15 ),
805 pcbIUScale.mmToIU( 1.15 ) ) );
806 pasteAperture->SetLayerSet( LSET( { F_Paste } ) );
807 fp->Add( pasteAperture );
808
809 // Add a control pad whose paste IS authored (F.Cu + F.Mask + F.Paste). It must still
810 // appear on the paste layer, confirming the fix doesn't suppress legitimate paste pads.
811 FOOTPRINT* fp2 = new FOOTPRINT( &board );
812 fp2->SetReference( wxT( "R1" ) );
813 fp2->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 60 ), pcbIUScale.mmToIU( 60 ) ) );
814 board.Add( fp2 );
815
816 PAD* normalSmd = new PAD( fp2 );
817 normalSmd->SetNumber( wxT( "1" ) );
818 normalSmd->SetAttribute( PAD_ATTRIB::SMD );
820 normalSmd->SetSize( PADSTACK::ALL_LAYERS,
821 VECTOR2I( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) ) );
822 normalSmd->SetLayerSet( LSET( { F_Cu, F_Mask, F_Paste } ) );
823 fp2->Add( normalSmd );
824
825 // Implicit-mask control pad: F.Cu only. Mask must be added implicitly by the exporter,
826 // matching the #16658 fix. This guards against accidental removal of the mask code path.
827 PAD* implicitMaskSmd = new PAD( fp2 );
828 implicitMaskSmd->SetNumber( wxT( "2" ) );
829 implicitMaskSmd->SetAttribute( PAD_ATTRIB::SMD );
831 implicitMaskSmd->SetSize( PADSTACK::ALL_LAYERS,
832 VECTOR2I( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) ) );
833 implicitMaskSmd->SetPosition( fp2->GetPosition()
834 + VECTOR2I( pcbIUScale.mmToIU( 2.0 ), 0 ) );
835 implicitMaskSmd->SetLayerSet( LSET( { F_Cu } ) );
836 fp2->Add( implicitMaskSmd );
837
838 wxString tempPath = CreateTempFile();
839 std::map<std::string, UTF8> props;
840 props["units"] = "mm";
841 props["version"] = "C";
842 props["sigfig"] = "4";
843
844 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, &board, &props ) );
845 BOOST_REQUIRE( wxFileExists( tempPath ) );
846
847 std::ifstream xmlFile( tempPath.ToStdString() );
848 BOOST_REQUIRE( xmlFile.is_open() );
849
850 std::string xml( ( std::istreambuf_iterator<char>( xmlFile ) ),
851 std::istreambuf_iterator<char>() );
852
853 // Locate the F.Paste LayerFeature block, if any.
854 const std::string pasteOpen = "<LayerFeature layerRef=\"F.Paste\"";
855 size_t pasteStart = xml.find( pasteOpen );
856
857 if( pasteStart != std::string::npos )
858 {
859 size_t pasteEnd = xml.find( "</LayerFeature>", pasteStart );
860 BOOST_REQUIRE( pasteEnd != std::string::npos );
861
862 std::string pasteRegion = xml.substr( pasteStart, pasteEnd - pasteStart );
863
864 // The exposed thermal pad (U1 pin 33) must NOT appear on F.Paste.
866 pasteRegion.find( "<PinRef componentRef=\"U1\" pin=\"33\"" ) == std::string::npos,
867 "Copper-only thermal pad U1.33 must not appear on F.Paste layer feature" );
868
869 // The normal SMD pad (R1 pin 1) SHOULD still appear on F.Paste.
871 pasteRegion.find( "<PinRef componentRef=\"R1\" pin=\"1\"" ) != std::string::npos,
872 "Normal SMD pad with explicit F.Paste in layer set should still emit a paste "
873 "feature" );
874
875 // The implicit-mask control pad (R1 pin 2) had F.Cu only and must NOT have paste.
877 pasteRegion.find( "<PinRef componentRef=\"R1\" pin=\"2\"" ) == std::string::npos,
878 "Copper-only SMD pad R1.2 must not appear on F.Paste layer feature" );
879 }
880 else
881 {
882 // If no F.Paste layer feature was emitted at all the regression would be hidden, so
883 // require its presence (R1 must drive its creation).
884 BOOST_FAIL( "Expected an F.Paste LayerFeature for the explicitly-pasted control pad" );
885 }
886
887 // Mask must still be added implicitly for the thermal pad. Confirm an F.Mask
888 // LayerFeature exists with U1 pin 33 so the #16658 behavior is preserved for mask.
889 const std::string maskOpen = "<LayerFeature layerRef=\"F.Mask\"";
890 size_t maskStart = xml.find( maskOpen );
891 BOOST_REQUIRE_MESSAGE( maskStart != std::string::npos,
892 "F.Mask LayerFeature should still be emitted for SMD copper pads" );
893
894 size_t maskEnd = xml.find( "</LayerFeature>", maskStart );
895 BOOST_REQUIRE( maskEnd != std::string::npos );
896
897 std::string maskRegion = xml.substr( maskStart, maskEnd - maskStart );
898
900 maskRegion.find( "<PinRef componentRef=\"U1\" pin=\"33\"" ) != std::string::npos,
901 "Thermal pad with explicit F.Mask should appear on F.Mask layer feature" );
902
903 // The truly implicit case: R1 pin 2 had F.Cu only and must still acquire an F.Mask
904 // entry. This is the actual regression guard for the #16658 implicit-mask behavior.
906 maskRegion.find( "<PinRef componentRef=\"R1\" pin=\"2\"" ) != std::string::npos,
907 "SMD copper pad without explicit F.Mask must get an implicit F.Mask opening" );
908}
909
910
914static std::string ReadFile( const wxString& aPath )
915{
916 std::ifstream file( aPath.ToStdString() );
917 return std::string( ( std::istreambuf_iterator<char>( file ) ),
918 std::istreambuf_iterator<char>() );
919}
920
921
926static std::string LayerFeatureRegion( const std::string& aXml, const std::string& aLayerRef )
927{
928 const std::string open = "<LayerFeature layerRef=\"" + aLayerRef + "\"";
929 size_t start = aXml.find( open );
930
931 if( start == std::string::npos )
932 return std::string();
933
934 size_t end = aXml.find( "</LayerFeature>", start );
935
936 if( end == std::string::npos )
937 return std::string();
938
939 return aXml.substr( start, end - start );
940}
941
942
950BOOST_AUTO_TEST_CASE( GrRectCornerRadius_Issue24754 )
951{
952 BOARD board;
953 board.SetCopperLayerCount( 2 );
954
955 PCB_SHAPE* rect = new PCB_SHAPE( &board, SHAPE_T::RECTANGLE );
956 rect->SetLayer( Edge_Cuts );
957 rect->SetStart( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
958 rect->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 11.5 ), pcbIUScale.mmToIU( 17 ) ) );
959 rect->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.05 ), LINE_STYLE::SOLID ) );
960 rect->SetFilled( false );
961 rect->SetCornerRadius( pcbIUScale.mmToIU( 0.75 ) );
962 board.Add( rect );
963
964 wxString tempPath = CreateTempFile();
965 std::map<std::string, UTF8> props;
966 props["units"] = "mm";
967 props["version"] = "C";
968 props["sigfig"] = "4";
969
970 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, &board, &props ) );
971 BOOST_REQUIRE( wxFileExists( tempPath ) );
972
973 std::string xml = ReadFile( tempPath );
974
975 // The RectRound must round its corners and carry the 0.75 mm radius, not stroke_width/2.
976 size_t rectPos = xml.find( "<RectRound" );
977 BOOST_REQUIRE_MESSAGE( rectPos != std::string::npos, "Export should contain a RectRound" );
978
979 std::string rectNode = xml.substr( rectPos, xml.find( '>', rectPos ) - rectPos );
980
981 BOOST_CHECK_MESSAGE( rectNode.find( "radius=\"0.750\"" ) != std::string::npos,
982 "Rounded gr_rect must export its 0.75 mm corner radius. Node: "
983 + rectNode );
984 BOOST_CHECK_MESSAGE( rectNode.find( "upperRight=\"true\"" ) != std::string::npos
985 && rectNode.find( "lowerLeft=\"true\"" ) != std::string::npos,
986 "Rounded gr_rect must set its corner flags. Node: " + rectNode );
987 BOOST_CHECK_MESSAGE( rectNode.find( "radius=\"0.025\"" ) == std::string::npos,
988 "Corner radius must not collapse to stroke_width/2. Node: " + rectNode );
989}
990
991
998BOOST_AUTO_TEST_CASE( BackOnlyMaskNoFrontOpening_Issue24753 )
999{
1000 BOARD board;
1001 board.SetCopperLayerCount( 2 );
1002
1003 FOOTPRINT* fp = new FOOTPRINT( &board );
1004 fp->SetReference( wxT( "J29" ) );
1005 fp->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50 ), pcbIUScale.mmToIU( 50 ) ) );
1006 board.Add( fp );
1007
1008 // Through-hole pad masked on the back only: *.Cu + B.Mask, deliberately no F.Mask.
1009 PAD* pad = new PAD( fp );
1010 pad->SetNumber( wxT( "1" ) );
1011 pad->SetAttribute( PAD_ATTRIB::PTH );
1013 pad->SetSize( PADSTACK::ALL_LAYERS,
1014 VECTOR2I( pcbIUScale.mmToIU( 2.5 ), pcbIUScale.mmToIU( 4.0 ) ) );
1015 pad->SetDrillSize( VECTOR2I( pcbIUScale.mmToIU( 1.65 ), pcbIUScale.mmToIU( 1.65 ) ) );
1016 pad->SetLayerSet( LSET( { F_Cu, B_Cu, B_Mask } ) );
1017 fp->Add( pad );
1018
1019 wxString tempPath = CreateTempFile();
1020 std::map<std::string, UTF8> props;
1021 props["units"] = "mm";
1022 props["version"] = "C";
1023 props["sigfig"] = "4";
1024
1025 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, &board, &props ) );
1026 BOOST_REQUIRE( wxFileExists( tempPath ) );
1027
1028 std::string xml = ReadFile( tempPath );
1029
1030 // The pad must appear on B.Mask (authored) but never on F.Mask.
1031 std::string fMask = LayerFeatureRegion( xml, "F.Mask" );
1033 fMask.find( "<PinRef componentRef=\"J29\" pin=\"1\"" ) == std::string::npos,
1034 "Back-only masked pad must not appear on F.Mask layer feature" );
1035
1036 std::string bMask = LayerFeatureRegion( xml, "B.Mask" );
1038 bMask.find( "<PinRef componentRef=\"J29\" pin=\"1\"" ) != std::string::npos,
1039 "Back-only masked pad should appear on B.Mask layer feature" );
1040}
1041
1042
1050BOOST_AUTO_TEST_CASE( RoundRectMaskRadius_Issue24751 )
1051{
1052 BOARD board;
1053 board.SetCopperLayerCount( 2 );
1054
1055 FOOTPRINT* fp = new FOOTPRINT( &board );
1056 fp->SetReference( wxT( "R1" ) );
1057 fp->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 40 ), pcbIUScale.mmToIU( 40 ) ) );
1058 board.Add( fp );
1059
1060 PAD* pad = new PAD( fp );
1061 pad->SetNumber( wxT( "1" ) );
1062 pad->SetAttribute( PAD_ATTRIB::SMD );
1064 pad->SetSize( PADSTACK::ALL_LAYERS,
1065 VECTOR2I( pcbIUScale.mmToIU( 0.45 ), pcbIUScale.mmToIU( 0.30 ) ) );
1066 pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, 0.3333333333 );
1067 pad->SetLayerSet( LSET( { F_Cu, F_Mask } ) );
1068 pad->SetLocalSolderMaskMargin( pcbIUScale.mmToIU( -0.05 ) );
1069 fp->Add( pad );
1070
1071 wxString tempPath = CreateTempFile();
1072 std::map<std::string, UTF8> props;
1073 props["units"] = "mm";
1074 props["version"] = "C";
1075 props["sigfig"] = "4";
1076
1077 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, &board, &props ) );
1078 BOOST_REQUIRE( wxFileExists( tempPath ) );
1079
1080 std::string xml = ReadFile( tempPath );
1081
1082 // Copper roundrect radius is 0.10 mm; with a -0.05 mm per-side margin the mask aperture is
1083 // 0.35 x 0.20 with radius 0.05 mm. Both distinct primitives must be present.
1084 BOOST_CHECK_MESSAGE( xml.find( "radius=\"0.10\"" ) != std::string::npos,
1085 "Copper roundrect should keep its 0.10 mm radius" );
1086 BOOST_CHECK_MESSAGE( xml.find( "radius=\"0.050\"" ) != std::string::npos,
1087 "Mask roundrect aperture must shrink its radius to 0.05 mm" );
1088 BOOST_CHECK_MESSAGE( xml.find( "width=\"0.350\"" ) != std::string::npos,
1089 "Mask roundrect aperture should be 0.35 mm wide" );
1090}
1091
1092
1099BOOST_AUTO_TEST_CASE( MultiLayerFootprintGraphic_Issue24752 )
1100{
1101 BOARD board;
1102 board.SetCopperLayerCount( 2 );
1103
1104 FOOTPRINT* fp = new FOOTPRINT( &board );
1105 fp->SetReference( wxT( "G1" ) );
1106 fp->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 30 ), pcbIUScale.mmToIU( 30 ) ) );
1107 board.Add( fp );
1108
1109 PCB_SHAPE* poly = new PCB_SHAPE( fp, SHAPE_T::POLY );
1110 poly->SetLayerSet( LSET( { F_Cu, F_Mask } ) );
1111 poly->SetFilled( true );
1113
1114 SHAPE_POLY_SET polySet;
1115 polySet.NewOutline();
1116 polySet.Append( pcbIUScale.mmToIU( 30 ), pcbIUScale.mmToIU( 30 ) );
1117 polySet.Append( pcbIUScale.mmToIU( 31 ), pcbIUScale.mmToIU( 30 ) );
1118 polySet.Append( pcbIUScale.mmToIU( 31 ), pcbIUScale.mmToIU( 31 ) );
1119 polySet.Append( pcbIUScale.mmToIU( 30 ), pcbIUScale.mmToIU( 31 ) );
1120 poly->SetPolyShape( polySet );
1121 fp->Add( poly );
1122
1123 wxString tempPath = CreateTempFile();
1124 std::map<std::string, UTF8> props;
1125 props["units"] = "mm";
1126 props["version"] = "C";
1127 props["sigfig"] = "4";
1128
1129 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, &board, &props ) );
1130 BOOST_REQUIRE( wxFileExists( tempPath ) );
1131
1132 std::string xml = ReadFile( tempPath );
1133
1134 // The polygon graphic must show up under both F.Cu and F.Mask.
1135 std::string fCu = LayerFeatureRegion( xml, "F.Cu" );
1136 std::string fMask = LayerFeatureRegion( xml, "F.Mask" );
1137
1138 BOOST_CHECK_MESSAGE( fCu.find( "UserPrimitiveRef" ) != std::string::npos,
1139 "Footprint graphic should be present on F.Cu" );
1140 BOOST_CHECK_MESSAGE( fMask.find( "UserPrimitiveRef" ) != std::string::npos,
1141 "Multi-layer footprint graphic must also appear on F.Mask" );
1142}
1143
1144
1154BOOST_AUTO_TEST_CASE( SolderMaskMarginExpandsMaskPrimitive_Issue24749 )
1155{
1156 BOARD board;
1157
1158 FOOTPRINT* fp = new FOOTPRINT( &board );
1159 fp->SetReference( wxT( "FID4" ) );
1160 fp->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50 ), pcbIUScale.mmToIU( 50 ) ) );
1161 board.Add( fp );
1162
1163 PAD* pad = new PAD( fp );
1164 pad->SetNumber( wxT( "1" ) );
1165 pad->SetAttribute( PAD_ATTRIB::SMD );
1167 pad->SetSize( PADSTACK::ALL_LAYERS,
1168 VECTOR2I( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) ) );
1169 pad->SetLayerSet( LSET( { F_Cu, F_Mask } ) );
1170 pad->SetLocalSolderMaskMargin( pcbIUScale.mmToIU( 0.5 ) );
1171 fp->Add( pad );
1172
1173 wxString tempPath = CreateTempFile();
1174 std::map<std::string, UTF8> props;
1175 props["units"] = "mm";
1176 props["version"] = "C";
1177 props["sigfig"] = "4";
1178
1179 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, &board, &props ) );
1180 BOOST_REQUIRE( wxFileExists( tempPath ) );
1181
1182 std::ifstream xmlFile( tempPath.ToStdString() );
1183 BOOST_REQUIRE( xmlFile.is_open() );
1184
1185 std::string xml( ( std::istreambuf_iterator<char>( xmlFile ) ),
1186 std::istreambuf_iterator<char>() );
1187
1188 // Resolve the primitive id referenced by the F.Mask padstack entry, then confirm
1189 // that primitive's circle diameter is the mask-expanded 2.0 mm, not the 1.0 mm copper size.
1190 const std::string maskRef = "<PadstackPadDef layerRef=\"F.Mask\"";
1191 size_t maskDefPos = xml.find( maskRef );
1192 BOOST_REQUIRE_MESSAGE( maskDefPos != std::string::npos,
1193 "F.Mask padstack pad definition should be present" );
1194
1195 size_t refPos = xml.find( "StandardPrimitiveRef id=\"", maskDefPos );
1196 BOOST_REQUIRE( refPos != std::string::npos );
1197 refPos += std::string( "StandardPrimitiveRef id=\"" ).size();
1198 size_t refEnd = xml.find( '"', refPos );
1199 std::string maskPrimId = xml.substr( refPos, refEnd - refPos );
1200
1201 std::string entry = "<EntryStandard id=\"" + maskPrimId + "\"";
1202 size_t entryPos = xml.find( entry );
1203 BOOST_REQUIRE_MESSAGE( entryPos != std::string::npos,
1204 "Mask primitive EntryStandard should be defined" );
1205
1206 size_t diaPos = xml.find( "diameter=\"", entryPos );
1207 BOOST_REQUIRE( diaPos != std::string::npos );
1208 diaPos += std::string( "diameter=\"" ).size();
1209 size_t diaEnd = xml.find( '"', diaPos );
1210 double diameter = std::stod( xml.substr( diaPos, diaEnd - diaPos ) );
1211
1212 BOOST_CHECK_MESSAGE( std::abs( diameter - 2.0 ) < 1e-4,
1213 "F.Mask primitive diameter should be 2.0 mm (1.0 mm pad + 0.5 mm "
1214 "margin per side), got " << diameter );
1215}
1216
1217
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
General utilities for PCB file IO for QA programs.
Container for design settings for a BOARD object.
BOARD_STACKUP & GetStackupDescriptor()
Manage layers needed to make a physical board.
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
wxString m_FinishType
The name of external copper finish.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1346
void SetCopperLayerCount(int aCount)
Definition board.cpp:991
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1149
void SetCornerRadius(int aRadius)
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:152
void SetPosition(const VECTOR2I &aPos) override
void SetReference(const wxString &aReference)
Definition footprint.h:847
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
VECTOR2I GetPosition() const override
Definition footprint.h:403
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
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
void SetAttribute(PAD_ATTRIB aAttribute)
Definition pad.cpp:1615
void SetShape(PCB_LAYER_ID aLayer, PAD_SHAPE aShape)
Set the new shape of this pad.
Definition pad.h:193
void SetProperty(PAD_PROP aProperty)
Definition pad.cpp:1688
void SetNumber(const wxString &aNumber)
Set the pad number (note that it can be alphanumeric, such as the array reference "AA12").
Definition pad.h:142
void SetPosition(const VECTOR2I &aPos) override
Definition pad.cpp:234
void SetSize(PCB_LAYER_ID aLayer, const VECTOR2I &aSize)
Definition pad.cpp:254
void SetLayerSet(const LSET &aLayers) override
Definition pad.cpp:1931
A #PLUGIN derivation for saving and loading Pcbnew s-expression formatted files.
virtual void SetLayerSet(const LSET &aLayers) override
void SetEnd(const VECTOR2I &aEnd) override
void SetPolyShape(const SHAPE_POLY_SET &aShape) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
void SetStroke(const STROKE_PARAMS &aStroke) override
Represent a set of closed polygons.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
int NewOutline()
Creates a new empty polygon in the set and returns its index.
Simple container to manage line stroke parameters.
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:47
@ Edge_Cuts
Definition layer_ids.h:108
@ F_Paste
Definition layer_ids.h:100
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ In3_Cu
Definition layer_ids.h:64
@ 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
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ ROUNDRECT
Definition padstack.h:57
@ RECTANGLE
Definition padstack.h:54
@ HEATSINK
a pad used as heat sink, usually in SMD footprints
Definition padstack.h:120
wxString GetXsdPath(char aVersion)
bool ExportAndValidate(BOARD *aBoard, char aVersion, wxString &aErrorMsg)
std::vector< wxString > m_tempFiles
PCB_IO_KICAD_SEXPR m_kicadPlugin
wxString CreateTempFile(const wxString &aSuffix=wxT(""))
std::unique_ptr< BOARD > LoadBoard(const std::string &aRelativePath)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
static std::string ReadFile(const wxString &aPath)
Read an exported file's full contents into a std::string.
BOOST_AUTO_TEST_CASE(SurfaceFinishExport)
Test that surface finish is exported correctly (Issue #22690)
static std::string LayerFeatureRegion(const std::string &aXml, const std::string &aLayerRef)
Extract the text of the first LayerFeature block for a given layer reference.
std::string path
nlohmann::json output
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
VECTOR2I end
BOOST_TEST_CONTEXT("Test Clearance")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683