35static constexpr int CONNECT_TOL_NM = 1000;
40 return ( aA - aB ).EuclideanNorm() <= CONNECT_TOL_NM;
44bool IsHeuristicParserWarning(
const wxString& aMessage )
46 return aMessage.Contains( wxS(
"design rule set" ) )
47 || aMessage.Contains( wxS(
"inter-ruleset transition marker" ) )
48 || aMessage.Contains( wxS(
"no validated component boundaries found" ) )
49 || aMessage.Contains( wxS(
"parse error" ) )
50 || aMessage.Contains( wxS(
"parsing failed" ) )
51 || aMessage.Contains( wxS(
"outline traversal aborted" ) );
55class DIPTRACE_WARNING_CAPTURE :
public wxLog
58 std::vector<wxString> m_warnings;
61 void DoLogRecord( wxLogLevel aLevel,
const wxString& aMessage,
62 const wxLogRecordInfo& )
override
64 if( aLevel == wxLOG_Warning )
65 m_warnings.push_back( aMessage );
70int CountDisconnectedEdgeCutsEndpoints(
const BOARD& aBoard,
int& aTotalEndpoints )
72 std::vector<VECTOR2I> endpoints;
84 endpoints.push_back( shape->
GetStart() );
85 endpoints.push_back( shape->
GetEnd() );
88 aTotalEndpoints =
static_cast<int>( endpoints.size() );
92 for(
size_t i = 0; i < endpoints.size(); i++ )
94 bool connected =
false;
96 for(
size_t j = 0; j < endpoints.size(); j++ )
101 if( PointsNear( endpoints[i], endpoints[j] ) )
116int CountDisconnectedTraceEndpoints(
const BOARD& aBoard,
int& aTotalEndpoints )
118 std::map<int, std::vector<VECTOR2I>> netAnchors;
119 std::unordered_map<int, std::unordered_multimap<int64_t, VECTOR2I>> netAnchorBuckets;
121 auto cellCoord = [](
int aValue ) ->
int
124 return aValue / CONNECT_TOL_NM;
126 return -( ( -aValue + CONNECT_TOL_NM - 1 ) / CONNECT_TOL_NM );
129 auto cellKey = [](
int aCellX,
int aCellY ) -> int64_t
131 return (
static_cast<int64_t
>( aCellX ) << 32 ) ^
static_cast<uint32_t
>( aCellY );
134 auto addAnchor = [&](
int aNetCode,
const VECTOR2I& aPos )
136 netAnchors[aNetCode].push_back( aPos );
138 int cx = cellCoord( aPos.x );
139 int cy = cellCoord( aPos.y );
140 netAnchorBuckets[aNetCode].emplace( cellKey( cx, cy ), aPos );
145 int netCode = track->GetNetCode();
152 addAnchor( netCode, track->GetStart() );
153 addAnchor( netCode, track->GetEnd() );
157 addAnchor( netCode, track->GetPosition() );
163 for(
const PAD*
pad : fp->Pads() )
165 int netCode =
pad->GetNetCode();
168 addAnchor( netCode,
pad->GetPosition() );
173 int disconnected = 0;
180 int netCode = track->GetNetCode();
185 auto bucketIt = netAnchorBuckets.find( netCode );
187 if( bucketIt == netAnchorBuckets.end() )
190 const auto& buckets = bucketIt->second;
191 const VECTOR2I endpoints[2] = { track->GetStart(), track->GetEnd() };
193 for(
const VECTOR2I& endpoint : endpoints )
198 int cx = cellCoord( endpoint.x );
199 int cy = cellCoord( endpoint.y );
201 for(
int dx = -1; dx <= 1 && nearbyCount < 2; dx++ )
203 for(
int dy = -1; dy <= 1 && nearbyCount < 2; dy++ )
205 auto range = buckets.equal_range( cellKey( cx + dx, cy + dy ) );
207 for(
auto it = range.first; it != range.second; ++it )
209 if( PointsNear( endpoint, it->second ) )
213 if( nearbyCount >= 2 )
220 if( nearbyCount < 2 )
229int CountViaNetShorts(
const BOARD& aBoard,
int& aCheckedVias, std::vector<std::string>* aReports =
nullptr )
238 std::vector<NET_ANCHOR> anchors;
242 int netCode = track->GetNetCode();
249 if( copperLayers.none() )
254 anchors.push_back( { track->GetStart(), netCode, copperLayers } );
255 anchors.push_back( { track->GetEnd(), netCode, copperLayers } );
259 anchors.push_back( { track->GetPosition(), netCode, copperLayers } );
265 for(
const PAD*
pad : fp->Pads() )
267 int netCode =
pad->GetNetCode();
270 if( netCode > 0 && !padLayers.none() )
271 anchors.push_back( {
pad->GetPosition(), netCode, padLayers } );
284 int viaNet =
via->GetNetCode();
287 if( viaNet <= 0 || viaLayers.none() )
291 bool shortFound =
false;
293 for(
const NET_ANCHOR&
anchor : anchors )
295 if(
anchor.netCode == viaNet )
298 if( ( viaLayers &
anchor.layers ).none() )
301 if( PointsNear(
via->GetPosition(),
anchor.pos ) )
305 if( aReports && aReports->size() < 20 )
309 std::string viaName =
310 viaNetInfo ? std::string( viaNetInfo->
GetNetname().utf8_str() ) :
std::to_string( viaNet );
311 std::string otherName = otherNetInfo ? std::string( otherNetInfo->
GetNetname().utf8_str() )
314 aReports->push_back(
"via(" + viaName +
") at (" + std::to_string(
via->GetPosition().x ) +
","
315 + std::to_string(
via->GetPosition().y ) +
") overlaps net " + otherName );
330int CountPadsOutsideBoardOutline(
BOARD& aBoard,
int& aTotalPads,
bool& aHasOutline )
344 for(
const PAD*
pad : fp->Pads() )
348 if( !boardOutline.
Contains(
pad->GetPosition(), -1, CONNECT_TOL_NM ) )
357bool IsRectLikeSmdPadShape(
PAD_SHAPE aShape )
363bool HasDipExtension(
const std::filesystem::path& aPath )
365 std::string ext = aPath.extension().string();
366 std::transform( ext.begin(), ext.end(), ext.begin(),
367 [](
unsigned char c )
369 return static_cast<char>( std::tolower( c ) );
371 return ext ==
".dip";
375const FOOTPRINT* FindFootprintByRef(
const BOARD& aBoard,
const wxString& aRef )
379 if( fp->GetReference() == aRef )
387int CardinalDeg(
double aDegrees )
389 int deg =
static_cast<int>( std::lround( aDegrees ) );
390 deg = ( ( deg % 360 ) + 360 ) % 360;
392 int cardinal =
static_cast<int>( std::lround( deg / 90.0 ) ) * 90;
393 return ( ( cardinal % 360 ) + 360 ) % 360;
397int NormalizeDeg(
double aDegrees )
399 int deg =
static_cast<int>( std::lround( aDegrees ) );
400 return ( ( deg % 360 ) + 360 ) % 360;
404int DipXmlSpokeMode( std::string aSpoke )
406 aSpoke.erase( std::remove_if( aSpoke.begin(), aSpoke.end(),
407 [](
unsigned char c ) { return std::isspace( c ) != 0; } ),
409 std::transform( aSpoke.begin(), aSpoke.end(), aSpoke.begin(),
410 [](
unsigned char c ) { return static_cast<char>( std::tolower( c ) ); } );
412 if( aSpoke ==
"direct" )
415 if( aSpoke ==
"2spoke90" )
418 if( aSpoke ==
"2spoke" )
421 if( aSpoke ==
"4spoke45" )
424 if( aSpoke ==
"4spoke" )
432 bool aIslandConnection )
434 if( aIslandInternal || aIslandConnection )
444long long DipXmlMinimumAreaToKiCadIu2(
double aMinimumAreaMm )
448 long long dipUnits =
static_cast<long long>( std::llround( aMinimumAreaMm * 30000.0 ) );
449 long long linearIu = dipUnits * 100 / 3;
450 return linearIu * linearIu;
454struct DIPXML_PAD_STYLE
456 bool isSurface =
false;
457 bool isThrough =
false;
458 bool isRoundHole =
false;
463struct DIPXML_PATTERN_PAD
465 std::string styleName;
466 int angleCardinalDeg = 0;
470struct DIPXML_BOARD_MODEL
472 std::unordered_map<std::string, DIPXML_PAD_STYLE> styles;
473 std::unordered_map<std::string, std::unordered_map<std::string, DIPXML_PATTERN_PAD>> patterns;
474 std::vector<std::tuple<std::string, std::string, int>> components;
475 std::unordered_map<int, std::string> netNames;
477 struct DIPXML_COPPER_POUR
482 double clearanceMm = 0.0;
483 double lineWidthMm = 0.0;
484 double minimumAreaMm = 0.0;
486 double spokeWidthMm = 0.0;
487 bool islandRegion =
false;
488 bool islandInternal =
false;
489 bool islandConnection =
false;
492 std::vector<DIPXML_COPPER_POUR> copperPours;
493 int traceViaPointsRaw = 0;
494 int traceViaPointsStyleZeroRaw = 0;
495 int traceViaPointsUniqueNetPos = 0;
496 int viaComponentCount = 0;
500std::string ToUtf8(
const wxString& aText )
502 return std::string( aText.utf8_str() );
506wxString ChildTextByName(
const wxXmlNode* aParent,
const wxString& aName )
511 for(
const wxXmlNode* child = aParent->GetChildren(); child; child = child->GetNext() )
513 if( child->GetType() == wxXML_ELEMENT_NODE && child->GetName() == aName )
517 for(
const wxXmlNode*
text = child->GetChildren();
text;
text =
text->GetNext() )
519 if(
text->GetType() == wxXML_TEXT_NODE ||
text->GetType() == wxXML_CDATA_SECTION_NODE )
520 out +=
text->GetContent();
533bool ParseDoubleAttr(
const wxString& aRaw,
double& aOut )
539 return !tmp.IsEmpty() && tmp.ToDouble( &aOut );
543int CardinalDegFromRadians(
const wxString& aRadiansRaw )
545 double radians = 0.0;
547 if( !ParseDoubleAttr( aRadiansRaw, radians ) )
550 return CardinalDeg( radians * 180.0 /
M_PI );
558 if( copperCount < 2 )
565 return copperCount - 1;
569 int innerDelta =
static_cast<int>( aLayer -
In1_Cu );
571 if( innerDelta % 2 != 0 )
574 int idx = 1 + innerDelta / 2;
575 int maxInnerIdx = copperCount - 2;
577 if( idx >= 1 && idx <= maxInnerIdx )
585bool LoadDipXmlModel(
const std::string& aPath, DIPXML_BOARD_MODEL& aOut )
589 if( !doc.Load( wxString::FromUTF8( aPath ) ) )
592 wxXmlNode* root = doc.GetRoot();
597 std::set<std::string> traceViaNetPointKeys;
599 std::function<void( wxXmlNode* )> walk = [&]( wxXmlNode* node )
601 for( ; node; node = node->GetNext() )
603 if( node->GetType() == wxXML_ELEMENT_NODE )
605 if( node->GetName() == wxT(
"PadStyle" ) )
607 std::string styleName = ToUtf8( node->GetAttribute( wxT(
"Name" ), wxString() ) );
609 if( !styleName.empty() )
611 DIPXML_PAD_STYLE style;
612 wxString type = node->GetAttribute( wxT(
"Type" ), wxString() );
613 wxString holeType = node->GetAttribute( wxT(
"HoleType" ), wxString() );
614 style.isSurface = ( type.CmpNoCase( wxT(
"Surface" ) ) == 0 );
615 style.isThrough = ( type.CmpNoCase( wxT(
"Through" ) ) == 0 );
616 style.isRoundHole = ( holeType.CmpNoCase( wxT(
"Round" ) ) == 0 );
617 ParseDoubleAttr( node->GetAttribute( wxT(
"Hole" ), wxT(
"0" ) ), style.holeMm );
618 aOut.styles[styleName] = style;
621 else if( node->GetName() == wxT(
"Pattern" ) )
623 std::string patternStyle = ToUtf8( node->GetAttribute( wxT(
"PatternStyle" ), wxString() ) );
625 if( !patternStyle.empty() )
627 auto& padMap = aOut.patterns[patternStyle];
628 wxXmlNode* padsNode =
nullptr;
630 for( wxXmlNode* child = node->GetChildren(); child; child = child->GetNext() )
632 if( child->GetType() == wxXML_ELEMENT_NODE && child->GetName() == wxT(
"Pads" ) )
641 for( wxXmlNode* padNode = padsNode->GetChildren(); padNode; padNode = padNode->GetNext() )
643 if( padNode->GetType() != wxXML_ELEMENT_NODE || padNode->GetName() != wxT(
"Pad" ) )
648 wxString padKey = ChildTextByName( padNode, wxT(
"Number" ) );
650 if( padKey.IsEmpty() )
651 padKey = padNode->GetAttribute( wxT(
"Id" ), wxString() );
653 std::string key = ToUtf8( padKey );
658 DIPXML_PATTERN_PAD
pad;
659 pad.styleName = ToUtf8( padNode->GetAttribute( wxT(
"Style" ), wxString() ) );
660 pad.angleCardinalDeg =
661 CardinalDegFromRadians( padNode->GetAttribute( wxT(
"Angle" ), wxT(
"0" ) ) );
667 else if( node->GetName() == wxT(
"Component" ) )
669 if( node->GetAttribute( wxT(
"Type" ), wxString() ).CmpNoCase( wxT(
"Via" ) ) == 0 )
670 aOut.viaComponentCount++;
672 wxString ref = ChildTextByName( node, wxT(
"RefDes" ) );
676 std::string refUtf8 = ToUtf8( ref );
677 std::string patternStyle = ToUtf8( node->GetAttribute( wxT(
"PatternStyle" ), wxString() ) );
678 int angleCardinal = CardinalDegFromRadians( node->GetAttribute( wxT(
"Angle" ), wxT(
"0" ) ) );
679 aOut.components.emplace_back( std::move( refUtf8 ), std::move( patternStyle ), angleCardinal );
682 else if( node->GetName() == wxT(
"Net" ) )
686 if( node->GetAttribute( wxT(
"Id" ), wxString() ).ToLong( &netId ) )
688 wxString netName = ChildTextByName( node, wxT(
"Name" ) );
689 aOut.netNames[
static_cast<int>( netId )] = ToUtf8( netName );
691 for( wxXmlNode* child = node->GetChildren(); child; child = child->GetNext() )
693 if( child->GetType() != wxXML_ELEMENT_NODE || child->GetName() != wxT(
"Traces" ) )
696 for( wxXmlNode* traceNode = child->GetChildren(); traceNode;
697 traceNode = traceNode->GetNext() )
699 if( traceNode->GetType() != wxXML_ELEMENT_NODE
700 || traceNode->GetName() != wxT(
"Trace" ) )
705 for( wxXmlNode* traceChild = traceNode->GetChildren(); traceChild;
706 traceChild = traceChild->GetNext() )
708 if( traceChild->GetType() != wxXML_ELEMENT_NODE
709 || traceChild->GetName() != wxT(
"Points" ) )
714 for( wxXmlNode* pointNode = traceChild->GetChildren(); pointNode;
715 pointNode = pointNode->GetNext() )
717 if( pointNode->GetType() != wxXML_ELEMENT_NODE
718 || pointNode->GetName() != wxT(
"Point" ) )
723 wxString viaStyle = pointNode->GetAttribute( wxT(
"ViaStyle" ), wxString() );
725 if( viaStyle.IsEmpty() )
728 aOut.traceViaPointsRaw++;
730 long viaStyleId = -1;
732 if( viaStyle.ToLong( &viaStyleId ) && viaStyleId == 0 )
733 aOut.traceViaPointsStyleZeroRaw++;
735 std::string key = std::to_string( netId ) +
"|"
736 + ToUtf8( pointNode->GetAttribute( wxT(
"X" ), wxString() ) )
738 + ToUtf8( pointNode->GetAttribute( wxT(
"Y" ), wxString() ) );
739 traceViaNetPointKeys.insert( std::move( key ) );
746 else if( node->GetName() == wxT(
"CopperPour" ) )
751 double lineWidth = 0.0;
752 double minimumArea = 0.0;
753 double spokeWidth = 0.0;
755 if( node->GetAttribute( wxT(
"NetId" ), wxString() ).ToLong( &netId )
756 && node->GetAttribute( wxT(
"Lay" ), wxString() ).ToLong( &lay ) )
758 ParseDoubleAttr( node->GetAttribute( wxT(
"Clearance" ), wxT(
"0" ) ),
clearance );
759 ParseDoubleAttr( node->GetAttribute( wxT(
"LineWidth" ), wxT(
"0" ) ), lineWidth );
760 ParseDoubleAttr( node->GetAttribute( wxT(
"MinimumArea" ), wxT(
"0" ) ), minimumArea );
761 ParseDoubleAttr( node->GetAttribute( wxT(
"SpokeWidth" ), wxT(
"0" ) ), spokeWidth );
764 node->GetAttribute( wxT(
"Priority" ), wxT(
"0" ) ).ToLong( &priority );
766 DIPXML_BOARD_MODEL::DIPXML_COPPER_POUR pour;
767 pour.netId =
static_cast<int>( netId );
768 pour.layer =
static_cast<int>( lay );
769 pour.priority =
static_cast<int>( priority );
771 pour.lineWidthMm = lineWidth;
772 pour.minimumAreaMm = minimumArea;
773 pour.spoke = ToUtf8( node->GetAttribute( wxT(
"Spoke" ), wxString() ) );
774 pour.spokeWidthMm = spokeWidth;
776 node->GetAttribute( wxT(
"IslandRegion" ), wxT(
"N" ) ).CmpNoCase( wxT(
"Y" ) ) == 0;
777 pour.islandInternal =
778 node->GetAttribute( wxT(
"IslandInternal" ), wxT(
"N" ) ).CmpNoCase( wxT(
"Y" ) ) == 0;
779 pour.islandConnection = node->GetAttribute( wxT(
"IslandConnection" ), wxT(
"N" ) )
780 .CmpNoCase( wxT(
"Y" ) )
782 aOut.copperPours.push_back( pour );
787 if( node->GetChildren() )
788 walk( node->GetChildren() );
793 aOut.traceViaPointsUniqueNetPos =
static_cast<int>( traceViaNetPointKeys.size() );
810 auto board = LoadBoard(
"z80_board.dip" );
815 for(
const FOOTPRINT* fp : board->Footprints() )
816 totalPads +=
static_cast<int>( fp->Pads().size() );
818 BOOST_CHECK_MESSAGE( totalPads > 400,
"Z80 board should have >400 total pads, got " + std::to_string( totalPads ) );
829 auto board = LoadBoard(
"z80_board.dip" );
835 for(
const FOOTPRINT* fp : board->Footprints() )
837 int padCount =
static_cast<int>( fp->Pads().size() );
839 if( padCount > maxPads )
846 BOOST_CHECK_MESSAGE( maxPads >= 28,
"Z80 board should have a footprint with >=28 pads (Z80 DIP-40), "
848 + std::to_string( maxPads ) );
850 BOOST_CHECK_MESSAGE( icCount >= 5,
"Z80 board should have >=5 footprints with >=14 pads (ICs), "
852 + std::to_string( icCount ) );
864 auto board = LoadBoard(
"z80_board.dip" );
868 int reasonablePads = 0;
871 for(
const FOOTPRINT* fp : board->Footprints() )
873 for(
const PAD*
pad : fp->Pads() )
880 double maxDim = std::max( widthMm, heightMm );
882 if( maxDim >= 0.8 && maxDim <= 5.0 )
890 BOOST_REQUIRE_GT( totalPads, 0 );
892 double reasonablePercent = 100.0 * reasonablePads / totalPads;
893 double tinyPercent = 100.0 * tinyPads / totalPads;
896 "At least 50% of pads should be 0.8-5.0mm, got " + std::to_string( reasonablePercent ) +
"% ("
897 + std::to_string( reasonablePads ) +
"/" + std::to_string( totalPads ) +
")" );
900 "Less than 20% of pads should be <0.5mm, got " + std::to_string( tinyPercent ) +
"% ("
901 + std::to_string( tinyPads ) +
"/" + std::to_string( totalPads ) +
")" );
912 auto board = LoadBoard(
"z80_board.dip" );
915 bool foundGoodSpacing =
false;
916 double toleranceMm = 0.3;
918 for(
const FOOTPRINT* fp : board->Footprints() )
920 if( fp->Pads().size() < 14 )
924 std::vector<VECTOR2I> positions;
926 for(
const PAD*
pad : fp->Pads() )
928 VECTOR2I local =
pad->GetPosition() - fp->GetPosition();
929 positions.push_back( local );
933 std::sort( positions.begin(), positions.end(),
936 if( std::abs( a.x - b.x ) < 100000 )
943 for(
size_t i = 1; i < positions.size(); i++ )
945 if(
std::abs( positions[i].x - positions[i - 1].x ) > 100000 )
948 double spacingMm =
pcbIUScale.IUTomm(
std::abs( positions[i].y - positions[i - 1].y ) );
950 if(
std::abs( spacingMm - 2.54 ) < toleranceMm )
952 foundGoodSpacing =
true;
957 if( foundGoodSpacing )
961 BOOST_CHECK_MESSAGE( foundGoodSpacing,
"At least one multi-pin IC should have ~2.54mm DIP pin spacing" );
971 auto board = LoadBoard(
"z80_board.dip" );
975 int padsWithNets = 0;
977 for(
const FOOTPRINT* fp : board->Footprints() )
979 for(
const PAD*
pad : fp->Pads() )
983 if(
pad->GetNetCode() > 0 )
988 BOOST_REQUIRE_GT( totalPads, 0 );
990 double netPercent = 100.0 * padsWithNets / totalPads;
992 BOOST_CHECK_MESSAGE( padsWithNets > 0,
"At least some pads should have net assignments, got 0 out of "
993 + std::to_string( totalPads ) );
996 "At least 30% of pads should have nets, got " + std::to_string( netPercent ) +
"%" );
1006 auto board = LoadBoard(
"z80_board.dip" );
1009 std::set<wxString> padNetNames;
1011 for(
const FOOTPRINT* fp : board->Footprints() )
1013 for(
const PAD*
pad : fp->Pads() )
1015 if(
pad->GetNetCode() > 0 )
1016 padNetNames.insert(
pad->GetNet()->GetNetname() );
1020 BOOST_CHECK_MESSAGE( padNetNames.count( wxT(
"GND" ) ) > 0,
"GND should appear on at least one pad" );
1022 BOOST_CHECK_MESSAGE( padNetNames.count( wxT(
"A0" ) ) > 0,
"A0 should appear on at least one pad" );
1024 BOOST_CHECK_MESSAGE( padNetNames.count( wxT(
"D0" ) ) > 0,
"D0 should appear on at least one pad" );
1041 std::vector<TestCase> cases = {
1042 {
"project4.dip", 30, 27 }, {
"z80_board.dip", 200, 104 }, {
"logic_probe.dip", 100, 113 },
1043 {
"keyboard.dip", 200, 123 }, {
"156bus_narrow.dip", 20, 17 },
1046 for(
const TestCase& tc : cases )
1048 auto board = LoadBoard( tc.file );
1049 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1053 for(
const FOOTPRINT* fp : board->Footprints() )
1054 totalPads +=
static_cast<int>( fp->Pads().size() );
1056 BOOST_CHECK_MESSAGE( totalPads >= tc.minPads, tc.file +
": expected >=" + std::to_string( tc.minPads )
1057 +
" pads, got " + std::to_string( totalPads ) );
1075 std::vector<TestCase> cases = {
1076 {
"project4.dip", 100 }, {
"156bus_narrow.dip", 30 }, {
"z80_board.dip", 1500 },
1077 {
"logic_probe.dip", 400 }, {
"keyboard.dip", 400 },
1080 for(
const TestCase& tc : cases )
1082 auto board = LoadBoard( tc.file );
1083 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1087 for(
const PCB_TRACK* trk : board->Tracks() )
1093 BOOST_CHECK_MESSAGE( trackCount >= tc.minTracks, tc.file +
": expected >=" + std::to_string( tc.minTracks )
1094 +
" tracks, got " + std::to_string( trackCount ) );
1116 std::vector<TestCase> cases = {
1117 {
"project4.dip", 50, 200 }, {
"z80_board.dip", 400, 1000 }, {
"logic_probe.dip", 10, 100 },
1118 {
"156bus_narrow.dip", 0, 5 }, {
"keyboard.dip", 10, 40 },
1121 for(
const TestCase& tc : cases )
1123 auto board = LoadBoard( tc.file );
1124 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1128 for(
const PCB_TRACK* trk : board->Tracks() )
1134 BOOST_CHECK_MESSAGE( viaCount >= tc.minVias, tc.file +
": expected >=" + std::to_string( tc.minVias )
1135 +
" vias, got " + std::to_string( viaCount ) );
1137 BOOST_CHECK_MESSAGE( viaCount <= tc.maxVias, tc.file +
": expected <=" + std::to_string( tc.maxVias )
1138 +
" vias, got " + std::to_string( viaCount ) );
1150 auto board = LoadBoard(
"z80_board.dip" );
1153 int totalTracks = 0;
1154 int reasonableTracks = 0;
1156 int reasonableVias = 0;
1158 for(
const PCB_TRACK* trk : board->Tracks() )
1163 double widthMm =
pcbIUScale.IUTomm( trk->GetWidth() );
1165 if( widthMm >= 0.1 && widthMm <= 3.0 )
1174 if( diamMm >= 0.3 && diamMm <= 2.0 )
1179 if( totalTracks > 0 )
1181 double pct = 100.0 * reasonableTracks / totalTracks;
1183 BOOST_CHECK_MESSAGE( pct > 90.0,
"At least 90% of tracks should have reasonable widths (0.1-3.0mm), got "
1184 + std::to_string( pct ) +
"%" );
1189 double pct = 100.0 * reasonableVias / totalVias;
1191 BOOST_CHECK_MESSAGE( pct > 90.0,
"At least 90% of vias should have reasonable diameters (0.3-2.0mm), got "
1192 + std::to_string( pct ) +
"%" );
1203 auto board = LoadBoard(
"z80_board.dip" );
1206 int totalTracks = 0;
1207 int tracksWithNets = 0;
1208 std::set<wxString> trackNetNames;
1210 for(
const PCB_TRACK* trk : board->Tracks() )
1216 if( trk->GetNetCode() > 0 )
1219 trackNetNames.insert( trk->GetNet()->GetNetname() );
1224 BOOST_REQUIRE_GT( totalTracks, 0 );
1226 double netPct = 100.0 * tracksWithNets / totalTracks;
1229 "At least 90% of tracks should have net assignments, got " + std::to_string( netPct ) +
"%" );
1231 BOOST_CHECK_MESSAGE( trackNetNames.count( wxT(
"GND" ) ) > 0,
"GND net should appear on tracks" );
1233 BOOST_CHECK_MESSAGE( trackNetNames.count( wxT(
"A0" ) ) > 0,
"A0 net should appear on tracks" );
1248 std::vector<TestCase> cases = {
1249 {
"project4.dip", 0 }, {
"156bus_narrow.dip", 1 }, {
"z80_board.dip", 2 },
1250 {
"logic_probe.dip", 2 }, {
"keyboard.dip", 1 },
1253 for(
const TestCase& tc : cases )
1255 auto board = LoadBoard( tc.file );
1256 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + tc.file );
1258 int zoneCount =
static_cast<int>( board->Zones().size() );
1260 BOOST_CHECK_MESSAGE( zoneCount == tc.expected, tc.file +
": expected " + std::to_string( tc.expected )
1261 +
" zones, got " + std::to_string( zoneCount ) );
1272 auto board = LoadBoard(
"z80_board.dip" );
1275 int zonesWithNets = 0;
1276 std::set<wxString> zoneNetNames;
1278 for(
const ZONE* zone : board->Zones() )
1280 if( zone->GetNetCode() > 0 )
1283 zoneNetNames.insert( zone->GetNet()->GetNetname() );
1288 "All zones should have net assignments, got " + std::to_string( zonesWithNets ) +
"/"
1289 + std::to_string( board->Zones().size() ) );
1291 BOOST_CHECK_MESSAGE( zoneNetNames.size() >= 1,
"At least 1 distinct net should appear on zones" );
1302 auto board = LoadBoard(
"z80_board.dip" );
1304 BOOST_REQUIRE_GT( board->Zones().size(), 0u );
1306 for(
const ZONE* zone : board->Zones() )
1315 "Zone outline should have at least 3 vertices, got " + std::to_string( vertexCount ) );
1321 BOOST_CHECK_MESSAGE( widthMm > 5.0,
"Zone width should be >5mm, got " + std::to_string( widthMm ) +
"mm" );
1323 BOOST_CHECK_MESSAGE( heightMm > 5.0,
"Zone height should be >5mm, got " + std::to_string( heightMm ) +
"mm" );
1333 auto board = LoadBoard(
"z80_board.dip" );
1336 for(
const ZONE* zone : board->Zones() )
1341 + std::to_string(
static_cast<int>( layer ) ) );
1352 auto board = LoadBoard(
"z80_board.dip" );
1355 int footprintsWithGraphics = 0;
1356 int totalGraphics = 0;
1358 for(
const FOOTPRINT* fp : board->Footprints() )
1360 int graphicCount = 0;
1362 for(
const BOARD_ITEM* item : fp->GraphicalItems() )
1368 if( graphicCount > 0 )
1370 footprintsWithGraphics++;
1371 totalGraphics += graphicCount;
1375 BOOST_CHECK_MESSAGE( footprintsWithGraphics > 0,
"At least some footprints should have outline graphics, got 0" );
1378 "Board should have >10 total footprint graphics, got " + std::to_string( totalGraphics ) );
1389 auto board = LoadBoard(
"z80_board.dip" );
1392 int reasonableCount = 0;
1395 for(
const FOOTPRINT* fp : board->Footprints() )
1397 bool hasGraphics =
false;
1399 for(
const BOARD_ITEM* item : fp->GraphicalItems() )
1414 for(
const BOARD_ITEM* item : fp->GraphicalItems() )
1434 double maxDim = std::max( widthMm, heightMm );
1436 if( maxDim >= 2.0 && maxDim <= 80.0 )
1438 else if( maxDim < 0.5 )
1442 BOOST_CHECK_MESSAGE( reasonableCount > 0,
"At least some footprints should have reasonably-sized "
1443 "outline graphics (2-80mm)" );
1446 "No footprint graphics should be tiny (<0.5mm), got " + std::to_string( tinyCount ) );
1456 const std::vector<std::string> files = {
1457 "project4.dip",
"156bus_narrow.dip",
"z80_board.dip",
"logic_probe.dip",
"keyboard.dip",
1460 for(
const std::string& file : files )
1462 auto board = LoadBoard( file );
1463 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1465 int totalEndpoints = 0;
1466 int disconnected = CountDisconnectedEdgeCutsEndpoints( *board, totalEndpoints );
1468 BOOST_CHECK_MESSAGE( totalEndpoints > 0, file +
": expected Edge.Cuts endpoints, got 0" );
1471 file +
": expected contiguous outline; found " + std::to_string( disconnected )
1472 +
" disconnected endpoints out of " + std::to_string( totalEndpoints ) );
1483 const std::vector<std::string> files = {
1489 for(
const std::string& file : files )
1491 auto board = LoadBoard( file );
1492 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1494 int totalEndpoints = 0;
1495 int disconnected = CountDisconnectedTraceEndpoints( *board, totalEndpoints );
1496 BOOST_REQUIRE_MESSAGE( totalEndpoints > 0, file +
": expected routed trace endpoints, got 0" );
1498 double disconnectedPct = 100.0 * disconnected / totalEndpoints;
1501 file +
": disconnected trace endpoints = " + std::to_string( disconnected ) +
"/"
1502 + std::to_string( totalEndpoints ) +
" (" + std::to_string( disconnectedPct )
1514 const std::vector<std::string> files = {
1516 "156bus_narrow.dip",
1519 int footprints0805 = 0;
1522 for(
const std::string& file : files )
1524 auto board = LoadBoard( file );
1525 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1527 for(
const FOOTPRINT* fp : board->Footprints() )
1529 wxString fpName = wxString::FromUTF8( fp->GetFPID().GetLibItemName() ).Upper();
1531 if( !fpName.Contains(
"0805" ) )
1536 if( fp->Pads().size() != 2 )
1539 bool padsValid =
true;
1540 std::vector<VECTOR2I> padPos;
1542 for(
const PAD*
pad : fp->Pads() )
1552 if( !IsRectLikeSmdPadShape( shape ) )
1562 if( widthMm < 0.2 || widthMm > 2.5 || heightMm < 0.2 || heightMm > 2.5 )
1568 padPos.push_back(
pad->GetPosition() );
1573 double pitchMm =
pcbIUScale.IUTomm( ( padPos[0] - padPos[1] ).EuclideanNorm() );
1575 if( pitchMm < 0.5 || pitchMm > 3.5 )
1585 "Expected at least 3 imported 0805 footprints, got " + std::to_string( footprints0805 ) );
1588 "All imported 0805 footprints should satisfy SMD/pad-shape/pitch sanity; "
1589 "valid=" + std::to_string( valid0805 )
1590 +
", total=" + std::to_string( footprints0805 ) );
1596 const std::vector<std::string> files = {
1603 for(
const std::string& file : files )
1605 auto board = LoadBoard( file );
1606 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1608 int checkedVias = 0;
1609 std::vector<std::string> shortReports;
1610 int shortedVias = CountViaNetShorts( *board, checkedVias, &shortReports );
1612 for(
const std::string& report : shortReports )
1615 BOOST_CHECK_MESSAGE( shortedVias == 0, file +
": vias shorting distinct nets = " + std::to_string( shortedVias )
1616 +
" out of " + std::to_string( checkedVias ) +
" vias" );
1623 const std::vector<std::string> files = {
1630 for(
const std::string& file : files )
1632 auto board = LoadBoard( file );
1633 BOOST_REQUIRE_MESSAGE( board,
"Failed to load " + file );
1636 bool hasOutline =
false;
1637 int outsidePads = CountPadsOutsideBoardOutline( *board, totalPads, hasOutline );
1639 BOOST_REQUIRE_MESSAGE( hasOutline, file +
": board outline not available" );
1640 BOOST_REQUIRE_MESSAGE( totalPads > 0, file +
": no pads found" );
1642 if( outsidePads > 0 )
1645 board->GetBoardPolygonOutlines( outline,
true );
1648 for(
const FOOTPRINT* fp : board->Footprints() )
1650 for(
const PAD*
pad : fp->Pads() )
1652 if( outline.
Contains(
pad->GetPosition(), -1, CONNECT_TOL_NM ) )
1655 BOOST_TEST_MESSAGE( file +
": outside pad " + std::string( fp->GetReference().utf8_str() ) +
":"
1656 + std::string(
pad->GetNumber().utf8_str() ) +
" at ("
1657 + std::to_string(
pad->GetPosition().x ) +
","
1658 + std::to_string(
pad->GetPosition().y ) +
")" );
1660 if( ++reported >= 20 )
1664 if( reported >= 20 )
1669 BOOST_CHECK_MESSAGE( outsidePads == 0, file +
": pads outside board outline = " + std::to_string( outsidePads )
1670 +
"/" + std::to_string( totalPads ) );
1677 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
1678 std::string examplesDir =
1679 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
1681 if( !std::filesystem::exists( examplesDir ) )
1683 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ViewerExamplesOptional" );
1687 auto pcb2 = LoadBoardFromPath( examplesDir +
"/PCB_2.dip" );
1694 for(
const PCB_TRACK* trk : pcb2->Tracks() )
1703 "PCB_2: via count should not exceed segment count; tracks=" + std::to_string( pcb2Tracks )
1704 +
", vias=" + std::to_string( pcb2Vias ) );
1706 int viasWithDrill = 0;
1707 int viasAt191Mil = 0;
1709 for(
const PCB_TRACK* trk : pcb2->Tracks() )
1715 int drillIU =
via->GetDrillValue();
1722 double drillMm =
pcbIUScale.IUTomm( drillIU );
1723 double targetMm = 19.1 * 0.0254;
1725 if(
std::abs( drillMm - targetMm ) <= 0.02 )
1729 BOOST_REQUIRE_MESSAGE( viasWithDrill > 0,
"PCB_2: expected vias with non-zero drill" );
1731 "PCB_2: vias with 19.1mil drill = " + std::to_string( viasAt191Mil ) +
"/"
1732 + std::to_string( viasWithDrill ) );
1734 int pcb2ShortVias = 0;
1735 int pcb2CheckedVias = 0;
1736 pcb2ShortVias = CountViaNetShorts( *pcb2, pcb2CheckedVias );
1738 "PCB_2: vias shorting distinct nets = " + std::to_string( pcb2ShortVias ) );
1740 const ZONE* pcb2BottomZone =
nullptr;
1742 for(
const ZONE* zone : pcb2->Zones() )
1744 if( zone->GetLayer() ==
B_Cu )
1746 pcb2BottomZone = zone;
1751 BOOST_REQUIRE_MESSAGE( pcb2BottomZone,
"PCB_2: expected a B.Cu copper zone" );
1755 BOOST_CHECK_MESSAGE( pcb2ZoneNet == wxString(
"Net 7" ),
"PCB_2: B.Cu zone net should be 'Net 7', got '"
1756 + std::string( pcb2ZoneNet.utf8_str() ) +
"'" );
1760 int pcb2Net7PthPads = 0;
1761 int pcb2Net7At90 = 0;
1763 for(
const FOOTPRINT* fp : pcb2->Footprints() )
1765 for(
const PAD*
pad : fp->Pads() )
1770 if( !
pad->GetNet() ||
pad->GetNet()->GetNetname() != wxString(
"Net 7" ) )
1775 if( ( NormalizeDeg(
pad->GetThermalSpokeAngle().AsDegrees() ) % 180 ) == 90 )
1780 BOOST_REQUIRE_GT( pcb2Net7PthPads, 0 );
1784 bool pcb2HasOutline =
false;
1785 int pcb2OutsidePads = CountPadsOutsideBoardOutline( *pcb2, pcb2Pads, pcb2HasOutline );
1788 if( pcb2OutsidePads > 0 )
1791 pcb2->GetBoardPolygonOutlines( outline,
true );
1794 for(
const FOOTPRINT* fp : pcb2->Footprints() )
1796 for(
const PAD*
pad : fp->Pads() )
1798 if( !outline.
Contains(
pad->GetPosition(), -1, CONNECT_TOL_NM ) )
1800 BOOST_TEST_MESSAGE(
"PCB_2 outside pad: " + std::string( fp->GetReference().utf8_str() ) +
":"
1801 + std::string(
pad->GetNumber().utf8_str() )
1802 +
" fpLayer=" + std::string( pcb2->GetLayerName( fp->GetLayer() ).utf8_str() )
1803 +
" fpOrient=" + std::to_string( fp->GetOrientation().AsDegrees() ) +
" pad=("
1804 + std::to_string(
pad->GetPosition().x ) +
","
1805 + std::to_string(
pad->GetPosition().y ) +
")" );
1808 if( reported >= 20 )
1813 if( reported >= 20 )
1818 BOOST_CHECK_MESSAGE( pcb2OutsidePads == 0,
"PCB_2: pads outside outline = " + std::to_string( pcb2OutsidePads ) );
1820 const FOOTPRINT* j1 = FindFootprintByRef( *pcb2, wxT(
"J1" ) );
1821 const FOOTPRINT* j4 = FindFootprintByRef( *pcb2, wxT(
"J4" ) );
1822 const FOOTPRINT* j7 = FindFootprintByRef( *pcb2, wxT(
"J7" ) );
1823 const FOOTPRINT* j8 = FindFootprintByRef( *pcb2, wxT(
"J8" ) );
1824 const FOOTPRINT* j10 = FindFootprintByRef( *pcb2, wxT(
"J10" ) );
1825 const FOOTPRINT* u2 = FindFootprintByRef( *pcb2, wxT(
"U2" ) );
1826 const FOOTPRINT* u3 = FindFootprintByRef( *pcb2, wxT(
"U3" ) );
1828 BOOST_REQUIRE_MESSAGE( j1,
"PCB_2: footprint J1 not found" );
1829 BOOST_REQUIRE_MESSAGE( j4,
"PCB_2: footprint J4 not found" );
1830 BOOST_REQUIRE_MESSAGE( j7,
"PCB_2: footprint J7 not found" );
1831 BOOST_REQUIRE_MESSAGE( j8,
"PCB_2: footprint J8 not found" );
1832 BOOST_REQUIRE_MESSAGE( j10,
"PCB_2: footprint J10 not found" );
1833 BOOST_REQUIRE_MESSAGE( u2,
"PCB_2: footprint U2 not found" );
1834 BOOST_REQUIRE_MESSAGE( u3,
"PCB_2: footprint U3 not found" );
1842 int j4SilkSegments = 0;
1857 BOOST_CHECK_MESSAGE( j4SilkSegments >= 4,
"PCB_2: J4 should import as a silkscreen box; silk segments="
1858 + std::to_string( j4SilkSegments ) );
1866 "PCB_2: J1 pad should be SMD, got attribute="
1867 + std::to_string(
static_cast<int>(
pad->GetAttribute() ) ) );
1870 BOOST_REQUIRE_GT( j1Pads, 0 );
1881 "PCB_2: J8 pad drill should be circular" );
1885 BOOST_REQUIRE_GT( j8Pads, 0 );
1887 int u2PadsExpectedRotated = 0;
1891 long padNumLong = 0;
1893 if( !
pad->GetNumber().ToLong( &padNumLong ) )
1896 int padNum =
static_cast<int>( padNumLong );
1898 if( ( padNum >= 12 && padNum <= 22 ) || ( padNum >= 34 && padNum <= 44 ) )
1900 u2PadsExpectedRotated++;
1902 int padDeg = CardinalDeg(
pad->GetOrientation().AsDegrees() );
1904 +
" should be rotated by 90 degrees (got "
1905 + std::to_string( padDeg ) +
")" );
1911 auto pcb4 = LoadBoardFromPath( examplesDir +
"/PCB_4.dip" );
1913 BOOST_CHECK_GT( pcb4->Footprints().size(), 0u );
1916 auto pcb6 = LoadBoardFromPath( examplesDir +
"/PCB_6.dip" );
1932 int gndInnerZones = 0;
1934 for(
const ZONE* zone : pcb6->Zones() )
1936 wxString netName = zone->GetNet() ? zone->GetNet()->GetNetname() : wxString();
1938 if( zone->GetLayer() ==
In2_Cu )
1942 if( netName == wxString(
"3V3" ) )
1944 else if( netName == wxString(
"5V" ) )
1946 else if( netName == wxString(
"VCC" ) )
1949 else if( zone->GetLayer() ==
In1_Cu && netName == wxString(
"GND" ) )
1963 int pcb6ThermalZones = 0;
1964 int pcb6SpokeWidthMatches = 0;
1966 for(
const ZONE* zone : pcb6->Zones() )
1971 if(
std::abs(
pcbIUScale.IUTomm( zone->GetThermalReliefSpokeWidth() ) - 0.33 ) <= 0.03 )
1972 pcb6SpokeWidthMatches++;
1979 int pcb6PowerNetPthPads = 0;
1980 int pcb6PowerNetPadsAt45 = 0;
1982 for(
const FOOTPRINT* fp : pcb6->Footprints() )
1984 for(
const PAD*
pad : fp->Pads() )
1989 wxString netName =
pad->GetNet()->GetNetname();
1991 if( netName != wxString(
"GND" ) && netName != wxString(
"3V3" )
1992 && netName != wxString(
"5V" ) && netName != wxString(
"VCC" ) )
1997 pcb6PowerNetPthPads++;
1999 if( ( NormalizeDeg(
pad->GetThermalSpokeAngle().AsDegrees() ) % 180 ) == 45 )
2000 pcb6PowerNetPadsAt45++;
2004 BOOST_REQUIRE_GT( pcb6PowerNetPthPads, 0 );
2007 const FOOTPRINT* ic4 = FindFootprintByRef( *pcb6, wxT(
"IC4" ) );
2008 BOOST_REQUIRE_MESSAGE( ic4,
"PCB_6: footprint IC4 not found" );
2011 const FOOTPRINT* q4 = FindFootprintByRef( *pcb6, wxT(
"Q4" ) );
2012 BOOST_REQUIRE_MESSAGE( q4,
"PCB_6: footprint Q4 not found" );
2015 int q4SilkSegments = 0;
2035 "PCB_6: Q4 should have 4 silkscreen segments, got " + std::to_string( q4SilkSegments ) );
2037 "PCB_6: Q4 should have 1 silkscreen arc, got " + std::to_string( q4SilkArcs ) );
2039 const FOOTPRINT* u10 = FindFootprintByRef( *pcb6, wxT(
"U10" ) );
2040 BOOST_REQUIRE_MESSAGE( u10,
"PCB_6: footprint U10 not found" );
2042 int u10NpthPads = 0;
2043 int u10Drill508 = 0;
2044 int u10Outer5747 = 0;
2045 std::vector<VECTOR2I> u10NpthLocals;
2053 u10NpthLocals.push_back(
pad->GetPosition() - u10->
GetPosition() );
2059 "PCB_6: U10 NPTH drill should be circular" );
2067 if(
std::abs( drillMm - 5.08 ) <= 0.02 )
2070 if(
std::abs( outerMm - 5.7467 ) <= 0.03 )
2077 BOOST_REQUIRE_EQUAL( u10NpthLocals.size(), 2u );
2079 VECTOR2I sym = u10NpthLocals[0] + u10NpthLocals[1];
2082 BOOST_CHECK_SMALL(
std::abs( sym.
x ), symTol );
2083 BOOST_CHECK_SMALL(
std::abs( sym.
y ), symTol );
2085 double holeSpanMm =
pcbIUScale.IUTomm( ( u10NpthLocals[0] - u10NpthLocals[1] ).EuclideanNorm() );
2086 BOOST_CHECK_SMALL(
std::abs( holeSpanMm - 24.9934 ), 0.05 );
2088 auto cnc = LoadBoardFromPath( examplesDir +
"/CNC_controller.dip" );
2091 BOOST_CHECK_GT( cnc->Footprints().size(), 120u );
2092 BOOST_CHECK_LT( cnc->Footprints().size(), 200u );
2095 int cncViaCount = 0;
2097 for(
const PCB_TRACK* trk : cnc->Tracks() )
2103 BOOST_CHECK_GT( cncViaCount, 300 );
2106 int cncThtThermal = 0;
2108 for(
const ZONE* zone : cnc->Zones() )
2117 BOOST_CHECK_GE( cncThtThermal, 1 );
2123 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
2124 std::string examplesDir =
2125 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
2127 if( !std::filesystem::exists( examplesDir ) )
2129 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ViewerExamplesDipXmlParityOptional" );
2137 int minComparedFootprints;
2138 int minComparedPads;
2141 static const std::array<SAMPLE, 3> samples = { {
2142 {
"PCB_2.dip",
"PCB_2.dipxml", 60, 200 },
2143 {
"PCB_4.dip",
"PCB_4.dipxml", 60, 200 },
2144 {
"PCB_6.dip",
"PCB_6.dipxml", 100, 700 },
2147 for(
const SAMPLE& sample : samples )
2149 std::string dipPath = examplesDir +
"/" + sample.dip;
2150 std::string xmlPath = examplesDir +
"/" + sample.dipxml;
2152 if( !std::filesystem::exists( dipPath ) || !std::filesystem::exists( xmlPath ) )
2154 BOOST_TEST_MESSAGE(
"Skipping " + std::string( sample.dip ) +
" parity check; missing .dip or .dipxml" );
2158 DIPXML_BOARD_MODEL
model;
2159 BOOST_REQUIRE_MESSAGE( LoadDipXmlModel( xmlPath,
model ),
"Failed to load DipXML model: " + xmlPath );
2161 auto board = LoadBoardFromPath( dipPath );
2164 int importedViaCount = 0;
2166 for(
const PCB_TRACK* trk : board->Tracks() )
2172 int expectedViaCount =
model.traceViaPointsUniqueNetPos +
model.viaComponentCount;
2175 std::string( sample.dip ) +
": via parity mismatch; imported="
2176 + std::to_string( importedViaCount )
2177 +
" expected(trace-via unique net+xy="
2178 + std::to_string(
model.traceViaPointsUniqueNetPos )
2179 +
", standalone via components=" + std::to_string(
model.viaComponentCount )
2180 +
", trace-via raw points=" + std::to_string(
model.traceViaPointsRaw ) +
")" );
2182 int comparedFootprints = 0;
2183 int footprintAngleMismatches = 0;
2184 int comparedPads = 0;
2185 int padAngleMismatches = 0;
2186 int padTypeMismatches = 0;
2187 int padDrillMismatches = 0;
2188 int missingPatternPads = 0;
2189 int zoneKeyMismatches = 0;
2190 int zoneClearanceMismatches = 0;
2191 int zoneMinWidthMismatches = 0;
2192 int zoneConnectionMismatches = 0;
2193 int zoneSpokeWidthMismatches = 0;
2194 int zoneIslandModeMismatches = 0;
2195 int zoneMinAreaMismatches = 0;
2196 int zonePriorityMismatches = 0;
2197 std::vector<std::string> missingPadReports;
2198 std::vector<std::string> zoneMismatchReports;
2200 for(
const auto& [ref, patternStyle, componentAngleCardinal] :
model.components )
2202 const FOOTPRINT* fp = FindFootprintByRef( *board, wxString::FromUTF8( ref ) );
2207 comparedFootprints++;
2210 footprintAngleMismatches++;
2212 auto patternIt =
model.patterns.find( patternStyle );
2214 if( patternIt ==
model.patterns.end() )
2217 const auto& patternPads = patternIt->second;
2219 for(
const auto& [padKey, padExpected] : patternPads )
2221 const PAD* foundPad =
nullptr;
2225 if( ToUtf8(
pad->GetNumber() ) == padKey )
2234 missingPatternPads++;
2236 if( missingPadReports.size() < 20 )
2238 missingPadReports.push_back( ref +
":" + padKey +
" style=" + padExpected.styleName
2239 +
" patt=" + patternStyle );
2247 auto styleIt =
model.styles.find( padExpected.styleName );
2249 if( styleIt !=
model.styles.end() )
2251 const DIPXML_PAD_STYLE& style = styleIt->second;
2253 if( style.isSurface )
2256 padTypeMismatches++;
2258 else if( style.isThrough )
2261 padTypeMismatches++;
2263 if( style.holeMm > 0.0 )
2267 if( drill.
x <= 0 || drill.
y <= 0 )
2269 padDrillMismatches++;
2271 else if( style.isRoundHole
2274 padDrillMismatches++;
2282 if( padSize.
x != padSize.
y )
2285 int expectedParity = padExpected.angleCardinalDeg % 180;
2287 if( gotParity != expectedParity )
2288 padAngleMismatches++;
2294 std::string( sample.dip )
2295 +
": compared footprints=" + std::to_string( comparedFootprints ) );
2297 std::string( sample.dip ) +
": compared pads=" + std::to_string( comparedPads ) );
2303 if( !
model.copperPours.empty() )
2305 std::map<std::string, std::vector<int>> expectedZonesByKey;
2306 std::map<std::string, std::vector<int>> expectedZoneMinWidthsByKey;
2307 std::map<std::string, std::vector<int>> expectedZoneConnectionsByKey;
2308 std::map<std::string, std::vector<int>> expectedZoneSpokeWidthsByKey;
2309 std::map<std::string, std::vector<int>> expectedZoneIslandModesByKey;
2310 std::map<std::string, std::vector<long long>> expectedZoneMinAreaByKey;
2311 std::map<std::string, std::vector<int>> expectedZonePrioritiesByKey;
2312 std::map<std::string, std::vector<int>> importedZonePrioritiesByKey;
2313 std::map<std::string, std::vector<int>> importedZonesByKey;
2314 std::map<std::string, std::vector<int>> importedZoneMinWidthsByKey;
2315 std::map<std::string, std::vector<int>> importedZoneConnectionsByKey;
2316 std::map<std::string, std::vector<int>> importedZoneSpokeWidthsByKey;
2317 std::map<std::string, std::vector<int>> importedZoneIslandModesByKey;
2318 std::map<std::string, std::vector<long long>> importedZoneMinAreaByKey;
2320 for(
const auto& pour :
model.copperPours )
2322 std::string netName;
2323 auto netIt =
model.netNames.find( pour.netId );
2325 if( netIt !=
model.netNames.end() )
2326 netName = netIt->second;
2328 std::string key = std::to_string( pour.layer ) +
"|" + netName;
2329 expectedZonePrioritiesByKey[key].push_back( pour.priority );
2330 expectedZonesByKey[key].push_back(
static_cast<int>( std::lround( pour.clearanceMm * 1000.0 ) ) );
2331 expectedZoneMinWidthsByKey[key].push_back(
2332 static_cast<int>( std::lround( pour.lineWidthMm * 1000.0 ) ) );
2333 expectedZoneSpokeWidthsByKey[key].push_back(
2334 static_cast<int>( std::lround( pour.spokeWidthMm * 1000.0 ) ) );
2336 int spokeMode = DipXmlSpokeMode( pour.spoke );
2337 int connection = ( spokeMode == 0 ) ? 0 : 1;
2338 expectedZoneConnectionsByKey[key].push_back( connection );
2341 pour.islandRegion, pour.islandInternal, pour.islandConnection );
2342 expectedZoneIslandModesByKey[key].push_back(
static_cast<int>( islandMode ) );
2346 expectedZoneMinAreaByKey[key].push_back(
2347 DipXmlMinimumAreaToKiCadIu2( pour.minimumAreaMm ) );
2351 for(
const ZONE* zone : board->Zones() )
2355 if( zone->GetZoneName() == wxString(
"DipTrace Plane" ) )
2358 int dipLayer = DipLayerIndexFromKiCadLayer( *board, zone->GetLayer() );
2363 std::string netName = zone->GetNet() ? ToUtf8( zone->GetNet()->GetNetname() ) : std::string();
2364 std::string key = std::to_string( dipLayer ) +
"|" + netName;
2365 int clearanceUm =
static_cast<int>(
2366 std::lround(
pcbIUScale.IUTomm( zone->GetLocalClearance().value_or( 0 ) ) * 1000.0 ) );
2367 int minWidthUm =
static_cast<int>( std::lround(
pcbIUScale.IUTomm( zone->GetMinThickness() ) * 1000.0 ) );
2368 int spokeWidthUm =
static_cast<int>(
2369 std::lround(
pcbIUScale.IUTomm( zone->GetThermalReliefSpokeWidth() ) * 1000.0 ) );
2371 int islandMode =
static_cast<int>( zone->GetIslandRemovalMode() );
2372 importedZonePrioritiesByKey[key].push_back( zone->GetAssignedPriority() );
2373 importedZonesByKey[key].push_back( clearanceUm );
2374 importedZoneMinWidthsByKey[key].push_back( minWidthUm );
2375 importedZoneSpokeWidthsByKey[key].push_back( spokeWidthUm );
2376 importedZoneConnectionsByKey[key].push_back( connection );
2377 importedZoneIslandModesByKey[key].push_back( islandMode );
2380 importedZoneMinAreaByKey[key].push_back( zone->GetMinIslandArea() );
2383 std::set<std::string> allKeys;
2385 for(
const auto& [key,
_] : expectedZonesByKey )
2386 allKeys.insert( key );
2388 for(
const auto& [key,
_] : importedZonesByKey )
2389 allKeys.insert( key );
2391 for(
const std::string& key : allKeys )
2393 auto expIt = expectedZonesByKey.find( key );
2394 auto gotIt = importedZonesByKey.find( key );
2396 if( expIt == expectedZonesByKey.end() || gotIt == importedZonesByKey.end() )
2398 zoneKeyMismatches++;
2400 if( zoneMismatchReports.size() < 20 )
2402 zoneMismatchReports.push_back( std::string(
"key-missing " ) + key +
" exp="
2403 + std::to_string( expIt != expectedZonesByKey.end() ) +
" got="
2404 + std::to_string( gotIt != importedZonesByKey.end() ) );
2410 auto expVals = expIt->second;
2411 auto gotVals = gotIt->second;
2412 auto expWidthVals = expectedZoneMinWidthsByKey[key];
2413 auto gotWidthVals = importedZoneMinWidthsByKey[key];
2414 auto expConnVals = expectedZoneConnectionsByKey[key];
2415 auto gotConnVals = importedZoneConnectionsByKey[key];
2416 auto expSpokeWidthVals = expectedZoneSpokeWidthsByKey[key];
2417 auto gotSpokeWidthVals = importedZoneSpokeWidthsByKey[key];
2418 auto expIslandVals = expectedZoneIslandModesByKey[key];
2419 auto gotIslandVals = importedZoneIslandModesByKey[key];
2420 auto expMinAreaVals = expectedZoneMinAreaByKey[key];
2421 auto gotMinAreaVals = importedZoneMinAreaByKey[key];
2422 auto expPriorityVals = expectedZonePrioritiesByKey[key];
2423 auto gotPriorityVals = importedZonePrioritiesByKey[key];
2424 std::sort( expPriorityVals.begin(), expPriorityVals.end() );
2425 std::sort( gotPriorityVals.begin(), gotPriorityVals.end() );
2426 std::sort( expVals.begin(), expVals.end() );
2427 std::sort( gotVals.begin(), gotVals.end() );
2428 std::sort( expWidthVals.begin(), expWidthVals.end() );
2429 std::sort( gotWidthVals.begin(), gotWidthVals.end() );
2430 std::sort( expConnVals.begin(), expConnVals.end() );
2431 std::sort( gotConnVals.begin(), gotConnVals.end() );
2432 std::sort( expSpokeWidthVals.begin(), expSpokeWidthVals.end() );
2433 std::sort( gotSpokeWidthVals.begin(), gotSpokeWidthVals.end() );
2434 std::sort( expIslandVals.begin(), expIslandVals.end() );
2435 std::sort( gotIslandVals.begin(), gotIslandVals.end() );
2436 std::sort( expMinAreaVals.begin(), expMinAreaVals.end() );
2437 std::sort( gotMinAreaVals.begin(), gotMinAreaVals.end() );
2439 if( expVals.size() != gotVals.size()
2440 || expWidthVals.size() != gotWidthVals.size()
2441 || expConnVals.size() != gotConnVals.size()
2442 || expSpokeWidthVals.size() != gotSpokeWidthVals.size()
2443 || expIslandVals.size() != gotIslandVals.size()
2444 || expMinAreaVals.size() != gotMinAreaVals.size()
2445 || expPriorityVals.size() != gotPriorityVals.size() )
2447 zoneKeyMismatches++;
2449 if( zoneMismatchReports.size() < 20 )
2451 zoneMismatchReports.push_back( std::string(
"key-count " ) + key
2452 +
" expN=" + std::to_string( expVals.size() )
2453 +
" gotN=" + std::to_string( gotVals.size() ) );
2459 for(
size_t i = 0; i < expVals.size(); i++ )
2462 if(
std::abs( expVals[i] - gotVals[i] ) > 20 )
2464 zoneClearanceMismatches++;
2466 if( zoneMismatchReports.size() < 20 )
2468 zoneMismatchReports.push_back( std::string(
"clearance " ) + key
2469 +
" expUm=" + std::to_string( expVals[i] )
2470 +
" gotUm=" + std::to_string( gotVals[i] ) );
2474 if(
std::abs( expWidthVals[i] - gotWidthVals[i] ) > 20 )
2476 zoneMinWidthMismatches++;
2478 if( zoneMismatchReports.size() < 20 )
2480 zoneMismatchReports.push_back( std::string(
"min-width " ) + key
2481 +
" expUm=" + std::to_string( expWidthVals[i] )
2482 +
" gotUm=" + std::to_string( gotWidthVals[i] ) );
2486 if( expPriorityVals[i] != gotPriorityVals[i] )
2488 zonePriorityMismatches++;
2490 if( zoneMismatchReports.size() < 20 )
2492 zoneMismatchReports.push_back( std::string(
"priority " ) + key
2493 +
" exp=" + std::to_string( expPriorityVals[i] )
2494 +
" got=" + std::to_string( gotPriorityVals[i] ) );
2498 if( expConnVals[i] != gotConnVals[i] )
2500 zoneConnectionMismatches++;
2502 if( zoneMismatchReports.size() < 20 )
2504 zoneMismatchReports.push_back( std::string(
"connection " ) + key
2505 +
" exp=" + std::to_string( expConnVals[i] )
2506 +
" got=" + std::to_string( gotConnVals[i] ) );
2510 if(
std::abs( expSpokeWidthVals[i] - gotSpokeWidthVals[i] ) > 20 )
2512 zoneSpokeWidthMismatches++;
2514 if( zoneMismatchReports.size() < 20 )
2516 zoneMismatchReports.push_back( std::string(
"spoke-width " ) + key
2517 +
" expUm=" + std::to_string( expSpokeWidthVals[i] )
2518 +
" gotUm=" + std::to_string( gotSpokeWidthVals[i] ) );
2522 if( expIslandVals[i] != gotIslandVals[i] )
2524 zoneIslandModeMismatches++;
2526 if( zoneMismatchReports.size() < 20 )
2528 zoneMismatchReports.push_back( std::string(
"island-mode " ) + key
2529 +
" exp=" + std::to_string( expIslandVals[i] )
2530 +
" got=" + std::to_string( gotIslandVals[i] ) );
2535 for(
size_t i = 0; i < expMinAreaVals.size(); i++ )
2537 long long diff = std::llabs( expMinAreaVals[i] - gotMinAreaVals[i] );
2538 long long tol = std::max<long long>( 1'000'000'000LL, expMinAreaVals[i] / 100 );
2542 zoneMinAreaMismatches++;
2544 if( zoneMismatchReports.size() < 20 )
2546 zoneMismatchReports.push_back( std::string(
"island-area " ) + key
2547 +
" exp=" + std::to_string( expMinAreaVals[i] )
2548 +
" got=" + std::to_string( gotMinAreaVals[i] ) );
2565 +
": dipxml parity footprintComparisons=" + std::to_string( comparedFootprints )
2566 +
", padComparisons=" + std::to_string( comparedPads )
2567 +
", missingPatternPads=" + std::to_string( missingPatternPads ) );
2569 if( !missingPadReports.empty() )
2571 for(
const std::string& rep : missingPadReports )
2575 if( !zoneMismatchReports.empty() )
2577 for(
const std::string& rep : zoneMismatchReports )
2586 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
2587 std::string examplesDir =
2588 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
2590 if( !std::filesystem::exists( examplesDir ) )
2592 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ViewerExamplesViaParityOptional" );
2600 bool expectAllTraceViaStyleZero =
false;
2603 static const std::array<SAMPLE, 4> samples = { {
2604 {
"PCB_2.dip",
"PCB_2.dipxml",
false },
2605 {
"PCB_4.dip",
"PCB_4.dipxml",
false },
2606 {
"PCB_6.dip",
"PCB_6.dipxml",
false },
2607 {
"CNC_controller.dip",
"CNC_controller.dipxml",
true },
2610 for(
const SAMPLE& sample : samples )
2612 std::string dipPath = examplesDir +
"/" + sample.dip;
2613 std::string xmlPath = examplesDir +
"/" + sample.dipxml;
2615 if( !std::filesystem::exists( dipPath ) || !std::filesystem::exists( xmlPath ) )
2617 BOOST_TEST_MESSAGE(
"Skipping " + std::string( sample.dip ) +
" via parity check; missing .dip or .dipxml" );
2621 DIPXML_BOARD_MODEL
model;
2622 BOOST_REQUIRE_MESSAGE( LoadDipXmlModel( xmlPath,
model ),
"Failed to load DipXML model: " + xmlPath );
2624 auto board = LoadBoardFromPath( dipPath );
2627 int importedViaCount = 0;
2629 for(
const PCB_TRACK* trk : board->Tracks() )
2635 int expectedViaCount =
model.traceViaPointsUniqueNetPos +
model.viaComponentCount;
2638 std::string( sample.dip ) +
": via parity mismatch; imported="
2639 + std::to_string( importedViaCount )
2640 +
" expected(trace-via unique net+xy="
2641 + std::to_string(
model.traceViaPointsUniqueNetPos )
2642 +
", standalone via components=" + std::to_string(
model.viaComponentCount )
2643 +
", trace-via raw points=" + std::to_string(
model.traceViaPointsRaw )
2644 +
", trace-via style0 raw="
2645 + std::to_string(
model.traceViaPointsStyleZeroRaw ) +
")" );
2647 if( sample.expectAllTraceViaStyleZero )
2649 BOOST_REQUIRE_MESSAGE(
model.traceViaPointsRaw > 0,
2650 std::string( sample.dip ) +
": expected routed via points in DipXML" );
2663 const char* corpusEnv = std::getenv(
"DIPTRACE_EXTERNAL_CORPUS_DIR" );
2665 if( !corpusEnv || !*corpusEnv )
2667 BOOST_TEST_MESSAGE(
"DIPTRACE_EXTERNAL_CORPUS_DIR not set; skipping external corpus sweep" );
2671 std::filesystem::path corpusRoot( corpusEnv );
2673 if( !std::filesystem::exists( corpusRoot ) )
2675 BOOST_TEST_MESSAGE(
"External corpus path does not exist; skipping external corpus sweep" );
2679 std::vector<std::filesystem::path> dipFiles;
2681 for(
const auto& entry : std::filesystem::recursive_directory_iterator( corpusRoot ) )
2683 if( entry.is_regular_file() && HasDipExtension( entry.path() ) )
2684 dipFiles.push_back( entry.path() );
2687 std::sort( dipFiles.begin(), dipFiles.end() );
2689 BOOST_REQUIRE_MESSAGE( !dipFiles.empty(),
"No .dip files found under: " + corpusRoot.string() );
2692 int skippedUnreadable = 0;
2694 for(
const std::filesystem::path&
path : dipFiles )
2696 if( !m_plugin.CanReadBoard(
path.string() ) )
2698 skippedUnreadable++;
2702 std::unique_ptr<BOARD> board;
2703 auto* capture =
new DIPTRACE_WARNING_CAPTURE();
2704 wxLog* oldLog = wxLog::SetActiveTarget( capture );
2709 ~LOG_GUARD() { wxLog::SetActiveTarget( old ); }
2710 } logGuard{ oldLog };
2714 board = LoadBoardFromPath(
path.string() );
2718 BOOST_ERROR(
path.string() +
": IO_ERROR: " + std::string( e.
What().utf8_str() ) );
2721 catch(
const std::exception& e )
2723 BOOST_ERROR(
path.string() +
": exception: " + std::string( e.
what() ) );
2727 BOOST_REQUIRE_MESSAGE( board,
"Failed to load: " +
path.string() );
2730 for(
const wxString& warning : capture->m_warnings )
2733 path.string() +
": unexpected heuristic parser warning: "
2734 + std::string( warning.utf8_str() ) );
2737 int outlineEndpoints = 0;
2738 int outlineDisconnected = CountDisconnectedEdgeCutsEndpoints( *board, outlineEndpoints );
2740 if( outlineEndpoints > 0 )
2743 + std::to_string( outlineDisconnected ) +
"/"
2744 + std::to_string( outlineEndpoints ) );
2751 if( skippedUnreadable > 0 )
2752 BOOST_TEST_MESSAGE(
"Skipped " << skippedUnreadable <<
" .dip files without DTBOARD magic" );
2763 static const std::array<const char*, 5>
boards = {
2764 "z80_board.dip",
"keyboard.dip",
"logic_probe.dip",
"project4.dip",
"156bus_narrow.dip"
2767 int boardsWithRules = 0;
2782 std::vector<std::shared_ptr<DRC_RULE>> rules;
2786 BOOST_REQUIRE_NO_THROW( rulesParser.
Parse( rules, &
reporter ) );
2788 std::string(
name ) +
" rules should parse without error:\n"
2789 +
reporter.GetMessages().ToStdString() +
"\n--- rules ---\n"
2790 + dru.ToStdString() );
2791 BOOST_CHECK_GT( rules.size(), 0u );
2795 "Expected at least one committed board to generate zone DRC rules" );
2807 const char* examplesEnv = std::getenv(
"DIPTRACE_VIEWER_EXAMPLES_DIR" );
2808 std::string examplesDir =
2809 examplesEnv && *examplesEnv ? examplesEnv :
"/home/seth/Downloads/DipTrace Viewer/Examples";
2810 std::string cncPath = examplesDir +
"/CNC_controller.dip";
2812 if( !std::filesystem::exists( cncPath ) )
2814 BOOST_TEST_MESSAGE(
"Viewer examples path not found; skipping ZoneDesignRulesParityOptional" );
2824 std::vector<std::shared_ptr<DRC_RULE>> rules;
2828 BOOST_REQUIRE_NO_THROW( rulesParser.
Parse( rules, &
reporter ) );
2830 "CNC rules should parse without error:\n" +
reporter.GetMessages().ToStdString() );
2832 int edgeClearanceIU = 0;
2833 int solidViaRules = 0;
2835 for(
const std::shared_ptr<DRC_RULE>& rule : rules )
2848 BOOST_CHECK_GT( solidViaRules, 0 );
2864 std::map<std::string, double>
expected;
2867 const std::vector<CASE> cases = {
2868 {
"rotate.dip", { {
"C1", 0.0 }, {
"C2", 45.0 }, {
"C3", 90.0 }, {
"C4", 309.33 } } },
2869 {
"rotate4.dip", { {
"C1", 0.0 }, {
"C2", 90.0 }, {
"C3", 45.0 }, {
"C4", 327.96 } } },
2872 for(
const CASE& tc : cases )
2874 auto board = LoadBoard( tc.file );
2877 std::map<std::string, double> seen;
2879 for(
FOOTPRINT* fp : board->Footprints() )
2880 seen[fp->GetReference().ToStdString()] = fp->GetOrientation().Normalize().AsDegrees();
2882 for(
const auto& [ref, deg] : tc.expected )
2884 BOOST_REQUIRE_MESSAGE( seen.count( ref ), tc.file +
": missing " + ref );
2886 double got = seen[ref];
2887 double gap =
std::abs( got - deg );
2888 gap = std::min( gap, 360.0 - gap );
2890 BOOST_CHECK_MESSAGE( gap < 0.1, tc.file +
": " + ref +
" orientation " + std::to_string( got )
2891 +
" 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
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.
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