38static constexpr int CONNECT_TOL_NM = 1000;
43 return ( aA - aB ).EuclideanNorm() <= CONNECT_TOL_NM;
47bool IsHeuristicParserWarning(
const wxString& aMessage )
49 return aMessage.Contains( wxS(
"design rule set" ) )
50 || aMessage.Contains( wxS(
"inter-ruleset transition marker" ) )
51 || aMessage.Contains( wxS(
"no validated component boundaries found" ) )
52 || aMessage.Contains( wxS(
"parse error" ) )
53 || aMessage.Contains( wxS(
"parsing failed" ) )
54 || aMessage.Contains( wxS(
"outline traversal aborted" ) );
58class DIPTRACE_WARNING_CAPTURE :
public wxLog
61 std::vector<wxString> m_warnings;
64 void DoLogRecord( wxLogLevel aLevel,
const wxString& aMessage,
65 const wxLogRecordInfo& )
override
67 if( aLevel == wxLOG_Warning )
68 m_warnings.push_back( aMessage );
73int CountDisconnectedEdgeCutsEndpoints(
const BOARD& aBoard,
int& aTotalEndpoints )
75 std::vector<VECTOR2I> endpoints;
87 endpoints.push_back( shape->
GetStart() );
88 endpoints.push_back( shape->
GetEnd() );
91 aTotalEndpoints =
static_cast<int>( endpoints.size() );
95 for(
size_t i = 0; i < endpoints.size(); i++ )
97 bool connected =
false;
99 for(
size_t j = 0; j < endpoints.size(); j++ )
104 if( PointsNear( endpoints[i], endpoints[j] ) )
119int CountDisconnectedTraceEndpoints(
const BOARD& aBoard,
int& aTotalEndpoints )
121 std::map<int, std::vector<VECTOR2I>> netAnchors;
122 std::unordered_map<int, std::unordered_multimap<int64_t, VECTOR2I>> netAnchorBuckets;
124 auto cellCoord = [](
int aValue ) ->
int
127 return aValue / CONNECT_TOL_NM;
129 return -( ( -aValue + CONNECT_TOL_NM - 1 ) / CONNECT_TOL_NM );
132 auto cellKey = [](
int aCellX,
int aCellY ) -> int64_t
134 return (
static_cast<int64_t
>( aCellX ) << 32 ) ^
static_cast<uint32_t
>( aCellY );
137 auto addAnchor = [&](
int aNetCode,
const VECTOR2I& aPos )
139 netAnchors[aNetCode].push_back( aPos );
141 int cx = cellCoord( aPos.x );
142 int cy = cellCoord( aPos.y );
143 netAnchorBuckets[aNetCode].emplace( cellKey( cx, cy ), aPos );
148 int netCode = track->GetNetCode();
155 addAnchor( netCode, track->GetStart() );
156 addAnchor( netCode, track->GetEnd() );
160 addAnchor( netCode, track->GetPosition() );
166 for(
const PAD*
pad : fp->Pads() )
168 int netCode =
pad->GetNetCode();
171 addAnchor( netCode,
pad->GetPosition() );
176 int disconnected = 0;
183 int netCode = track->GetNetCode();
188 auto bucketIt = netAnchorBuckets.find( netCode );
190 if( bucketIt == netAnchorBuckets.end() )
193 const auto& buckets = bucketIt->second;
194 const VECTOR2I endpoints[2] = { track->GetStart(), track->GetEnd() };
196 for(
const VECTOR2I& endpoint : endpoints )
201 int cx = cellCoord( endpoint.x );
202 int cy = cellCoord( endpoint.y );
204 for(
int dx = -1; dx <= 1 && nearbyCount < 2; dx++ )
206 for(
int dy = -1; dy <= 1 && nearbyCount < 2; dy++ )
208 auto range = buckets.equal_range( cellKey( cx + dx, cy + dy ) );
210 for(
auto it = range.first; it != range.second; ++it )
212 if( PointsNear( endpoint, it->second ) )
216 if( nearbyCount >= 2 )
223 if( nearbyCount < 2 )
232int CountViaNetShorts(
const BOARD& aBoard,
int& aCheckedVias, std::vector<std::string>* aReports =
nullptr )
241 std::vector<NET_ANCHOR> anchors;
245 int netCode = track->GetNetCode();
252 if( copperLayers.none() )
257 anchors.push_back( { track->GetStart(), netCode, copperLayers } );
258 anchors.push_back( { track->GetEnd(), netCode, copperLayers } );
262 anchors.push_back( { track->GetPosition(), netCode, copperLayers } );
268 for(
const PAD*
pad : fp->Pads() )
270 int netCode =
pad->GetNetCode();
273 if( netCode > 0 && !padLayers.none() )
274 anchors.push_back( {
pad->GetPosition(), netCode, padLayers } );
287 int viaNet =
via->GetNetCode();
290 if( viaNet <= 0 || viaLayers.none() )
294 bool shortFound =
false;
296 for(
const NET_ANCHOR&
anchor : anchors )
298 if(
anchor.netCode == viaNet )
301 if( ( viaLayers &
anchor.layers ).none() )
304 if( PointsNear(
via->GetPosition(),
anchor.pos ) )
308 if( aReports && aReports->size() < 20 )
312 std::string viaName =
313 viaNetInfo ? std::string( viaNetInfo->
GetNetname().utf8_str() ) :
std::to_string( viaNet );
314 std::string otherName = otherNetInfo ? std::string( otherNetInfo->
GetNetname().utf8_str() )
317 aReports->push_back(
"via(" + viaName +
") at (" + std::to_string(
via->GetPosition().x ) +
","
318 + std::to_string(
via->GetPosition().y ) +
") overlaps net " + otherName );
333int CountPadsOutsideBoardOutline(
BOARD& aBoard,
int& aTotalPads,
bool& aHasOutline )
347 for(
const PAD*
pad : fp->Pads() )
351 if( !boardOutline.
Contains(
pad->GetPosition(), -1, CONNECT_TOL_NM ) )
360bool IsRectLikeSmdPadShape(
PAD_SHAPE aShape )
366bool HasDipExtension(
const std::filesystem::path& aPath )
368 std::string ext = aPath.extension().string();
369 std::transform( ext.begin(), ext.end(), ext.begin(),
370 [](
unsigned char c )
372 return static_cast<char>( std::tolower( c ) );
374 return ext ==
".dip";
378const FOOTPRINT* FindFootprintByRef(
const BOARD& aBoard,
const wxString& aRef )
382 if( fp->GetReference() == aRef )
390int CardinalDeg(
double aDegrees )
392 int deg =
static_cast<int>( std::lround( aDegrees ) );
393 deg = ( ( deg % 360 ) + 360 ) % 360;
395 int cardinal =
static_cast<int>( std::lround( deg / 90.0 ) ) * 90;
396 return ( ( cardinal % 360 ) + 360 ) % 360;
400int NormalizeDeg(
double aDegrees )
402 int deg =
static_cast<int>( std::lround( aDegrees ) );
403 return ( ( deg % 360 ) + 360 ) % 360;
407int DipXmlSpokeMode( std::string aSpoke )
409 aSpoke.erase( std::remove_if( aSpoke.begin(), aSpoke.end(),
410 [](
unsigned char c ) { return std::isspace( c ) != 0; } ),
412 std::transform( aSpoke.begin(), aSpoke.end(), aSpoke.begin(),
413 [](
unsigned char c ) { return static_cast<char>( std::tolower( c ) ); } );
415 if( aSpoke ==
"direct" )
418 if( aSpoke ==
"2spoke90" )
421 if( aSpoke ==
"2spoke" )
424 if( aSpoke ==
"4spoke45" )
427 if( aSpoke ==
"4spoke" )
435 bool aIslandConnection )
437 if( aIslandInternal || aIslandConnection )
447long long DipXmlMinimumAreaToKiCadIu2(
double aMinimumAreaMm )
451 long long dipUnits =
static_cast<long long>( std::llround( aMinimumAreaMm * 30000.0 ) );
452 long long linearIu = dipUnits * 100 / 3;
453 return linearIu * linearIu;
457struct DIPXML_PAD_STYLE
459 bool isSurface =
false;
460 bool isThrough =
false;
461 bool isRoundHole =
false;
466struct DIPXML_PATTERN_PAD
468 std::string styleName;
469 int angleCardinalDeg = 0;
473struct DIPXML_BOARD_MODEL
475 std::unordered_map<std::string, DIPXML_PAD_STYLE> styles;
476 std::unordered_map<std::string, std::unordered_map<std::string, DIPXML_PATTERN_PAD>> patterns;
477 std::vector<std::tuple<std::string, std::string, int>> components;
478 std::unordered_map<int, std::string> netNames;
480 struct DIPXML_COPPER_POUR
485 double clearanceMm = 0.0;
486 double lineWidthMm = 0.0;
487 double minimumAreaMm = 0.0;
489 double spokeWidthMm = 0.0;
490 bool islandRegion =
false;
491 bool islandInternal =
false;
492 bool islandConnection =
false;
495 std::vector<DIPXML_COPPER_POUR> copperPours;
496 int traceViaPointsRaw = 0;
497 int traceViaPointsStyleZeroRaw = 0;
498 int traceViaPointsUniqueNetPos = 0;
499 int viaComponentCount = 0;
503std::string ToUtf8(
const wxString& aText )
505 return std::string( aText.utf8_str() );
509wxString ChildTextByName(
const wxXmlNode* aParent,
const wxString& aName )
514 for(
const wxXmlNode* child = aParent->GetChildren(); child; child = child->GetNext() )
516 if( child->GetType() == wxXML_ELEMENT_NODE && child->GetName() == aName )
520 for(
const wxXmlNode*
text = child->GetChildren();
text;
text =
text->GetNext() )
522 if(
text->GetType() == wxXML_TEXT_NODE ||
text->GetType() == wxXML_CDATA_SECTION_NODE )
523 out +=
text->GetContent();
536bool ParseDoubleAttr(
const wxString& aRaw,
double& aOut )
542 return !tmp.IsEmpty() && tmp.ToDouble( &aOut );
546int CardinalDegFromRadians(
const wxString& aRadiansRaw )
548 double radians = 0.0;
550 if( !ParseDoubleAttr( aRadiansRaw, radians ) )
553 return CardinalDeg( radians * 180.0 /
M_PI );
561 if( copperCount < 2 )
568 return copperCount - 1;
572 int innerDelta =
static_cast<int>( aLayer -
In1_Cu );
574 if( innerDelta % 2 != 0 )
577 int idx = 1 + innerDelta / 2;
578 int maxInnerIdx = copperCount - 2;
580 if( idx >= 1 && idx <= maxInnerIdx )
588bool LoadDipXmlModel(
const std::string& aPath, DIPXML_BOARD_MODEL& aOut )
592 if( !doc.Load( wxString::FromUTF8( aPath ) ) )
595 wxXmlNode* root = doc.GetRoot();
600 std::set<std::string> traceViaNetPointKeys;
602 std::function<void( wxXmlNode* )> walk = [&]( wxXmlNode* node )
604 for( ; node; node = node->GetNext() )
606 if( node->GetType() == wxXML_ELEMENT_NODE )
608 if( node->GetName() == wxT(
"PadStyle" ) )
610 std::string styleName = ToUtf8( node->GetAttribute( wxT(
"Name" ), wxString() ) );
612 if( !styleName.empty() )
614 DIPXML_PAD_STYLE style;
615 wxString type = node->GetAttribute( wxT(
"Type" ), wxString() );
616 wxString holeType = node->GetAttribute( wxT(
"HoleType" ), wxString() );
617 style.isSurface = ( type.CmpNoCase( wxT(
"Surface" ) ) == 0 );
618 style.isThrough = ( type.CmpNoCase( wxT(
"Through" ) ) == 0 );
619 style.isRoundHole = ( holeType.CmpNoCase( wxT(
"Round" ) ) == 0 );
620 ParseDoubleAttr( node->GetAttribute( wxT(
"Hole" ), wxT(
"0" ) ), style.holeMm );
621 aOut.styles[styleName] = style;
624 else if( node->GetName() == wxT(
"Pattern" ) )
626 std::string patternStyle = ToUtf8( node->GetAttribute( wxT(
"PatternStyle" ), wxString() ) );
628 if( !patternStyle.empty() )
630 auto& padMap = aOut.patterns[patternStyle];
631 wxXmlNode* padsNode =
nullptr;
633 for( wxXmlNode* child = node->GetChildren(); child; child = child->GetNext() )
635 if( child->GetType() == wxXML_ELEMENT_NODE && child->GetName() == wxT(
"Pads" ) )
644 for( wxXmlNode* padNode = padsNode->GetChildren(); padNode; padNode = padNode->GetNext() )
646 if( padNode->GetType() != wxXML_ELEMENT_NODE || padNode->GetName() != wxT(
"Pad" ) )
651 wxString padKey = ChildTextByName( padNode, wxT(
"Number" ) );
653 if( padKey.IsEmpty() )
654 padKey = padNode->GetAttribute( wxT(
"Id" ), wxString() );
656 std::string key = ToUtf8( padKey );
661 DIPXML_PATTERN_PAD
pad;
662 pad.styleName = ToUtf8( padNode->GetAttribute( wxT(
"Style" ), wxString() ) );
663 pad.angleCardinalDeg =
664 CardinalDegFromRadians( padNode->GetAttribute( wxT(
"Angle" ), wxT(
"0" ) ) );
670 else if( node->GetName() == wxT(
"Component" ) )
672 if( node->GetAttribute( wxT(
"Type" ), wxString() ).CmpNoCase( wxT(
"Via" ) ) == 0 )
673 aOut.viaComponentCount++;
675 wxString ref = ChildTextByName( node, wxT(
"RefDes" ) );
679 std::string refUtf8 = ToUtf8( ref );
680 std::string patternStyle = ToUtf8( node->GetAttribute( wxT(
"PatternStyle" ), wxString() ) );
681 int angleCardinal = CardinalDegFromRadians( node->GetAttribute( wxT(
"Angle" ), wxT(
"0" ) ) );
682 aOut.components.emplace_back( std::move( refUtf8 ), std::move( patternStyle ), angleCardinal );
685 else if( node->GetName() == wxT(
"Net" ) )
689 if( node->GetAttribute( wxT(
"Id" ), wxString() ).ToLong( &netId ) )
691 wxString netName = ChildTextByName( node, wxT(
"Name" ) );
692 aOut.netNames[
static_cast<int>( netId )] = ToUtf8( netName );
694 for( wxXmlNode* child = node->GetChildren(); child; child = child->GetNext() )
696 if( child->GetType() != wxXML_ELEMENT_NODE || child->GetName() != wxT(
"Traces" ) )
699 for( wxXmlNode* traceNode = child->GetChildren(); traceNode;
700 traceNode = traceNode->GetNext() )
702 if( traceNode->GetType() != wxXML_ELEMENT_NODE
703 || traceNode->GetName() != wxT(
"Trace" ) )
708 for( wxXmlNode* traceChild = traceNode->GetChildren(); traceChild;
709 traceChild = traceChild->GetNext() )
711 if( traceChild->GetType() != wxXML_ELEMENT_NODE
712 || traceChild->GetName() != wxT(
"Points" ) )
717 for( wxXmlNode* pointNode = traceChild->GetChildren(); pointNode;
718 pointNode = pointNode->GetNext() )
720 if( pointNode->GetType() != wxXML_ELEMENT_NODE
721 || pointNode->GetName() != wxT(
"Point" ) )
726 wxString viaStyle = pointNode->GetAttribute( wxT(
"ViaStyle" ), wxString() );
728 if( viaStyle.IsEmpty() )
731 aOut.traceViaPointsRaw++;
733 long viaStyleId = -1;
735 if( viaStyle.ToLong( &viaStyleId ) && viaStyleId == 0 )
736 aOut.traceViaPointsStyleZeroRaw++;
738 std::string key = std::to_string( netId ) +
"|"
739 + ToUtf8( pointNode->GetAttribute( wxT(
"X" ), wxString() ) )
741 + ToUtf8( pointNode->GetAttribute( wxT(
"Y" ), wxString() ) );
742 traceViaNetPointKeys.insert( std::move( key ) );
749 else if( node->GetName() == wxT(
"CopperPour" ) )
754 double lineWidth = 0.0;
755 double minimumArea = 0.0;
756 double spokeWidth = 0.0;
758 if( node->GetAttribute( wxT(
"NetId" ), wxString() ).ToLong( &netId )
759 && node->GetAttribute( wxT(
"Lay" ), wxString() ).ToLong( &lay ) )
761 ParseDoubleAttr( node->GetAttribute( wxT(
"Clearance" ), wxT(
"0" ) ),
clearance );
762 ParseDoubleAttr( node->GetAttribute( wxT(
"LineWidth" ), wxT(
"0" ) ), lineWidth );
763 ParseDoubleAttr( node->GetAttribute( wxT(
"MinimumArea" ), wxT(
"0" ) ), minimumArea );
764 ParseDoubleAttr( node->GetAttribute( wxT(
"SpokeWidth" ), wxT(
"0" ) ), spokeWidth );
767 node->GetAttribute( wxT(
"Priority" ), wxT(
"0" ) ).ToLong( &priority );
769 DIPXML_BOARD_MODEL::DIPXML_COPPER_POUR pour;
770 pour.netId =
static_cast<int>( netId );
771 pour.layer =
static_cast<int>( lay );
772 pour.priority =
static_cast<int>( priority );
774 pour.lineWidthMm = lineWidth;
775 pour.minimumAreaMm = minimumArea;
776 pour.spoke = ToUtf8( node->GetAttribute( wxT(
"Spoke" ), wxString() ) );
777 pour.spokeWidthMm = spokeWidth;
779 node->GetAttribute( wxT(
"IslandRegion" ), wxT(
"N" ) ).CmpNoCase( wxT(
"Y" ) ) == 0;
780 pour.islandInternal =
781 node->GetAttribute( wxT(
"IslandInternal" ), wxT(
"N" ) ).CmpNoCase( wxT(
"Y" ) ) == 0;
782 pour.islandConnection = node->GetAttribute( wxT(
"IslandConnection" ), wxT(
"N" ) )
783 .CmpNoCase( wxT(
"Y" ) )
785 aOut.copperPours.push_back( pour );
790 if( node->GetChildren() )
791 walk( node->GetChildren() );
796 aOut.traceViaPointsUniqueNetPos =
static_cast<int>( traceViaNetPointKeys.size() );
813 auto board = LoadBoard(
"z80_board.dip" );
818 for(
const FOOTPRINT* fp : board->Footprints() )
819 totalPads +=
static_cast<int>( fp->Pads().size() );
821 BOOST_CHECK_MESSAGE( totalPads > 400,
"Z80 board should have >400 total pads, got " + std::to_string( totalPads ) );
832 auto board = LoadBoard(
"z80_board.dip" );
838 for(
const FOOTPRINT* fp : board->Footprints() )
840 int padCount =
static_cast<int>( fp->Pads().size() );
842 if( padCount > maxPads )
849 BOOST_CHECK_MESSAGE( maxPads >= 28,
"Z80 board should have a footprint with >=28 pads (Z80 DIP-40), "
851 + std::to_string( maxPads ) );
853 BOOST_CHECK_MESSAGE( icCount >= 5,
"Z80 board should have >=5 footprints with >=14 pads (ICs), "
855 + std::to_string( icCount ) );
867 auto board = LoadBoard(
"z80_board.dip" );
871 int reasonablePads = 0;
874 for(
const FOOTPRINT* fp : board->Footprints() )
876 for(
const PAD*
pad : fp->Pads() )
883 double maxDim = std::max( widthMm, heightMm );
885 if( maxDim >= 0.8 && maxDim <= 5.0 )
893 BOOST_REQUIRE_GT( totalPads, 0 );
895 double reasonablePercent = 100.0 * reasonablePads / totalPads;
896 double tinyPercent = 100.0 * tinyPads / totalPads;
898 BOOST_CHECK_MESSAGE( reasonablePercent > 50.0,
899 "At least 50% of pads should be 0.8-5.0mm, got " + std::to_string( reasonablePercent ) +
"% ("
900 + std::to_string( reasonablePads ) +
"/" + std::to_string( totalPads ) +
")" );
902 BOOST_CHECK_MESSAGE( tinyPercent < 20.0,
903 "Less than 20% of pads should be <0.5mm, got " + std::to_string( tinyPercent ) +
"% ("
904 + std::to_string( tinyPads ) +
"/" + std::to_string( totalPads ) +
")" );
915 auto board = LoadBoard(
"z80_board.dip" );
918 bool foundGoodSpacing =
false;
919 double toleranceMm = 0.3;
921 for(
const FOOTPRINT* fp : board->Footprints() )
923 if( fp->Pads().size() < 14 )
927 std::vector<VECTOR2I> positions;
929 for(
const PAD*
pad : fp->Pads() )
931 VECTOR2I local =
pad->GetPosition() - fp->GetPosition();
932 positions.push_back( local );
936 std::sort( positions.begin(), positions.end(),
939 if( std::abs( a.x - b.x ) < 100000 )
946 for(
size_t i = 1; i < positions.size(); i++ )
948 if(
std::abs( positions[i].x - positions[i - 1].x ) > 100000 )
951 double spacingMm =
pcbIUScale.IUTomm(
std::abs( positions[i].y - positions[i - 1].y ) );
953 if(
std::abs( spacingMm - 2.54 ) < toleranceMm )
955 foundGoodSpacing =
true;
960 if( foundGoodSpacing )
964 BOOST_CHECK_MESSAGE( foundGoodSpacing,
"At least one multi-pin IC should have ~2.54mm DIP pin spacing" );
974 auto board = LoadBoard(
"z80_board.dip" );
978 int padsWithNets = 0;
980 for(
const FOOTPRINT* fp : board->Footprints() )
982 for(
const PAD*
pad : fp->Pads() )
986 if(
pad->GetNetCode() > 0 )
991 BOOST_REQUIRE_GT( totalPads, 0 );
993 double netPercent = 100.0 * padsWithNets / totalPads;
995 BOOST_CHECK_MESSAGE( padsWithNets > 0,
"At least some pads should have net assignments, got 0 out of "
996 + std::to_string( totalPads ) );
998 BOOST_CHECK_MESSAGE( netPercent > 30.0,
999 "At least 30% of pads should have nets, got " + std::to_string( netPercent ) +
"%" );
1009 auto board = LoadBoard(
"z80_board.dip" );
1012 std::set<wxString> padNetNames;
1014 for(
const FOOTPRINT* fp : board->Footprints() )
1016 for(
const PAD*
pad : fp->Pads() )
1018 if(
pad->GetNetCode() > 0 )
1019 padNetNames.insert(
pad->GetNet()->GetNetname() );
1023 BOOST_CHECK_MESSAGE( padNetNames.count( wxT(
"GND" ) ) > 0,
"GND should appear on at least one pad" );
1025 BOOST_CHECK_MESSAGE( padNetNames.count( wxT(
"A0" ) ) > 0,
"A0 should appear on at least one pad" );
1027 BOOST_CHECK_MESSAGE( padNetNames.count( wxT(
"D0" ) ) > 0,
"D0 should appear on at least one pad" );
1044 std::vector<TestCase> cases = {
1045 {
"project4.dip", 30, 27 }, {
"z80_board.dip", 200, 104 }, {
"logic_probe.dip", 100, 113 },
1046 {
"keyboard.dip", 200, 123 }, {
"156bus_narrow.dip", 20, 17 },
1049 for(
const TestCase& tc : cases )
1051 auto board = LoadBoard( tc.file );
1052 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1056 for(
const FOOTPRINT* fp : board->Footprints() )
1057 totalPads +=
static_cast<int>( fp->Pads().size() );
1059 BOOST_CHECK_MESSAGE( totalPads >= tc.minPads, tc.file +
": expected >=" + std::to_string( tc.minPads )
1060 +
" pads, got " + std::to_string( totalPads ) );
1078 std::vector<TestCase> cases = {
1079 {
"project4.dip", 100 }, {
"156bus_narrow.dip", 30 }, {
"z80_board.dip", 1500 },
1080 {
"logic_probe.dip", 400 }, {
"keyboard.dip", 400 },
1083 for(
const TestCase& tc : cases )
1085 auto board = LoadBoard( tc.file );
1086 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1090 for(
const PCB_TRACK* trk : board->Tracks() )
1096 BOOST_CHECK_MESSAGE( trackCount >= tc.minTracks, tc.file +
": expected >=" + std::to_string( tc.minTracks )
1097 +
" tracks, got " + std::to_string( trackCount ) );
1119 std::vector<TestCase> cases = {
1120 {
"project4.dip", 50, 200 }, {
"z80_board.dip", 400, 1000 }, {
"logic_probe.dip", 10, 100 },
1121 {
"156bus_narrow.dip", 0, 5 }, {
"keyboard.dip", 10, 40 },
1124 for(
const TestCase& tc : cases )
1126 auto board = LoadBoard( tc.file );
1127 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1131 for(
const PCB_TRACK* trk : board->Tracks() )
1137 BOOST_CHECK_MESSAGE( viaCount >= tc.minVias, tc.file +
": expected >=" + std::to_string( tc.minVias )
1138 +
" vias, got " + std::to_string( viaCount ) );
1140 BOOST_CHECK_MESSAGE( viaCount <= tc.maxVias, tc.file +
": expected <=" + std::to_string( tc.maxVias )
1141 +
" vias, got " + std::to_string( viaCount ) );
1153 auto board = LoadBoard(
"z80_board.dip" );
1156 int totalTracks = 0;
1157 int reasonableTracks = 0;
1159 int reasonableVias = 0;
1161 for(
const PCB_TRACK* trk : board->Tracks() )
1166 double widthMm =
pcbIUScale.IUTomm( trk->GetWidth() );
1168 if( widthMm >= 0.1 && widthMm <= 3.0 )
1177 if( diamMm >= 0.3 && diamMm <= 2.0 )
1182 if( totalTracks > 0 )
1184 double pct = 100.0 * reasonableTracks / totalTracks;
1186 BOOST_CHECK_MESSAGE( pct > 90.0,
"At least 90% of tracks should have reasonable widths (0.1-3.0mm), got "
1187 + std::to_string( pct ) +
"%" );
1192 double pct = 100.0 * reasonableVias / totalVias;
1194 BOOST_CHECK_MESSAGE( pct > 90.0,
"At least 90% of vias should have reasonable diameters (0.3-2.0mm), got "
1195 + std::to_string( pct ) +
"%" );
1206 auto board = LoadBoard(
"z80_board.dip" );
1209 int totalTracks = 0;
1210 int tracksWithNets = 0;
1211 std::set<wxString> trackNetNames;
1213 for(
const PCB_TRACK* trk : board->Tracks() )
1219 if( trk->GetNetCode() > 0 )
1222 trackNetNames.insert( trk->GetNet()->GetNetname() );
1227 BOOST_REQUIRE_GT( totalTracks, 0 );
1229 double netPct = 100.0 * tracksWithNets / totalTracks;
1231 BOOST_CHECK_MESSAGE( netPct > 90.0,
1232 "At least 90% of tracks should have net assignments, got " + std::to_string( netPct ) +
"%" );
1234 BOOST_CHECK_MESSAGE( trackNetNames.count( wxT(
"GND" ) ) > 0,
"GND net should appear on tracks" );
1236 BOOST_CHECK_MESSAGE( trackNetNames.count( wxT(
"A0" ) ) > 0,
"A0 net should appear on tracks" );
1251 std::vector<TestCase> cases = {
1252 {
"project4.dip", 0 }, {
"156bus_narrow.dip", 1 }, {
"z80_board.dip", 2 },
1253 {
"logic_probe.dip", 2 }, {
"keyboard.dip", 1 },
1256 for(
const TestCase& tc : cases )
1258 auto board = LoadBoard( tc.file );
1259 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1261 int zoneCount =
static_cast<int>( board->Zones().size() );
1263 BOOST_CHECK_MESSAGE( zoneCount == tc.expected, tc.file +
": expected " + std::to_string( tc.expected )
1264 +
" zones, got " + std::to_string( zoneCount ) );
1275 auto board = LoadBoard(
"z80_board.dip" );
1278 int zonesWithNets = 0;
1279 std::set<wxString> zoneNetNames;
1281 for(
const ZONE* zone : board->Zones() )
1283 if( zone->GetNetCode() > 0 )
1286 zoneNetNames.insert( zone->GetNet()->GetNetname() );
1290 BOOST_CHECK_MESSAGE( zonesWithNets ==
static_cast<int>( board->Zones().size() ),
1291 "All zones should have net assignments, got " + std::to_string( zonesWithNets ) +
"/"
1292 + std::to_string( board->Zones().size() ) );
1294 BOOST_CHECK_MESSAGE( zoneNetNames.size() >= 1,
"At least 1 distinct net should appear on zones" );
1305 auto board = LoadBoard(
"z80_board.dip" );
1307 BOOST_REQUIRE_GT( board->Zones().size(), 0u );
1309 for(
const ZONE* zone : board->Zones() )
1317 BOOST_CHECK_MESSAGE( vertexCount >= 3,
1318 "Zone outline should have at least 3 vertices, got " + std::to_string( vertexCount ) );
1324 BOOST_CHECK_MESSAGE( widthMm > 5.0,
"Zone width should be >5mm, got " + std::to_string( widthMm ) +
"mm" );
1326 BOOST_CHECK_MESSAGE( heightMm > 5.0,
"Zone height should be >5mm, got " + std::to_string( heightMm ) +
"mm" );
1336 auto board = LoadBoard(
"z80_board.dip" );
1339 for(
const ZONE* zone : board->Zones() )
1343 BOOST_CHECK_MESSAGE(
IsCopperLayer( layer ),
"Zone should be on a copper layer, got layer "
1344 + std::to_string(
static_cast<int>( layer ) ) );
1355 auto board = LoadBoard(
"z80_board.dip" );
1358 int footprintsWithGraphics = 0;
1359 int totalGraphics = 0;
1361 for(
const FOOTPRINT* fp : board->Footprints() )
1363 int graphicCount = 0;
1365 for(
const BOARD_ITEM* item : fp->GraphicalItems() )
1371 if( graphicCount > 0 )
1373 footprintsWithGraphics++;
1374 totalGraphics += graphicCount;
1378 BOOST_CHECK_MESSAGE( footprintsWithGraphics > 0,
"At least some footprints should have outline graphics, got 0" );
1380 BOOST_CHECK_MESSAGE( totalGraphics > 10,
1381 "Board should have >10 total footprint graphics, got " + std::to_string( totalGraphics ) );
1392 auto board = LoadBoard(
"z80_board.dip" );
1395 int reasonableCount = 0;
1398 for(
const FOOTPRINT* fp : board->Footprints() )
1400 bool hasGraphics =
false;
1402 for(
const BOARD_ITEM* item : fp->GraphicalItems() )
1417 for(
const BOARD_ITEM* item : fp->GraphicalItems() )
1437 double maxDim = std::max( widthMm, heightMm );
1439 if( maxDim >= 2.0 && maxDim <= 80.0 )
1441 else if( maxDim < 0.5 )
1445 BOOST_CHECK_MESSAGE( reasonableCount > 0,
"At least some footprints should have reasonably-sized "
1446 "outline graphics (2-80mm)" );
1448 BOOST_CHECK_MESSAGE( tinyCount == 0,
1449 "No footprint graphics should be tiny (<0.5mm), got " + std::to_string( tinyCount ) );
1459 const std::vector<std::string> files = {
1460 "project4.dip",
"156bus_narrow.dip",
"z80_board.dip",
"logic_probe.dip",
"keyboard.dip",
1463 for(
const std::string& file : files )
1465 auto board = LoadBoard( file );
1466 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1468 int totalEndpoints = 0;
1469 int disconnected = CountDisconnectedEdgeCutsEndpoints( *board, totalEndpoints );
1471 BOOST_CHECK_MESSAGE( totalEndpoints > 0, file +
": expected Edge.Cuts endpoints, got 0" );
1473 BOOST_CHECK_MESSAGE( disconnected == 0,
1474 file +
": expected contiguous outline; found " + std::to_string( disconnected )
1475 +
" disconnected endpoints out of " + std::to_string( totalEndpoints ) );
1486 const std::vector<std::string> files = {
1492 for(
const std::string& file : files )
1494 auto board = LoadBoard( file );
1495 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1497 int totalEndpoints = 0;
1498 int disconnected = CountDisconnectedTraceEndpoints( *board, totalEndpoints );
1499 BOOST_REQUIRE_MESSAGE( totalEndpoints > 0, file +
": expected routed trace endpoints, got 0" );
1501 double disconnectedPct = 100.0 * disconnected / totalEndpoints;
1503 BOOST_CHECK_MESSAGE( disconnectedPct <= 15.0,
1504 file +
": disconnected trace endpoints = " + std::to_string( disconnected ) +
"/"
1505 + std::to_string( totalEndpoints ) +
" (" + std::to_string( disconnectedPct )
1517 const std::vector<std::string> files = {
1519 "156bus_narrow.dip",
1522 int footprints0805 = 0;
1525 for(
const std::string& file : files )
1527 auto board = LoadBoard( file );
1528 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1530 for(
const FOOTPRINT* fp : board->Footprints() )
1532 wxString fpName = wxString::FromUTF8( fp->GetFPID().GetLibItemName() ).Upper();
1534 if( !fpName.Contains(
"0805" ) )
1539 if( fp->Pads().size() != 2 )
1542 bool padsValid =
true;
1543 std::vector<VECTOR2I> padPos;
1545 for(
const PAD*
pad : fp->Pads() )
1555 if( !IsRectLikeSmdPadShape( shape ) )
1565 if( widthMm < 0.2 || widthMm > 2.5 || heightMm < 0.2 || heightMm > 2.5 )
1571 padPos.push_back(
pad->GetPosition() );
1576 double pitchMm =
pcbIUScale.IUTomm( ( padPos[0] - padPos[1] ).EuclideanNorm() );
1578 if( pitchMm < 0.5 || pitchMm > 3.5 )
1587 BOOST_CHECK_MESSAGE( footprints0805 >= 3,
1588 "Expected at least 3 imported 0805 footprints, got " + std::to_string( footprints0805 ) );
1590 BOOST_CHECK_MESSAGE( valid0805 == footprints0805,
1591 "All imported 0805 footprints should satisfy SMD/pad-shape/pitch sanity; "
1592 "valid=" + std::to_string( valid0805 )
1593 +
", total=" + std::to_string( footprints0805 ) );
1599 const std::vector<std::string> files = {
1606 for(
const std::string& file : files )
1608 auto board = LoadBoard( file );
1609 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1611 int checkedVias = 0;
1612 std::vector<std::string> shortReports;
1613 int shortedVias = CountViaNetShorts( *board, checkedVias, &shortReports );
1615 for(
const std::string& report : shortReports )
1618 BOOST_CHECK_MESSAGE( shortedVias == 0, file +
": vias shorting distinct nets = " + std::to_string( shortedVias )
1619 +
" out of " + std::to_string( checkedVias ) +
" vias" );
1626 const std::vector<std::string> files = {
1633 for(
const std::string& file : files )
1635 auto board = LoadBoard( file );
1636 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1639 bool hasOutline =
false;
1640 int outsidePads = CountPadsOutsideBoardOutline( *board, totalPads, hasOutline );
1642 BOOST_REQUIRE_MESSAGE( hasOutline, file +
": board outline not available" );
1643 BOOST_REQUIRE_MESSAGE( totalPads > 0, file +
": no pads found" );
1645 if( outsidePads > 0 )
1648 board->GetBoardPolygonOutlines( outline,
true );
1651 for(
const FOOTPRINT* fp : board->Footprints() )
1653 for(
const PAD*
pad : fp->Pads() )
1655 if( outline.
Contains(
pad->GetPosition(), -1, CONNECT_TOL_NM ) )
1658 BOOST_TEST_MESSAGE( file +
": outside pad " + std::string( fp->GetReference().utf8_str() ) +
":"
1659 + std::string(
pad->GetNumber().utf8_str() ) +
" at ("
1660 + std::to_string(
pad->GetPosition().x ) +
","
1661 + std::to_string(
pad->GetPosition().y ) +
")" );
1663 if( ++reported >= 20 )
1667 if( reported >= 20 )
1672 BOOST_CHECK_MESSAGE( outsidePads == 0, file +
": pads outside board outline = " + std::to_string( outsidePads )
1673 +
"/" + std::to_string( totalPads ) );
1680 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
1681 std::string examplesDir =
1682 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
1684 if( !std::filesystem::exists( examplesDir ) )
1686 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ViewerExamplesOptional" );
1690 auto pcb2 = LoadBoardFromPath( examplesDir +
"/PCB_2.dip" );
1697 for(
const PCB_TRACK* trk : pcb2->Tracks() )
1705 BOOST_CHECK_MESSAGE( pcb2Vias <= pcb2Tracks,
1706 "PCB_2: via count should not exceed segment count; tracks=" + std::to_string( pcb2Tracks )
1707 +
", vias=" + std::to_string( pcb2Vias ) );
1709 int viasWithDrill = 0;
1710 int viasAt191Mil = 0;
1712 for(
const PCB_TRACK* trk : pcb2->Tracks() )
1718 int drillIU =
via->GetDrillValue();
1725 double drillMm =
pcbIUScale.IUTomm( drillIU );
1726 double targetMm = 19.1 * 0.0254;
1728 if(
std::abs( drillMm - targetMm ) <= 0.02 )
1732 BOOST_REQUIRE_MESSAGE( viasWithDrill > 0,
"PCB_2: expected vias with non-zero drill" );
1733 BOOST_CHECK_MESSAGE( viasAt191Mil == viasWithDrill,
1734 "PCB_2: vias with 19.1mil drill = " + std::to_string( viasAt191Mil ) +
"/"
1735 + std::to_string( viasWithDrill ) );
1737 int pcb2ShortVias = 0;
1738 int pcb2CheckedVias = 0;
1739 pcb2ShortVias = CountViaNetShorts( *pcb2, pcb2CheckedVias );
1740 BOOST_CHECK_MESSAGE( pcb2ShortVias == 0,
1741 "PCB_2: vias shorting distinct nets = " + std::to_string( pcb2ShortVias ) );
1743 const ZONE* pcb2BottomZone =
nullptr;
1745 for(
const ZONE* zone : pcb2->Zones() )
1747 if( zone->GetLayer() ==
B_Cu )
1749 pcb2BottomZone = zone;
1754 BOOST_REQUIRE_MESSAGE( pcb2BottomZone,
"PCB_2: expected a B.Cu copper zone" );
1758 BOOST_CHECK_MESSAGE( pcb2ZoneNet == wxString(
"Net 7" ),
"PCB_2: B.Cu zone net should be 'Net 7', got '"
1759 + std::string( pcb2ZoneNet.utf8_str() ) +
"'" );
1763 int pcb2Net7PthPads = 0;
1764 int pcb2Net7At90 = 0;
1766 for(
const FOOTPRINT* fp : pcb2->Footprints() )
1768 for(
const PAD*
pad : fp->Pads() )
1773 if( !
pad->GetNet() ||
pad->GetNet()->GetNetname() != wxString(
"Net 7" ) )
1778 if( ( NormalizeDeg(
pad->GetThermalSpokeAngle().AsDegrees() ) % 180 ) == 90 )
1783 BOOST_REQUIRE_GT( pcb2Net7PthPads, 0 );
1787 bool pcb2HasOutline =
false;
1788 int pcb2OutsidePads = CountPadsOutsideBoardOutline( *pcb2, pcb2Pads, pcb2HasOutline );
1791 if( pcb2OutsidePads > 0 )
1794 pcb2->GetBoardPolygonOutlines( outline,
true );
1797 for(
const FOOTPRINT* fp : pcb2->Footprints() )
1799 for(
const PAD*
pad : fp->Pads() )
1801 if( !outline.
Contains(
pad->GetPosition(), -1, CONNECT_TOL_NM ) )
1803 BOOST_TEST_MESSAGE(
"PCB_2 outside pad: " + std::string( fp->GetReference().utf8_str() ) +
":"
1804 + std::string(
pad->GetNumber().utf8_str() )
1805 +
" fpLayer=" + std::string( pcb2->GetLayerName( fp->GetLayer() ).utf8_str() )
1806 +
" fpOrient=" + std::to_string( fp->GetOrientation().AsDegrees() ) +
" pad=("
1807 + std::to_string(
pad->GetPosition().x ) +
","
1808 + std::to_string(
pad->GetPosition().y ) +
")" );
1811 if( reported >= 20 )
1816 if( reported >= 20 )
1821 BOOST_CHECK_MESSAGE( pcb2OutsidePads == 0,
"PCB_2: pads outside outline = " + std::to_string( pcb2OutsidePads ) );
1823 const FOOTPRINT* j1 = FindFootprintByRef( *pcb2, wxT(
"J1" ) );
1824 const FOOTPRINT* j4 = FindFootprintByRef( *pcb2, wxT(
"J4" ) );
1825 const FOOTPRINT* j7 = FindFootprintByRef( *pcb2, wxT(
"J7" ) );
1826 const FOOTPRINT* j8 = FindFootprintByRef( *pcb2, wxT(
"J8" ) );
1827 const FOOTPRINT* j10 = FindFootprintByRef( *pcb2, wxT(
"J10" ) );
1828 const FOOTPRINT* u2 = FindFootprintByRef( *pcb2, wxT(
"U2" ) );
1829 const FOOTPRINT* u3 = FindFootprintByRef( *pcb2, wxT(
"U3" ) );
1831 BOOST_REQUIRE_MESSAGE( j1,
"PCB_2: footprint J1 not found" );
1832 BOOST_REQUIRE_MESSAGE( j4,
"PCB_2: footprint J4 not found" );
1833 BOOST_REQUIRE_MESSAGE( j7,
"PCB_2: footprint J7 not found" );
1834 BOOST_REQUIRE_MESSAGE( j8,
"PCB_2: footprint J8 not found" );
1835 BOOST_REQUIRE_MESSAGE( j10,
"PCB_2: footprint J10 not found" );
1836 BOOST_REQUIRE_MESSAGE( u2,
"PCB_2: footprint U2 not found" );
1837 BOOST_REQUIRE_MESSAGE( u3,
"PCB_2: footprint U3 not found" );
1845 int j4SilkSegments = 0;
1860 BOOST_CHECK_MESSAGE( j4SilkSegments >= 4,
"PCB_2: J4 should import as a silkscreen box; silk segments="
1861 + std::to_string( j4SilkSegments ) );
1869 "PCB_2: J1 pad should be SMD, got attribute="
1870 + std::to_string(
static_cast<int>(
pad->GetAttribute() ) ) );
1873 BOOST_REQUIRE_GT( j1Pads, 0 );
1882 BOOST_CHECK_MESSAGE(
pad->GetAttribute() ==
PAD_ATTRIB::PTH,
"PCB_2: J8 pad should be through-hole" );
1884 "PCB_2: J8 pad drill should be circular" );
1888 BOOST_REQUIRE_GT( j8Pads, 0 );
1890 int u2PadsExpectedRotated = 0;
1894 long padNumLong = 0;
1896 if( !
pad->GetNumber().ToLong( &padNumLong ) )
1899 int padNum =
static_cast<int>( padNumLong );
1901 if( ( padNum >= 12 && padNum <= 22 ) || ( padNum >= 34 && padNum <= 44 ) )
1903 u2PadsExpectedRotated++;
1905 int padDeg = CardinalDeg(
pad->GetOrientation().AsDegrees() );
1906 BOOST_CHECK_MESSAGE( ( padDeg % 180 ) == 90,
"PCB_2: U2 pad " + std::to_string( padNum )
1907 +
" should be rotated by 90 degrees (got "
1908 + std::to_string( padDeg ) +
")" );
1914 auto pcb4 = LoadBoardFromPath( examplesDir +
"/PCB_4.dip" );
1916 BOOST_CHECK_GT( pcb4->Footprints().size(), 0u );
1919 auto pcb6 = LoadBoardFromPath( examplesDir +
"/PCB_6.dip" );
1935 int gndInnerZones = 0;
1937 for(
const ZONE* zone : pcb6->Zones() )
1939 wxString netName = zone->GetNet() ? zone->GetNet()->GetNetname() : wxString();
1941 if( zone->GetLayer() ==
In2_Cu )
1945 if( netName == wxString(
"3V3" ) )
1947 else if( netName == wxString(
"5V" ) )
1949 else if( netName == wxString(
"VCC" ) )
1952 else if( zone->GetLayer() ==
In1_Cu && netName == wxString(
"GND" ) )
1966 int pcb6ThermalZones = 0;
1967 int pcb6SpokeWidthMatches = 0;
1969 for(
const ZONE* zone : pcb6->Zones() )
1974 if(
std::abs(
pcbIUScale.IUTomm( zone->GetThermalReliefSpokeWidth() ) - 0.33 ) <= 0.03 )
1975 pcb6SpokeWidthMatches++;
1982 int pcb6PowerNetPthPads = 0;
1983 int pcb6PowerNetPadsAt45 = 0;
1985 for(
const FOOTPRINT* fp : pcb6->Footprints() )
1987 for(
const PAD*
pad : fp->Pads() )
1992 wxString netName =
pad->GetNet()->GetNetname();
1994 if( netName != wxString(
"GND" ) && netName != wxString(
"3V3" )
1995 && netName != wxString(
"5V" ) && netName != wxString(
"VCC" ) )
2000 pcb6PowerNetPthPads++;
2002 if( ( NormalizeDeg(
pad->GetThermalSpokeAngle().AsDegrees() ) % 180 ) == 45 )
2003 pcb6PowerNetPadsAt45++;
2007 BOOST_REQUIRE_GT( pcb6PowerNetPthPads, 0 );
2010 const FOOTPRINT* ic4 = FindFootprintByRef( *pcb6, wxT(
"IC4" ) );
2011 BOOST_REQUIRE_MESSAGE( ic4,
"PCB_6: footprint IC4 not found" );
2014 const FOOTPRINT* q4 = FindFootprintByRef( *pcb6, wxT(
"Q4" ) );
2015 BOOST_REQUIRE_MESSAGE( q4,
"PCB_6: footprint Q4 not found" );
2018 int q4SilkSegments = 0;
2037 BOOST_CHECK_MESSAGE( q4SilkSegments == 4,
2038 "PCB_6: Q4 should have 4 silkscreen segments, got " + std::to_string( q4SilkSegments ) );
2039 BOOST_CHECK_MESSAGE( q4SilkArcs == 1,
2040 "PCB_6: Q4 should have 1 silkscreen arc, got " + std::to_string( q4SilkArcs ) );
2042 const FOOTPRINT* u10 = FindFootprintByRef( *pcb6, wxT(
"U10" ) );
2043 BOOST_REQUIRE_MESSAGE( u10,
"PCB_6: footprint U10 not found" );
2045 int u10NpthPads = 0;
2046 int u10Drill508 = 0;
2047 int u10Outer5747 = 0;
2048 std::vector<VECTOR2I> u10NpthLocals;
2056 u10NpthLocals.push_back(
pad->GetPosition() - u10->
GetPosition() );
2062 "PCB_6: U10 NPTH drill should be circular" );
2065 BOOST_CHECK_MESSAGE(
pad->GetNetCode() <= 0,
"PCB_6: U10 NPTH should not have a net assignment" );
2070 if(
std::abs( drillMm - 5.08 ) <= 0.02 )
2073 if(
std::abs( outerMm - 5.7467 ) <= 0.03 )
2080 BOOST_REQUIRE_EQUAL( u10NpthLocals.size(), 2u );
2082 VECTOR2I sym = u10NpthLocals[0] + u10NpthLocals[1];
2085 BOOST_CHECK_SMALL(
std::abs( sym.
x ), symTol );
2086 BOOST_CHECK_SMALL(
std::abs( sym.
y ), symTol );
2088 double holeSpanMm =
pcbIUScale.IUTomm( ( u10NpthLocals[0] - u10NpthLocals[1] ).EuclideanNorm() );
2089 BOOST_CHECK_SMALL(
std::abs( holeSpanMm - 24.9934 ), 0.05 );
2091 auto cnc = LoadBoardFromPath( examplesDir +
"/CNC_controller.dip" );
2094 BOOST_CHECK_GT( cnc->Footprints().size(), 120u );
2095 BOOST_CHECK_LT( cnc->Footprints().size(), 200u );
2098 int cncViaCount = 0;
2100 for(
const PCB_TRACK* trk : cnc->Tracks() )
2106 BOOST_CHECK_GT( cncViaCount, 300 );
2109 int cncThtThermal = 0;
2111 for(
const ZONE* zone : cnc->Zones() )
2120 BOOST_CHECK_GE( cncThtThermal, 1 );
2126 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
2127 std::string examplesDir =
2128 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
2130 if( !std::filesystem::exists( examplesDir ) )
2132 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ViewerExamplesDipXmlParityOptional" );
2140 int minComparedFootprints;
2141 int minComparedPads;
2144 static const std::array<SAMPLE, 3> samples = { {
2145 {
"PCB_2.dip",
"PCB_2.dipxml", 60, 200 },
2146 {
"PCB_4.dip",
"PCB_4.dipxml", 60, 200 },
2147 {
"PCB_6.dip",
"PCB_6.dipxml", 100, 700 },
2150 for(
const SAMPLE& sample : samples )
2152 std::string dipPath = examplesDir +
"/" + sample.dip;
2153 std::string xmlPath = examplesDir +
"/" + sample.dipxml;
2155 if( !std::filesystem::exists( dipPath ) || !std::filesystem::exists( xmlPath ) )
2157 BOOST_TEST_MESSAGE(
"Skipping " + std::string( sample.dip ) +
" parity check; missing .dip or .dipxml" );
2161 DIPXML_BOARD_MODEL
model;
2162 BOOST_REQUIRE_MESSAGE( LoadDipXmlModel( xmlPath,
model ),
"Failed to load DipXML model: " + xmlPath );
2164 auto board = LoadBoardFromPath( dipPath );
2167 int importedViaCount = 0;
2169 for(
const PCB_TRACK* trk : board->Tracks() )
2175 int expectedViaCount =
model.traceViaPointsUniqueNetPos +
model.viaComponentCount;
2177 BOOST_CHECK_MESSAGE( importedViaCount == expectedViaCount,
2178 std::string( sample.dip ) +
": via parity mismatch; imported="
2179 + std::to_string( importedViaCount )
2180 +
" expected(trace-via unique net+xy="
2181 + std::to_string(
model.traceViaPointsUniqueNetPos )
2182 +
", standalone via components=" + std::to_string(
model.viaComponentCount )
2183 +
", trace-via raw points=" + std::to_string(
model.traceViaPointsRaw ) +
")" );
2185 int comparedFootprints = 0;
2186 int footprintAngleMismatches = 0;
2187 int comparedPads = 0;
2188 int padAngleMismatches = 0;
2189 int padTypeMismatches = 0;
2190 int padDrillMismatches = 0;
2191 int missingPatternPads = 0;
2192 int zoneKeyMismatches = 0;
2193 int zoneClearanceMismatches = 0;
2194 int zoneMinWidthMismatches = 0;
2195 int zoneConnectionMismatches = 0;
2196 int zoneSpokeWidthMismatches = 0;
2197 int zoneIslandModeMismatches = 0;
2198 int zoneMinAreaMismatches = 0;
2199 int zonePriorityMismatches = 0;
2200 std::vector<std::string> missingPadReports;
2201 std::vector<std::string> zoneMismatchReports;
2203 for(
const auto& [ref, patternStyle, componentAngleCardinal] :
model.components )
2205 const FOOTPRINT* fp = FindFootprintByRef( *board, wxString::FromUTF8( ref ) );
2210 comparedFootprints++;
2213 footprintAngleMismatches++;
2215 auto patternIt =
model.patterns.find( patternStyle );
2217 if( patternIt ==
model.patterns.end() )
2220 const auto& patternPads = patternIt->second;
2222 for(
const auto& [padKey, padExpected] : patternPads )
2224 const PAD* foundPad =
nullptr;
2228 if( ToUtf8(
pad->GetNumber() ) == padKey )
2237 missingPatternPads++;
2239 if( missingPadReports.size() < 20 )
2241 missingPadReports.push_back( ref +
":" + padKey +
" style=" + padExpected.styleName
2242 +
" patt=" + patternStyle );
2250 auto styleIt =
model.styles.find( padExpected.styleName );
2252 if( styleIt !=
model.styles.end() )
2254 const DIPXML_PAD_STYLE& style = styleIt->second;
2256 if( style.isSurface )
2259 padTypeMismatches++;
2261 else if( style.isThrough )
2264 padTypeMismatches++;
2266 if( style.holeMm > 0.0 )
2270 if( drill.
x <= 0 || drill.
y <= 0 )
2272 padDrillMismatches++;
2274 else if( style.isRoundHole
2277 padDrillMismatches++;
2285 if( padSize.
x != padSize.
y )
2288 int expectedParity = padExpected.angleCardinalDeg % 180;
2290 if( gotParity != expectedParity )
2291 padAngleMismatches++;
2296 BOOST_CHECK_MESSAGE( comparedFootprints >= sample.minComparedFootprints,
2297 std::string( sample.dip )
2298 +
": compared footprints=" + std::to_string( comparedFootprints ) );
2299 BOOST_CHECK_MESSAGE( comparedPads >= sample.minComparedPads,
2300 std::string( sample.dip ) +
": compared pads=" + std::to_string( comparedPads ) );
2306 if( !
model.copperPours.empty() )
2308 std::map<std::string, std::vector<int>> expectedZonesByKey;
2309 std::map<std::string, std::vector<int>> expectedZoneMinWidthsByKey;
2310 std::map<std::string, std::vector<int>> expectedZoneConnectionsByKey;
2311 std::map<std::string, std::vector<int>> expectedZoneSpokeWidthsByKey;
2312 std::map<std::string, std::vector<int>> expectedZoneIslandModesByKey;
2313 std::map<std::string, std::vector<long long>> expectedZoneMinAreaByKey;
2314 std::map<std::string, std::vector<int>> expectedZonePrioritiesByKey;
2315 std::map<std::string, std::vector<int>> importedZonePrioritiesByKey;
2316 std::map<std::string, std::vector<int>> importedZonesByKey;
2317 std::map<std::string, std::vector<int>> importedZoneMinWidthsByKey;
2318 std::map<std::string, std::vector<int>> importedZoneConnectionsByKey;
2319 std::map<std::string, std::vector<int>> importedZoneSpokeWidthsByKey;
2320 std::map<std::string, std::vector<int>> importedZoneIslandModesByKey;
2321 std::map<std::string, std::vector<long long>> importedZoneMinAreaByKey;
2323 for(
const auto& pour :
model.copperPours )
2325 std::string netName;
2326 auto netIt =
model.netNames.find( pour.netId );
2328 if( netIt !=
model.netNames.end() )
2329 netName = netIt->second;
2331 std::string key = std::to_string( pour.layer ) +
"|" + netName;
2332 expectedZonePrioritiesByKey[key].push_back( pour.priority );
2333 expectedZonesByKey[key].push_back(
static_cast<int>( std::lround( pour.clearanceMm * 1000.0 ) ) );
2334 expectedZoneMinWidthsByKey[key].push_back(
2335 static_cast<int>( std::lround( pour.lineWidthMm * 1000.0 ) ) );
2336 expectedZoneSpokeWidthsByKey[key].push_back(
2337 static_cast<int>( std::lround( pour.spokeWidthMm * 1000.0 ) ) );
2339 int spokeMode = DipXmlSpokeMode( pour.spoke );
2340 int connection = ( spokeMode == 0 ) ? 0 : 1;
2341 expectedZoneConnectionsByKey[key].push_back( connection );
2344 pour.islandRegion, pour.islandInternal, pour.islandConnection );
2345 expectedZoneIslandModesByKey[key].push_back(
static_cast<int>( islandMode ) );
2349 expectedZoneMinAreaByKey[key].push_back(
2350 DipXmlMinimumAreaToKiCadIu2( pour.minimumAreaMm ) );
2354 for(
const ZONE* zone : board->Zones() )
2358 if( zone->GetZoneName() == wxString(
"DipTrace Plane" ) )
2361 int dipLayer = DipLayerIndexFromKiCadLayer( *board, zone->GetLayer() );
2366 std::string netName = zone->GetNet() ? ToUtf8( zone->GetNet()->GetNetname() ) : std::string();
2367 std::string key = std::to_string( dipLayer ) +
"|" + netName;
2368 int clearanceUm =
static_cast<int>(
2369 std::lround(
pcbIUScale.IUTomm( zone->GetLocalClearance().value_or( 0 ) ) * 1000.0 ) );
2370 int minWidthUm =
static_cast<int>( std::lround(
pcbIUScale.IUTomm( zone->GetMinThickness() ) * 1000.0 ) );
2371 int spokeWidthUm =
static_cast<int>(
2372 std::lround(
pcbIUScale.IUTomm( zone->GetThermalReliefSpokeWidth() ) * 1000.0 ) );
2374 int islandMode =
static_cast<int>( zone->GetIslandRemovalMode() );
2375 importedZonePrioritiesByKey[key].push_back( zone->GetAssignedPriority() );
2376 importedZonesByKey[key].push_back( clearanceUm );
2377 importedZoneMinWidthsByKey[key].push_back( minWidthUm );
2378 importedZoneSpokeWidthsByKey[key].push_back( spokeWidthUm );
2379 importedZoneConnectionsByKey[key].push_back( connection );
2380 importedZoneIslandModesByKey[key].push_back( islandMode );
2383 importedZoneMinAreaByKey[key].push_back( zone->GetMinIslandArea() );
2386 std::set<std::string> allKeys;
2388 for(
const auto& [key,
_] : expectedZonesByKey )
2389 allKeys.insert( key );
2391 for(
const auto& [key,
_] : importedZonesByKey )
2392 allKeys.insert( key );
2394 for(
const std::string& key : allKeys )
2396 auto expIt = expectedZonesByKey.find( key );
2397 auto gotIt = importedZonesByKey.find( key );
2399 if( expIt == expectedZonesByKey.end() || gotIt == importedZonesByKey.end() )
2401 zoneKeyMismatches++;
2403 if( zoneMismatchReports.size() < 20 )
2405 zoneMismatchReports.push_back( std::string(
"key-missing " ) + key +
" exp="
2406 + std::to_string( expIt != expectedZonesByKey.end() ) +
" got="
2407 + std::to_string( gotIt != importedZonesByKey.end() ) );
2413 auto expVals = expIt->second;
2414 auto gotVals = gotIt->second;
2415 auto expWidthVals = expectedZoneMinWidthsByKey[key];
2416 auto gotWidthVals = importedZoneMinWidthsByKey[key];
2417 auto expConnVals = expectedZoneConnectionsByKey[key];
2418 auto gotConnVals = importedZoneConnectionsByKey[key];
2419 auto expSpokeWidthVals = expectedZoneSpokeWidthsByKey[key];
2420 auto gotSpokeWidthVals = importedZoneSpokeWidthsByKey[key];
2421 auto expIslandVals = expectedZoneIslandModesByKey[key];
2422 auto gotIslandVals = importedZoneIslandModesByKey[key];
2423 auto expMinAreaVals = expectedZoneMinAreaByKey[key];
2424 auto gotMinAreaVals = importedZoneMinAreaByKey[key];
2425 auto expPriorityVals = expectedZonePrioritiesByKey[key];
2426 auto gotPriorityVals = importedZonePrioritiesByKey[key];
2427 std::sort( expPriorityVals.begin(), expPriorityVals.end() );
2428 std::sort( gotPriorityVals.begin(), gotPriorityVals.end() );
2429 std::sort( expVals.begin(), expVals.end() );
2430 std::sort( gotVals.begin(), gotVals.end() );
2431 std::sort( expWidthVals.begin(), expWidthVals.end() );
2432 std::sort( gotWidthVals.begin(), gotWidthVals.end() );
2433 std::sort( expConnVals.begin(), expConnVals.end() );
2434 std::sort( gotConnVals.begin(), gotConnVals.end() );
2435 std::sort( expSpokeWidthVals.begin(), expSpokeWidthVals.end() );
2436 std::sort( gotSpokeWidthVals.begin(), gotSpokeWidthVals.end() );
2437 std::sort( expIslandVals.begin(), expIslandVals.end() );
2438 std::sort( gotIslandVals.begin(), gotIslandVals.end() );
2439 std::sort( expMinAreaVals.begin(), expMinAreaVals.end() );
2440 std::sort( gotMinAreaVals.begin(), gotMinAreaVals.end() );
2442 if( expVals.size() != gotVals.size()
2443 || expWidthVals.size() != gotWidthVals.size()
2444 || expConnVals.size() != gotConnVals.size()
2445 || expSpokeWidthVals.size() != gotSpokeWidthVals.size()
2446 || expIslandVals.size() != gotIslandVals.size()
2447 || expMinAreaVals.size() != gotMinAreaVals.size()
2448 || expPriorityVals.size() != gotPriorityVals.size() )
2450 zoneKeyMismatches++;
2452 if( zoneMismatchReports.size() < 20 )
2454 zoneMismatchReports.push_back( std::string(
"key-count " ) + key
2455 +
" expN=" + std::to_string( expVals.size() )
2456 +
" gotN=" + std::to_string( gotVals.size() ) );
2462 for(
size_t i = 0; i < expVals.size(); i++ )
2465 if(
std::abs( expVals[i] - gotVals[i] ) > 20 )
2467 zoneClearanceMismatches++;
2469 if( zoneMismatchReports.size() < 20 )
2471 zoneMismatchReports.push_back( std::string(
"clearance " ) + key
2472 +
" expUm=" + std::to_string( expVals[i] )
2473 +
" gotUm=" + std::to_string( gotVals[i] ) );
2477 if(
std::abs( expWidthVals[i] - gotWidthVals[i] ) > 20 )
2479 zoneMinWidthMismatches++;
2481 if( zoneMismatchReports.size() < 20 )
2483 zoneMismatchReports.push_back( std::string(
"min-width " ) + key
2484 +
" expUm=" + std::to_string( expWidthVals[i] )
2485 +
" gotUm=" + std::to_string( gotWidthVals[i] ) );
2489 if( expPriorityVals[i] != gotPriorityVals[i] )
2491 zonePriorityMismatches++;
2493 if( zoneMismatchReports.size() < 20 )
2495 zoneMismatchReports.push_back( std::string(
"priority " ) + key
2496 +
" exp=" + std::to_string( expPriorityVals[i] )
2497 +
" got=" + std::to_string( gotPriorityVals[i] ) );
2501 if( expConnVals[i] != gotConnVals[i] )
2503 zoneConnectionMismatches++;
2505 if( zoneMismatchReports.size() < 20 )
2507 zoneMismatchReports.push_back( std::string(
"connection " ) + key
2508 +
" exp=" + std::to_string( expConnVals[i] )
2509 +
" got=" + std::to_string( gotConnVals[i] ) );
2513 if(
std::abs( expSpokeWidthVals[i] - gotSpokeWidthVals[i] ) > 20 )
2515 zoneSpokeWidthMismatches++;
2517 if( zoneMismatchReports.size() < 20 )
2519 zoneMismatchReports.push_back( std::string(
"spoke-width " ) + key
2520 +
" expUm=" + std::to_string( expSpokeWidthVals[i] )
2521 +
" gotUm=" + std::to_string( gotSpokeWidthVals[i] ) );
2525 if( expIslandVals[i] != gotIslandVals[i] )
2527 zoneIslandModeMismatches++;
2529 if( zoneMismatchReports.size() < 20 )
2531 zoneMismatchReports.push_back( std::string(
"island-mode " ) + key
2532 +
" exp=" + std::to_string( expIslandVals[i] )
2533 +
" got=" + std::to_string( gotIslandVals[i] ) );
2538 for(
size_t i = 0; i < expMinAreaVals.size(); i++ )
2540 long long diff = std::llabs( expMinAreaVals[i] - gotMinAreaVals[i] );
2541 long long tol = std::max<long long>( 1'000'000'000LL, expMinAreaVals[i] / 100 );
2545 zoneMinAreaMismatches++;
2547 if( zoneMismatchReports.size() < 20 )
2549 zoneMismatchReports.push_back( std::string(
"island-area " ) + key
2550 +
" exp=" + std::to_string( expMinAreaVals[i] )
2551 +
" got=" + std::to_string( gotMinAreaVals[i] ) );
2568 +
": dipxml parity footprintComparisons=" + std::to_string( comparedFootprints )
2569 +
", padComparisons=" + std::to_string( comparedPads )
2570 +
", missingPatternPads=" + std::to_string( missingPatternPads ) );
2572 if( !missingPadReports.empty() )
2574 for(
const std::string& rep : missingPadReports )
2578 if( !zoneMismatchReports.empty() )
2580 for(
const std::string& rep : zoneMismatchReports )
2589 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
2590 std::string examplesDir =
2591 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
2593 if( !std::filesystem::exists( examplesDir ) )
2595 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ViewerExamplesViaParityOptional" );
2603 bool expectAllTraceViaStyleZero =
false;
2606 static const std::array<SAMPLE, 4> samples = { {
2607 {
"PCB_2.dip",
"PCB_2.dipxml",
false },
2608 {
"PCB_4.dip",
"PCB_4.dipxml",
false },
2609 {
"PCB_6.dip",
"PCB_6.dipxml",
false },
2610 {
"CNC_controller.dip",
"CNC_controller.dipxml",
true },
2613 for(
const SAMPLE& sample : samples )
2615 std::string dipPath = examplesDir +
"/" + sample.dip;
2616 std::string xmlPath = examplesDir +
"/" + sample.dipxml;
2618 if( !std::filesystem::exists( dipPath ) || !std::filesystem::exists( xmlPath ) )
2620 BOOST_TEST_MESSAGE(
"Skipping " + std::string( sample.dip ) +
" via parity check; missing .dip or .dipxml" );
2624 DIPXML_BOARD_MODEL
model;
2625 BOOST_REQUIRE_MESSAGE( LoadDipXmlModel( xmlPath,
model ),
"Failed to load DipXML model: " + xmlPath );
2627 auto board = LoadBoardFromPath( dipPath );
2630 int importedViaCount = 0;
2632 for(
const PCB_TRACK* trk : board->Tracks() )
2638 int expectedViaCount =
model.traceViaPointsUniqueNetPos +
model.viaComponentCount;
2640 BOOST_CHECK_MESSAGE( importedViaCount == expectedViaCount,
2641 std::string( sample.dip ) +
": via parity mismatch; imported="
2642 + std::to_string( importedViaCount )
2643 +
" expected(trace-via unique net+xy="
2644 + std::to_string(
model.traceViaPointsUniqueNetPos )
2645 +
", standalone via components=" + std::to_string(
model.viaComponentCount )
2646 +
", trace-via raw points=" + std::to_string(
model.traceViaPointsRaw )
2647 +
", trace-via style0 raw="
2648 + std::to_string(
model.traceViaPointsStyleZeroRaw ) +
")" );
2650 if( sample.expectAllTraceViaStyleZero )
2652 BOOST_REQUIRE_MESSAGE(
model.traceViaPointsRaw > 0,
2653 std::string( sample.dip ) +
": expected routed via points in DipXML" );
2666 const char* corpusEnv = std::getenv(
"DIPTRACE_EXTERNAL_CORPUS_DIR" );
2668 if( !corpusEnv || !*corpusEnv )
2670 BOOST_TEST_MESSAGE(
"DIPTRACE_EXTERNAL_CORPUS_DIR not set; skipping external corpus sweep" );
2674 std::filesystem::path corpusRoot( corpusEnv );
2676 if( !std::filesystem::exists( corpusRoot ) )
2678 BOOST_TEST_MESSAGE(
"External corpus path does not exist; skipping external corpus sweep" );
2682 std::vector<std::filesystem::path> dipFiles;
2684 for(
const auto& entry : std::filesystem::recursive_directory_iterator( corpusRoot ) )
2686 if( entry.is_regular_file() && HasDipExtension( entry.path() ) )
2687 dipFiles.push_back( entry.path() );
2690 std::sort( dipFiles.begin(), dipFiles.end() );
2692 BOOST_REQUIRE_MESSAGE( !dipFiles.empty(),
"No .dip files found under: " + corpusRoot.string() );
2695 int skippedUnreadable = 0;
2697 for(
const std::filesystem::path&
path : dipFiles )
2699 if( !m_plugin.CanReadBoard(
path.string() ) )
2701 skippedUnreadable++;
2705 std::unique_ptr<BOARD> board;
2707 DIPTRACE_WARNING_CAPTURE capture;
2712 board = LoadBoardFromPath(
path.string() );
2716 BOOST_ERROR(
path.string() +
": IO_ERROR: " + std::string( e.
What().utf8_str() ) );
2719 catch(
const std::exception& e )
2721 BOOST_ERROR(
path.string() +
": exception: " + std::string( e.
what() ) );
2725 BOOST_REQUIRE_MESSAGE( board,
"Failed to load: " +
path.string() );
2728 for(
const wxString& warning : capture.m_warnings )
2730 BOOST_CHECK_MESSAGE( !IsHeuristicParserWarning( warning ),
2731 path.string() +
": unexpected heuristic parser warning: "
2732 + std::string( warning.utf8_str() ) );
2735 int outlineEndpoints = 0;
2736 int outlineDisconnected = CountDisconnectedEdgeCutsEndpoints( *board, outlineEndpoints );
2738 if( outlineEndpoints > 0 )
2740 BOOST_CHECK_MESSAGE( outlineDisconnected == 0,
path.string() +
": disconnected Edge.Cuts endpoints = "
2741 + std::to_string( outlineDisconnected ) +
"/"
2742 + std::to_string( outlineEndpoints ) );
2746 BOOST_CHECK_MESSAGE( loaded > 0,
"External corpus sweep loaded zero boards" );
2748 if( skippedUnreadable > 0 )
2749 BOOST_TEST_MESSAGE(
"Skipped " << skippedUnreadable <<
" .dip files without DTBOARD magic" );
2760 static const std::array<const char*, 5> boards = {
2761 "z80_board.dip",
"keyboard.dip",
"logic_probe.dip",
"project4.dip",
"156bus_narrow.dip"
2764 int boardsWithRules = 0;
2766 for(
const char*
name : boards )
2779 std::vector<std::shared_ptr<DRC_RULE>> rules;
2783 BOOST_REQUIRE_NO_THROW( rulesParser.
Parse( rules, &
reporter ) );
2785 std::string(
name ) +
" rules should parse without error:\n"
2786 +
reporter.GetMessages().ToStdString() +
"\n--- rules ---\n"
2787 + dru.ToStdString() );
2788 BOOST_CHECK_GT( rules.size(), 0u );
2791 BOOST_CHECK_MESSAGE( boardsWithRules > 0,
2792 "Expected at least one committed board to generate zone DRC rules" );
2804 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
2805 std::string examplesDir =
2806 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
2807 std::string cncPath = examplesDir +
"/CNC_controller.dip";
2809 if( !std::filesystem::exists( cncPath ) )
2811 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ZoneDesignRulesParityOptional" );
2821 std::vector<std::shared_ptr<DRC_RULE>> rules;
2825 BOOST_REQUIRE_NO_THROW( rulesParser.
Parse( rules, &
reporter ) );
2827 "CNC rules should parse without error:\n" +
reporter.GetMessages().ToStdString() );
2829 int edgeClearanceIU = 0;
2830 int solidViaRules = 0;
2832 for(
const std::shared_ptr<DRC_RULE>& rule : rules )
2845 BOOST_CHECK_GT( solidViaRules, 0 );
2861 std::map<std::string, double>
expected;
2864 const std::vector<CASE> cases = {
2865 {
"rotate.dip", { {
"C1", 0.0 }, {
"C2", 45.0 }, {
"C3", 90.0 }, {
"C4", 309.33 } } },
2866 {
"rotate4.dip", { {
"C1", 0.0 }, {
"C2", 90.0 }, {
"C3", 45.0 }, {
"C4", 327.96 } } },
2869 for(
const CASE& tc : cases )
2871 auto board = LoadBoard( tc.file );
2874 std::map<std::string, double> seen;
2876 for(
FOOTPRINT* fp : board->Footprints() )
2877 seen[fp->GetReference().ToStdString()] = fp->GetOrientation().Normalize().AsDegrees();
2879 for(
const auto& [ref, deg] : tc.expected )
2881 BOOST_REQUIRE_MESSAGE( seen.count( ref ), tc.file +
": missing " + ref );
2883 double got = seen[ref];
2884 double gap =
std::abs( got - deg );
2885 gap = std::min( gap, 360.0 - gap );
2887 BOOST_CHECK_MESSAGE( gap < 0.1, tc.file +
": " + ref +
" orientation " + std::to_string( got )
2888 +
" deg, expected " + std::to_string( deg ) );
constexpr EDA_IU_SCALE pcbIUScale
NETINFO_ITEM * GetNet() const
Return #NET_INFO object for a given item.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Information pertinent to a Pcbnew printed circuit board.
NETINFO_ITEM * FindNet(int aNetcode) const
Search for a net with the given netcode.
int GetCopperLayerCount() const
const FOOTPRINTS & Footprints() const
const TRACKS & Tracks() const
bool GetBoardPolygonOutlines(SHAPE_POLY_SET &aOutlines, bool aInferOutlineIfNecessary, OUTLINE_ERROR_HANDLER *aErrorHandler=nullptr, bool aAllowUseArcsInPolygons=false, bool aIncludeNPTHAsOutlines=false)
Extract the board outlines and build a closed polygon from lines, arcs and circle items on edge cut l...
const DRAWINGS & Drawings() const
constexpr size_type GetWidth() const
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
constexpr size_type GetHeight() const
Parses a DipTrace .dip binary board file and populates a KiCad BOARD.
wxString GenerateDesignRules() const
Build a KiCad custom design-rule (.kicad_dru) document for the per-zone DipTrace properties that have...
void Parse()
Parse the file and populate the board. Throws IO_ERROR on failure.
const MINOPTMAX< int > & GetValue() const
ZONE_CONNECTION m_ZoneConnection
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
virtual const char * what() const override
std::exception interface, returned as UTF-8
LSET is a set of PCB_LAYER_IDs.
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Handle the data for a net.
const wxString & GetNetname() const
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
PAD_ATTRIB GetAttribute() const
VECTOR2I GetDrillSize() const
VECTOR2I GetSize(PCB_LAYER_ID aLayer) const
PAD_DRILL_SHAPE GetDrillShape() const
EDA_ANGLE GetFPRelativeOrientation() const
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
A scoped application of a wxLog target.
int PointCount() const
Return the number of points (vertices) in this line chain.
Represent a set of closed polygons.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int OutlineCount() const
Return the number of outlines in the set.
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
A wrapper for reporting to a wxString object.
Handle a list of polygons defining a copper zone.
ZONE_CONNECTION GetPadConnection() const
int GetThermalReliefSpokeWidth() const
@ ZONE_CONNECTION_CONSTRAINT
@ EDGE_CLEARANCE_CONSTRAINT
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
PCB_LAYER_ID
A quick note on layer IDs:
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.
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_CASE(TotalPadCount)
Verify that the Z80 board produces a substantial pad count.
Shared fixture and includes for the DipTrace PCB benchmark test suite.
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
IbisParser parser & reporter
VECTOR3I expected(15, 30, 45)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
VECTOR2< int32_t > VECTOR2I
ISLAND_REMOVAL_MODE
Whether or not to remove isolated islands from a zone.
@ THERMAL
Use thermal relief for pads.
@ THT_THERMAL
Thermal relief only for THT pads.
@ FULL
pads are covered by copper