#include #include #include #include #include #include #include #include #include #include #include #include #include const int UI_EPSILON = Mils2iu( 5 ); wxPoint test::DRC_TEST_PROVIDER_CLEARANCE_BASE::getLocation( TRACK* aTrack, ZONE_CONTAINER* aConflictZone ) { SHAPE_POLY_SET* conflictOutline; if( aConflictZone->IsFilled() ) conflictOutline = const_cast( &aConflictZone->GetFilledPolysList() ); else conflictOutline = aConflictZone->Outline(); wxPoint pt1 = aTrack->GetPosition(); wxPoint pt2 = aTrack->GetEnd(); // If the mid-point is in the zone, then that's a fine place for the marker if( conflictOutline->SquaredDistance( ( pt1 + pt2 ) / 2 ) == 0 ) return ( pt1 + pt2 ) / 2; // Otherwise do a binary search for a "good enough" marker location else { while( GetLineLength( pt1, pt2 ) > UI_EPSILON ) { if( conflictOutline->SquaredDistance( pt1 ) < conflictOutline->SquaredDistance( pt2 ) ) pt2 = ( pt1 + pt2 ) / 2; else pt1 = ( pt1 + pt2 ) / 2; } // Once we're within UI_EPSILON pt1 and pt2 are "equivalent" return pt1; } } wxPoint test::DRC_TEST_PROVIDER_CLEARANCE_BASE::getLocation( TRACK* aTrack, const SEG& aConflictSeg ) { wxPoint pt1 = aTrack->GetPosition(); wxPoint pt2 = aTrack->GetEnd(); // Do a binary search along the track for a "good enough" marker location while( GetLineLength( pt1, pt2 ) > UI_EPSILON ) { if( aConflictSeg.SquaredDistance( pt1 ) < aConflictSeg.SquaredDistance( pt2 ) ) pt2 = ( pt1 + pt2 ) / 2; else pt1 = ( pt1 + pt2 ) / 2; } // Once we're within UI_EPSILON pt1 and pt2 are "equivalent" return pt1; } /* * Test if distance between a segment and a pad is > minClearance. Return the actual * distance if it is less. */ bool test::DRC_TEST_PROVIDER_CLEARANCE_BASE::checkClearanceSegmToPad( const SEG& refSeg, int refSegWidth, const D_PAD* pad, int minClearance, int* aActualDist ) { if( ( pad->GetShape() == PAD_SHAPE_CIRCLE || pad->GetShape() == PAD_SHAPE_OVAL ) ) { /* Treat an oval pad as a line segment along the hole's major axis, * shortened by half its minor axis. * A circular pad is just a degenerate case of an oval hole. */ wxPoint padStart, padEnd; int padWidth; pad->GetOblongGeometry( pad->GetSize(), &padStart, &padEnd, &padWidth ); padStart += pad->ShapePos(); padEnd += pad->ShapePos(); SEG padSeg( padStart, padEnd ); int widths = ( padWidth + refSegWidth ) / 2; int center2centerAllowed = minClearance + widths; // Avoid square-roots if possible (for performance) SEG::ecoord center2center_squared = refSeg.SquaredDistance( padSeg ); if( center2center_squared < SEG::Square( center2centerAllowed ) ) { *aActualDist = std::max( 0.0, sqrt( center2center_squared ) - widths ); return false; } } else if( ( pad->GetShape() == PAD_SHAPE_RECT || pad->GetShape() == PAD_SHAPE_ROUNDRECT ) && ( (int) pad->GetOrientation() % 900 == 0 ) ) { EDA_RECT padBBox = pad->GetBoundingBox(); int widths = refSegWidth / 2; // Note a ROUNDRECT pad with a corner radius = r can be treated as a smaller // RECT (size - 2*r) with a clearance increased by r if( pad->GetShape() == PAD_SHAPE_ROUNDRECT ) { padBBox.Inflate( - pad->GetRoundRectCornerRadius() ); widths += pad->GetRoundRectCornerRadius(); } SHAPE_RECT padShape( padBBox.GetPosition(), padBBox.GetWidth(), padBBox.GetHeight() ); int actual; if( padShape.DoCollide( refSeg, minClearance + widths, &actual ) ) { *aActualDist = std::max( 0, actual - widths ); return false; } } else // Convert the rest to polygons { SHAPE_POLY_SET polyset; BOARD* board = pad->GetBoard(); int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; pad->TransformShapeWithClearanceToPolygon( polyset, 0, maxError ); const SHAPE_LINE_CHAIN& refpoly = polyset.COutline( 0 ); int widths = refSegWidth / 2; int actual; if( !poly2segmentDRC( (wxPoint*) &refpoly.CPoint( 0 ), refpoly.PointCount(), (wxPoint) refSeg.A, (wxPoint) refSeg.B, minClearance + widths, &actual ) ) { *aActualDist = std::max( 0, actual - widths ); return false; } } return true; } bool test::DRC_TEST_PROVIDER_CLEARANCE_BASE::checkClearancePadToPad( D_PAD* aRefPad, D_PAD* aPad, int aMinClearance, int* aActual ) { // relativePadPos is the aPad shape position relative to the aRefPad shape position wxPoint relativePadPos = aPad->ShapePos() - aRefPad->ShapePos(); int center2center = KiROUND( EuclideanNorm( relativePadPos ) ); // Quick test: Clearance is OK if the bounding circles are further away than aMinClearance if( center2center - aRefPad->GetBoundingRadius() - aPad->GetBoundingRadius() >= aMinClearance ) return true; /* Here, pads are near and DRC depends on the pad shapes. We must compare distance using * a fine shape analysis. * Because a circle or oval shape is the easier shape to test, swap pads to have aRefPad be * a PAD_SHAPE_CIRCLE or PAD_SHAPE_OVAL. If aRefPad = TRAPEZOID and aPad = RECT, also swap. */ bool swap_pads; swap_pads = false; // swap pads to make comparisons easier // Note also a ROUNDRECT pad with a corner radius = r can be considered as // a smaller RECT (size - 2*r) with a clearance increased by r // priority is aRefPad = ROUND then OVAL then RECT/ROUNDRECT then other if( aRefPad->GetShape() != aPad->GetShape() && aRefPad->GetShape() != PAD_SHAPE_CIRCLE ) { // pad ref shape is here oval, rect, roundrect, chamfered rect, trapezoid or custom switch( aPad->GetShape() ) { case PAD_SHAPE_CIRCLE: swap_pads = true; break; case PAD_SHAPE_OVAL: swap_pads = true; break; case PAD_SHAPE_RECT: case PAD_SHAPE_ROUNDRECT: if( aRefPad->GetShape() != PAD_SHAPE_OVAL ) swap_pads = true; break; case PAD_SHAPE_TRAPEZOID: case PAD_SHAPE_CHAMFERED_RECT: case PAD_SHAPE_CUSTOM: break; } } if( swap_pads ) { std::swap( aRefPad, aPad ); relativePadPos = -relativePadPos; } bool diag = true; if( ( aRefPad->GetShape() == PAD_SHAPE_CIRCLE || aRefPad->GetShape() == PAD_SHAPE_OVAL ) ) { /* Treat an oval pad as a line segment along the hole's major axis, * shortened by half its minor axis. * A circular pad is just a degenerate case of an oval hole. */ wxPoint refPadStart, refPadEnd; int refPadWidth; aRefPad->GetOblongGeometry( aRefPad->GetSize(), &refPadStart, &refPadEnd, &refPadWidth ); refPadStart += aRefPad->ShapePos(); refPadEnd += aRefPad->ShapePos(); SEG refPadSeg( refPadStart, refPadEnd ); diag = checkClearanceSegmToPad( refPadSeg, refPadWidth, aPad, aMinClearance, aActual ); } else { int dist_extra = 0; // corners of aRefPad (used only for rect/roundrect/trap pad) wxPoint polyref[4]; // corners of aRefPad (used only for custom pad) SHAPE_POLY_SET polysetref; if( aRefPad->GetShape() == PAD_SHAPE_ROUNDRECT ) { int padRadius = aRefPad->GetRoundRectCornerRadius(); dist_extra = padRadius; GetRoundRectCornerCenters( polyref, padRadius, wxPoint( 0, 0 ), aRefPad->GetSize(), aRefPad->GetOrientation() ); } else if( aRefPad->GetShape() == PAD_SHAPE_CHAMFERED_RECT ) { BOARD* board = aRefPad->GetBoard(); int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; // The reference pad can be rotated. Calculate the rotated coordinates. // (note, the ref pad position is the origin of coordinates for this drc test) int padRadius = aRefPad->GetRoundRectCornerRadius(); TransformRoundChamferedRectToPolygon( polysetref, wxPoint( 0, 0 ), aRefPad->GetSize(), aRefPad->GetOrientation(), padRadius, aRefPad->GetChamferRectRatio(), aRefPad->GetChamferPositions(), maxError ); } else if( aRefPad->GetShape() == PAD_SHAPE_CUSTOM ) { polysetref.Append( aRefPad->GetCustomShapeAsPolygon() ); // The reference pad can be rotated. Calculate the rotated coordinates. // (note, the ref pad position is the origin of coordinates for this drc test) aRefPad->CustomShapeAsPolygonToBoardPosition( &polysetref, wxPoint( 0, 0 ), aRefPad->GetOrientation() ); } else { // BuildPadPolygon has meaning for rect a trapeziod shapes and returns the 4 corners. aRefPad->BuildPadPolygon( polyref, wxSize( 0, 0 ), aRefPad->GetOrientation() ); } // corners of aPad (used only for rect/roundrect/trap pad) wxPoint polycompare[4]; // corners of aPad (used only custom pad) SHAPE_POLY_SET polysetcompare; switch( aPad->GetShape() ) { case PAD_SHAPE_ROUNDRECT: case PAD_SHAPE_RECT: case PAD_SHAPE_CHAMFERED_RECT: case PAD_SHAPE_TRAPEZOID: case PAD_SHAPE_CUSTOM: if( aPad->GetShape() == PAD_SHAPE_ROUNDRECT ) { int padRadius = aPad->GetRoundRectCornerRadius(); dist_extra = padRadius; GetRoundRectCornerCenters( polycompare, padRadius, relativePadPos, aPad->GetSize(), aPad->GetOrientation() ); } else if( aPad->GetShape() == PAD_SHAPE_CHAMFERED_RECT ) { BOARD* board = aRefPad->GetBoard(); int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; // The pad to compare can be rotated. Calculate the rotated coordinates. // ( note, the pad to compare position is the relativePadPos for this drc test) int padRadius = aPad->GetRoundRectCornerRadius(); TransformRoundChamferedRectToPolygon( polysetcompare, relativePadPos, aPad->GetSize(), aPad->GetOrientation(), padRadius, aPad->GetChamferRectRatio(), aPad->GetChamferPositions(), maxError ); } else if( aPad->GetShape() == PAD_SHAPE_CUSTOM ) { polysetcompare.Append( aPad->GetCustomShapeAsPolygon() ); // The pad to compare can be rotated. Calculate the rotated coordinates. // ( note, the pad to compare position is the relativePadPos for this drc test) aPad->CustomShapeAsPolygonToBoardPosition( &polysetcompare, relativePadPos, aPad->GetOrientation() ); } else { aPad->BuildPadPolygon( polycompare, wxSize( 0, 0 ), aPad->GetOrientation() ); // Move aPad shape to relativePadPos for( int ii = 0; ii < 4; ii++ ) polycompare[ii] += relativePadPos; } // And now test polygons: We have 3 cases: // one poly is complex and the other is basic (has only 4 corners) // both polys are complex // both polys are basic (have only 4 corners) the most usual case if( polysetref.OutlineCount() && polysetcompare.OutlineCount() == 0) { const SHAPE_LINE_CHAIN& refpoly = polysetref.COutline( 0 ); // And now test polygons: if( !poly2polyDRC( (wxPoint*) &refpoly.CPoint( 0 ), refpoly.PointCount(), polycompare, 4, aMinClearance + dist_extra, aActual ) ) { *aActual = std::max( 0, *aActual - dist_extra ); diag = false; } } else if( polysetref.OutlineCount() == 0 && polysetcompare.OutlineCount()) { const SHAPE_LINE_CHAIN& cmppoly = polysetcompare.COutline( 0 ); // And now test polygons: if( !poly2polyDRC((wxPoint*) &cmppoly.CPoint( 0 ), cmppoly.PointCount(), polyref, 4, aMinClearance + dist_extra, aActual ) ) { *aActual = std::max( 0, *aActual - dist_extra ); diag = false; } } else if( polysetref.OutlineCount() && polysetcompare.OutlineCount() ) { const SHAPE_LINE_CHAIN& refpoly = polysetref.COutline( 0 ); const SHAPE_LINE_CHAIN& cmppoly = polysetcompare.COutline( 0 ); // And now test polygons: if( !poly2polyDRC((wxPoint*) &refpoly.CPoint( 0 ), refpoly.PointCount(), (wxPoint*) &cmppoly.CPoint( 0 ), cmppoly.PointCount(), aMinClearance + dist_extra, aActual ) ) { *aActual = std::max( 0, *aActual - dist_extra ); diag = false; } } else { if( !poly2polyDRC( polyref, 4, polycompare, 4, aMinClearance + dist_extra, aActual ) ) { *aActual = std::max( 0, *aActual - dist_extra ); diag = false; } } break; default: wxLogDebug( wxT( "DRC::checkClearancePadToPad: unexpected pad shape %d" ), aPad->GetShape() ); break; } } return diag; } bool test::DRC_TEST_PROVIDER_CLEARANCE_BASE::poly2segmentDRC( wxPoint* aTref, int aTrefCount, wxPoint aSegStart, wxPoint aSegEnd, int aDist, int* aActual ) { /* Test if the segment is contained in the polygon. * This case is not covered by the following check if the segment is * completely contained in the polygon (because edges don't intersect)! */ if( TestPointInsidePolygon( aTref, aTrefCount, aSegStart ) ) { *aActual = 0; return false; } for( int ii = 0, jj = aTrefCount-1; ii < aTrefCount; jj = ii, ii++ ) { // for all edges in polygon double d; if( TestForIntersectionOfStraightLineSegments( aTref[ii].x, aTref[ii].y, aTref[jj].x, aTref[jj].y, aSegStart.x, aSegStart.y, aSegEnd.x, aSegEnd.y, NULL, NULL, &d ) ) { *aActual = 0; return false; } if( d < aDist ) { *aActual = KiROUND( d ); return false; } } return true; } /** * compare 2 convex polygons and return true if distance > aDist (if no error DRC) * i.e if for each edge of the first polygon distance from each edge of the other polygon * is >= aDist */ bool test::DRC_TEST_PROVIDER_CLEARANCE_BASE::poly2polyDRC( wxPoint* aTref, int aTrefCount, wxPoint* aTtest, int aTtestCount, int aAllowedDist, int* actualDist ) { /* Test if one polygon is contained in the other and thus the polygon overlap. * This case is not covered by the following check if one polygone is * completely contained in the other (because edges don't intersect)! */ if( TestPointInsidePolygon( aTref, aTrefCount, aTtest[0] ) ) { *actualDist = 0; return false; } if( TestPointInsidePolygon( aTtest, aTtestCount, aTref[0] ) ) { *actualDist = 0; return false; } for( int ii = 0, jj = aTrefCount - 1; ii < aTrefCount; jj = ii, ii++ ) { // for all edges in aTref for( int kk = 0, ll = aTtestCount - 1; kk < aTtestCount; ll = kk, kk++ ) { // for all edges in aTtest double d; int intersect = TestForIntersectionOfStraightLineSegments( aTref[ii].x, aTref[ii].y, aTref[jj].x, aTref[jj].y, aTtest[kk].x, aTtest[kk].y, aTtest[ll].x, aTtest[ll].y, nullptr, nullptr, &d ); if( intersect ) { *actualDist = 0; return false; } if( d < aAllowedDist ) { *actualDist = KiROUND( d ); return false; } } } return true; }