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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
32
36
39
40#include <board.h>
43#include <footprint.h>
44#include <pad.h>
45#include <pcb_track.h>
46#include <base_units.h>
47
48#include <wx/dir.h>
49#include <wx/file.h>
50#include <wx/filename.h>
51#include <wx/process.h>
52#include <wx/txtstrm.h>
53
54#include <fstream>
55#include <sstream>
56
57
58namespace
59{
60
64bool IsXmllintAvailable()
65{
66 wxArrayString output;
67 wxArrayString errors;
68 int result = wxExecute( "xmllint --version", output, errors, wxEXEC_SYNC );
69 return result == 0;
70}
71
72
77wxString ValidateXmlWithXsd( const wxString& aXmlPath, const wxString& aXsdPath )
78{
79 wxString cmd = wxString::Format( "xmllint --noout --schema \"%s\" \"%s\"",
80 aXsdPath, aXmlPath );
81
82 wxArrayString output;
83 wxArrayString errors;
84 int result = wxExecute( cmd, output, errors, wxEXEC_SYNC );
85
86 if( result != 0 )
87 {
88 wxString errorMsg;
89
90 for( const wxString& line : errors )
91 errorMsg += line + "\n";
92
93 return errorMsg;
94 }
95
96 return wxEmptyString;
97}
98
99
103bool FileContainsPattern( const wxString& aFilePath, const wxString& aPattern )
104{
105 std::ifstream file( aFilePath.ToStdString() );
106
107 if( !file.is_open() )
108 return false;
109
110 std::stringstream buffer;
111 buffer << file.rdbuf();
112 std::string content = buffer.str();
113
114 return content.find( aPattern.ToStdString() ) != std::string::npos;
115}
116
117
122static const std::vector<std::string> VALIDATION_TEST_BOARDS = {
123 "custom_pads.kicad_pcb",
124 "notched_zones.kicad_pcb",
125 "sliver.kicad_pcb",
126 "tracks_arcs_vias.kicad_pcb",
127 "issue7241.kicad_pcb",
128 "issue10906.kicad_pcb",
129 "issue22798.kicad_pcb",
130 "padstacks_complex.kicad_pcb",
131 "issue12609.kicad_pcb",
132 "issue22794.kicad_pcb",
133};
134
135} // anonymous namespace
136
137
139{
141 m_xmllintAvailable( IsXmllintAvailable() )
142 {
143 }
144
146 {
147 // Clean up temporary files
148 for( const wxString& path : m_tempFiles )
149 {
150 if( wxFileExists( path ) )
151 wxRemoveFile( path );
152 }
153 }
154
155 wxString CreateTempFile( const wxString& aSuffix = wxT( "" ) )
156 {
157 wxString path = wxFileName::CreateTempFileName( wxT( "kicad_ipc2581_test" ) );
158
159 if( !aSuffix.IsEmpty() )
160 path += aSuffix;
161 else
162 path += wxT( ".xml" );
163
164 m_tempFiles.push_back( path );
165 return path;
166 }
167
168 wxString GetXsdPath( char aVersion )
169 {
170 wxString filename = ( aVersion == 'C' ) ? wxT( "IPC-2581C.xsd" ) : wxT( "IPC-2581B1.xsd" );
171 return KI_TEST::GetPcbnewTestDataDir() + "ipc2581/" + filename;
172 }
173
174 std::unique_ptr<BOARD> LoadBoard( const std::string& aRelativePath )
175 {
176 std::string fullPath = KI_TEST::GetPcbnewTestDataDir() + aRelativePath;
177 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
178
179 m_kicadPlugin.LoadBoard( fullPath, board.get(), nullptr, nullptr );
180
181 return board;
182 }
183
184 bool ExportAndValidate( BOARD* aBoard, char aVersion, wxString& aErrorMsg )
185 {
186 wxString tempPath = CreateTempFile();
187
188 std::map<std::string, UTF8> props;
189 props["units"] = "mm";
190 props["version"] = std::string( 1, aVersion );
191 props["sigfig"] = "3";
192
193 try
194 {
195 m_ipc2581Plugin.SaveBoard( tempPath, aBoard, &props );
196 }
197 catch( const std::exception& e )
198 {
199 aErrorMsg = wxString::Format( "Export failed: %s", e.what() );
200 return false;
201 }
202
203 if( !wxFileExists( tempPath ) )
204 {
205 aErrorMsg = "Export file was not created";
206 return false;
207 }
208
210 {
211 wxString xsdPath = GetXsdPath( aVersion );
212
213 if( wxFileExists( xsdPath ) )
214 {
215 aErrorMsg = ValidateXmlWithXsd( tempPath, xsdPath );
216 return aErrorMsg.IsEmpty();
217 }
218 }
219
220 // If xmllint not available, just check that export succeeded
221 return true;
222 }
223
225 std::vector<wxString> m_tempFiles;
228};
229
230
231BOOST_FIXTURE_TEST_SUITE( Ipc2581Export, IPC2581_EXPORT_FIXTURE )
232
233
234
242BOOST_AUTO_TEST_CASE( SurfaceFinishExport )
243{
244 // Load a board with ENIG surface finish (issue3812.kicad_pcb has ENIG)
245 std::unique_ptr<BOARD> board = LoadBoard( "issue3812.kicad_pcb" );
246
247 BOOST_REQUIRE( board );
248
249 // Verify the board has ENIG finish
250 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
251 BOOST_CHECK_EQUAL( stackup.m_FinishType, wxT( "ENIG" ) );
252
253 // Export to IPC-2581 version C
254 wxString tempPath = CreateTempFile();
255
256 std::map<std::string, UTF8> props;
257 props["units"] = "mm";
258 props["version"] = "C";
259 props["sigfig"] = "3";
260
261 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
262
263 BOOST_REQUIRE( wxFileExists( tempPath ) );
264
265 // Verify SurfaceFinish element is present with correct type attribute
266 // Schema requires: <SurfaceFinish type="ENIG-N"/>
267 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "<SurfaceFinish" ) ),
268 "SurfaceFinish element should be present" );
269 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "type=\"ENIG-N\"" ) ),
270 "SurfaceFinish type should be ENIG-N" );
271
272 // Verify coating layers are present
273 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "COATING_TOP" ) ),
274 "COATING_TOP layer should be present" );
275 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "COATING_BOTTOM" ) ),
276 "COATING_BOTTOM layer should be present" );
277 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "layerFunction=\"COATINGCOND\"" ) ),
278 "Coating layers should have layerFunction=COATINGCOND" );
279
280 // Note: XSD validation is done separately in SchemaValidation tests.
281 // This test focuses on verifying the surface finish elements are present.
282}
283
284
288BOOST_AUTO_TEST_CASE( NoSurfaceFinishExport )
289{
290 // Load a board without surface finish (vme-wren.kicad_pcb has "None")
291 std::unique_ptr<BOARD> board = LoadBoard( "vme-wren.kicad_pcb" );
292
293 BOOST_REQUIRE( board );
294
295 // Verify the board has no finish
296 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
297 BOOST_CHECK( stackup.m_FinishType == wxT( "None" ) || stackup.m_FinishType.IsEmpty() );
298
299 // Export to IPC-2581 version C
300 wxString tempPath = CreateTempFile();
301
302 std::map<std::string, UTF8> props;
303 props["units"] = "mm";
304 props["version"] = "C";
305 props["sigfig"] = "3";
306
307 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
308
309 BOOST_REQUIRE( wxFileExists( tempPath ) );
310
311 // Verify SurfaceFinish element is NOT present
312 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "<SurfaceFinish" ) ),
313 "SurfaceFinish element should not be present for 'None' finish" );
314
315 // Verify coating layers are NOT present
316 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "COATING_TOP" ) ),
317 "COATING_TOP layer should not be present" );
318 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "COATING_BOTTOM" ) ),
319 "COATING_BOTTOM layer should not be present" );
320
321 // Note: XSD validation is done separately in SchemaValidation tests.
322 // This test focuses on verifying coating layers are NOT present for "None" finish.
323}
324
325
332BOOST_AUTO_TEST_CASE( SchemaValidationVersionB )
333{
334 if( !m_xmllintAvailable )
335 {
336 BOOST_WARN_MESSAGE( false, "xmllint not available, skipping schema validation tests" );
337 return;
338 }
339
340 wxString xsdPath = GetXsdPath( 'B' );
341
342 if( !wxFileExists( xsdPath ) )
343 {
344 BOOST_WARN_MESSAGE( false, "IPC-2581B1.xsd not found, skipping schema validation" );
345 return;
346 }
347
348 for( const std::string& boardFile : VALIDATION_TEST_BOARDS )
349 {
350 BOOST_TEST_CONTEXT( "Board: " << boardFile << " (Version B)" )
351 {
352 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
353
354 if( !board )
355 {
356 BOOST_WARN_MESSAGE( false, "Could not load board: " + boardFile );
357 continue;
358 }
359
360 wxString errorMsg;
361 bool valid = ExportAndValidate( board.get(), 'B', errorMsg );
362
363 BOOST_CHECK_MESSAGE( valid, "IPC-2581B validation failed for " + boardFile + ": " + errorMsg );
364 }
365 }
366}
367
368
375BOOST_AUTO_TEST_CASE( SchemaValidationVersionC )
376{
377 if( !m_xmllintAvailable )
378 {
379 BOOST_WARN_MESSAGE( false, "xmllint not available, skipping schema validation tests" );
380 return;
381 }
382
383 wxString xsdPath = GetXsdPath( 'C' );
384
385 if( !wxFileExists( xsdPath ) )
386 {
387 BOOST_WARN_MESSAGE( false, "IPC-2581C.xsd not found, skipping schema validation" );
388 return;
389 }
390
391 for( const std::string& boardFile : VALIDATION_TEST_BOARDS )
392 {
393 BOOST_TEST_CONTEXT( "Board: " << boardFile << " (Version C)" )
394 {
395 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
396
397 if( !board )
398 {
399 BOOST_WARN_MESSAGE( false, "Could not load board: " + boardFile );
400 continue;
401 }
402
403 wxString errorMsg;
404 bool valid = ExportAndValidate( board.get(), 'C', errorMsg );
405
406 BOOST_CHECK_MESSAGE( valid, "IPC-2581C validation failed for " + boardFile + ": " + errorMsg );
407 }
408 }
409}
410
411
418BOOST_AUTO_TEST_CASE( ComplexBoardExport )
419{
420 // Test boards with specific complex features
421 static const std::vector<std::string> complexBoards = {
422 "intersectingzones.kicad_pcb",
423 "custom_pads.kicad_pcb",
424 };
425
426 for( const std::string& boardFile : complexBoards )
427 {
428 BOOST_TEST_CONTEXT( "Complex board: " << boardFile )
429 {
430 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
431
432 if( !board )
433 {
434 BOOST_WARN_MESSAGE( false, "Could not load board: " + boardFile );
435 continue;
436 }
437
438 // Test both versions
439 for( char version : { 'B', 'C' } )
440 {
441 BOOST_TEST_CONTEXT( "Version " << version )
442 {
443 wxString errorMsg;
444 bool valid = ExportAndValidate( board.get(), version, errorMsg );
445
446 BOOST_CHECK_MESSAGE( valid,
447 wxString::Format( "Export/validation failed for %s version %c: %s",
448 boardFile, version, errorMsg ) );
449 }
450 }
451 }
452 }
453}
454
455
463BOOST_AUTO_TEST_CASE( SmdPadSolderMaskExport_Issue16658 )
464{
465 // Load a board with standard SMD components (capacitors using SMD footprints)
466 std::unique_ptr<BOARD> board = LoadBoard( "issue16658/issue16658.kicad_pcb" );
467
468 BOOST_REQUIRE( board );
469
470 // Verify the board has SMD pads with implicit mask openings
471 bool hasSmtPad = false;
472
473 for( FOOTPRINT* fp : board->Footprints() )
474 {
475 for( PAD* pad : fp->Pads() )
476 {
477 if( pad->GetAttribute() == PAD_ATTRIB::SMD )
478 {
479 hasSmtPad = true;
480
481 // Verify pad is on copper but NOT explicitly on mask layer
482 bool isOnCopperOnly = pad->IsOnLayer( F_Cu ) && !pad->IsOnLayer( F_Mask );
483
484 if( isOnCopperOnly )
485 {
486 // This is the condition we're testing
487 break;
488 }
489 }
490 }
491
492 if( hasSmtPad )
493 break;
494 }
495
496 BOOST_REQUIRE_MESSAGE( hasSmtPad, "Test board should have SMD pads" );
497
498 // Export to IPC-2581 version C
499 wxString tempPath = CreateTempFile();
500
501 std::map<std::string, UTF8> props;
502 props["units"] = "mm";
503 props["version"] = "C";
504 props["sigfig"] = "3";
505
506 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
507
508 BOOST_REQUIRE( wxFileExists( tempPath ) );
509
510 // Verify that F_Mask layer features are present in the export
511 // (this was the bug - mask layers were empty for SMD pads)
512 bool hasFMaskLayer = FileContainsPattern( tempPath, wxT( "layerRef=\"F.Mask\"" ) )
513 || FileContainsPattern( tempPath, wxT( "layerRef=\"TSM\"" ) );
514
515 BOOST_CHECK_MESSAGE( hasFMaskLayer,
516 "IPC-2581 export should contain F.Mask layer features for SMD pads" );
517
518 // Also check for LayerFeature element with mask layer reference
519 bool hasLayerFeature = FileContainsPattern( tempPath, wxT( "<LayerFeature" ) );
520 BOOST_CHECK_MESSAGE( hasLayerFeature, "IPC-2581 export should contain LayerFeature elements" );
521}
522
523
531BOOST_AUTO_TEST_CASE( EmptyRefDesProducesValidXml )
532{
533 std::unique_ptr<BOARD> board = LoadBoard( "padstacks_complex.kicad_pcb" );
534 BOOST_REQUIRE( board );
535
536 for( char version : { 'B', 'C' } )
537 {
538 BOOST_TEST_CONTEXT( "Version " << version )
539 {
540 wxString tempPath = CreateTempFile();
541
542 std::map<std::string, UTF8> props;
543 props["units"] = "mm";
544 props["version"] = std::string( 1, version );
545 props["sigfig"] = "3";
546
547 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
548 BOOST_REQUIRE( wxFileExists( tempPath ) );
549
550 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "refDes=\"\"" ) ),
551 "Empty refDes attribute found" );
552 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "<RefDes name=\"\"" ) ),
553 "Empty RefDes/@name attribute found" );
554 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "componentRef=\"\"" ) ),
555 "Empty PinRef/@componentRef attribute found" );
556 }
557
558 m_ipc2581Plugin = PCB_IO_IPC2581();
559 }
560}
561
562
571BOOST_AUTO_TEST_CASE( FlippedComponentRotation )
572{
573 std::unique_ptr<BOARD> board = LoadBoard( "issue12609.kicad_pcb" );
574 BOOST_REQUIRE( board );
575
576 wxString tempPath = CreateTempFile();
577
578 std::map<std::string, UTF8> props;
579 props["units"] = "mm";
580 props["version"] = "C";
581 props["sigfig"] = "3";
582
583 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
584 BOOST_REQUIRE( wxFileExists( tempPath ) );
585
586 // C5 is on B.Cu at 90 degrees. Its Component Xform must be rotation="90.0",
587 // not the inverted "270.0". Check by finding Component refDes="C5" and verifying
588 // its Xform has rotation="90.0".
589 std::ifstream xmlFile( tempPath.ToStdString() );
590 BOOST_REQUIRE( xmlFile.is_open() );
591
592 std::string xmlContent( ( std::istreambuf_iterator<char>( xmlFile ) ),
593 std::istreambuf_iterator<char>() );
594
595 // Find the C5 component and check its rotation
596 size_t c5Pos = xmlContent.find( "refDes=\"C5\"" );
597
598 if( c5Pos == std::string::npos )
599 c5Pos = xmlContent.find( "refDes=\"NOREF_" );
600
601 BOOST_REQUIRE_MESSAGE( c5Pos != std::string::npos,
602 "C5 component should exist in export" );
603
604 // Look for the Xform within the next 200 chars after refDes="C5"
605 std::string c5Region = xmlContent.substr( c5Pos, 200 );
606 BOOST_CHECK_MESSAGE( c5Region.find( "rotation=\"90.0\"" ) != std::string::npos
607 || c5Region.find( "rotation=\"90.00\"" ) != std::string::npos,
608 "C5 component rotation should be 90, not inverted. Region: "
609 + c5Region );
610}
611
612
616BOOST_AUTO_TEST_CASE( ContentBomRef )
617{
618 std::unique_ptr<BOARD> board = LoadBoard( "issue12609.kicad_pcb" );
619 BOOST_REQUIRE( board );
620
621 wxString tempPath = CreateTempFile();
622
623 std::map<std::string, UTF8> props;
624 props["units"] = "mm";
625 props["version"] = "C";
626 props["sigfig"] = "3";
627
628 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
629 BOOST_REQUIRE( wxFileExists( tempPath ) );
630
631 bool hasBom = FileContainsPattern( tempPath, wxT( "<Bom " ) );
632 bool hasBomRef = FileContainsPattern( tempPath, wxT( "<BomRef " ) );
633
634 if( hasBom )
635 {
636 BOOST_CHECK_MESSAGE( hasBomRef,
637 "Content should have BomRef when Bom section is present" );
638 }
639}
640
641
655BOOST_AUTO_TEST_CASE( KnockoutTextMultiContour_Issue23968 )
656{
657 std::unique_ptr<BOARD> board = LoadBoard( "test_copper_graphics.kicad_pcb" );
658 BOOST_REQUIRE( board );
659
660 for( char version : { 'B', 'C' } )
661 {
662 BOOST_TEST_CONTEXT( "Version " << version )
663 {
664 wxString errorMsg;
665 bool valid = ExportAndValidate( board.get(), version, errorMsg );
666
667 BOOST_CHECK_MESSAGE( valid,
668 wxString::Format( "Knockout text export should be schema-valid "
669 "(version %c): %s", version, errorMsg ) );
670 }
671
672 m_ipc2581Plugin = PCB_IO_IPC2581();
673 }
674}
675
676
688BOOST_AUTO_TEST_CASE( BackdrillSpecEncoding )
689{
690 // Build a minimal 6-layer synthetic board so we can place a via whose
691 // backdrill targets a specific must-cut layer.
692 BOARD board;
693 board.SetCopperLayerCount( 6 );
694
697
698 // Front-side backdrill: drill from F_Cu, must cut through In3_Cu. The
699 // must-not-cut layer should therefore resolve to In4_Cu (the next signal
700 // layer past must-cut going inward from the start surface).
701 auto* via = new PCB_VIA( &board );
702 via->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) ) );
703 via->SetLayerPair( F_Cu, B_Cu );
704 via->SetDrill( pcbIUScale.mmToIU( 0.30 ) );
705 via->SetWidth( pcbIUScale.mmToIU( 0.60 ) );
706 via->SetSecondaryDrillSize( pcbIUScale.mmToIU( 0.40 ) );
707 via->SetSecondaryDrillStartLayer( F_Cu );
708 via->SetSecondaryDrillEndLayer( In3_Cu );
709 via->SetFrontPostMachiningMode( PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK );
710 board.Add( via );
711
712 wxString tempPath = CreateTempFile();
713 std::map<std::string, UTF8> props;
714 props["units"] = "mm";
715 props["version"] = "C";
716 props["sigfig"] = "4";
717
718 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, &board, &props ) );
719 BOOST_REQUIRE( wxFileExists( tempPath ) );
720
722 FileContainsPattern( tempPath, wxT( "<Backdrill type=\"START_LAYER\"" ) ),
723 "Backdrill spec should declare a START_LAYER child" );
725 FileContainsPattern( tempPath, wxT( "<Backdrill type=\"MUST_NOT_CUT_LAYER\"" ) ),
726 "Backdrill spec should declare a MUST_NOT_CUT_LAYER child" );
728 FileContainsPattern( tempPath, wxT( "<Backdrill type=\"MAX_STUB_LENGTH\"" ) ),
729 "Backdrill spec should declare a MAX_STUB_LENGTH child" );
730
731 // Schema requires layer references via Property layerOrGroupRef, not as
732 // Backdrill attributes.
733 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT( "layerOrGroupRef=" ) ),
734 "Backdrill must convey layers through Property layerOrGroupRef" );
735 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "startLayerRef=" ) ),
736 "Backdrill should not use schema-invalid startLayerRef attribute" );
737 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "mustNotCutLayerRef=" ) ),
738 "Backdrill should not use schema-invalid mustNotCutLayerRef attribute" );
739 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "maxStubLength=" ) ),
740 "Backdrill should not use schema-invalid maxStubLength attribute" );
741 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "postMachining=" ) ),
742 "Backdrill should not use the non-standard postMachining attribute" );
743
744 // The must-not-cut layer for a backdrill from F_Cu through In3_Cu in a
745 // 6-layer stack must resolve to In4_Cu, not the must-cut layer itself.
747 FileContainsPattern( tempPath, wxT( "layerOrGroupRef=\"In4.Cu\"" ) ),
748 "Front backdrill must-not-cut layer should be In4.Cu" );
749
750 // The third (primary) backdrill spec slot has been removed; through-drills
751 // must not be exported as backdrill specs.
752 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT( "BD_1C" ) ),
753 "Exporter should not emit a primary backdrill spec slot" );
754
755 // Counterbore/countersink encoded as a Backdrill type=OTHER child with
756 // a comment, never as a non-standard postMachining attribute.
758 FileContainsPattern( tempPath, wxT( "<Backdrill type=\"OTHER\"" ) ),
759 "Post-machining hint should produce a Backdrill type=OTHER child" );
761 FileContainsPattern( tempPath, wxT( "comment=\"post-machining=COUNTERSINK\"" ) ),
762 "OTHER Backdrill should carry the post-machining comment" );
763}
764
765
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
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:323
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1247
void SetCopperLayerCount(int aCount)
Definition board.cpp:943
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1101
Definition pad.h:55
A #PLUGIN derivation for saving and loading Pcbnew s-expression formatted files.
@ B_Cu
Definition layer_ids.h:65
@ F_Mask
Definition layer_ids.h:97
@ In3_Cu
Definition layer_ids.h:68
@ F_Cu
Definition layer_ids.h:64
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:99
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()
BOOST_AUTO_TEST_CASE(SurfaceFinishExport)
Test that surface finish is exported correctly (Issue #22690)
std::string path
nlohmann::json output
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
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:687