56#include <wx/filename.h>
57#include <wx/process.h>
58#include <wx/txtstrm.h>
60#include <wx/xml/xml.h>
61#include <wx/wfstream.h>
62#include <wx/zipstrm.h>
75void CollectXmlElements( wxXmlNode* aNode,
const wxString& aName, std::vector<wxXmlNode*>& aResult )
77 for( wxXmlNode* node = aNode; node; node = node->GetNext() )
79 if( node->GetName() == aName )
80 aResult.push_back( node );
82 CollectXmlElements( node->GetChildren(), aName, aResult );
87wxXmlNode* FindXmlChild( wxXmlNode* aParent,
const wxString& aName )
89 for( wxXmlNode* child = aParent->GetChildren(); child; child = child->GetNext() )
91 if( child->GetName() == aName )
102bool IsXmllintAvailable()
104 wxArrayString output;
105 wxArrayString errors;
106 int result = wxExecute(
"xmllint --version", output, errors, wxEXEC_SYNC );
112bool XmllintRequired()
115 return wxGetEnv( wxS(
"KICAD_QA_REQUIRE_XMLLINT" ), &value ) && value == wxS(
"1" );
119wxString XmllintFailureMessage(
int aExitCode,
const wxArrayString& aErrors )
122 return wxEmptyString;
124 if( aErrors.IsEmpty() )
125 return wxString::Format(
"xmllint exited with status %d", aExitCode );
129 for(
const wxString& line : aErrors )
130 errorMsg += line +
"\n";
140wxString ValidateXmlWithXsd(
const wxString& aXmlPath,
const wxString& aXsdPath )
142 wxString cmd = wxString::Format(
"xmllint --noout --schema \"%s\" \"%s\"", aXsdPath, aXmlPath );
144 wxArrayString output;
145 wxArrayString errors;
146 int result = wxExecute( cmd, output, errors, wxEXEC_SYNC );
148 return XmllintFailureMessage(
result, errors );
155bool FileContainsPattern(
const wxString& aFilePath,
const wxString& aPattern )
157 std::ifstream file( aFilePath.ToStdString() );
159 if( !file.is_open() )
162 std::stringstream buffer;
163 buffer << file.rdbuf();
164 std::string content = buffer.str();
166 return content.find( aPattern.ToStdString() ) != std::string::npos;
170wxXmlNode* FindXmlEntry( wxXmlNode* aRoot,
const wxString& aName,
const wxString& aId )
172 std::vector<wxXmlNode*> entries;
173 CollectXmlElements( aRoot, aName, entries );
175 for( wxXmlNode* entry : entries )
177 if( entry->GetAttribute( wxS(
"id" ) ) == aId )
185wxXmlNode* SecondaryDrillProperty( wxXmlNode* aSpec,
const wxString& aType )
187 for( wxXmlNode* child = aSpec->GetChildren(); child; child = child->GetNext() )
189 if( child->GetName() == wxS(
"SecondaryDrill" ) && child->GetAttribute( wxS(
"type" ) ) == aType )
190 return FindXmlChild( child, wxS(
"Property" ) );
197bool XmlRegionHasCoordinateNear(
const std::string& aRegion,
char aAxis,
double aExpected,
double aTolerance )
199 std::regex coordinateRegex( std::string( 1, aAxis ) +
"=\"(-?[0-9]+(?:\\.[0-9]+)?)\"" );
201 for( std::sregex_iterator it( aRegion.begin(), aRegion.end(), coordinateRegex ),
end; it !=
end; ++it )
203 if(
std::abs( std::stod( ( *it )[1].str() ) - aExpected ) <= aTolerance )
215static const std::vector<std::string> VALIDATION_TEST_BOARDS = {
216 "custom_pads.kicad_pcb",
217 "notched_zones.kicad_pcb",
219 "tracks_arcs_vias.kicad_pcb",
220 "issue7241.kicad_pcb",
221 "issue10906.kicad_pcb",
222 "issue22798.kicad_pcb",
223 "padstacks_complex.kicad_pcb",
224 "issue12609.kicad_pcb",
225 "issue22794.kicad_pcb",
243 if( wxFileExists(
path ) )
244 wxRemoveFile(
path );
250 wxString
path = wxFileName::CreateTempFileName( wxT(
"kicad_ipc2581_test" ) );
252 if( !aSuffix.IsEmpty() )
255 path += wxT(
".xml" );
263 wxString filename = ( aVersion ==
'C' ) ? wxT(
"IPC-2581C.xsd" ) : wxT(
"IPC-2581B1.xsd" );
272 aError = wxS(
"xmllint is unavailable" );
273 else if( !wxFileExists( aXsdPath ) )
274 aError = wxS(
"IPC-2581 schema is missing: " ) + aXsdPath;
276 return aError.IsEmpty();
286 if( XmllintRequired() )
287 BOOST_ERROR(
"KICAD_QA_REQUIRE_XMLLINT=1: " << error.ToStdString() );
289 BOOST_WARN_MESSAGE(
false, error.ToStdString() );
294 std::unique_ptr<BOARD>
LoadBoard(
const std::string& aRelativePath )
302 const std::string& aMode = std::string(),
303 const std::string& aRefDes = std::string(),
304 const std::string& aSections = std::string() )
308 std::map<std::string, UTF8> props;
309 props[
"units"] =
"mm";
310 props[
"version"] = std::string( 1, aVersion );
311 props[
"sigfig"] =
"3";
314 props[
"mode"] = aMode;
316 if( !aRefDes.empty() )
318 props[
"refdes"] = aRefDes;
319 props[
"netnames"] =
"anonymize";
322 if( !aSections.empty() )
323 props[
"sections"] = aSections;
329 catch(
const std::exception& e )
331 aErrorMsg = wxString::Format(
"Export failed: %s", e.what() );
335 if( !wxFileExists( tempPath ) )
337 aErrorMsg =
"Export file was not created";
342 wxString unavailable;
346 if( XmllintRequired() )
348 aErrorMsg = wxS(
"KICAD_QA_REQUIRE_XMLLINT=1: " ) + unavailable;
355 aErrorMsg = ValidateXmlWithXsd( tempPath, xsdPath );
356 return aErrorMsg.IsEmpty();
371 std::unique_ptr<BOARD> board = LoadBoard(
"odbpp/pad_roles.kicad_pcb" );
373 using HOLE_KEY = std::tuple<int, int, int>;
377 for(
bool nonPlated : {
false,
true } )
385 if( op.m_SourceItem->Type() !=
PCB_PAD_T )
391 expected.emplace( op.m_Position.x, -op.m_Position.y, op.m_Diameter );
395 BOOST_REQUIRE_GT(
expected.size(), 0 );
396 BOOST_REQUIRE_GT( slots, 0 );
398 wxString output = CreateTempFile();
399 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"6" } };
400 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( output, *board, &props ) );
401 wxXmlDocument document;
403 std::vector<wxXmlNode*> holes;
404 CollectXmlElements( document.GetRoot(), wxS(
"Hole" ), holes );
405 std::multiset<HOLE_KEY>
actual;
407 auto toIu = [](
const wxString& aValue )
409 return static_cast<int>( std::llround( std::stod( aValue.ToStdString() ) *
PCB_IU_PER_MM ) );
412 for( wxXmlNode* hole : holes )
414 wxString plating = hole->GetAttribute( wxS(
"platingStatus" ) );
416 if( plating == wxS(
"PLATED" ) || plating == wxS(
"NONPLATED" ) )
418 actual.emplace( toIu( hole->GetAttribute( wxS(
"x" ) ) ),
419 toIu( hole->GetAttribute( wxS(
"y" ) ) ),
420 toIu( hole->GetAttribute( wxS(
"diameter" ) ) ) );
430 std::unique_ptr<BOARD> board = LoadBoard(
"ipc2581/user10-content.kicad_pcb" );
434 bool hasUser10Drawing =
false;
436 for(
BOARD_ITEM* drawing : board->Drawings() )
437 hasUser10Drawing |= drawing->GetLayer() ==
User_10;
440 wxString output = CreateTempFile();
441 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"6" } };
442 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( output, *board, &props ) );
443 wxXmlDocument document;
445 std::vector<wxXmlNode*> layers;
446 std::vector<wxXmlNode*> features;
447 CollectXmlElements( document.GetRoot(), wxS(
"Layer" ), layers );
448 CollectXmlElements( document.GetRoot(), wxS(
"LayerFeature" ), features );
449 std::set<wxString> names;
450 bool user10Declared =
false;
451 bool user10HasFeature =
false;
453 for( wxXmlNode* layer : layers )
455 wxString
name = layer->GetAttribute( wxS(
"name" ) );
456 names.insert(
name );
458 if(
name == wxS(
"User.10" ) )
460 user10Declared =
true;
461 BOOST_CHECK_EQUAL( layer->GetAttribute( wxS(
"layerFunction" ) ), wxString( wxS(
"DOCUMENT" ) ) );
462 BOOST_CHECK_EQUAL( layer->GetAttribute( wxS(
"polarity" ) ), wxString( wxS(
"POSITIVE" ) ) );
463 BOOST_CHECK_EQUAL( layer->GetAttribute( wxS(
"side" ) ), wxString( wxS(
"NONE" ) ) );
467 for( wxXmlNode* feature : features )
469 wxString reference = feature->GetAttribute( wxS(
"layerRef" ) );
470 BOOST_CHECK( !reference.IsEmpty() );
471 BOOST_CHECK( names.count( reference ) != 0 );
473 if( reference == wxS(
"User.10" ) )
474 user10HasFeature = feature->GetChildren() !=
nullptr;
477 BOOST_CHECK( user10Declared );
478 BOOST_CHECK( user10HasFeature );
480 if( SchemaCheckReady(
'C' ) )
482 wxString schemaError = ValidateXmlWithXsd( output, GetXsdPath(
'C' ) );
483 BOOST_CHECK_MESSAGE( schemaError.IsEmpty(), schemaError.ToStdString() );
490 std::unique_ptr<BOARD> board = LoadBoard(
"ipc2581/netted_unnumbered.kicad_pcb" );
493 PAD* target =
nullptr;
497 for(
FOOTPRINT* fp : board->Footprints() )
499 for(
size_t i = 0; i < fp->Pads().size(); ++i )
503 if(
pad->GetNumber().IsEmpty() &&
pad->GetNetCode() > 0 )
513 BOOST_REQUIRE_EQUAL( matches, 1u );
520 wxString output = CreateTempFile();
521 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"6" } };
523 sections.
Set( IPC2581::SECTION::LOGICAL_NET );
525 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( output, *board, &props ) );
526 wxXmlDocument document;
528 std::vector<wxXmlNode*> pins;
529 std::vector<wxXmlNode*> pinRefs;
530 std::vector<wxXmlNode*> logicalNets;
531 CollectXmlElements( document.GetRoot(), wxS(
"Pin" ), pins );
532 CollectXmlElements( document.GetRoot(), wxS(
"PinRef" ), pinRefs );
533 CollectXmlElements( document.GetRoot(), wxS(
"LogicalNet" ), logicalNets );
534 BOOST_REQUIRE_GT( logicalNets.size(), 0u );
535 bool hasPackagePin =
false;
536 bool hasPadPinRef =
false;
537 bool hasLogicalPinRef =
false;
539 for( wxXmlNode*
pin : pins )
540 hasPackagePin |=
pin->GetAttribute( wxS(
"number" ) ) == expectedPin;
542 for( wxXmlNode* ref : pinRefs )
544 if( ref->GetAttribute( wxS(
"componentRef" ) ) != component
545 || ref->GetAttribute( wxS(
"pin" ) ) != expectedPin )
550 wxXmlNode* parent = ref->GetParent();
552 if( parent->GetName() == wxS(
"Pad" ) )
554 else if( parent->GetName() == wxS(
"LogicalNet" ) && parent->GetAttribute( wxS(
"name" ) ) == netName )
555 hasLogicalPinRef =
true;
558 BOOST_CHECK( hasPackagePin );
559 BOOST_CHECK( hasPadPinRef );
560 BOOST_CHECK( hasLogicalPinRef );
566 std::unique_ptr<BOARD> board = LoadBoard(
"drc_via_stack/FillFromBoardHonoursTheBoardDefault.kicad_pcb" );
572 BOOST_REQUIRE_EQUAL( board->Tracks().size(), 2u );
574 for(
const PCB_TRACK* track : board->Tracks() )
582 wxString output = CreateTempFile();
583 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"6" } };
584 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( output, *board, &props ) );
585 wxXmlDocument document;
587 std::vector<wxXmlNode*> layers;
588 CollectXmlElements( document.GetRoot(), wxS(
"Layer" ), layers );
589 std::set<wxString> fillingNames;
590 std::set<wxString> tentingNames;
592 for( wxXmlNode* layer : layers )
594 wxString function = layer->GetAttribute( wxS(
"layerFunction" ) );
596 if( function == wxS(
"HOLEFILL" ) && FindXmlChild( layer, wxS(
"Span" ) ) )
597 fillingNames.insert( layer->GetAttribute( wxS(
"name" ) ) );
599 if( function == wxS(
"COATINGNONCOND" ) && !FindXmlChild( layer, wxS(
"Span" ) ) )
600 tentingNames.insert( layer->GetAttribute( wxS(
"name" ) ) );
603 BOOST_REQUIRE_EQUAL( fillingNames.size(), 2u );
604 BOOST_REQUIRE_EQUAL( tentingNames.size(), 1u );
605 std::vector<wxXmlNode*> padDefs;
606 CollectXmlElements( document.GetRoot(), wxS(
"PadstackPadDef" ), padDefs );
607 std::set<wxString> padRefs;
609 for( wxXmlNode* padDef : padDefs )
610 padRefs.insert( padDef->GetAttribute( wxS(
"layerRef" ) ) );
612 for(
const wxString&
name : fillingNames )
613 BOOST_CHECK( padRefs.contains(
name ) );
615 for(
const wxString&
name : tentingNames )
616 BOOST_CHECK( padRefs.contains(
name ) );
622 std::unique_ptr<BOARD> board = LoadBoard(
"issue14130.kicad_pcb" );
624 wxString output = wxString( ( dir.
Path() /
"ipc.zip" ).wstring() );
633 reporter.GetMessages().ToStdString() );
635 wxFFileInputStream input( output );
636 wxZipInputStream archive( input );
637 std::unique_ptr<wxArchiveEntry> entry( archive.GetNextEntry() );
639 BOOST_CHECK( entry->GetName() == wxS(
"ipc.xml" ) );
640 std::unique_ptr<wxArchiveEntry>
next( archive.GetNextEntry() );
641 BOOST_CHECK( !
next );
647 std::unique_ptr<BOARD> board = LoadBoard(
"issue14130.kicad_pcb" );
651 wxString output = wxString( ( dir.
Path() /
"empty.xml" ).wstring() );
653 job.
FromJson( nlohmann::json{ {
"mode",
"USERDEF" }, {
"sections",
"" }, {
"custom_sections",
true } } );
658 BOOST_CHECK(
reporter.GetMessages().Contains( wxS(
"No IPC-2581 content selected" ) ) );
659 BOOST_CHECK( !wxFileExists( output ) );
665 std::unique_ptr<BOARD> board = LoadBoard(
"variant_test/variant_test.kicad_pcb" );
668 wxString current = board->GetCurrentVariant();
670 wxString output = wxString( ( dir.
Path() /
"variant.xml" ).wstring() );
677 reporter.GetMessages().ToStdString() );
679 wxXmlDocument document;
681 std::vector<wxXmlNode*> refdes;
682 CollectXmlElements( document.GetRoot(), wxS(
"RefDes" ), refdes );
685 for( wxXmlNode* node : refdes )
687 if( node->GetAttribute( wxS(
"name" ) ) == wxS(
"R2" ) )
689 BOOST_CHECK_EQUAL( node->GetAttribute( wxS(
"populate" ) ), wxString( wxS(
"true" ) ) );
694 BOOST_CHECK( found );
696 std::vector<wxXmlNode*> components;
697 CollectXmlElements( document.GetRoot(), wxS(
"Component" ), components );
698 bool foundPart =
false;
700 for( wxXmlNode* node : components )
702 if( node->GetAttribute( wxS(
"refDes" ) ) == wxS(
"R1" ) )
704 BOOST_CHECK_EQUAL( node->GetAttribute( wxS(
"part" ) ), wxString( wxS(
"test" ) ) );
709 BOOST_CHECK( foundPart );
715 std::unique_ptr<BOARD> board = LoadBoard(
"issue23451/3Rs_bv.kicad_pcb" );
718 bool hasBackBlindVia =
false;
720 for(
PCB_TRACK* track : board->Tracks() )
731 wxString output = CreateTempFile();
732 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"6" } };
733 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( output, *board, &props ) );
735 wxXmlDocument document;
737 std::vector<wxXmlNode*> layers;
738 CollectXmlElements( document.GetRoot(), wxS(
"Layer" ), layers );
739 int matchingSpans = 0;
741 for( wxXmlNode* layer : layers )
743 if( layer->GetAttribute( wxS(
"layerFunction" ) ) != wxS(
"DRILL" ) )
746 wxXmlNode* span = FindXmlChild( layer, wxS(
"Span" ) );
748 if( span && span->GetAttribute( wxS(
"fromLayer" ) ) == board->GetLayerName(
In2_Cu )
749 && span->GetAttribute( wxS(
"toLayer" ) ) == board->GetLayerName(
B_Cu ) )
761 std::unique_ptr<BOARD> board = LoadBoard(
"custom_pads.kicad_pcb" );
769 std::map<std::string, UTF8> props;
770 props[
"units"] =
"mm";
771 props[
"version"] =
"C";
772 props[
"sigfig"] =
"3";
773 m_ipc2581Plugin.SaveBoard( CreateTempFile(), *board, &props );
792 std::unique_ptr<BOARD> board = LoadBoard(
"issue3812.kicad_pcb" );
797 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
801 wxString tempPath = CreateTempFile();
803 std::map<std::string, UTF8> props;
804 props[
"units"] =
"mm";
805 props[
"version"] =
"C";
806 props[
"sigfig"] =
"3";
808 m_ipc2581Plugin.SaveBoard( tempPath, *board, &props );
814 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"<SurfaceFinish" ) ),
815 "SurfaceFinish element should be present" );
816 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"type=\"ENIG-N\"" ) ),
817 "SurfaceFinish type should be ENIG-N" );
820 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"COATING_TOP" ) ),
821 "COATING_TOP layer should be present" );
822 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"COATING_BOTTOM" ) ),
823 "COATING_BOTTOM layer should be present" );
824 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"layerFunction=\"COATINGCOND\"" ) ),
825 "Coating layers should have layerFunction=COATINGCOND" );
838 std::unique_ptr<BOARD> board = LoadBoard(
"vme-wren.kicad_pcb" );
843 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
847 wxString tempPath = CreateTempFile();
849 std::map<std::string, UTF8> props;
850 props[
"units"] =
"mm";
851 props[
"version"] =
"C";
852 props[
"sigfig"] =
"3";
854 m_ipc2581Plugin.SaveBoard( tempPath, *board, &props );
859 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"<SurfaceFinish" ) ),
860 "SurfaceFinish element should not be present for 'None' finish" );
863 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"COATING_TOP" ) ),
864 "COATING_TOP layer should not be present" );
865 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"COATING_BOTTOM" ) ),
866 "COATING_BOTTOM layer should not be present" );
875 std::unique_ptr<BOARD> board = LoadBoard(
"padstacks_complex.kicad_pcb" );
879 m_xmllintAvailable =
false;
881 BOOST_CHECK( ExportAndValidate( *board,
'C', errorMsg ) );
882 BOOST_CHECK( errorMsg.IsEmpty() );
884 requireXmllint.
SetValue( wxS(
"1" ) );
885 bool valid = ExportAndValidate( *board,
'C', errorMsg );
886 BOOST_CHECK( !valid );
887 BOOST_CHECK( errorMsg.Contains( wxS(
"xmllint" ) ) );
888 BOOST_CHECK( errorMsg.Contains( wxS(
"KICAD_QA_REQUIRE_XMLLINT" ) ) );
890 m_xmllintAvailable =
true;
891 wxString missingXsd = GetXsdPath(
'C' ) + wxS(
".missing" );
892 BOOST_CHECK( !SchemaCheckEnabled( missingXsd, errorMsg ) );
893 BOOST_CHECK( errorMsg.Contains( missingXsd ) );
899 if( !SchemaCheckReady(
'C' ) )
902 std::unique_ptr<BOARD> board = LoadBoard(
"padstacks_complex.kicad_pcb" );
905 wxString validPath = CreateTempFile();
906 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" } };
907 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( validPath, *board, &props ) );
908 BOOST_CHECK( ValidateXmlWithXsd( validPath, GetXsdPath(
'C' ) ).IsEmpty() );
910 wxXmlDocument document;
912 wxXmlNode* content = FindXmlChild( document.GetRoot(), wxS(
"Content" ) );
914 content->AddChild(
new wxXmlNode( wxXML_ELEMENT_NODE, wxS(
"KiCadNotInSchema" ) ) );
916 wxString invalidPath = CreateTempFile();
918 wxString error = ValidateXmlWithXsd( invalidPath, GetXsdPath(
'C' ) );
919 BOOST_CHECK( error.Contains( wxS(
"KiCadNotInSchema" ) ) );
925 wxArrayString errors;
926 BOOST_CHECK( XmllintFailureMessage( 0, errors ).IsEmpty() );
928 wxString silentFailure = XmllintFailureMessage( -1, errors );
929 BOOST_CHECK( !silentFailure.IsEmpty() );
930 BOOST_CHECK( silentFailure.Contains( wxS(
"-1" ) ) );
932 errors.Add( wxS(
"broken.xml fails to validate" ) );
933 BOOST_CHECK_EQUAL( XmllintFailureMessage( 1, errors ), wxString( wxS(
"broken.xml fails to validate\n" ) ) );
945 if( !SchemaCheckReady(
'B' ) )
948 for(
const std::string& boardFile : VALIDATION_TEST_BOARDS )
952 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
956 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
961 bool valid = ExportAndValidate( *board,
'B', errorMsg );
963 BOOST_CHECK_MESSAGE( valid,
"IPC-2581B validation failed for " + boardFile +
": " + errorMsg );
977 if( !SchemaCheckReady(
'C' ) )
980 for(
const std::string& boardFile : VALIDATION_TEST_BOARDS )
984 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
988 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
993 bool valid = ExportAndValidate( *board,
'C', errorMsg );
995 BOOST_CHECK_MESSAGE( valid,
"IPC-2581C validation failed for " + boardFile +
": " + errorMsg );
1013 if( !SchemaCheckReady(
'C' ) )
1016 static const std::vector<std::string> modes = {
"userdef",
"bom",
"stackup",
"fabrication",
1017 "assembly",
"test",
"stencil" };
1020 static const std::vector<std::string> boards = {
"padstacks_complex.kicad_pcb",
1021 "custom_pads.kicad_pcb",
1022 "tracks_arcs_vias.kicad_pcb" };
1024 for(
const std::string& boardFile : boards )
1026 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
1030 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
1034 for(
const std::string& mode : modes )
1036 for(
char version : {
'B',
'C' } )
1038 if( !SchemaCheckReady( version ) )
1041 for(
const std::string& refdes : { std::string(), std::string(
"omit" ) } )
1044 <<
" Version: " << version
1045 <<
" RefDes: " << ( refdes.empty() ?
"include" : refdes ) )
1048 bool valid = ExportAndValidate( *board, version, errorMsg, mode,
1051 BOOST_CHECK_MESSAGE( valid,
"validation failed: " + errorMsg );
1066 if( !SchemaCheckReady(
'C' ) )
1070 static const std::vector<std::string> sectionKeys = {
"AOU",
"ACOU",
"KACOU",
"ABOU" };
1072 std::unique_ptr<BOARD> board = LoadBoard(
"padstacks_complex.kicad_pcb" );
1076 for(
const std::string& sections : sectionKeys )
1078 for(
char version : {
'B',
'C' } )
1080 if( !SchemaCheckReady( version ) )
1086 bool valid = ExportAndValidate( *board, version, errorMsg,
"userdef",
1087 std::string(), sections );
1090 if( version ==
'B' && sections.find(
'C' ) == std::string::npos )
1091 BOOST_CHECK_MESSAGE( !valid,
"IPC-2581B accepted components with no package" );
1093 BOOST_CHECK_MESSAGE( valid,
"validation failed: " + errorMsg );
1103 static const std::vector<std::string> complexBoards = {
1104 "intersectingzones.kicad_pcb",
1105 "custom_pads.kicad_pcb",
1108 for(
const std::string& boardFile : complexBoards )
1112 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
1116 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
1121 for(
char version : {
'B',
'C' } )
1126 bool valid = ExportAndValidate( *board, version, errorMsg );
1128 BOOST_CHECK_MESSAGE( valid,
1129 wxString::Format(
"Export/validation failed for %s version %c: %s",
1130 boardFile, version, errorMsg ) );
1155 wxString tempPath = CreateTempFile();
1157 std::map<std::string, UTF8> props;
1158 props[
"units"] =
"mm";
1159 props[
"version"] =
"C";
1160 props[
"sigfig"] =
"6";
1162 m_ipc2581Plugin.SaveBoard( tempPath, board, &props );
1165 BOOST_CHECK( FileContainsPattern( tempPath, wxT(
"<Line " ) ) );
1166 BOOST_CHECK( !FileContainsPattern( tempPath, wxT(
"<Arc " ) ) );
1185 textbox->
SetText( wxT(
"IPC textbox" ) );
1193 board.
Add( textbox );
1195 wxString tempPath = CreateTempFile();
1197 std::map<std::string, UTF8> props;
1198 props[
"units"] =
"mm";
1199 props[
"version"] =
"C";
1200 props[
"sigfig"] =
"6";
1202 m_ipc2581Plugin.SaveBoard( tempPath, board, &props );
1205 std::ifstream xmlFile( tempPath.ToStdString() );
1208 std::string xmlContent( ( std::istreambuf_iterator<char>( xmlFile ) ), std::istreambuf_iterator<char>() );
1209 size_t textStart = xmlContent.find(
"value=\"IPC textbox\"" );
1210 BOOST_REQUIRE_MESSAGE( textStart != std::string::npos,
"Export should contain the text box feature" );
1212 size_t setEnd = xmlContent.find(
"</Set>", textStart );
1215 std::string textFeature = xmlContent.substr( textStart, setEnd - textStart );
1217 BOOST_CHECK_MESSAGE( XmlRegionHasCoordinateNear( textFeature,
'x', 100.0, 5.0 ),
1218 "Text box geometry should use its X drawing position" );
1219 BOOST_CHECK_MESSAGE( XmlRegionHasCoordinateNear( textFeature,
'y', -50.0, 5.0 ),
1220 "Text box geometry should use its Y drawing position" );
1231 std::unique_ptr<BOARD> board = LoadBoard(
"issue16658/issue16658.kicad_pcb" );
1235 bool hasMaskedSmtPad =
false;
1237 for(
FOOTPRINT* fp : board->Footprints() )
1239 for(
PAD*
pad : fp->Pads() )
1242 hasMaskedSmtPad =
true;
1246 BOOST_REQUIRE_MESSAGE( hasMaskedSmtPad,
"Test board should have an authored F.Mask SMD pad" );
1249 wxString tempPath = CreateTempFile();
1251 std::map<std::string, UTF8> props;
1252 props[
"units"] =
"mm";
1253 props[
"version"] =
"C";
1254 props[
"sigfig"] =
"3";
1256 m_ipc2581Plugin.SaveBoard( tempPath, *board, &props );
1262 bool hasFMaskLayer = FileContainsPattern( tempPath, wxT(
"layerRef=\"F.Mask\"" ) )
1263 || FileContainsPattern( tempPath, wxT(
"layerRef=\"TSM\"" ) );
1265 BOOST_CHECK_MESSAGE( hasFMaskLayer,
1266 "IPC-2581 export should contain F.Mask layer features for SMD pads" );
1269 bool hasLayerFeature = FileContainsPattern( tempPath, wxT(
"<LayerFeature" ) );
1270 BOOST_CHECK_MESSAGE( hasLayerFeature,
"IPC-2581 export should contain LayerFeature elements" );
1280 const std::string open =
"<LayerFeature layerRef=\"" + aLayerRef +
"\"";
1281 size_t start = aXml.find( open );
1283 if( start == std::string::npos )
1284 return std::string();
1286 size_t end = aXml.find(
"</LayerFeature>", start );
1288 if(
end == std::string::npos )
1289 return std::string();
1291 return aXml.substr( start,
end - start );
1297 std::unique_ptr<BOARD> board = LoadBoard(
"connect/connect.kicad_pcb" );
1300 wxString tempPath = CreateTempFile();
1301 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"6" } };
1302 m_ipc2581Plugin.SaveBoard( tempPath, *board, &props );
1304 std::ifstream stream( tempPath.ToStdString(), std::ios::binary );
1306 std::string xml( std::istreambuf_iterator<char>{ stream }, std::istreambuf_iterator<char>{} );
1307 const std::string thermalPin =
"<PinRef componentRef=\"U102\" pin=\"65\"";
1308 auto countThermalPins = [&](
const std::string& aLayer )
1315 while( ( pos = mask.find( thermalPin, pos ) ) != std::string::npos )
1318 pos += thermalPin.size();
1338 std::unique_ptr<BOARD> board = LoadBoard(
"padstacks_complex.kicad_pcb" );
1341 for(
char version : {
'B',
'C' } )
1345 wxString tempPath = CreateTempFile();
1347 std::map<std::string, UTF8> props;
1348 props[
"units"] =
"mm";
1349 props[
"version"] = std::string( 1, version );
1350 props[
"sigfig"] =
"3";
1352 m_ipc2581Plugin.SaveBoard( tempPath, *board, &props );
1355 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"refDes=\"\"" ) ),
1356 "Empty refDes attribute found" );
1357 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"<RefDes name=\"\"" ) ),
1358 "Empty RefDes/@name attribute found" );
1359 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"componentRef=\"\"" ) ),
1360 "Empty PinRef/@componentRef attribute found" );
1378 std::unique_ptr<BOARD> board = LoadBoard(
"issue12609.kicad_pcb" );
1381 wxString tempPath = CreateTempFile();
1383 std::map<std::string, UTF8> props;
1384 props[
"units"] =
"mm";
1385 props[
"version"] =
"C";
1386 props[
"sigfig"] =
"3";
1388 m_ipc2581Plugin.SaveBoard( tempPath, *board, &props );
1394 std::ifstream xmlFile( tempPath.ToStdString() );
1397 std::string xmlContent( ( std::istreambuf_iterator<char>( xmlFile ) ),
1398 std::istreambuf_iterator<char>() );
1401 size_t c5Pos = xmlContent.find(
"refDes=\"C5\"" );
1403 if( c5Pos == std::string::npos )
1404 c5Pos = xmlContent.find(
"refDes=\"NOREF_" );
1406 BOOST_REQUIRE_MESSAGE( c5Pos != std::string::npos,
1407 "C5 component should exist in export" );
1410 std::string c5Region = xmlContent.substr( c5Pos, 200 );
1411 BOOST_CHECK_MESSAGE( c5Region.find(
"rotation=\"90.0\"" ) != std::string::npos
1412 || c5Region.find(
"rotation=\"90.00\"" ) != std::string::npos,
1413 "C5 component rotation should be 90, not inverted. Region: "
1423 std::unique_ptr<BOARD> board = LoadBoard(
"issue12609.kicad_pcb" );
1426 wxString tempPath = CreateTempFile();
1428 std::map<std::string, UTF8> props;
1429 props[
"units"] =
"mm";
1430 props[
"version"] =
"C";
1431 props[
"sigfig"] =
"3";
1433 m_ipc2581Plugin.SaveBoard( tempPath, *board, &props );
1436 bool hasBom = FileContainsPattern( tempPath, wxT(
"<Bom " ) );
1437 bool hasBomRef = FileContainsPattern( tempPath, wxT(
"<BomRef " ) );
1441 BOOST_CHECK_MESSAGE( hasBomRef,
1442 "Content should have BomRef when Bom section is present" );
1462 std::unique_ptr<BOARD> board = LoadBoard(
"test_copper_graphics.kicad_pcb" );
1465 for(
char version : {
'B',
'C' } )
1470 bool valid = ExportAndValidate( *board, version, errorMsg );
1472 BOOST_CHECK_MESSAGE( valid,
1473 wxString::Format(
"Knockout text export should be schema-valid "
1474 "(version %c): %s", version, errorMsg ) );
1485 std::unique_ptr<BOARD> board = LoadBoard(
"odbpp/pad_backdrill.kicad_pcb" );
1487 const PAD* machinedPad =
dynamic_cast<const PAD*
>(
1488 board->ResolveItem(
KIID( wxS(
"34428461-7652-43ea-869c-ef8abe86da6c" ) ),
true ) );
1494 wxString output = CreateTempFile();
1495 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"6" } };
1497 IPC2581::SECTION::OUTER_COPPER, IPC2581::SECTION::INNER_COPPER };
1499 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( output, *board, &props ) );
1500 wxXmlDocument document;
1502 std::vector<wxXmlNode*> specs;
1503 std::vector<wxXmlNode*> holes;
1504 CollectXmlElements( document.GetRoot(), wxS(
"Spec" ), specs );
1505 CollectXmlElements( document.GetRoot(), wxS(
"Hole" ), holes );
1506 std::map<wxString, wxXmlNode*> specsByName;
1508 for( wxXmlNode* spec : specs )
1509 specsByName.emplace( spec->GetAttribute( wxS(
"name" ) ), spec );
1511 int matchingHoles = 0;
1513 for( wxXmlNode* hole : holes )
1515 std::map<wxString, wxXmlNode*> bySide;
1517 for( wxXmlNode* ref = hole->GetChildren(); ref; ref = ref->GetNext() )
1519 if( ref->GetName() != wxS(
"SpecRef" ) )
1522 auto spec = specsByName.find( ref->GetAttribute( wxS(
"id" ) ) );
1524 if( spec == specsByName.end() )
1527 wxXmlNode* side = SecondaryDrillProperty( spec->second, wxS(
"SIDE" ) );
1530 bySide.emplace( side->GetAttribute( wxS(
"text" ) ), spec->second );
1533 if( bySide.count( wxS(
"TOP" ) ) == 0 || bySide.count( wxS(
"BOTTOM" ) ) == 0 )
1538 for(
const auto& [side, spec] : bySide )
1540 wxXmlNode* diameter = SecondaryDrillProperty( spec, wxS(
"MAJOR_DIAMETER" ) );
1542 BOOST_CHECK_EQUAL( diameter->GetAttribute( wxS(
"unit" ) ), wxString( wxS(
"MM" ) ) );
1545 BOOST_CHECK_SMALL( value - ( side == wxS(
"TOP" ) ? 1.2 : 1.4 ), 0.000001 );
1547 wxXmlNode* depth = SecondaryDrillProperty( spec, wxS(
"DEPTH" ) );
1548 wxXmlNode* angle = SecondaryDrillProperty( spec, wxS(
"ANGLE" ) );
1550 if( side == wxS(
"TOP" ) )
1552 BOOST_CHECK( depth ==
nullptr );
1554 BOOST_CHECK_EQUAL( angle->GetAttribute( wxS(
"unit" ) ), wxString( wxS(
"DEGREES" ) ) );
1556 BOOST_CHECK_SMALL( value - 90.0, 0.000001 );
1560 BOOST_CHECK( angle ==
nullptr );
1562 BOOST_CHECK_EQUAL( depth->GetAttribute( wxS(
"unit" ) ), wxString( wxS(
"MM" ) ) );
1564 BOOST_CHECK_SMALL( value - 0.3, 0.000001 );
1570 BOOST_CHECK( !FileContainsPattern( output, wxT(
"post-machining=" ) ) );
1572 if( SchemaCheckReady(
'C' ) )
1574 wxString schemaError = ValidateXmlWithXsd( output, GetXsdPath(
'C' ) );
1575 BOOST_CHECK_MESSAGE( schemaError.IsEmpty(), schemaError.ToStdString() );
1578 wxString bOutput = CreateTempFile();
1579 props[
"version"] =
"B";
1580 props.erase(
"sections" );
1581 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( bOutput, *board, &props ) );
1582 BOOST_CHECK( !FileContainsPattern( bOutput, wxT(
"<SecondaryDrill" ) ) );
1583 BOOST_CHECK( !FileContainsPattern( bOutput, wxT(
"post-machining=" ) ) );
1590 std::unique_ptr<BOARD> board = LoadBoard(
"issue25101.kicad_pcb" );
1592 const PAD*
pad =
dynamic_cast<const PAD*
>(
1593 board->ResolveItem(
KIID( wxS(
"3fad97f9-a9d0-4388-8119-c4cdfce21fd9" ) ),
true ) );
1596 VECTOR2I offset =
pad->GetOffset(
pad->Padstack().EffectiveLayerFor(
pad->GetLayer() ) );
1599 wxString output = CreateTempFile();
1600 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"6" } };
1601 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( output, *board, &props ) );
1602 wxXmlDocument document;
1604 std::vector<wxXmlNode*> slots;
1605 CollectXmlElements( document.GetRoot(), wxS(
"SlotCavity" ), slots );
1606 BOOST_REQUIRE_GE( slots.size(), 4u );
1607 VECTOR2I holeCenter =
pad->GetEffectiveHoleShape()->GetCenter();
1608 double expectedX =
pcbIUScale.IUTomm( holeCenter.
x );
1609 double expectedY = -
pcbIUScale.IUTomm( holeCenter.
y );
1610 double nearest = 1e9;
1612 for( wxXmlNode* slot : slots )
1614 wxXmlNode*
location = FindXmlChild( slot, wxS(
"Location" ) );
1620 nearest = std::min( nearest, std::hypot( x - expectedX, y - expectedY ) );
1623 BOOST_CHECK_SMALL( nearest, 0.001 );
1630 std::unique_ptr<BOARD> board = LoadBoard(
"odbpp/pad_backdrill.kicad_pcb" );
1633 for(
char version : {
'B',
'C' } )
1635 wxString output = CreateTempFile();
1636 std::map<std::string, UTF8> props = { {
"units",
"mm" },
1637 {
"version", version ==
'B' ?
"B" :
"C" },
1638 {
"sigfig",
"6" } };
1639 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( output, *board, &props ) );
1641 if( version ==
'B' )
1643 wxXmlDocument document;
1645 std::vector<wxXmlNode*> slots;
1646 CollectXmlElements( document.GetRoot(), wxS(
"SlotCavity" ), slots );
1647 BOOST_REQUIRE_GE( slots.size(), 1u );
1649 for( wxXmlNode* slot : slots )
1653 for( wxXmlNode* child = slot->GetChildren(); child; child = child->GetNext() )
1655 if( child->GetType() == wxXML_ELEMENT_NODE )
1656 BOOST_CHECK( child->GetName() == wxS(
"Outline" ) );
1661 if( !SchemaCheckReady( version ) )
1664 wxString schemaError = ValidateXmlWithXsd( output, GetXsdPath( version ) );
1665 BOOST_CHECK_MESSAGE( schemaError.IsEmpty(),
"Revision " << version <<
": " << schemaError.ToStdString() );
1701 via->SetSecondaryDrillStartLayer(
F_Cu );
1702 via->SetSecondaryDrillEndLayer(
In3_Cu );
1706 wxString tempPath = CreateTempFile();
1707 std::map<std::string, UTF8> props;
1708 props[
"units"] =
"mm";
1709 props[
"version"] =
"C";
1710 props[
"sigfig"] =
"4";
1712 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, board, &props ) );
1715 BOOST_CHECK_MESSAGE(
1716 FileContainsPattern( tempPath, wxT(
"<Backdrill type=\"START_LAYER\"" ) ),
1717 "Backdrill spec should declare a START_LAYER child" );
1718 BOOST_CHECK_MESSAGE(
1719 FileContainsPattern( tempPath, wxT(
"<Backdrill type=\"MUST_NOT_CUT_LAYER\"" ) ),
1720 "Backdrill spec should declare a MUST_NOT_CUT_LAYER child" );
1721 BOOST_CHECK_MESSAGE(
1722 FileContainsPattern( tempPath, wxT(
"<Backdrill type=\"MAX_STUB_LENGTH\"" ) ),
1723 "Backdrill spec should declare a MAX_STUB_LENGTH child" );
1727 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"layerOrGroupRef=" ) ),
1728 "Backdrill must convey layers through Property layerOrGroupRef" );
1729 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"startLayerRef=" ) ),
1730 "Backdrill should not use schema-invalid startLayerRef attribute" );
1731 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"mustNotCutLayerRef=" ) ),
1732 "Backdrill should not use schema-invalid mustNotCutLayerRef attribute" );
1733 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"maxStubLength=" ) ),
1734 "Backdrill should not use schema-invalid maxStubLength attribute" );
1735 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"postMachining=" ) ),
1736 "Backdrill should not use the non-standard postMachining attribute" );
1740 BOOST_CHECK_MESSAGE(
1741 FileContainsPattern( tempPath, wxT(
"layerOrGroupRef=\"In4.Cu\"" ) ),
1742 "Front backdrill must-not-cut layer should be In4.Cu" );
1746 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"BD_1C" ) ),
1747 "Exporter should not emit a primary backdrill spec slot" );
1750 BOOST_CHECK( !FileContainsPattern( tempPath, wxT(
"<Backdrill type=\"OTHER\"" ) ) );
1751 BOOST_CHECK( !FileContainsPattern( tempPath, wxT(
"<SecondaryDrill" ) ) );
1761 std::unique_ptr<BOARD> board = LoadBoard(
"connect/connect.kicad_pcb" );
1763 bool thermalMask =
false;
1764 bool normalPaste =
false;
1765 bool copperOnly =
false;
1766 bool maskAperture =
false;
1768 for(
FOOTPRINT* footprint : board->Footprints() )
1770 for(
PAD*
pad : footprint->Pads() )
1772 if( footprint->GetReference() == wxS(
"U102" ) )
1774 if(
pad->GetNumber() == wxS(
"65" ) &&
pad->IsOnLayer(
F_Mask ) )
1780 if(
pad->GetNumber() == wxS(
"1" ) &&
pad->IsOnLayer(
F_Paste ) )
1783 else if( footprint->GetReference() == wxS(
"J301" ) )
1785 if(
pad->GetNumber() == wxS(
"1" ) &&
pad->IsOnLayer(
F_Cu )
1792 maskAperture =
true;
1801 wxString tempPath = CreateTempFile();
1802 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"4" } };
1803 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, *board, &props ) );
1805 std::ifstream stream( tempPath.ToStdString(), std::ios::binary );
1807 std::string xml( std::istreambuf_iterator<char>{ stream }, std::istreambuf_iterator<char>{} );
1813 BOOST_CHECK(
paste.find(
"<PinRef componentRef=\"U102\" pin=\"65\"" ) == std::string::npos );
1814 BOOST_CHECK(
paste.find(
"<PinRef componentRef=\"U102\" pin=\"1\"" ) != std::string::npos );
1815 BOOST_CHECK(
paste.find(
"<PinRef componentRef=\"J301\" pin=\"1\"" ) == std::string::npos );
1816 BOOST_CHECK( mask.find(
"<PinRef componentRef=\"U102\" pin=\"65\"" ) != std::string::npos );
1817 BOOST_CHECK( mask.find(
"<PinRef componentRef=\"J301\" pin=\"1\"" ) == std::string::npos );
1842 wxString tempPath = CreateTempFile();
1843 std::map<std::string, UTF8> props;
1844 props[
"units"] =
"mm";
1845 props[
"version"] =
"C";
1846 props[
"sigfig"] =
"4";
1848 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, board, &props ) );
1854 size_t rectPos = xml.find(
"<RectRound" );
1855 BOOST_REQUIRE_MESSAGE( rectPos != std::string::npos,
"Export should contain a RectRound" );
1857 std::string rectNode = xml.substr( rectPos, xml.find(
'>', rectPos ) - rectPos );
1859 BOOST_CHECK_MESSAGE( rectNode.find(
"radius=\"0.750\"" ) != std::string::npos,
1860 "Rounded gr_rect must export its 0.75 mm corner radius. Node: "
1862 BOOST_CHECK_MESSAGE( rectNode.find(
"upperRight=\"true\"" ) != std::string::npos
1863 && rectNode.find(
"lowerLeft=\"true\"" ) != std::string::npos,
1864 "Rounded gr_rect must set its corner flags. Node: " + rectNode );
1865 BOOST_CHECK_MESSAGE( rectNode.find(
"radius=\"0.025\"" ) == std::string::npos,
1866 "Corner radius must not collapse to stroke_width/2. Node: " + rectNode );
1889 pad->SetNumber( wxT(
"1" ) );
1897 wxString tempPath = CreateTempFile();
1898 std::map<std::string, UTF8> props;
1899 props[
"units"] =
"mm";
1900 props[
"version"] =
"C";
1901 props[
"sigfig"] =
"4";
1903 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, board, &props ) );
1910 BOOST_CHECK_MESSAGE( fMask.find(
"<PinRef componentRef=\"J29\" pin=\"1\"" ) == std::string::npos,
1911 "Back-only masked pad must not appear on F.Mask layer feature" );
1914 BOOST_CHECK_MESSAGE( bMask.find(
"<PinRef componentRef=\"J29\" pin=\"1\"" ) != std::string::npos,
1915 "Back-only masked pad should appear on B.Mask layer feature" );
1938 pad->SetNumber( wxT(
"1" ) );
1944 pad->SetLocalSolderMaskMargin(
pcbIUScale.mmToIU( -0.05 ) );
1947 wxString tempPath = CreateTempFile();
1948 std::map<std::string, UTF8> props;
1949 props[
"units"] =
"mm";
1950 props[
"version"] =
"C";
1951 props[
"sigfig"] =
"4";
1953 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, board, &props ) );
1960 BOOST_CHECK_MESSAGE( xml.find(
"radius=\"0.10\"" ) != std::string::npos,
1961 "Copper roundrect should keep its 0.10 mm radius" );
1962 BOOST_CHECK_MESSAGE( xml.find(
"radius=\"0.050\"" ) != std::string::npos,
1963 "Mask roundrect aperture must shrink its radius to 0.05 mm" );
1964 BOOST_CHECK_MESSAGE( xml.find(
"width=\"0.350\"" ) != std::string::npos,
1965 "Mask roundrect aperture should be 0.35 mm wide" );
1999 wxString tempPath = CreateTempFile();
2000 std::map<std::string, UTF8> props;
2001 props[
"units"] =
"mm";
2002 props[
"version"] =
"C";
2003 props[
"sigfig"] =
"4";
2005 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, board, &props ) );
2014 BOOST_CHECK_MESSAGE( fCu.find(
"UserPrimitiveRef" ) != std::string::npos,
2015 "Footprint graphic should be present on F.Cu" );
2016 BOOST_CHECK_MESSAGE( fMask.find(
"UserPrimitiveRef" ) != std::string::npos,
2017 "Multi-layer footprint graphic must also appear on F.Mask" );
2041 pad->SetNumber( wxT(
"1" ) );
2049 wxString tempPath = CreateTempFile();
2050 std::map<std::string, UTF8> props;
2051 props[
"units"] =
"mm";
2052 props[
"version"] =
"C";
2053 props[
"sigfig"] =
"4";
2055 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, board, &props ) );
2058 std::ifstream xmlFile( tempPath.ToStdString() );
2061 std::string xml( ( std::istreambuf_iterator<char>( xmlFile ) ),
2062 std::istreambuf_iterator<char>() );
2066 const std::string maskRef =
"<PadstackPadDef layerRef=\"F.Mask\"";
2067 size_t maskDefPos = xml.find( maskRef );
2068 BOOST_REQUIRE_MESSAGE( maskDefPos != std::string::npos,
2069 "F.Mask padstack pad definition should be present" );
2071 size_t refPos = xml.find(
"StandardPrimitiveRef id=\"", maskDefPos );
2073 refPos += std::string(
"StandardPrimitiveRef id=\"" ).size();
2074 size_t refEnd = xml.find(
'"', refPos );
2075 std::string maskPrimId = xml.substr( refPos, refEnd - refPos );
2077 std::string entry =
"<EntryStandard id=\"" + maskPrimId +
"\"";
2078 size_t entryPos = xml.find( entry );
2079 BOOST_REQUIRE_MESSAGE( entryPos != std::string::npos,
2080 "Mask primitive EntryStandard should be defined" );
2082 size_t diaPos = xml.find(
"diameter=\"", entryPos );
2084 diaPos += std::string(
"diameter=\"" ).size();
2085 size_t diaEnd = xml.find(
'"', diaPos );
2086 double diameter = std::stod( xml.substr( diaPos, diaEnd - diaPos ) );
2088 BOOST_CHECK_MESSAGE(
std::abs( diameter - 2.0 ) < 1e-4,
2089 "F.Mask primitive diameter should be 2.0 mm (1.0 mm pad + 0.5 mm "
2090 "margin per side), got " << diameter );
2099 static const std::vector<std::string> boards = {
2100 "ipc2581/dielectric-sublayer.kicad_pcb",
2101 "ipc2581/edgecuts-circles-only.kicad_pcb",
2102 "ipc2581/filled-silk-circle.kicad_pcb",
2103 "ipc2581/filled-tented-via.kicad_pcb",
2104 "ipc2581/textbox-border.kicad_pcb",
2105 "ipc2581/thirty-copper-layers.kicad_pcb",
2106 "ipc2581/whitespace-text.kicad_pcb",
2109 for(
const std::string& boardFile : boards )
2113 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
2117 bool valid = ExportAndValidate( *board,
'C', errorMsg );
2119 BOOST_CHECK_MESSAGE( valid,
"IPC-2581C validation failed for " + boardFile +
": " + errorMsg );
2131 std::unique_ptr<BOARD> board = LoadBoard(
"ipc2581/edgecuts-circles-only.kicad_pcb" );
2134 wxString tempPath = CreateTempFile();
2135 std::map<std::string, UTF8> props;
2136 props[
"units"] =
"mm";
2137 props[
"version"] =
"C";
2138 props[
"sigfig"] =
"3";
2140 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, *board, &props ) );
2143 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"<Profile" ) ),
2144 "A board with no closed outline must not write a Profile" );
2153 std::unique_ptr<BOARD> board = LoadBoard(
"ipc2581/filled-tented-via.kicad_pcb" );
2156 wxString tempPath = CreateTempFile();
2157 std::map<std::string, UTF8> props;
2158 props[
"units"] =
"mm";
2159 props[
"version"] =
"C";
2160 props[
"sigfig"] =
"3";
2162 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, *board, &props ) );
2168 BOOST_REQUIRE_MESSAGE( !region.empty(),
"Export should contain the F.Cu_2 process layer" );
2170 BOOST_CHECK_MESSAGE( region.find(
"<Set" ) != std::string::npos,
2171 "Process-layer via pads must be wrapped in a Set" );
2172 BOOST_CHECK_MESSAGE( region.find(
"x=\"120.0\" y=\"-115.0\"" ) != std::string::npos,
2173 "Process-layer via pad must be at the via position" );
2174 BOOST_CHECK_MESSAGE( region.find(
"x=\"0.0\" y=\"0.0\"" ) == std::string::npos,
2175 "Process-layer via pad must not be at (0,0)" );
2181 std::unique_ptr<BOARD> board = LoadBoard(
"ipc2581/round_unequal_drill.kicad_pcb" );
2184 const PAD*
pad =
dynamic_cast<const PAD*
>(
2185 board->ResolveItem(
KIID( wxS(
"bd1ca0c2-74bb-4a01-94e6-11796af0cb7e" ) ),
true ) );
2188 BOOST_REQUIRE_NE(
pad->GetDrillSizeX(),
pad->GetDrillSizeY() );
2190 wxString tempPath = CreateTempFile();
2191 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"6" } };
2192 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, *board, &props ) );
2194 wxXmlDocument document;
2196 wxXmlNode* root = document.GetRoot();
2202 auto isAtPad = [&]( wxXmlNode* aNode )
2204 return aNode &&
std::abs( wxAtof( aNode->GetAttribute( wxS(
"x" ) ) ) - x ) <= 0.001
2205 &&
std::abs( wxAtof( aNode->GetAttribute( wxS(
"y" ) ) ) - y ) <= 0.001;
2208 std::vector<wxXmlNode*> holes;
2209 CollectXmlElements( root, wxS(
"Hole" ), holes );
2210 double layerHole = 0;
2212 for( wxXmlNode* hole : holes )
2214 if( hole->GetAttribute( wxS(
"platingStatus" ) ) == wxS(
"PLATED" ) && isAtPad( hole ) )
2215 layerHole = wxAtof( hole->GetAttribute( wxS(
"diameter" ) ) );
2218 BOOST_REQUIRE_GT( layerHole, 0 );
2220 std::set<wxString> padstackNames;
2221 std::vector<wxXmlNode*> pads;
2222 CollectXmlElements( root, wxS(
"Pad" ), pads );
2224 for( wxXmlNode* padNode : pads )
2226 if( padNode->HasAttribute( wxS(
"padstackDefRef" ) ) && isAtPad( FindXmlChild( padNode, wxS(
"Location" ) ) ) )
2227 padstackNames.insert( padNode->GetAttribute( wxS(
"padstackDefRef" ) ) );
2230 BOOST_REQUIRE_EQUAL( padstackNames.size(), 1 );
2232 std::vector<wxXmlNode*> padstacks;
2233 CollectXmlElements( root, wxS(
"PadStackDef" ), padstacks );
2234 wxXmlNode* padstack =
nullptr;
2236 for( wxXmlNode* candidate : padstacks )
2238 if( candidate->GetAttribute( wxS(
"name" ) ) == *padstackNames.begin() )
2239 padstack = candidate;
2243 wxXmlNode* padstackHole = FindXmlChild( padstack, wxS(
"PadstackHoleDef" ) );
2244 BOOST_REQUIRE_MESSAGE( padstackHole,
"Round unequal drill padstack must define its hole" );
2245 BOOST_CHECK_EQUAL( padstackHole->GetAttribute( wxS(
"platingStatus" ) ), wxS(
"PLATED" ) );
2246 BOOST_CHECK_SMALL( wxAtof( padstackHole->GetAttribute( wxS(
"diameter" ) ) ) - layerHole, 0.001 );
2247 BOOST_CHECK_SMALL( layerHole -
pcbIUScale.IUTomm( std::min(
pad->GetDrillSizeX(),
pad->GetDrillSizeY() ) ),
2254 std::unique_ptr<BOARD> board = LoadBoard(
"odbpp/slot_machining.kicad_pcb" );
2257 const PAD* machinedSlot =
dynamic_cast<const PAD*
>(
2258 board->ResolveItem(
KIID( wxS(
"34428461-7652-43ea-869c-ef8abe86da6c" ) ),
true ) );
2264 wxString output = CreateTempFile();
2265 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"6" } };
2266 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( output, *board, &props ) );
2268 wxXmlDocument document;
2270 std::vector<wxXmlNode*> specs;
2271 std::vector<wxXmlNode*> slots;
2272 std::map<wxString, wxXmlNode*> specsByName;
2273 CollectXmlElements( document.GetRoot(), wxS(
"Spec" ), specs );
2274 CollectXmlElements( document.GetRoot(), wxS(
"SlotCavity" ), slots );
2276 for( wxXmlNode* spec : specs )
2277 specsByName.emplace( spec->GetAttribute( wxS(
"name" ) ), spec );
2279 int machinedSlots = 0;
2281 for( wxXmlNode* slot : slots )
2283 std::map<wxString, double> diameterBySide;
2285 for( wxXmlNode* ref = slot->GetParent()->GetChildren(); ref; ref = ref->GetNext() )
2287 if( ref->GetName() != wxS(
"SpecRef" ) )
2290 auto spec = specsByName.find( ref->GetAttribute( wxS(
"id" ) ) );
2292 wxXmlNode* side = SecondaryDrillProperty( spec->second, wxS(
"SIDE" ) );
2293 wxXmlNode* diameter = SecondaryDrillProperty( spec->second, wxS(
"MAJOR_DIAMETER" ) );
2295 diameterBySide[side->GetAttribute( wxS(
"text" ) )] = wxAtof( diameter->GetAttribute( wxS(
"value" ) ) );
2298 if( diameterBySide.empty() )
2302 BOOST_CHECK_SMALL( diameterBySide[wxS(
"TOP" )] - 1.2, 0.000001 );
2303 BOOST_CHECK_SMALL( diameterBySide[wxS(
"BOTTOM" )] - 1.4, 0.000001 );
2308 if( SchemaCheckReady(
'C' ) )
2310 wxString schemaError = ValidateXmlWithXsd( output, GetXsdPath(
'C' ) );
2311 BOOST_CHECK_MESSAGE( schemaError.IsEmpty(), schemaError.ToStdString() );
2318 std::unique_ptr<BOARD> board = LoadBoard(
"issue5990.kicad_pcb" );
2321 size_t sourceCount = 0;
2323 for(
FOOTPRINT* footprint : board->Footprints() )
2325 for(
PAD*
pad : footprint->Pads() )
2340 BOOST_REQUIRE_EQUAL( sourceCount, 4u );
2343 wxString output = CreateTempFile();
2344 std::map<std::string, UTF8> props = { {
"units",
"mm" }, {
"version",
"C" }, {
"sigfig",
"6" } };
2345 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( output, *board, &props ) );
2346 wxXmlDocument document;
2348 wxXmlNode* root = document.GetRoot();
2351 auto circleDiameter = [&]( wxXmlNode* aParent )
2353 wxXmlNode* ref = FindXmlChild( aParent, wxS(
"StandardPrimitiveRef" ) );
2355 ref ? FindXmlEntry( root, wxS(
"EntryStandard" ), ref->GetAttribute( wxS(
"id" ) ) ) :
nullptr;
2356 wxXmlNode*
circle = entry ? FindXmlChild( entry, wxS(
"Circle" ) ) :
nullptr;
2360 ref = FindXmlChild( aParent, wxS(
"UserPrimitiveRef" ) );
2361 entry = ref ? FindXmlEntry( root, wxS(
"EntryUser" ), ref->GetAttribute( wxS(
"id" ) ) ) :
nullptr;
2362 wxXmlNode* special = entry ? FindXmlChild( entry, wxS(
"UserSpecial" ) ) :
nullptr;
2363 circle = special ? FindXmlChild( special, wxS(
"Circle" ) ) :
nullptr;
2366 return circle ? wxAtof(
circle->GetAttribute( wxS(
"diameter" ) ) ) : -1.0;
2369 std::vector<wxXmlNode*> padstacks;
2370 CollectXmlElements( root, wxS(
"PadStackDef" ), padstacks );
2371 wxString targetName;
2372 double padstackDiameter = -1.0;
2373 size_t padstackCount = 0;
2375 for( wxXmlNode* padstack : padstacks )
2377 wxXmlNode* hole = FindXmlChild( padstack, wxS(
"PadstackHoleDef" ) );
2379 if( !hole || hole->GetAttribute( wxS(
"platingStatus" ) ) != wxS(
"NONPLATED" )
2380 ||
std::abs( wxAtof( hole->GetAttribute( wxS(
"diameter" ) ) ) - 2.7 ) > 0.001 )
2385 for( wxXmlNode* paddef = padstack->GetChildren(); paddef; paddef = paddef->GetNext() )
2387 if( paddef->GetName() == wxS(
"PadstackPadDef" )
2388 && paddef->GetAttribute( wxS(
"layerRef" ) ) == wxS(
"F.Mask" ) )
2390 targetName = padstack->GetAttribute( wxS(
"name" ) );
2391 padstackDiameter = circleDiameter( paddef );
2397 BOOST_REQUIRE_EQUAL( padstackCount, 1u );
2399 BOOST_CHECK_SMALL( padstackDiameter - 5.0, 0.001 );
2400 std::vector<wxXmlNode*> features;
2401 CollectXmlElements( root, wxS(
"LayerFeature" ), features );
2404 for( wxXmlNode* feature : features )
2406 if( feature->GetAttribute( wxS(
"layerRef" ) ) != wxS(
"F.Mask" ) )
2409 std::vector<wxXmlNode*> pads;
2410 CollectXmlElements( feature->GetChildren(), wxS(
"Pad" ), pads );
2412 for( wxXmlNode*
pad : pads )
2414 if(
pad->GetAttribute( wxS(
"padstackDefRef" ) ) == targetName )
2416 BOOST_CHECK_SMALL( circleDiameter(
pad ) - 5.0, 0.001 );
2428 std::unique_ptr<BOARD> board = LoadBoard(
"variant_test/variant_test.kicad_pcb" );
2431 std::multiset<wxString> sourceRefs;
2433 for(
FOOTPRINT* footprint : board->Footprints() )
2434 sourceRefs.insert( footprint->GetReference() );
2436 BOOST_REQUIRE_EQUAL( sourceRefs.count( wxS(
"R2" ) ), 2 );
2438 for(
char version : {
'B',
'C' } )
2442 wxString
path = CreateTempFile();
2443 std::map<std::string, UTF8> props = { {
"units",
"mm" },
2444 {
"version", std::string( 1, version ) },
2445 {
"OEMRef",
"Reference" } };
2446 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard(
path, *board, &props ) );
2448 wxXmlDocument document;
2450 wxXmlNode* root = document.GetRoot();
2453 std::vector<wxXmlNode*> components;
2454 std::vector<wxXmlNode*> bomItems;
2455 std::vector<wxXmlNode*> avlItems;
2456 std::vector<wxXmlNode*> pinRefs;
2457 std::vector<wxXmlNode*> logicalNetPins;
2458 CollectXmlElements( root, wxS(
"Component" ), components );
2459 CollectXmlElements( root, wxS(
"BomItem" ), bomItems );
2460 CollectXmlElements( root, wxS(
"AvlItem" ), avlItems );
2461 CollectXmlElements( root, wxS(
"PinRef" ), pinRefs );
2462 CollectXmlElements( root, wxS(
"LogicalNetPin" ), logicalNetPins );
2464 std::multiset<wxString> parts;
2465 std::set<wxString> componentRefs;
2467 for( wxXmlNode* component : components )
2469 parts.insert( component->GetAttribute( wxS(
"part" ) ) );
2470 componentRefs.insert( component->GetAttribute( wxS(
"refDes" ) ) );
2473 BOOST_CHECK( parts == sourceRefs );
2476 std::map<wxString, wxString> partByRef;
2477 std::set<wxString> bomParts;
2479 for( wxXmlNode* item : bomItems )
2481 wxString part = item->GetAttribute( wxS(
"OEMDesignNumberRef" ) );
2482 bomParts.insert( part );
2483 std::vector<wxXmlNode*> refs;
2484 CollectXmlElements( item->GetChildren(), wxS(
"RefDes" ), refs );
2486 if( part == wxS(
"R2" ) )
2488 BOOST_CHECK_EQUAL( item->GetAttribute( wxS(
"quantity" ) ), wxString( wxS(
"2" ) ) );
2492 for( wxXmlNode* ref : refs )
2493 BOOST_CHECK( partByRef.emplace( ref->GetAttribute( wxS(
"name" ) ), part ).second );
2498 for( wxXmlNode* component : components )
2500 wxString ref = component->GetAttribute( wxS(
"refDes" ) );
2504 std::set<wxString> avlParts;
2506 for( wxXmlNode* item : avlItems )
2507 avlParts.insert( item->GetAttribute( wxS(
"OEMDesignNumber" ) ) );
2509 BOOST_CHECK( bomParts == avlParts );
2510 BOOST_CHECK( bomParts == std::set<wxString>( sourceRefs.begin(), sourceRefs.end() ) );
2512 for( wxXmlNode* ref : pinRefs )
2513 BOOST_CHECK( componentRefs.count( ref->GetAttribute( wxS(
"componentRef" ) ) ) );
2515 for( wxXmlNode* ref : logicalNetPins )
2516 BOOST_CHECK( componentRefs.count( ref->GetAttribute( wxS(
"componentRef" ) ) ) );
2518 if( SchemaCheckReady( version ) )
2519 BOOST_CHECK( ValidateXmlWithXsd(
path, GetXsdPath( version ) ).IsEmpty() );
2523 for(
char version : {
'B',
'C' } )
2527 wxString
path = CreateTempFile();
2528 std::map<std::string, UTF8> props = { {
"units",
"mm" },
2529 {
"version", std::string( 1, version ) },
2530 {
"OEMRef",
"Reference" },
2531 {
"refdes",
"omit" } };
2532 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard(
path, *board, &props ) );
2534 wxXmlDocument document;
2537 std::vector<wxXmlNode*> components;
2538 std::vector<wxXmlNode*> bomRefs;
2539 std::vector<wxXmlNode*> pinRefs;
2540 CollectXmlElements( document.GetRoot(), wxS(
"Component" ), components );
2541 CollectXmlElements( document.GetRoot(), wxS(
"RefDes" ), bomRefs );
2542 CollectXmlElements( document.GetRoot(), wxS(
"PinRef" ), pinRefs );
2544 std::multiset<wxString> parts;
2545 std::set<wxString> anonRefs;
2547 for( wxXmlNode* ref : bomRefs )
2549 wxString
name = ref->GetAttribute( wxS(
"name" ) );
2550 BOOST_CHECK(
name.StartsWith( wxS(
"CMP_" ) ) );
2551 BOOST_CHECK( anonRefs.insert(
name ).second );
2554 for( wxXmlNode* component : components )
2556 parts.insert( component->GetAttribute( wxS(
"part" ) ) );
2557 wxString ref = component->GetAttribute( wxS(
"refDes" ) );
2559 if( version ==
'B' )
2560 BOOST_CHECK( anonRefs.count( ref ) );
2562 BOOST_CHECK( ref.IsEmpty() );
2565 BOOST_CHECK( parts == sourceRefs );
2568 for( wxXmlNode* ref : pinRefs )
2570 if( version ==
'B' )
2571 BOOST_CHECK( anonRefs.count( ref->GetAttribute( wxS(
"componentRef" ) ) ) );
2573 BOOST_CHECK( ref->GetAttribute( wxS(
"componentRef" ) ).IsEmpty() );
2576 if( SchemaCheckReady( version ) )
2577 BOOST_CHECK( ValidateXmlWithXsd(
path, GetXsdPath( version ) ).IsEmpty() );
constexpr double PCB_IU_PER_MM
Pcbnew IU is 1 nanometer.
constexpr EDA_IU_SCALE pcbIUScale
General utilities for PCB file IO for QA programs.
wxString GetNetname() const
virtual void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Container for design settings for a BOARD object.
BOARD_STACKUP & GetStackupDescriptor()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
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.
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
void SetCopperLayerCount(int aCount)
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
virtual void SetFilled(bool aFlag)
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
virtual void SetText(const wxString &aText)
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
SECTION_SET & Set(SECTION aSection, bool aOn=true)
std::vector< wxString > m_variantNames
void FromJson(const nlohmann::json &aJson) override
void SetConfiguredOutputPath(const wxString &aPath)
Sets the configured output path for the job, this path is always saved to file.
RAII helper that sets a process-level environment variable for the lifetime of the object.
void SetValue(const wxString &aValue)
Set a new value for the environment variable (which will still only be in effect for the lifetime of ...
const std::filesystem::path & Path() const
Get the path to the temporary directory as a std::filesystem::path.
LSET is a set of PCB_LAYER_IDs.
Handle the data for a net.
@ NORMAL
Shape is the same on all layers.
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
PAD_DRILL_POST_MACHINING_MODE GetBackPostMachiningMode() const
PAD_DRILL_POST_MACHINING_MODE GetFrontPostMachiningMode() const
void SetMid(const VECTOR2I &aMid)
A PCB_IO derivation for saving and loading Pcbnew s-expression formatted files.
void SetCornerRadius(int aCornerRadius) override
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
void SetBorderEnabled(bool enabled)
Disables the border, this is done by changing the stroke internally.
void SetMarginTop(int aTop)
void SetMarginLeft(int aLeft)
void SetTextThickness(int aWidth) override
The TextThickness is that set by the user.
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true) override
void SetEnd(const VECTOR2I &aEnd)
void SetStart(const VECTOR2I &aStart)
virtual void SetWidth(int aWidth)
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.
A wrapper for reporting to a wxString object.
bool IsDrillSlot(const PAD &aPad)
Whether a pad's hole is a slot rather than a round drill.
std::vector< DRILL_OPERATION > EnumerateDrillOperations(const BOARD &aBoard, const DRILL_QUERY &aQuery)
The one place that decides what the board's holes are.
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
FAB_PIN MakeFabPin(const PAD &aPad, size_t aIndexInFootprint)
aIndexInFootprint is the pad's position in its footprint, used to name NPTH and unnumbered pads
bool GenerateIpc2581File(JOB_EXPORT_PCB_IPC2581 &aJob, BOARD *aBoard, PROGRESS_REPORTER *aProgressReporter, REPORTER *aReporter)
Write the IPC-2581 file or archive of aJob and record it as a job output.
wxString SectionKeyString(const SECTION_SET &aSet)
Give aSet as a sectionKey attribute value.
SECTION_SET LegacySections()
Schema sections of the content that KiCad wrote before the function modes.
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
std::string LoadStringData(const wxString &aPath)
Load the contents of a file into a string.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
@ SMD
Smd pad, appears on the solder paste layer (default)
@ PTH
Plated through hole pad.
One machining action, which is also one NC hit and one tool assignment.
Selects which operations EnumerateDrillOperations() returns.
bool m_NonPlatedOnly
Emit only non-plated holes.
wxString GetXsdPath(char aVersion) const
PCB_IO_IPC2581 m_ipc2581Plugin
bool SchemaCheckEnabled(const wxString &aXsdPath, wxString &aError) const
bool ExportAndValidate(BOARD &aBoard, char aVersion, wxString &aErrorMsg, const std::string &aMode=std::string(), const std::string &aRefDes=std::string(), const std::string &aSections=std::string())
std::vector< wxString > m_tempFiles
PCB_IO_KICAD_SEXPR m_kicadPlugin
~IPC2581_EXPORT_FIXTURE()
wxString CreateTempFile(const wxString &aSuffix=wxT(""))
std::unique_ptr< BOARD > LoadBoard(const std::string &aRelativePath)
bool SchemaCheckReady(char aVersion) const
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(Ipc2581PadHoleMatchesNcDrill)
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.
IbisParser parser & reporter
VECTOR3I expected(15, 30, 45)
BOOST_TEST_CONTEXT("Test Clearance")
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
@ PCB_PAD_T
class PAD, a pad in a footprint
VECTOR2< int32_t > VECTOR2I
double ToDouble(KI_INT128 aValue)