39static constexpr int CONNECT_TOL_NM = 1000;
44 return ( aA - aB ).EuclideanNorm() <= CONNECT_TOL_NM;
48bool IsHeuristicParserWarning(
const wxString& aMessage )
50 return aMessage.Contains( wxS(
"design rule set" ) )
51 || aMessage.Contains( wxS(
"inter-ruleset transition marker" ) )
52 || aMessage.Contains( wxS(
"no validated component boundaries found" ) )
53 || aMessage.Contains( wxS(
"parse error" ) )
54 || aMessage.Contains( wxS(
"parsing failed" ) )
55 || aMessage.Contains( wxS(
"outline traversal aborted" ) );
59class DIPTRACE_WARNING_CAPTURE :
public wxLog
62 std::vector<wxString> m_warnings;
65 void DoLogRecord( wxLogLevel aLevel,
const wxString& aMessage,
66 const wxLogRecordInfo& )
override
68 if( aLevel == wxLOG_Warning )
69 m_warnings.push_back( aMessage );
74int CountDisconnectedEdgeCutsEndpoints(
const BOARD& aBoard,
int& aTotalEndpoints )
76 std::vector<VECTOR2I> endpoints;
88 endpoints.push_back( shape->
GetStart() );
89 endpoints.push_back( shape->
GetEnd() );
92 aTotalEndpoints =
static_cast<int>( endpoints.size() );
96 for(
size_t i = 0; i < endpoints.size(); i++ )
98 bool connected =
false;
100 for(
size_t j = 0; j < endpoints.size(); j++ )
105 if( PointsNear( endpoints[i], endpoints[j] ) )
120int CountDisconnectedTraceEndpoints(
const BOARD& aBoard,
int& aTotalEndpoints )
122 std::map<int, std::vector<VECTOR2I>> netAnchors;
123 std::unordered_map<int, std::unordered_multimap<int64_t, VECTOR2I>> netAnchorBuckets;
125 auto cellCoord = [](
int aValue ) ->
int
128 return aValue / CONNECT_TOL_NM;
130 return -( ( -aValue + CONNECT_TOL_NM - 1 ) / CONNECT_TOL_NM );
133 auto cellKey = [](
int aCellX,
int aCellY ) -> int64_t
135 return (
static_cast<int64_t
>( aCellX ) << 32 ) ^
static_cast<uint32_t
>( aCellY );
138 auto addAnchor = [&](
int aNetCode,
const VECTOR2I& aPos )
140 netAnchors[aNetCode].push_back( aPos );
142 int cx = cellCoord( aPos.x );
143 int cy = cellCoord( aPos.y );
144 netAnchorBuckets[aNetCode].emplace( cellKey( cx, cy ), aPos );
149 int netCode = track->GetNetCode();
156 addAnchor( netCode, track->GetStart() );
157 addAnchor( netCode, track->GetEnd() );
161 addAnchor( netCode, track->GetPosition() );
167 for(
const PAD*
pad : fp->Pads() )
169 int netCode =
pad->GetNetCode();
172 addAnchor( netCode,
pad->GetPosition() );
177 int disconnected = 0;
184 int netCode = track->GetNetCode();
189 auto bucketIt = netAnchorBuckets.find( netCode );
191 if( bucketIt == netAnchorBuckets.end() )
194 const auto& buckets = bucketIt->second;
195 const VECTOR2I endpoints[2] = { track->GetStart(), track->GetEnd() };
197 for(
const VECTOR2I& endpoint : endpoints )
202 int cx = cellCoord( endpoint.x );
203 int cy = cellCoord( endpoint.y );
205 for(
int dx = -1; dx <= 1 && nearbyCount < 2; dx++ )
207 for(
int dy = -1; dy <= 1 && nearbyCount < 2; dy++ )
209 auto range = buckets.equal_range( cellKey( cx + dx, cy + dy ) );
211 for(
auto it = range.first; it != range.second; ++it )
213 if( PointsNear( endpoint, it->second ) )
217 if( nearbyCount >= 2 )
224 if( nearbyCount < 2 )
233int CountViaNetShorts(
const BOARD& aBoard,
int& aCheckedVias, std::vector<std::string>* aReports =
nullptr )
242 std::vector<NET_ANCHOR> anchors;
246 int netCode = track->GetNetCode();
253 if( copperLayers.none() )
258 anchors.push_back( { track->GetStart(), netCode, copperLayers } );
259 anchors.push_back( { track->GetEnd(), netCode, copperLayers } );
263 anchors.push_back( { track->GetPosition(), netCode, copperLayers } );
269 for(
const PAD*
pad : fp->Pads() )
271 int netCode =
pad->GetNetCode();
274 if( netCode > 0 && !padLayers.none() )
275 anchors.push_back( {
pad->GetPosition(), netCode, padLayers } );
288 int viaNet =
via->GetNetCode();
291 if( viaNet <= 0 || viaLayers.none() )
295 bool shortFound =
false;
297 for(
const NET_ANCHOR&
anchor : anchors )
299 if(
anchor.netCode == viaNet )
302 if( ( viaLayers &
anchor.layers ).none() )
305 if( PointsNear(
via->GetPosition(),
anchor.pos ) )
309 if( aReports && aReports->size() < 20 )
313 std::string viaName =
314 viaNetInfo ? std::string( viaNetInfo->
GetNetname().utf8_str() ) :
std::to_string( viaNet );
315 std::string otherName = otherNetInfo ? std::string( otherNetInfo->
GetNetname().utf8_str() )
318 aReports->push_back(
"via(" + viaName +
") at (" + std::to_string(
via->GetPosition().x ) +
","
319 + std::to_string(
via->GetPosition().y ) +
") overlaps net " + otherName );
334int CountPadsOutsideBoardOutline(
BOARD& aBoard,
int& aTotalPads,
bool& aHasOutline )
348 for(
const PAD*
pad : fp->Pads() )
352 if( !boardOutline.
Contains(
pad->GetPosition(), -1, CONNECT_TOL_NM ) )
361bool IsRectLikeSmdPadShape(
PAD_SHAPE aShape )
367bool HasDipExtension(
const std::filesystem::path& aPath )
369 std::string ext = aPath.extension().string();
370 std::transform( ext.begin(), ext.end(), ext.begin(),
371 [](
unsigned char c )
373 return static_cast<char>( std::tolower( c ) );
375 return ext ==
".dip";
379const FOOTPRINT* FindFootprintByRef(
const BOARD& aBoard,
const wxString& aRef )
383 if( fp->GetReference() == aRef )
391int CardinalDeg(
double aDegrees )
393 int deg =
static_cast<int>( std::lround( aDegrees ) );
394 deg = ( ( deg % 360 ) + 360 ) % 360;
396 int cardinal =
static_cast<int>( std::lround( deg / 90.0 ) ) * 90;
397 return ( ( cardinal % 360 ) + 360 ) % 360;
401int NormalizeDeg(
double aDegrees )
403 int deg =
static_cast<int>( std::lround( aDegrees ) );
404 return ( ( deg % 360 ) + 360 ) % 360;
408int DipXmlSpokeMode( std::string aSpoke )
410 aSpoke.erase( std::remove_if( aSpoke.begin(), aSpoke.end(),
411 [](
unsigned char c ) { return std::isspace( c ) != 0; } ),
413 std::transform( aSpoke.begin(), aSpoke.end(), aSpoke.begin(),
414 [](
unsigned char c ) { return static_cast<char>( std::tolower( c ) ); } );
416 if( aSpoke ==
"direct" )
419 if( aSpoke ==
"2spoke90" )
422 if( aSpoke ==
"2spoke" )
425 if( aSpoke ==
"4spoke45" )
428 if( aSpoke ==
"4spoke" )
436 bool aIslandConnection )
438 if( aIslandInternal || aIslandConnection )
448long long DipXmlMinimumAreaToKiCadIu2(
double aMinimumAreaMm )
452 long long dipUnits =
static_cast<long long>( std::llround( aMinimumAreaMm * 30000.0 ) );
453 long long linearIu = dipUnits * 100 / 3;
454 return linearIu * linearIu;
458struct DIPXML_PAD_STYLE
460 bool isSurface =
false;
461 bool isThrough =
false;
462 bool isRoundHole =
false;
467struct DIPXML_PATTERN_PAD
469 std::string styleName;
470 int angleCardinalDeg = 0;
474struct DIPXML_BOARD_MODEL
476 std::unordered_map<std::string, DIPXML_PAD_STYLE> styles;
477 std::unordered_map<std::string, std::unordered_map<std::string, DIPXML_PATTERN_PAD>> patterns;
478 std::vector<std::tuple<std::string, std::string, int>> components;
479 std::unordered_map<int, std::string> netNames;
481 struct DIPXML_COPPER_POUR
486 double clearanceMm = 0.0;
487 double lineWidthMm = 0.0;
488 double minimumAreaMm = 0.0;
490 double spokeWidthMm = 0.0;
491 bool islandRegion =
false;
492 bool islandInternal =
false;
493 bool islandConnection =
false;
496 std::vector<DIPXML_COPPER_POUR> copperPours;
497 int traceViaPointsRaw = 0;
498 int traceViaPointsStyleZeroRaw = 0;
499 int traceViaPointsUniqueNetPos = 0;
500 int viaComponentCount = 0;
504std::string ToUtf8(
const wxString& aText )
506 return std::string( aText.utf8_str() );
510wxString ChildTextByName(
const wxXmlNode* aParent,
const wxString& aName )
515 for(
const wxXmlNode* child = aParent->GetChildren(); child; child = child->GetNext() )
517 if( child->GetType() == wxXML_ELEMENT_NODE && child->GetName() == aName )
521 for(
const wxXmlNode*
text = child->GetChildren();
text;
text =
text->GetNext() )
523 if(
text->GetType() == wxXML_TEXT_NODE ||
text->GetType() == wxXML_CDATA_SECTION_NODE )
524 out +=
text->GetContent();
537bool ParseDoubleAttr(
const wxString& aRaw,
double& aOut )
543 return !tmp.IsEmpty() && tmp.ToDouble( &aOut );
547int CardinalDegFromRadians(
const wxString& aRadiansRaw )
549 double radians = 0.0;
551 if( !ParseDoubleAttr( aRadiansRaw, radians ) )
554 return CardinalDeg( radians * 180.0 /
M_PI );
562 if( copperCount < 2 )
569 return copperCount - 1;
573 int innerDelta =
static_cast<int>( aLayer -
In1_Cu );
575 if( innerDelta % 2 != 0 )
578 int idx = 1 + innerDelta / 2;
579 int maxInnerIdx = copperCount - 2;
581 if( idx >= 1 && idx <= maxInnerIdx )
589bool LoadDipXmlModel(
const std::string& aPath, DIPXML_BOARD_MODEL& aOut )
593 if( !doc.Load( wxString::FromUTF8( aPath ) ) )
596 wxXmlNode* root = doc.GetRoot();
601 std::set<std::string> traceViaNetPointKeys;
603 std::function<void( wxXmlNode* )> walk = [&]( wxXmlNode* node )
605 for( ; node; node = node->GetNext() )
607 if( node->GetType() == wxXML_ELEMENT_NODE )
609 if( node->GetName() == wxT(
"PadStyle" ) )
611 std::string styleName = ToUtf8( node->GetAttribute( wxT(
"Name" ), wxString() ) );
613 if( !styleName.empty() )
615 DIPXML_PAD_STYLE style;
616 wxString type = node->GetAttribute( wxT(
"Type" ), wxString() );
617 wxString holeType = node->GetAttribute( wxT(
"HoleType" ), wxString() );
618 style.isSurface = ( type.CmpNoCase( wxT(
"Surface" ) ) == 0 );
619 style.isThrough = ( type.CmpNoCase( wxT(
"Through" ) ) == 0 );
620 style.isRoundHole = ( holeType.CmpNoCase( wxT(
"Round" ) ) == 0 );
621 ParseDoubleAttr( node->GetAttribute( wxT(
"Hole" ), wxT(
"0" ) ), style.holeMm );
622 aOut.styles[styleName] = style;
625 else if( node->GetName() == wxT(
"Pattern" ) )
627 std::string patternStyle = ToUtf8( node->GetAttribute( wxT(
"PatternStyle" ), wxString() ) );
629 if( !patternStyle.empty() )
631 auto& padMap = aOut.patterns[patternStyle];
632 wxXmlNode* padsNode =
nullptr;
634 for( wxXmlNode* child = node->GetChildren(); child; child = child->GetNext() )
636 if( child->GetType() == wxXML_ELEMENT_NODE && child->GetName() == wxT(
"Pads" ) )
645 for( wxXmlNode* padNode = padsNode->GetChildren(); padNode; padNode = padNode->GetNext() )
647 if( padNode->GetType() != wxXML_ELEMENT_NODE || padNode->GetName() != wxT(
"Pad" ) )
652 wxString padKey = ChildTextByName( padNode, wxT(
"Number" ) );
654 if( padKey.IsEmpty() )
655 padKey = padNode->GetAttribute( wxT(
"Id" ), wxString() );
657 std::string key = ToUtf8( padKey );
662 DIPXML_PATTERN_PAD
pad;
663 pad.styleName = ToUtf8( padNode->GetAttribute( wxT(
"Style" ), wxString() ) );
664 pad.angleCardinalDeg =
665 CardinalDegFromRadians( padNode->GetAttribute( wxT(
"Angle" ), wxT(
"0" ) ) );
671 else if( node->GetName() == wxT(
"Component" ) )
673 if( node->GetAttribute( wxT(
"Type" ), wxString() ).CmpNoCase( wxT(
"Via" ) ) == 0 )
674 aOut.viaComponentCount++;
676 wxString ref = ChildTextByName( node, wxT(
"RefDes" ) );
680 std::string refUtf8 = ToUtf8( ref );
681 std::string patternStyle = ToUtf8( node->GetAttribute( wxT(
"PatternStyle" ), wxString() ) );
682 int angleCardinal = CardinalDegFromRadians( node->GetAttribute( wxT(
"Angle" ), wxT(
"0" ) ) );
683 aOut.components.emplace_back( std::move( refUtf8 ), std::move( patternStyle ), angleCardinal );
686 else if( node->GetName() == wxT(
"Net" ) )
690 if( node->GetAttribute( wxT(
"Id" ), wxString() ).ToLong( &netId ) )
692 wxString netName = ChildTextByName( node, wxT(
"Name" ) );
693 aOut.netNames[
static_cast<int>( netId )] = ToUtf8( netName );
695 for( wxXmlNode* child = node->GetChildren(); child; child = child->GetNext() )
697 if( child->GetType() != wxXML_ELEMENT_NODE || child->GetName() != wxT(
"Traces" ) )
700 for( wxXmlNode* traceNode = child->GetChildren(); traceNode;
701 traceNode = traceNode->GetNext() )
703 if( traceNode->GetType() != wxXML_ELEMENT_NODE
704 || traceNode->GetName() != wxT(
"Trace" ) )
709 for( wxXmlNode* traceChild = traceNode->GetChildren(); traceChild;
710 traceChild = traceChild->GetNext() )
712 if( traceChild->GetType() != wxXML_ELEMENT_NODE
713 || traceChild->GetName() != wxT(
"Points" ) )
718 for( wxXmlNode* pointNode = traceChild->GetChildren(); pointNode;
719 pointNode = pointNode->GetNext() )
721 if( pointNode->GetType() != wxXML_ELEMENT_NODE
722 || pointNode->GetName() != wxT(
"Point" ) )
727 wxString viaStyle = pointNode->GetAttribute( wxT(
"ViaStyle" ), wxString() );
729 if( viaStyle.IsEmpty() )
732 aOut.traceViaPointsRaw++;
734 long viaStyleId = -1;
736 if( viaStyle.ToLong( &viaStyleId ) && viaStyleId == 0 )
737 aOut.traceViaPointsStyleZeroRaw++;
739 std::string key = std::to_string( netId ) +
"|"
740 + ToUtf8( pointNode->GetAttribute( wxT(
"X" ), wxString() ) )
742 + ToUtf8( pointNode->GetAttribute( wxT(
"Y" ), wxString() ) );
743 traceViaNetPointKeys.insert( std::move( key ) );
750 else if( node->GetName() == wxT(
"CopperPour" ) )
755 double lineWidth = 0.0;
756 double minimumArea = 0.0;
757 double spokeWidth = 0.0;
759 if( node->GetAttribute( wxT(
"NetId" ), wxString() ).ToLong( &netId )
760 && node->GetAttribute( wxT(
"Lay" ), wxString() ).ToLong( &lay ) )
762 ParseDoubleAttr( node->GetAttribute( wxT(
"Clearance" ), wxT(
"0" ) ),
clearance );
763 ParseDoubleAttr( node->GetAttribute( wxT(
"LineWidth" ), wxT(
"0" ) ), lineWidth );
764 ParseDoubleAttr( node->GetAttribute( wxT(
"MinimumArea" ), wxT(
"0" ) ), minimumArea );
765 ParseDoubleAttr( node->GetAttribute( wxT(
"SpokeWidth" ), wxT(
"0" ) ), spokeWidth );
768 node->GetAttribute( wxT(
"Priority" ), wxT(
"0" ) ).ToLong( &priority );
770 DIPXML_BOARD_MODEL::DIPXML_COPPER_POUR pour;
771 pour.netId =
static_cast<int>( netId );
772 pour.layer =
static_cast<int>( lay );
773 pour.priority =
static_cast<int>( priority );
775 pour.lineWidthMm = lineWidth;
776 pour.minimumAreaMm = minimumArea;
777 pour.spoke = ToUtf8( node->GetAttribute( wxT(
"Spoke" ), wxString() ) );
778 pour.spokeWidthMm = spokeWidth;
780 node->GetAttribute( wxT(
"IslandRegion" ), wxT(
"N" ) ).CmpNoCase( wxT(
"Y" ) ) == 0;
781 pour.islandInternal =
782 node->GetAttribute( wxT(
"IslandInternal" ), wxT(
"N" ) ).CmpNoCase( wxT(
"Y" ) ) == 0;
783 pour.islandConnection = node->GetAttribute( wxT(
"IslandConnection" ), wxT(
"N" ) )
784 .CmpNoCase( wxT(
"Y" ) )
786 aOut.copperPours.push_back( pour );
791 if( node->GetChildren() )
792 walk( node->GetChildren() );
797 aOut.traceViaPointsUniqueNetPos =
static_cast<int>( traceViaNetPointKeys.size() );
814 auto board = LoadBoard(
"z80_board.dip" );
819 for(
const FOOTPRINT* fp : board->Footprints() )
820 totalPads +=
static_cast<int>( fp->Pads().size() );
822 BOOST_CHECK_MESSAGE( totalPads > 400,
"Z80 board should have >400 total pads, got " + std::to_string( totalPads ) );
833 auto board = LoadBoard(
"z80_board.dip" );
839 for(
const FOOTPRINT* fp : board->Footprints() )
841 int padCount =
static_cast<int>( fp->Pads().size() );
843 if( padCount > maxPads )
850 BOOST_CHECK_MESSAGE( maxPads >= 28,
"Z80 board should have a footprint with >=28 pads (Z80 DIP-40), "
852 + std::to_string( maxPads ) );
854 BOOST_CHECK_MESSAGE( icCount >= 5,
"Z80 board should have >=5 footprints with >=14 pads (ICs), "
856 + std::to_string( icCount ) );
868 auto board = LoadBoard(
"z80_board.dip" );
872 int reasonablePads = 0;
875 for(
const FOOTPRINT* fp : board->Footprints() )
877 for(
const PAD*
pad : fp->Pads() )
884 double maxDim = std::max( widthMm, heightMm );
886 if( maxDim >= 0.8 && maxDim <= 5.0 )
894 BOOST_REQUIRE_GT( totalPads, 0 );
896 double reasonablePercent = 100.0 * reasonablePads / totalPads;
897 double tinyPercent = 100.0 * tinyPads / totalPads;
900 "At least 50% of pads should be 0.8-5.0mm, got " + std::to_string( reasonablePercent ) +
"% ("
901 + std::to_string( reasonablePads ) +
"/" + std::to_string( totalPads ) +
")" );
904 "Less than 20% of pads should be <0.5mm, got " + std::to_string( tinyPercent ) +
"% ("
905 + std::to_string( tinyPads ) +
"/" + std::to_string( totalPads ) +
")" );
916 auto board = LoadBoard(
"z80_board.dip" );
919 bool foundGoodSpacing =
false;
920 double toleranceMm = 0.3;
922 for(
const FOOTPRINT* fp : board->Footprints() )
924 if( fp->Pads().size() < 14 )
928 std::vector<VECTOR2I> positions;
930 for(
const PAD*
pad : fp->Pads() )
932 VECTOR2I local =
pad->GetPosition() - fp->GetPosition();
933 positions.push_back( local );
937 std::sort( positions.begin(), positions.end(),
940 if( std::abs( a.x - b.x ) < 100000 )
947 for(
size_t i = 1; i < positions.size(); i++ )
949 if(
std::abs( positions[i].x - positions[i - 1].x ) > 100000 )
952 double spacingMm =
pcbIUScale.IUTomm(
std::abs( positions[i].y - positions[i - 1].y ) );
954 if(
std::abs( spacingMm - 2.54 ) < toleranceMm )
956 foundGoodSpacing =
true;
961 if( foundGoodSpacing )
965 BOOST_CHECK_MESSAGE( foundGoodSpacing,
"At least one multi-pin IC should have ~2.54mm DIP pin spacing" );
975 auto board = LoadBoard(
"z80_board.dip" );
979 int padsWithNets = 0;
981 for(
const FOOTPRINT* fp : board->Footprints() )
983 for(
const PAD*
pad : fp->Pads() )
987 if(
pad->GetNetCode() > 0 )
992 BOOST_REQUIRE_GT( totalPads, 0 );
994 double netPercent = 100.0 * padsWithNets / totalPads;
996 BOOST_CHECK_MESSAGE( padsWithNets > 0,
"At least some pads should have net assignments, got 0 out of "
997 + std::to_string( totalPads ) );
1000 "At least 30% of pads should have nets, got " + std::to_string( netPercent ) +
"%" );
1010 auto board = LoadBoard(
"z80_board.dip" );
1013 std::set<wxString> padNetNames;
1015 for(
const FOOTPRINT* fp : board->Footprints() )
1017 for(
const PAD*
pad : fp->Pads() )
1019 if(
pad->GetNetCode() > 0 )
1020 padNetNames.insert(
pad->GetNet()->GetNetname() );
1024 BOOST_CHECK_MESSAGE( padNetNames.count( wxT(
"GND" ) ) > 0,
"GND should appear on at least one pad" );
1026 BOOST_CHECK_MESSAGE( padNetNames.count( wxT(
"A0" ) ) > 0,
"A0 should appear on at least one pad" );
1028 BOOST_CHECK_MESSAGE( padNetNames.count( wxT(
"D0" ) ) > 0,
"D0 should appear on at least one pad" );
1045 std::vector<TestCase> cases = {
1046 {
"project4.dip", 30, 27 }, {
"z80_board.dip", 200, 104 }, {
"logic_probe.dip", 100, 113 },
1047 {
"keyboard.dip", 200, 123 }, {
"156bus_narrow.dip", 20, 17 },
1050 for(
const TestCase& tc : cases )
1052 auto board = LoadBoard( tc.file );
1053 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1057 for(
const FOOTPRINT* fp : board->Footprints() )
1058 totalPads +=
static_cast<int>( fp->Pads().size() );
1060 BOOST_CHECK_MESSAGE( totalPads >= tc.minPads, tc.file +
": expected >=" + std::to_string( tc.minPads )
1061 +
" pads, got " + std::to_string( totalPads ) );
1079 std::vector<TestCase> cases = {
1080 {
"project4.dip", 100 }, {
"156bus_narrow.dip", 30 }, {
"z80_board.dip", 1500 },
1081 {
"logic_probe.dip", 400 }, {
"keyboard.dip", 400 },
1084 for(
const TestCase& tc : cases )
1086 auto board = LoadBoard( tc.file );
1087 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1091 for(
const PCB_TRACK* trk : board->Tracks() )
1097 BOOST_CHECK_MESSAGE( trackCount >= tc.minTracks, tc.file +
": expected >=" + std::to_string( tc.minTracks )
1098 +
" tracks, got " + std::to_string( trackCount ) );
1120 std::vector<TestCase> cases = {
1121 {
"project4.dip", 50, 200 }, {
"z80_board.dip", 400, 1000 }, {
"logic_probe.dip", 10, 100 },
1122 {
"156bus_narrow.dip", 0, 5 }, {
"keyboard.dip", 10, 40 },
1125 for(
const TestCase& tc : cases )
1127 auto board = LoadBoard( tc.file );
1128 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1132 for(
const PCB_TRACK* trk : board->Tracks() )
1138 BOOST_CHECK_MESSAGE( viaCount >= tc.minVias, tc.file +
": expected >=" + std::to_string( tc.minVias )
1139 +
" vias, got " + std::to_string( viaCount ) );
1141 BOOST_CHECK_MESSAGE( viaCount <= tc.maxVias, tc.file +
": expected <=" + std::to_string( tc.maxVias )
1142 +
" vias, got " + std::to_string( viaCount ) );
1154 auto board = LoadBoard(
"z80_board.dip" );
1157 int totalTracks = 0;
1158 int reasonableTracks = 0;
1160 int reasonableVias = 0;
1162 for(
const PCB_TRACK* trk : board->Tracks() )
1167 double widthMm =
pcbIUScale.IUTomm( trk->GetWidth() );
1169 if( widthMm >= 0.1 && widthMm <= 3.0 )
1178 if( diamMm >= 0.3 && diamMm <= 2.0 )
1183 if( totalTracks > 0 )
1185 double pct = 100.0 * reasonableTracks / totalTracks;
1187 BOOST_CHECK_MESSAGE( pct > 90.0,
"At least 90% of tracks should have reasonable widths (0.1-3.0mm), got "
1188 + std::to_string( pct ) +
"%" );
1193 double pct = 100.0 * reasonableVias / totalVias;
1195 BOOST_CHECK_MESSAGE( pct > 90.0,
"At least 90% of vias should have reasonable diameters (0.3-2.0mm), got "
1196 + std::to_string( pct ) +
"%" );
1207 auto board = LoadBoard(
"z80_board.dip" );
1210 int totalTracks = 0;
1211 int tracksWithNets = 0;
1212 std::set<wxString> trackNetNames;
1214 for(
const PCB_TRACK* trk : board->Tracks() )
1220 if( trk->GetNetCode() > 0 )
1223 trackNetNames.insert( trk->GetNet()->GetNetname() );
1228 BOOST_REQUIRE_GT( totalTracks, 0 );
1230 double netPct = 100.0 * tracksWithNets / totalTracks;
1233 "At least 90% of tracks should have net assignments, got " + std::to_string( netPct ) +
"%" );
1235 BOOST_CHECK_MESSAGE( trackNetNames.count( wxT(
"GND" ) ) > 0,
"GND net should appear on tracks" );
1237 BOOST_CHECK_MESSAGE( trackNetNames.count( wxT(
"A0" ) ) > 0,
"A0 net should appear on tracks" );
1252 std::vector<TestCase> cases = {
1253 {
"project4.dip", 0 }, {
"156bus_narrow.dip", 1 }, {
"z80_board.dip", 2 },
1254 {
"logic_probe.dip", 2 }, {
"keyboard.dip", 1 },
1257 for(
const TestCase& tc : cases )
1259 auto board = LoadBoard( tc.file );
1260 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1262 int zoneCount =
static_cast<int>( board->Zones().size() );
1264 BOOST_CHECK_MESSAGE( zoneCount == tc.expected, tc.file +
": expected " + std::to_string( tc.expected )
1265 +
" zones, got " + std::to_string( zoneCount ) );
1276 auto board = LoadBoard(
"z80_board.dip" );
1279 int zonesWithNets = 0;
1280 std::set<wxString> zoneNetNames;
1282 for(
const ZONE* zone : board->Zones() )
1284 if( zone->GetNetCode() > 0 )
1287 zoneNetNames.insert( zone->GetNet()->GetNetname() );
1292 "All zones should have net assignments, got " + std::to_string( zonesWithNets ) +
"/"
1293 + std::to_string( board->Zones().size() ) );
1295 BOOST_CHECK_MESSAGE( zoneNetNames.size() >= 1,
"At least 1 distinct net should appear on zones" );
1306 auto board = LoadBoard(
"z80_board.dip" );
1308 BOOST_REQUIRE_GT( board->Zones().size(), 0u );
1310 for(
const ZONE* zone : board->Zones() )
1319 "Zone outline should have at least 3 vertices, got " + std::to_string( vertexCount ) );
1325 BOOST_CHECK_MESSAGE( widthMm > 5.0,
"Zone width should be >5mm, got " + std::to_string( widthMm ) +
"mm" );
1327 BOOST_CHECK_MESSAGE( heightMm > 5.0,
"Zone height should be >5mm, got " + std::to_string( heightMm ) +
"mm" );
1337 auto board = LoadBoard(
"z80_board.dip" );
1340 for(
const ZONE* zone : board->Zones() )
1345 + std::to_string(
static_cast<int>( layer ) ) );
1356 auto board = LoadBoard(
"z80_board.dip" );
1359 int footprintsWithGraphics = 0;
1360 int totalGraphics = 0;
1362 for(
const FOOTPRINT* fp : board->Footprints() )
1364 int graphicCount = 0;
1366 for(
const BOARD_ITEM* item : fp->GraphicalItems() )
1372 if( graphicCount > 0 )
1374 footprintsWithGraphics++;
1375 totalGraphics += graphicCount;
1379 BOOST_CHECK_MESSAGE( footprintsWithGraphics > 0,
"At least some footprints should have outline graphics, got 0" );
1382 "Board should have >10 total footprint graphics, got " + std::to_string( totalGraphics ) );
1393 auto board = LoadBoard(
"z80_board.dip" );
1396 int reasonableCount = 0;
1399 for(
const FOOTPRINT* fp : board->Footprints() )
1401 bool hasGraphics =
false;
1403 for(
const BOARD_ITEM* item : fp->GraphicalItems() )
1418 for(
const BOARD_ITEM* item : fp->GraphicalItems() )
1438 double maxDim = std::max( widthMm, heightMm );
1440 if( maxDim >= 2.0 && maxDim <= 80.0 )
1442 else if( maxDim < 0.5 )
1446 BOOST_CHECK_MESSAGE( reasonableCount > 0,
"At least some footprints should have reasonably-sized "
1447 "outline graphics (2-80mm)" );
1450 "No footprint graphics should be tiny (<0.5mm), got " + std::to_string( tinyCount ) );
1460 const std::vector<std::string> files = {
1461 "project4.dip",
"156bus_narrow.dip",
"z80_board.dip",
"logic_probe.dip",
"keyboard.dip",
1464 for(
const std::string& file : files )
1466 auto board = LoadBoard( file );
1467 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1469 int totalEndpoints = 0;
1470 int disconnected = CountDisconnectedEdgeCutsEndpoints( *board, totalEndpoints );
1472 BOOST_CHECK_MESSAGE( totalEndpoints > 0, file +
": expected Edge.Cuts endpoints, got 0" );
1475 file +
": expected contiguous outline; found " + std::to_string( disconnected )
1476 +
" disconnected endpoints out of " + std::to_string( totalEndpoints ) );
1487 const std::vector<std::string> files = {
1493 for(
const std::string& file : files )
1495 auto board = LoadBoard( file );
1496 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1498 int totalEndpoints = 0;
1499 int disconnected = CountDisconnectedTraceEndpoints( *board, totalEndpoints );
1500 BOOST_REQUIRE_MESSAGE( totalEndpoints > 0, file +
": expected routed trace endpoints, got 0" );
1502 double disconnectedPct = 100.0 * disconnected / totalEndpoints;
1505 file +
": disconnected trace endpoints = " + std::to_string( disconnected ) +
"/"
1506 + std::to_string( totalEndpoints ) +
" (" + std::to_string( disconnectedPct )
1518 const std::vector<std::string> files = {
1520 "156bus_narrow.dip",
1523 int footprints0805 = 0;
1526 for(
const std::string& file : files )
1528 auto board = LoadBoard( file );
1529 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1531 for(
const FOOTPRINT* fp : board->Footprints() )
1533 wxString fpName = wxString::FromUTF8( fp->GetFPID().GetLibItemName() ).Upper();
1535 if( !fpName.Contains(
"0805" ) )
1540 if( fp->Pads().size() != 2 )
1543 bool padsValid =
true;
1544 std::vector<VECTOR2I> padPos;
1546 for(
const PAD*
pad : fp->Pads() )
1556 if( !IsRectLikeSmdPadShape( shape ) )
1566 if( widthMm < 0.2 || widthMm > 2.5 || heightMm < 0.2 || heightMm > 2.5 )
1572 padPos.push_back(
pad->GetPosition() );
1577 double pitchMm =
pcbIUScale.IUTomm( ( padPos[0] - padPos[1] ).EuclideanNorm() );
1579 if( pitchMm < 0.5 || pitchMm > 3.5 )
1589 "Expected at least 3 imported 0805 footprints, got " + std::to_string( footprints0805 ) );
1592 "All imported 0805 footprints should satisfy SMD/pad-shape/pitch sanity; "
1593 "valid=" + std::to_string( valid0805 )
1594 +
", total=" + std::to_string( footprints0805 ) );
1600 const std::vector<std::string> files = {
1607 for(
const std::string& file : files )
1609 auto board = LoadBoard( file );
1610 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1612 int checkedVias = 0;
1613 std::vector<std::string> shortReports;
1614 int shortedVias = CountViaNetShorts( *board, checkedVias, &shortReports );
1616 for(
const std::string& report : shortReports )
1619 BOOST_CHECK_MESSAGE( shortedVias == 0, file +
": vias shorting distinct nets = " + std::to_string( shortedVias )
1620 +
" out of " + std::to_string( checkedVias ) +
" vias" );
1627 const std::vector<std::string> files = {
1634 for(
const std::string& file : files )
1636 auto board = LoadBoard( file );
1637 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1640 bool hasOutline =
false;
1641 int outsidePads = CountPadsOutsideBoardOutline( *board, totalPads, hasOutline );
1643 BOOST_REQUIRE_MESSAGE( hasOutline, file +
": board outline not available" );
1644 BOOST_REQUIRE_MESSAGE( totalPads > 0, file +
": no pads found" );
1646 if( outsidePads > 0 )
1649 board->GetBoardPolygonOutlines( outline,
true );
1652 for(
const FOOTPRINT* fp : board->Footprints() )
1654 for(
const PAD*
pad : fp->Pads() )
1656 if( outline.
Contains(
pad->GetPosition(), -1, CONNECT_TOL_NM ) )
1659 BOOST_TEST_MESSAGE( file +
": outside pad " + std::string( fp->GetReference().utf8_str() ) +
":"
1660 + std::string(
pad->GetNumber().utf8_str() ) +
" at ("
1661 + std::to_string(
pad->GetPosition().x ) +
","
1662 + std::to_string(
pad->GetPosition().y ) +
")" );
1664 if( ++reported >= 20 )
1668 if( reported >= 20 )
1673 BOOST_CHECK_MESSAGE( outsidePads == 0, file +
": pads outside board outline = " + std::to_string( outsidePads )
1674 +
"/" + std::to_string( totalPads ) );
1681 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
1682 std::string examplesDir =
1683 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
1685 if( !std::filesystem::exists( examplesDir ) )
1687 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ViewerExamplesOptional" );
1691 auto pcb2 = LoadBoardFromPath( examplesDir +
"/PCB_2.dip" );
1698 for(
const PCB_TRACK* trk : pcb2->Tracks() )
1707 "PCB_2: via count should not exceed segment count; tracks=" + std::to_string( pcb2Tracks )
1708 +
", vias=" + std::to_string( pcb2Vias ) );
1710 int viasWithDrill = 0;
1711 int viasAt191Mil = 0;
1713 for(
const PCB_TRACK* trk : pcb2->Tracks() )
1719 int drillIU =
via->GetDrillValue();
1726 double drillMm =
pcbIUScale.IUTomm( drillIU );
1727 double targetMm = 19.1 * 0.0254;
1729 if(
std::abs( drillMm - targetMm ) <= 0.02 )
1733 BOOST_REQUIRE_MESSAGE( viasWithDrill > 0,
"PCB_2: expected vias with non-zero drill" );
1735 "PCB_2: vias with 19.1mil drill = " + std::to_string( viasAt191Mil ) +
"/"
1736 + std::to_string( viasWithDrill ) );
1738 int pcb2ShortVias = 0;
1739 int pcb2CheckedVias = 0;
1740 pcb2ShortVias = CountViaNetShorts( *pcb2, pcb2CheckedVias );
1742 "PCB_2: vias shorting distinct nets = " + std::to_string( pcb2ShortVias ) );
1744 const ZONE* pcb2BottomZone =
nullptr;
1746 for(
const ZONE* zone : pcb2->Zones() )
1748 if( zone->GetLayer() ==
B_Cu )
1750 pcb2BottomZone = zone;
1755 BOOST_REQUIRE_MESSAGE( pcb2BottomZone,
"PCB_2: expected a B.Cu copper zone" );
1759 BOOST_CHECK_MESSAGE( pcb2ZoneNet == wxString(
"Net 7" ),
"PCB_2: B.Cu zone net should be 'Net 7', got '"
1760 + std::string( pcb2ZoneNet.utf8_str() ) +
"'" );
1764 int pcb2Net7PthPads = 0;
1765 int pcb2Net7At90 = 0;
1767 for(
const FOOTPRINT* fp : pcb2->Footprints() )
1769 for(
const PAD*
pad : fp->Pads() )
1774 if( !
pad->GetNet() ||
pad->GetNet()->GetNetname() != wxString(
"Net 7" ) )
1779 if( ( NormalizeDeg(
pad->GetThermalSpokeAngle().AsDegrees() ) % 180 ) == 90 )
1784 BOOST_REQUIRE_GT( pcb2Net7PthPads, 0 );
1788 bool pcb2HasOutline =
false;
1789 int pcb2OutsidePads = CountPadsOutsideBoardOutline( *pcb2, pcb2Pads, pcb2HasOutline );
1792 if( pcb2OutsidePads > 0 )
1795 pcb2->GetBoardPolygonOutlines( outline,
true );
1798 for(
const FOOTPRINT* fp : pcb2->Footprints() )
1800 for(
const PAD*
pad : fp->Pads() )
1802 if( !outline.
Contains(
pad->GetPosition(), -1, CONNECT_TOL_NM ) )
1804 BOOST_TEST_MESSAGE(
"PCB_2 outside pad: " + std::string( fp->GetReference().utf8_str() ) +
":"
1805 + std::string(
pad->GetNumber().utf8_str() )
1806 +
" fpLayer=" + std::string( pcb2->GetLayerName( fp->GetLayer() ).utf8_str() )
1807 +
" fpOrient=" + std::to_string( fp->GetOrientation().AsDegrees() ) +
" pad=("
1808 + std::to_string(
pad->GetPosition().x ) +
","
1809 + std::to_string(
pad->GetPosition().y ) +
")" );
1812 if( reported >= 20 )
1817 if( reported >= 20 )
1822 BOOST_CHECK_MESSAGE( pcb2OutsidePads == 0,
"PCB_2: pads outside outline = " + std::to_string( pcb2OutsidePads ) );
1824 const FOOTPRINT* j1 = FindFootprintByRef( *pcb2, wxT(
"J1" ) );
1825 const FOOTPRINT* j4 = FindFootprintByRef( *pcb2, wxT(
"J4" ) );
1826 const FOOTPRINT* j7 = FindFootprintByRef( *pcb2, wxT(
"J7" ) );
1827 const FOOTPRINT* j8 = FindFootprintByRef( *pcb2, wxT(
"J8" ) );
1828 const FOOTPRINT* j10 = FindFootprintByRef( *pcb2, wxT(
"J10" ) );
1829 const FOOTPRINT* u2 = FindFootprintByRef( *pcb2, wxT(
"U2" ) );
1830 const FOOTPRINT* u3 = FindFootprintByRef( *pcb2, wxT(
"U3" ) );
1832 BOOST_REQUIRE_MESSAGE( j1,
"PCB_2: footprint J1 not found" );
1833 BOOST_REQUIRE_MESSAGE( j4,
"PCB_2: footprint J4 not found" );
1834 BOOST_REQUIRE_MESSAGE( j7,
"PCB_2: footprint J7 not found" );
1835 BOOST_REQUIRE_MESSAGE( j8,
"PCB_2: footprint J8 not found" );
1836 BOOST_REQUIRE_MESSAGE( j10,
"PCB_2: footprint J10 not found" );
1837 BOOST_REQUIRE_MESSAGE( u2,
"PCB_2: footprint U2 not found" );
1838 BOOST_REQUIRE_MESSAGE( u3,
"PCB_2: footprint U3 not found" );
1846 int j4SilkSegments = 0;
1861 BOOST_CHECK_MESSAGE( j4SilkSegments >= 4,
"PCB_2: J4 should import as a silkscreen box; silk segments="
1862 + std::to_string( j4SilkSegments ) );
1870 "PCB_2: J1 pad should be SMD, got attribute="
1871 + std::to_string(
static_cast<int>(
pad->GetAttribute() ) ) );
1874 BOOST_REQUIRE_GT( j1Pads, 0 );
1885 "PCB_2: J8 pad drill should be circular" );
1889 BOOST_REQUIRE_GT( j8Pads, 0 );
1891 int u2PadsExpectedRotated = 0;
1895 long padNumLong = 0;
1897 if( !
pad->GetNumber().ToLong( &padNumLong ) )
1900 int padNum =
static_cast<int>( padNumLong );
1902 if( ( padNum >= 12 && padNum <= 22 ) || ( padNum >= 34 && padNum <= 44 ) )
1904 u2PadsExpectedRotated++;
1906 int padDeg = CardinalDeg(
pad->GetOrientation().AsDegrees() );
1908 +
" should be rotated by 90 degrees (got "
1909 + std::to_string( padDeg ) +
")" );
1915 auto pcb4 = LoadBoardFromPath( examplesDir +
"/PCB_4.dip" );
1917 BOOST_CHECK_GT( pcb4->Footprints().size(), 0u );
1920 auto pcb6 = LoadBoardFromPath( examplesDir +
"/PCB_6.dip" );
1936 int gndInnerZones = 0;
1938 for(
const ZONE* zone : pcb6->Zones() )
1940 wxString netName = zone->GetNet() ? zone->GetNet()->GetNetname() : wxString();
1942 if( zone->GetLayer() ==
In2_Cu )
1946 if( netName == wxString(
"3V3" ) )
1948 else if( netName == wxString(
"5V" ) )
1950 else if( netName == wxString(
"VCC" ) )
1953 else if( zone->GetLayer() ==
In1_Cu && netName == wxString(
"GND" ) )
1967 int pcb6ThermalZones = 0;
1968 int pcb6SpokeWidthMatches = 0;
1970 for(
const ZONE* zone : pcb6->Zones() )
1975 if(
std::abs(
pcbIUScale.IUTomm( zone->GetThermalReliefSpokeWidth() ) - 0.33 ) <= 0.03 )
1976 pcb6SpokeWidthMatches++;
1983 int pcb6PowerNetPthPads = 0;
1984 int pcb6PowerNetPadsAt45 = 0;
1986 for(
const FOOTPRINT* fp : pcb6->Footprints() )
1988 for(
const PAD*
pad : fp->Pads() )
1993 wxString netName =
pad->GetNet()->GetNetname();
1995 if( netName != wxString(
"GND" ) && netName != wxString(
"3V3" )
1996 && netName != wxString(
"5V" ) && netName != wxString(
"VCC" ) )
2001 pcb6PowerNetPthPads++;
2003 if( ( NormalizeDeg(
pad->GetThermalSpokeAngle().AsDegrees() ) % 180 ) == 45 )
2004 pcb6PowerNetPadsAt45++;
2008 BOOST_REQUIRE_GT( pcb6PowerNetPthPads, 0 );
2011 const FOOTPRINT* ic4 = FindFootprintByRef( *pcb6, wxT(
"IC4" ) );
2012 BOOST_REQUIRE_MESSAGE( ic4,
"PCB_6: footprint IC4 not found" );
2015 const FOOTPRINT* q4 = FindFootprintByRef( *pcb6, wxT(
"Q4" ) );
2016 BOOST_REQUIRE_MESSAGE( q4,
"PCB_6: footprint Q4 not found" );
2019 int q4SilkSegments = 0;
2039 "PCB_6: Q4 should have 4 silkscreen segments, got " + std::to_string( q4SilkSegments ) );
2041 "PCB_6: Q4 should have 1 silkscreen arc, got " + std::to_string( q4SilkArcs ) );
2043 const FOOTPRINT* u10 = FindFootprintByRef( *pcb6, wxT(
"U10" ) );
2044 BOOST_REQUIRE_MESSAGE( u10,
"PCB_6: footprint U10 not found" );
2046 int u10NpthPads = 0;
2047 int u10Drill508 = 0;
2048 int u10Outer5747 = 0;
2049 std::vector<VECTOR2I> u10NpthLocals;
2057 u10NpthLocals.push_back(
pad->GetPosition() - u10->
GetPosition() );
2063 "PCB_6: U10 NPTH drill should be circular" );
2071 if(
std::abs( drillMm - 5.08 ) <= 0.02 )
2074 if(
std::abs( outerMm - 5.7467 ) <= 0.03 )
2081 BOOST_REQUIRE_EQUAL( u10NpthLocals.size(), 2u );
2083 VECTOR2I sym = u10NpthLocals[0] + u10NpthLocals[1];
2086 BOOST_CHECK_SMALL(
std::abs( sym.
x ), symTol );
2087 BOOST_CHECK_SMALL(
std::abs( sym.
y ), symTol );
2089 double holeSpanMm =
pcbIUScale.IUTomm( ( u10NpthLocals[0] - u10NpthLocals[1] ).EuclideanNorm() );
2090 BOOST_CHECK_SMALL(
std::abs( holeSpanMm - 24.9934 ), 0.05 );
2092 auto cnc = LoadBoardFromPath( examplesDir +
"/CNC_controller.dip" );
2095 BOOST_CHECK_GT( cnc->Footprints().size(), 120u );
2096 BOOST_CHECK_LT( cnc->Footprints().size(), 200u );
2099 int cncViaCount = 0;
2101 for(
const PCB_TRACK* trk : cnc->Tracks() )
2107 BOOST_CHECK_GT( cncViaCount, 300 );
2110 int cncThtThermal = 0;
2112 for(
const ZONE* zone : cnc->Zones() )
2121 BOOST_CHECK_GE( cncThtThermal, 1 );
2127 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
2128 std::string examplesDir =
2129 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
2131 if( !std::filesystem::exists( examplesDir ) )
2133 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ViewerExamplesDipXmlParityOptional" );
2141 int minComparedFootprints;
2142 int minComparedPads;
2145 static const std::array<SAMPLE, 3> samples = { {
2146 {
"PCB_2.dip",
"PCB_2.dipxml", 60, 200 },
2147 {
"PCB_4.dip",
"PCB_4.dipxml", 60, 200 },
2148 {
"PCB_6.dip",
"PCB_6.dipxml", 100, 700 },
2151 for(
const SAMPLE& sample : samples )
2153 std::string dipPath = examplesDir +
"/" + sample.dip;
2154 std::string xmlPath = examplesDir +
"/" + sample.dipxml;
2156 if( !std::filesystem::exists( dipPath ) || !std::filesystem::exists( xmlPath ) )
2158 BOOST_TEST_MESSAGE(
"Skipping " + std::string( sample.dip ) +
" parity check; missing .dip or .dipxml" );
2162 DIPXML_BOARD_MODEL
model;
2163 BOOST_REQUIRE_MESSAGE( LoadDipXmlModel( xmlPath,
model ),
"Failed to load DipXML model: " + xmlPath );
2165 auto board = LoadBoardFromPath( dipPath );
2168 int importedViaCount = 0;
2170 for(
const PCB_TRACK* trk : board->Tracks() )
2176 int expectedViaCount =
model.traceViaPointsUniqueNetPos +
model.viaComponentCount;
2179 std::string( sample.dip ) +
": via parity mismatch; imported="
2180 + std::to_string( importedViaCount )
2181 +
" expected(trace-via unique net+xy="
2182 + std::to_string(
model.traceViaPointsUniqueNetPos )
2183 +
", standalone via components=" + std::to_string(
model.viaComponentCount )
2184 +
", trace-via raw points=" + std::to_string(
model.traceViaPointsRaw ) +
")" );
2186 int comparedFootprints = 0;
2187 int footprintAngleMismatches = 0;
2188 int comparedPads = 0;
2189 int padAngleMismatches = 0;
2190 int padTypeMismatches = 0;
2191 int padDrillMismatches = 0;
2192 int missingPatternPads = 0;
2193 int zoneKeyMismatches = 0;
2194 int zoneClearanceMismatches = 0;
2195 int zoneMinWidthMismatches = 0;
2196 int zoneConnectionMismatches = 0;
2197 int zoneSpokeWidthMismatches = 0;
2198 int zoneIslandModeMismatches = 0;
2199 int zoneMinAreaMismatches = 0;
2200 int zonePriorityMismatches = 0;
2201 std::vector<std::string> missingPadReports;
2202 std::vector<std::string> zoneMismatchReports;
2204 for(
const auto& [ref, patternStyle, componentAngleCardinal] :
model.components )
2206 const FOOTPRINT* fp = FindFootprintByRef( *board, wxString::FromUTF8( ref ) );
2211 comparedFootprints++;
2214 footprintAngleMismatches++;
2216 auto patternIt =
model.patterns.find( patternStyle );
2218 if( patternIt ==
model.patterns.end() )
2221 const auto& patternPads = patternIt->second;
2223 for(
const auto& [padKey, padExpected] : patternPads )
2225 const PAD* foundPad =
nullptr;
2229 if( ToUtf8(
pad->GetNumber() ) == padKey )
2238 missingPatternPads++;
2240 if( missingPadReports.size() < 20 )
2242 missingPadReports.push_back( ref +
":" + padKey +
" style=" + padExpected.styleName
2243 +
" patt=" + patternStyle );
2251 auto styleIt =
model.styles.find( padExpected.styleName );
2253 if( styleIt !=
model.styles.end() )
2255 const DIPXML_PAD_STYLE& style = styleIt->second;
2257 if( style.isSurface )
2260 padTypeMismatches++;
2262 else if( style.isThrough )
2265 padTypeMismatches++;
2267 if( style.holeMm > 0.0 )
2271 if( drill.
x <= 0 || drill.
y <= 0 )
2273 padDrillMismatches++;
2275 else if( style.isRoundHole
2278 padDrillMismatches++;
2286 if( padSize.
x != padSize.
y )
2289 int expectedParity = padExpected.angleCardinalDeg % 180;
2291 if( gotParity != expectedParity )
2292 padAngleMismatches++;
2298 std::string( sample.dip )
2299 +
": compared footprints=" + std::to_string( comparedFootprints ) );
2301 std::string( sample.dip ) +
": compared pads=" + std::to_string( comparedPads ) );
2307 if( !
model.copperPours.empty() )
2309 std::map<std::string, std::vector<int>> expectedZonesByKey;
2310 std::map<std::string, std::vector<int>> expectedZoneMinWidthsByKey;
2311 std::map<std::string, std::vector<int>> expectedZoneConnectionsByKey;
2312 std::map<std::string, std::vector<int>> expectedZoneSpokeWidthsByKey;
2313 std::map<std::string, std::vector<int>> expectedZoneIslandModesByKey;
2314 std::map<std::string, std::vector<long long>> expectedZoneMinAreaByKey;
2315 std::map<std::string, std::vector<int>> expectedZonePrioritiesByKey;
2316 std::map<std::string, std::vector<int>> importedZonePrioritiesByKey;
2317 std::map<std::string, std::vector<int>> importedZonesByKey;
2318 std::map<std::string, std::vector<int>> importedZoneMinWidthsByKey;
2319 std::map<std::string, std::vector<int>> importedZoneConnectionsByKey;
2320 std::map<std::string, std::vector<int>> importedZoneSpokeWidthsByKey;
2321 std::map<std::string, std::vector<int>> importedZoneIslandModesByKey;
2322 std::map<std::string, std::vector<long long>> importedZoneMinAreaByKey;
2324 for(
const auto& pour :
model.copperPours )
2326 std::string netName;
2327 auto netIt =
model.netNames.find( pour.netId );
2329 if( netIt !=
model.netNames.end() )
2330 netName = netIt->second;
2332 std::string key = std::to_string( pour.layer ) +
"|" + netName;
2333 expectedZonePrioritiesByKey[key].push_back( pour.priority );
2334 expectedZonesByKey[key].push_back(
static_cast<int>( std::lround( pour.clearanceMm * 1000.0 ) ) );
2335 expectedZoneMinWidthsByKey[key].push_back(
2336 static_cast<int>( std::lround( pour.lineWidthMm * 1000.0 ) ) );
2337 expectedZoneSpokeWidthsByKey[key].push_back(
2338 static_cast<int>( std::lround( pour.spokeWidthMm * 1000.0 ) ) );
2340 int spokeMode = DipXmlSpokeMode( pour.spoke );
2341 int connection = ( spokeMode == 0 ) ? 0 : 1;
2342 expectedZoneConnectionsByKey[key].push_back( connection );
2345 pour.islandRegion, pour.islandInternal, pour.islandConnection );
2346 expectedZoneIslandModesByKey[key].push_back(
static_cast<int>( islandMode ) );
2350 expectedZoneMinAreaByKey[key].push_back(
2351 DipXmlMinimumAreaToKiCadIu2( pour.minimumAreaMm ) );
2355 for(
const ZONE* zone : board->Zones() )
2359 if( zone->GetZoneName() == wxString(
"DipTrace Plane" ) )
2362 int dipLayer = DipLayerIndexFromKiCadLayer( *board, zone->GetLayer() );
2367 std::string netName = zone->GetNet() ? ToUtf8( zone->GetNet()->GetNetname() ) : std::string();
2368 std::string key = std::to_string( dipLayer ) +
"|" + netName;
2369 int clearanceUm =
static_cast<int>(
2370 std::lround(
pcbIUScale.IUTomm( zone->GetLocalClearance().value_or( 0 ) ) * 1000.0 ) );
2371 int minWidthUm =
static_cast<int>( std::lround(
pcbIUScale.IUTomm( zone->GetMinThickness() ) * 1000.0 ) );
2372 int spokeWidthUm =
static_cast<int>(
2373 std::lround(
pcbIUScale.IUTomm( zone->GetThermalReliefSpokeWidth() ) * 1000.0 ) );
2375 int islandMode =
static_cast<int>( zone->GetIslandRemovalMode() );
2376 importedZonePrioritiesByKey[key].push_back( zone->GetAssignedPriority() );
2377 importedZonesByKey[key].push_back( clearanceUm );
2378 importedZoneMinWidthsByKey[key].push_back( minWidthUm );
2379 importedZoneSpokeWidthsByKey[key].push_back( spokeWidthUm );
2380 importedZoneConnectionsByKey[key].push_back( connection );
2381 importedZoneIslandModesByKey[key].push_back( islandMode );
2384 importedZoneMinAreaByKey[key].push_back( zone->GetMinIslandArea() );
2387 std::set<std::string> allKeys;
2389 for(
const auto& [key,
_] : expectedZonesByKey )
2390 allKeys.insert( key );
2392 for(
const auto& [key,
_] : importedZonesByKey )
2393 allKeys.insert( key );
2395 for(
const std::string& key : allKeys )
2397 auto expIt = expectedZonesByKey.find( key );
2398 auto gotIt = importedZonesByKey.find( key );
2400 if( expIt == expectedZonesByKey.end() || gotIt == importedZonesByKey.end() )
2402 zoneKeyMismatches++;
2404 if( zoneMismatchReports.size() < 20 )
2406 zoneMismatchReports.push_back( std::string(
"key-missing " ) + key +
" exp="
2407 + std::to_string( expIt != expectedZonesByKey.end() ) +
" got="
2408 + std::to_string( gotIt != importedZonesByKey.end() ) );
2414 auto expVals = expIt->second;
2415 auto gotVals = gotIt->second;
2416 auto expWidthVals = expectedZoneMinWidthsByKey[key];
2417 auto gotWidthVals = importedZoneMinWidthsByKey[key];
2418 auto expConnVals = expectedZoneConnectionsByKey[key];
2419 auto gotConnVals = importedZoneConnectionsByKey[key];
2420 auto expSpokeWidthVals = expectedZoneSpokeWidthsByKey[key];
2421 auto gotSpokeWidthVals = importedZoneSpokeWidthsByKey[key];
2422 auto expIslandVals = expectedZoneIslandModesByKey[key];
2423 auto gotIslandVals = importedZoneIslandModesByKey[key];
2424 auto expMinAreaVals = expectedZoneMinAreaByKey[key];
2425 auto gotMinAreaVals = importedZoneMinAreaByKey[key];
2426 auto expPriorityVals = expectedZonePrioritiesByKey[key];
2427 auto gotPriorityVals = importedZonePrioritiesByKey[key];
2428 std::sort( expPriorityVals.begin(), expPriorityVals.end() );
2429 std::sort( gotPriorityVals.begin(), gotPriorityVals.end() );
2430 std::sort( expVals.begin(), expVals.end() );
2431 std::sort( gotVals.begin(), gotVals.end() );
2432 std::sort( expWidthVals.begin(), expWidthVals.end() );
2433 std::sort( gotWidthVals.begin(), gotWidthVals.end() );
2434 std::sort( expConnVals.begin(), expConnVals.end() );
2435 std::sort( gotConnVals.begin(), gotConnVals.end() );
2436 std::sort( expSpokeWidthVals.begin(), expSpokeWidthVals.end() );
2437 std::sort( gotSpokeWidthVals.begin(), gotSpokeWidthVals.end() );
2438 std::sort( expIslandVals.begin(), expIslandVals.end() );
2439 std::sort( gotIslandVals.begin(), gotIslandVals.end() );
2440 std::sort( expMinAreaVals.begin(), expMinAreaVals.end() );
2441 std::sort( gotMinAreaVals.begin(), gotMinAreaVals.end() );
2443 if( expVals.size() != gotVals.size()
2444 || expWidthVals.size() != gotWidthVals.size()
2445 || expConnVals.size() != gotConnVals.size()
2446 || expSpokeWidthVals.size() != gotSpokeWidthVals.size()
2447 || expIslandVals.size() != gotIslandVals.size()
2448 || expMinAreaVals.size() != gotMinAreaVals.size()
2449 || expPriorityVals.size() != gotPriorityVals.size() )
2451 zoneKeyMismatches++;
2453 if( zoneMismatchReports.size() < 20 )
2455 zoneMismatchReports.push_back( std::string(
"key-count " ) + key
2456 +
" expN=" + std::to_string( expVals.size() )
2457 +
" gotN=" + std::to_string( gotVals.size() ) );
2463 for(
size_t i = 0; i < expVals.size(); i++ )
2466 if(
std::abs( expVals[i] - gotVals[i] ) > 20 )
2468 zoneClearanceMismatches++;
2470 if( zoneMismatchReports.size() < 20 )
2472 zoneMismatchReports.push_back( std::string(
"clearance " ) + key
2473 +
" expUm=" + std::to_string( expVals[i] )
2474 +
" gotUm=" + std::to_string( gotVals[i] ) );
2478 if(
std::abs( expWidthVals[i] - gotWidthVals[i] ) > 20 )
2480 zoneMinWidthMismatches++;
2482 if( zoneMismatchReports.size() < 20 )
2484 zoneMismatchReports.push_back( std::string(
"min-width " ) + key
2485 +
" expUm=" + std::to_string( expWidthVals[i] )
2486 +
" gotUm=" + std::to_string( gotWidthVals[i] ) );
2490 if( expPriorityVals[i] != gotPriorityVals[i] )
2492 zonePriorityMismatches++;
2494 if( zoneMismatchReports.size() < 20 )
2496 zoneMismatchReports.push_back( std::string(
"priority " ) + key
2497 +
" exp=" + std::to_string( expPriorityVals[i] )
2498 +
" got=" + std::to_string( gotPriorityVals[i] ) );
2502 if( expConnVals[i] != gotConnVals[i] )
2504 zoneConnectionMismatches++;
2506 if( zoneMismatchReports.size() < 20 )
2508 zoneMismatchReports.push_back( std::string(
"connection " ) + key
2509 +
" exp=" + std::to_string( expConnVals[i] )
2510 +
" got=" + std::to_string( gotConnVals[i] ) );
2514 if(
std::abs( expSpokeWidthVals[i] - gotSpokeWidthVals[i] ) > 20 )
2516 zoneSpokeWidthMismatches++;
2518 if( zoneMismatchReports.size() < 20 )
2520 zoneMismatchReports.push_back( std::string(
"spoke-width " ) + key
2521 +
" expUm=" + std::to_string( expSpokeWidthVals[i] )
2522 +
" gotUm=" + std::to_string( gotSpokeWidthVals[i] ) );
2526 if( expIslandVals[i] != gotIslandVals[i] )
2528 zoneIslandModeMismatches++;
2530 if( zoneMismatchReports.size() < 20 )
2532 zoneMismatchReports.push_back( std::string(
"island-mode " ) + key
2533 +
" exp=" + std::to_string( expIslandVals[i] )
2534 +
" got=" + std::to_string( gotIslandVals[i] ) );
2539 for(
size_t i = 0; i < expMinAreaVals.size(); i++ )
2541 long long diff = std::llabs( expMinAreaVals[i] - gotMinAreaVals[i] );
2542 long long tol = std::max<long long>( 1'000'000'000LL, expMinAreaVals[i] / 100 );
2546 zoneMinAreaMismatches++;
2548 if( zoneMismatchReports.size() < 20 )
2550 zoneMismatchReports.push_back( std::string(
"island-area " ) + key
2551 +
" exp=" + std::to_string( expMinAreaVals[i] )
2552 +
" got=" + std::to_string( gotMinAreaVals[i] ) );
2569 +
": dipxml parity footprintComparisons=" + std::to_string( comparedFootprints )
2570 +
", padComparisons=" + std::to_string( comparedPads )
2571 +
", missingPatternPads=" + std::to_string( missingPatternPads ) );
2573 if( !missingPadReports.empty() )
2575 for(
const std::string& rep : missingPadReports )
2579 if( !zoneMismatchReports.empty() )
2581 for(
const std::string& rep : zoneMismatchReports )
2590 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
2591 std::string examplesDir =
2592 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
2594 if( !std::filesystem::exists( examplesDir ) )
2596 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ViewerExamplesViaParityOptional" );
2604 bool expectAllTraceViaStyleZero =
false;
2607 static const std::array<SAMPLE, 4> samples = { {
2608 {
"PCB_2.dip",
"PCB_2.dipxml",
false },
2609 {
"PCB_4.dip",
"PCB_4.dipxml",
false },
2610 {
"PCB_6.dip",
"PCB_6.dipxml",
false },
2611 {
"CNC_controller.dip",
"CNC_controller.dipxml",
true },
2614 for(
const SAMPLE& sample : samples )
2616 std::string dipPath = examplesDir +
"/" + sample.dip;
2617 std::string xmlPath = examplesDir +
"/" + sample.dipxml;
2619 if( !std::filesystem::exists( dipPath ) || !std::filesystem::exists( xmlPath ) )
2621 BOOST_TEST_MESSAGE(
"Skipping " + std::string( sample.dip ) +
" via parity check; missing .dip or .dipxml" );
2625 DIPXML_BOARD_MODEL
model;
2626 BOOST_REQUIRE_MESSAGE( LoadDipXmlModel( xmlPath,
model ),
"Failed to load DipXML model: " + xmlPath );
2628 auto board = LoadBoardFromPath( dipPath );
2631 int importedViaCount = 0;
2633 for(
const PCB_TRACK* trk : board->Tracks() )
2639 int expectedViaCount =
model.traceViaPointsUniqueNetPos +
model.viaComponentCount;
2642 std::string( sample.dip ) +
": via parity mismatch; imported="
2643 + std::to_string( importedViaCount )
2644 +
" expected(trace-via unique net+xy="
2645 + std::to_string(
model.traceViaPointsUniqueNetPos )
2646 +
", standalone via components=" + std::to_string(
model.viaComponentCount )
2647 +
", trace-via raw points=" + std::to_string(
model.traceViaPointsRaw )
2648 +
", trace-via style0 raw="
2649 + std::to_string(
model.traceViaPointsStyleZeroRaw ) +
")" );
2651 if( sample.expectAllTraceViaStyleZero )
2653 BOOST_REQUIRE_MESSAGE(
model.traceViaPointsRaw > 0,
2654 std::string( sample.dip ) +
": expected routed via points in DipXML" );
2667 const char* corpusEnv = std::getenv(
"DIPTRACE_EXTERNAL_CORPUS_DIR" );
2669 if( !corpusEnv || !*corpusEnv )
2671 BOOST_TEST_MESSAGE(
"DIPTRACE_EXTERNAL_CORPUS_DIR not set; skipping external corpus sweep" );
2675 std::filesystem::path corpusRoot( corpusEnv );
2677 if( !std::filesystem::exists( corpusRoot ) )
2679 BOOST_TEST_MESSAGE(
"External corpus path does not exist; skipping external corpus sweep" );
2683 std::vector<std::filesystem::path> dipFiles;
2685 for(
const auto& entry : std::filesystem::recursive_directory_iterator( corpusRoot ) )
2687 if( entry.is_regular_file() && HasDipExtension( entry.path() ) )
2688 dipFiles.push_back( entry.path() );
2691 std::sort( dipFiles.begin(), dipFiles.end() );
2693 BOOST_REQUIRE_MESSAGE( !dipFiles.empty(),
"No .dip files found under: " + corpusRoot.string() );
2696 int skippedUnreadable = 0;
2698 for(
const std::filesystem::path&
path : dipFiles )
2700 if( !m_plugin.CanReadBoard(
path.string() ) )
2702 skippedUnreadable++;
2706 std::unique_ptr<BOARD> board;
2707 auto* capture =
new DIPTRACE_WARNING_CAPTURE();
2708 wxLog* oldLog = wxLog::SetActiveTarget( capture );
2713 ~LOG_GUARD() { wxLog::SetActiveTarget( old ); }
2714 } logGuard{ oldLog };
2718 board = LoadBoardFromPath(
path.string() );
2722 BOOST_ERROR(
path.string() +
": IO_ERROR: " + std::string( e.
What().utf8_str() ) );
2725 catch(
const std::exception& e )
2727 BOOST_ERROR(
path.string() +
": exception: " + std::string( e.
what() ) );
2731 BOOST_REQUIRE_MESSAGE( board,
"Failed to load: " +
path.string() );
2734 for(
const wxString& warning : capture->m_warnings )
2737 path.string() +
": unexpected heuristic parser warning: "
2738 + std::string( warning.utf8_str() ) );
2741 int outlineEndpoints = 0;
2742 int outlineDisconnected = CountDisconnectedEdgeCutsEndpoints( *board, outlineEndpoints );
2744 if( outlineEndpoints > 0 )
2747 + std::to_string( outlineDisconnected ) +
"/"
2748 + std::to_string( outlineEndpoints ) );
2755 if( skippedUnreadable > 0 )
2756 BOOST_TEST_MESSAGE(
"Skipped " << skippedUnreadable <<
" .dip files without DTBOARD magic" );
2767 static const std::array<const char*, 5>
boards = {
2768 "z80_board.dip",
"keyboard.dip",
"logic_probe.dip",
"project4.dip",
"156bus_narrow.dip"
2771 int boardsWithRules = 0;
2786 std::vector<std::shared_ptr<DRC_RULE>> rules;
2790 BOOST_REQUIRE_NO_THROW( rulesParser.
Parse( rules, &
reporter ) );
2792 std::string(
name ) +
" rules should parse without error:\n"
2793 +
reporter.GetMessages().ToStdString() +
"\n--- rules ---\n"
2794 + dru.ToStdString() );
2795 BOOST_CHECK_GT( rules.size(), 0u );
2799 "Expected at least one committed board to generate zone DRC rules" );
2811 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
2812 std::string examplesDir =
2813 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
2814 std::string cncPath = examplesDir +
"/CNC_controller.dip";
2816 if( !std::filesystem::exists( cncPath ) )
2818 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ZoneDesignRulesParityOptional" );
2828 std::vector<std::shared_ptr<DRC_RULE>> rules;
2832 BOOST_REQUIRE_NO_THROW( rulesParser.
Parse( rules, &
reporter ) );
2834 "CNC rules should parse without error:\n" +
reporter.GetMessages().ToStdString() );
2836 int edgeClearanceIU = 0;
2837 int solidViaRules = 0;
2839 for(
const std::shared_ptr<DRC_RULE>& rule : rules )
2852 BOOST_CHECK_GT( solidViaRules, 0 );
2868 std::map<std::string, double>
expected;
2871 const std::vector<CASE> cases = {
2872 {
"rotate.dip", { {
"C1", 0.0 }, {
"C2", 45.0 }, {
"C3", 90.0 }, {
"C4", 309.33 } } },
2873 {
"rotate4.dip", { {
"C1", 0.0 }, {
"C2", 90.0 }, {
"C3", 45.0 }, {
"C4", 327.96 } } },
2876 for(
const CASE& tc : cases )
2878 auto board = LoadBoard( tc.file );
2881 std::map<std::string, double> seen;
2883 for(
FOOTPRINT* fp : board->Footprints() )
2884 seen[fp->GetReference().ToStdString()] = fp->GetOrientation().Normalize().AsDegrees();
2886 for(
const auto& [ref, deg] : tc.expected )
2888 BOOST_REQUIRE_MESSAGE( seen.count( ref ), tc.file +
": missing " + ref );
2890 double got = seen[ref];
2891 double gap =
std::abs( got - deg );
2892 gap = std::min( gap, 360.0 - gap );
2894 BOOST_CHECK_MESSAGE( gap < 0.1, tc.file +
": " + ref +
" orientation " + std::to_string( got )
2895 +
" 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
! Temporary layer identifier to identify code that is not padstack-aware
const VECTOR2I & GetDrillSize() const
PAD_ATTRIB GetAttribute() const
PAD_DRILL_SHAPE GetDrillShape() const
EDA_ANGLE GetFPRelativeOrientation() const
const VECTOR2I & GetSize(PCB_LAYER_ID aLayer) 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.
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_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
std::vector< BOARD_BEST > boards
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
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