From 1cabc1bc0f0c159c04be2a304ff3a42ac2f1bf2e Mon Sep 17 00:00:00 2001 From: Tomasz Wlostowski Date: Fri, 19 Jun 2020 23:34:19 +0200 Subject: [PATCH] qa/drc_proto: rework common clearance code into base class, start working on hole clearance test refactor --- qa/drc_proto/CMakeLists.txt | 4 +- qa/drc_proto/drc_engine.cpp | 8 +- qa/drc_proto/drc_rule.cpp | 6 +- .../drc_test_provider_clearance_base.cpp | 469 ++++++++++++ .../drc_test_provider_clearance_base.h | 72 ++ .../drc_test_provider_copper_clearance.cpp | 669 +----------------- .../drc_test_provider_hole_clearance.cpp | 244 +++++++ 7 files changed, 823 insertions(+), 649 deletions(-) create mode 100644 qa/drc_proto/drc_test_provider_clearance_base.cpp create mode 100644 qa/drc_proto/drc_test_provider_clearance_base.h create mode 100644 qa/drc_proto/drc_test_provider_hole_clearance.cpp diff --git a/qa/drc_proto/CMakeLists.txt b/qa/drc_proto/CMakeLists.txt index 61d4944317..e065532215 100644 --- a/qa/drc_proto/CMakeLists.txt +++ b/qa/drc_proto/CMakeLists.txt @@ -40,7 +40,9 @@ add_executable( drc_proto drc_rule_parser.cpp drc_rules_proto_keywords.cpp drc_test_provider.cpp - drc_clearance_test.cpp + drc_test_provider_clearance_base.cpp + drc_test_provider_copper_clearance.cpp + drc_test_provider_hole_clearance.cpp drc_engine.cpp ../qa_utils/mocks.cpp ../pcbnew_utils/board_file_utils.cpp diff --git a/qa/drc_proto/drc_engine.cpp b/qa/drc_proto/drc_engine.cpp index 5fa75f7a63..12c74a92af 100644 --- a/qa/drc_proto/drc_engine.cpp +++ b/qa/drc_proto/drc_engine.cpp @@ -39,7 +39,9 @@ test::DRC_ENGINE::DRC_ENGINE( BOARD* aBoard, BOARD_DESIGN_SETTINGS *aSettings ) : m_board( aBoard ), - m_designSettings ( aSettings ) + m_designSettings ( aSettings ), + m_reporter( nullptr ), + m_progressReporter( nullptr ) { } @@ -109,7 +111,7 @@ void test::DRC_ENGINE::inferImplicitRules() } -static const int drc_debug_level = 0; +static const int drc_debug_level = 10; void test::drc_dbg( int level, const char* fmt, ... ) { @@ -128,7 +130,7 @@ void test::drc_dbg( int level, const char* fmt, ... ) bool test::DRC_ENGINE::CompileRules() { - ReportAux( wxString::Format( "Compiling Rules: " ) ); + ReportAux( wxString::Format( "Compiling Rules (%d rules, %d conditions): ", m_rules.size(), m_ruleConditions.size() ) ); for( auto provider : m_testProviders ) { diff --git a/qa/drc_proto/drc_rule.cpp b/qa/drc_proto/drc_rule.cpp index fa232c5002..5302d73767 100644 --- a/qa/drc_proto/drc_rule.cpp +++ b/qa/drc_proto/drc_rule.cpp @@ -44,13 +44,13 @@ test::DRC_RULE::~DRC_RULE() test::DRC_RULE_CONDITION::DRC_RULE_CONDITION() { - if( m_ucode ) - delete m_ucode; + m_ucode = nullptr; } test::DRC_RULE_CONDITION::~DRC_RULE_CONDITION() { - + if( m_ucode ) + delete m_ucode; } bool test::DRC_RULE_CONDITION::EvaluateFor( BOARD_ITEM* aItemA, BOARD_ITEM* aItemB ) diff --git a/qa/drc_proto/drc_test_provider_clearance_base.cpp b/qa/drc_proto/drc_test_provider_clearance_base.cpp new file mode 100644 index 0000000000..96aec7d954 --- /dev/null +++ b/qa/drc_proto/drc_test_provider_clearance_base.cpp @@ -0,0 +1,469 @@ +#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; +} diff --git a/qa/drc_proto/drc_test_provider_clearance_base.h b/qa/drc_proto/drc_test_provider_clearance_base.h new file mode 100644 index 0000000000..e98ed3136b --- /dev/null +++ b/qa/drc_proto/drc_test_provider_clearance_base.h @@ -0,0 +1,72 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef DRC_TEST_PROVIDER_CLEARANCE_BASE__H +#define DRC_TEST_PROVIDER_CLEARANCE_BASE__H + +#include + +#include + +namespace test { + +class DRC_TEST_PROVIDER_CLEARANCE_BASE : public DRC_TEST_PROVIDER +{ +public: + DRC_TEST_PROVIDER_CLEARANCE_BASE () : + DRC_TEST_PROVIDER() + { + + } + + virtual ~DRC_TEST_PROVIDER_CLEARANCE_BASE() + { + + } + +protected: + void testCopperDrawItem( BOARD_ITEM* aItem ); + + bool doPadToPadsDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, int x_limit ); + + bool checkClearanceSegmToPad( const SEG& refSeg, int refSegWidth, const D_PAD* pad, + int minClearance, int* aActualDist ); + bool checkClearancePadToPad( D_PAD* aRefPad, D_PAD* aPad, int aMinClearance, int* aActual ); + bool poly2segmentDRC( wxPoint* aTref, int aTrefCount, wxPoint aSegStart, wxPoint aSegEnd, + int aDist, int* aActual ); + bool poly2polyDRC( wxPoint* aTref, int aTrefCount, wxPoint* aTtest, int aTtestCount, + int aAllowedDist, int* actualDist ); + + wxPoint getLocation( TRACK* aTrack, const SEG& aConflictSeg ); + wxPoint getLocation( TRACK* aTrack, ZONE_CONTAINER* aConflictZone ); + + BOARD* m_board; + int m_largestClearance; + SHAPE_POLY_SET m_boardOutline; // The board outline including cutouts + bool m_boardOutlineValid; +}; + +}; + +#endif // DRC_TEST_PROVIDER_CLEARANCE_BASE__H diff --git a/qa/drc_proto/drc_test_provider_copper_clearance.cpp b/qa/drc_proto/drc_test_provider_copper_clearance.cpp index 5c7a8330cf..036d82c73c 100644 --- a/qa/drc_proto/drc_test_provider_copper_clearance.cpp +++ b/qa/drc_proto/drc_test_provider_copper_clearance.cpp @@ -11,30 +11,36 @@ #include #include -#include #include #include +#include namespace test { -class DRC_TEST_PROVIDER_CLEARANCE : public DRC_TEST_PROVIDER +class DRC_TEST_PROVIDER_COPPER_CLEARANCE : public DRC_TEST_PROVIDER_CLEARANCE_BASE { public: - DRC_TEST_PROVIDER_CLEARANCE () : - DRC_TEST_PROVIDER() + DRC_TEST_PROVIDER_COPPER_CLEARANCE () : + DRC_TEST_PROVIDER_CLEARANCE_BASE() { - } - virtual ~DRC_TEST_PROVIDER_CLEARANCE() + virtual ~DRC_TEST_PROVIDER_COPPER_CLEARANCE() { - } virtual bool Run() override; - virtual const wxString GetName() const override { return "clearance"; }; - virtual const wxString GetDescription() const override { return "Tests copper item clearance"; } + virtual const wxString GetName() const override + { + return "clearance"; + }; + + virtual const wxString GetDescription() const override + { + return "Tests copper item clearance"; + } + virtual std::set GetMatchingRuleIds() const override; private: @@ -42,88 +48,18 @@ private: void testTrackClearances(); void testCopperTextAndGraphics(); void testZones(); - void testCopperDrawItem( BOARD_ITEM* aItem ); void doTrackDrc( TRACK* aRefSeg, TRACKS::iterator aStartIt, TRACKS::iterator aEndIt, bool aTestZones ); bool doPadToPadsDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, int x_limit ); - - bool checkClearanceSegmToPad( const SEG& refSeg, int refSegWidth, const D_PAD* pad, - int minClearance, int* aActualDist ); - bool checkClearancePadToPad( D_PAD* aRefPad, D_PAD* aPad, int aMinClearance, int* aActual ); - bool poly2segmentDRC( wxPoint* aTref, int aTrefCount, wxPoint aSegStart, wxPoint aSegEnd, - int aDist, int* aActual ); - bool poly2polyDRC( wxPoint* aTref, int aTrefCount, wxPoint* aTtest, int aTtestCount, - int aAllowedDist, int* actualDist ); - - wxPoint getLocation( TRACK* aTrack, const SEG& aConflictSeg ); - wxPoint getLocation( TRACK* aTrack, ZONE_CONTAINER* aConflictZone ); - - BOARD* m_board; - int m_largestClearance; - SHAPE_POLY_SET m_boardOutline; // The board outline including cutouts - bool m_boardOutlineValid; }; }; -const int UI_EPSILON = Mils2iu( 5 ); -wxPoint test::DRC_TEST_PROVIDER_CLEARANCE::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::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; -} - - -bool test::DRC_TEST_PROVIDER_CLEARANCE::Run() +bool test::DRC_TEST_PROVIDER_COPPER_CLEARANCE::Run() { auto bds = m_drcEngine->GetDesignSettings(); - m_board = m_drcEngine->GetBoard(); m_largestClearance = bds->GetBiggestClearanceValue(); @@ -139,7 +75,7 @@ bool test::DRC_TEST_PROVIDER_CLEARANCE::Run() return true; } -void test::DRC_TEST_PROVIDER_CLEARANCE::testCopperTextAndGraphics() +void test::DRC_TEST_PROVIDER_COPPER_CLEARANCE::testCopperTextAndGraphics() { // Test copper items for clearance violations with vias, tracks and pads @@ -177,7 +113,7 @@ void test::DRC_TEST_PROVIDER_CLEARANCE::testCopperTextAndGraphics() } -void test::DRC_TEST_PROVIDER_CLEARANCE::testCopperDrawItem( BOARD_ITEM* aItem ) +void test::DRC_TEST_PROVIDER_COPPER_CLEARANCE::testCopperDrawItem( BOARD_ITEM* aItem ) { EDA_RECT bbox; std::vector itemShape; @@ -388,7 +324,7 @@ void test::DRC_TEST_PROVIDER_CLEARANCE::testCopperDrawItem( BOARD_ITEM* aItem ) } -void test::DRC_TEST_PROVIDER_CLEARANCE::testTrackClearances() +void test::DRC_TEST_PROVIDER_COPPER_CLEARANCE::testTrackClearances() { const int delta = 500; // This is the number of tests between 2 calls to the // progress bar @@ -416,7 +352,7 @@ void test::DRC_TEST_PROVIDER_CLEARANCE::testTrackClearances() } } -void test::DRC_TEST_PROVIDER_CLEARANCE::doTrackDrc( TRACK* aRefSeg, TRACKS::iterator aStartIt, +void test::DRC_TEST_PROVIDER_COPPER_CLEARANCE::doTrackDrc( TRACK* aRefSeg, TRACKS::iterator aStartIt, TRACKS::iterator aEndIt, bool aTestZones ) { BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); @@ -651,7 +587,7 @@ void test::DRC_TEST_PROVIDER_CLEARANCE::doTrackDrc( TRACK* aRefSeg, TRACKS::iter } -void test::DRC_TEST_PROVIDER_CLEARANCE::testPadClearances( ) +void test::DRC_TEST_PROVIDER_COPPER_CLEARANCE::testPadClearances( ) { auto bds = m_drcEngine->GetDesignSettings(); std::vector sortedPads; @@ -692,59 +628,13 @@ void test::DRC_TEST_PROVIDER_CLEARANCE::testPadClearances( ) ReportProgress( (double) ii / (double) sortedPads.size() ); ii++; -#if 0 - // fixme: move Board outline clearance to separate provider - if( m_boardOutlineValid ) - { - //int minClearance = bds->m_CopperEdgeClearance; - //m_clearanceSource = _( "board edge" ); + int x_limit = pad->GetPosition().x + pad->GetBoundingRadius() + max_size; - static DRAWSEGMENT dummyEdge; - dummyEdge.SetLayer( Edge_Cuts ); - - //if( pad->GetRuleClearance( &dummyEdge, &minClearance, &m_clearanceSource ) ) - // /* minClearance and m_clearanceSource set in GetRuleClearance() */; - // FIXME - // auto rule = m_drcEngine->EvalRuleForItems( pad, dummyEdge, DRC_RULE_CLEARANCE ); - // min_clearance = rule->Constraint().Min(); - int minClearance; - - for( auto it = m_boardOutline.IterateSegmentsWithHoles(); it; it++ ) - { - int actual; - - if( !checkClearanceSegmToPad( *it, 0, pad, minClearance, &actual ) ) - { - actual = std::max( 0, actual ); - DRC_ITEM* drcItem = new DRC_ITEM( DRCE_PAD_NEAR_EDGE ); - wxString msg; - - msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ), - /*m_clearanceSource FIXME*/ "" , - MessageTextFromValue( userUnits(), minClearance, true ), - MessageTextFromValue( userUnits(), actual, true ) ); - - drcItem->SetErrorMessage( msg ); - drcItem->SetItems( pad ); - - MARKER_PCB* marker = nullptr; // fixme new MARKER_PCB( drcItem, pad->GetPosition() ); - AddMarkerToPcb( marker ); - - break; - } - } - } -#endif - //if( !bds->Ignore( DRCE_PAD_NEAR_PAD ) || !bds->Ignore( DRCE_HOLE_NEAR_PAD ) ) - { - int x_limit = pad->GetPosition().x + pad->GetBoundingRadius() + max_size; - - doPadToPadsDrc( pad, &pad, listEnd, x_limit ); - } + doPadToPadsDrc( pad, &pad, listEnd, x_limit ); } } -bool test::DRC_TEST_PROVIDER_CLEARANCE::doPadToPadsDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, +bool test::DRC_TEST_PROVIDER_COPPER_CLEARANCE::doPadToPadsDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, int x_limit ) { const static LSET all_cu = LSET::AllCuMask(); @@ -773,109 +663,6 @@ bool test::DRC_TEST_PROVIDER_CLEARANCE::doPadToPadsDrc( D_PAD* aRefPad, D_PAD** if( pad->GetPosition().x > x_limit ) break; -#if 0 - -// fixme move hole clearance check to another provider - - // No problem if pads which are on copper layers are on different copper layers, - // (pads can be p on a technical layer, to build complex pads) - // but their hole (if any ) can create DRC error because they are on all - // copper layers, so we test them - if( ( pad->GetLayerSet() & layerMask ) == 0 && - ( pad->GetLayerSet() & all_cu ) != 0 && - ( aRefPad->GetLayerSet() & all_cu ) != 0 ) - { - // if holes are in the same location and have the same size and shape, - // this can be accepted - if( pad->GetPosition() == aRefPad->GetPosition() - && pad->GetDrillSize() == aRefPad->GetDrillSize() - && pad->GetDrillShape() == aRefPad->GetDrillShape() ) - { - if( aRefPad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE ) - continue; - - // for oval holes: must also have the same orientation - if( pad->GetOrientation() == aRefPad->GetOrientation() ) - continue; - } - - - // fixme move hole clearance check to another providers - - /* Here, we must test clearance between holes and pads - * dummy pad size and shape is adjusted to pad drill size and shape - */ - if( pad->GetDrillSize().x ) - { - // pad under testing has a hole, test this hole against pad reference - dummypad.SetPosition( pad->GetPosition() ); - dummypad.SetSize( pad->GetDrillSize() ); - dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? - PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); - dummypad.SetOrientation( pad->GetOrientation() ); - - int minClearance = 0; // fixme aRefPad->GetClearance( nullptr, &m_clearanceSource ); - - auto rule = m_drcEngine->MatchRulesForItems( DRC_RULE_ID_CLEARANCE, pad, &dummypad ); - int minClearance = rule->m_Value.Min(); - - int actual; - - if( !checkClearancePadToPad( aRefPad, &dummypad, minClearance, &actual ) ) - { - DRC_ITEM* drcItem = new DRC_ITEM( DRCE_HOLE_NEAR_PAD ); - wxString msg; - - msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ), - /* fixme m_clearanceSource */"", - MessageTextFromValue( userUnits(), minClearance, true ), - MessageTextFromValue( userUnits(), actual, true ) ); - - drcItem->SetErrorMessage( msg ); - drcItem->SetItems( pad, aRefPad ); - - MARKER_PCB* marker = nullptr; // fixme new MARKER_PCB( drcItem, pad->GetPosition() ); - AddMarkerToPcb( marker ); - return false; - } - } - - if( aRefPad->GetDrillSize().x ) // pad reference has a hole - { - dummypad.SetPosition( aRefPad->GetPosition() ); - dummypad.SetSize( aRefPad->GetDrillSize() ); - dummypad.SetShape( aRefPad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? - PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); - dummypad.SetOrientation( aRefPad->GetOrientation() ); - - // FIXME min_clearance = rule->Constraint().Min(); - //int minClearance = pad->GetClearance( nullptr, &m_clearanceSource ); - int minClearance; - int actual; - - if( !checkClearancePadToPad( pad, &dummypad, minClearance, &actual ) ) - { - DRC_ITEM* drcItem = new DRC_ITEM( DRCE_HOLE_NEAR_PAD ); - wxString msg; - - msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ), - /*m_clearanceSource FIXME */ "", - MessageTextFromValue( userUnits(), minClearance, true ), - MessageTextFromValue( userUnits(), actual, true ) ); - - drcItem->SetErrorMessage( msg ); - drcItem->SetItems( aRefPad, pad ); - - MARKER_PCB* marker = nullptr; // fixme new MARKER_PCB( drcItem, aRefPad->GetPosition() ); - AddMarkerToPcb( marker ); - return false; - } - } - - continue; - } -#endif - // The pad must be in a net (i.e pt_pad->GetNet() != 0 ), // But no problem if pads have the same netcode (same net) if( pad->GetNetCode() && ( aRefPad->GetNetCode() == pad->GetNetCode() ) ) @@ -930,409 +717,7 @@ bool test::DRC_TEST_PROVIDER_CLEARANCE::doPadToPadsDrc( D_PAD* aRefPad, D_PAD** } -/* - * 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::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::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::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::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; -} - - - -void test::DRC_TEST_PROVIDER_CLEARANCE::testZones() +void test::DRC_TEST_PROVIDER_COPPER_CLEARANCE::testZones() { // Test copper areas for valid netcodes -> fixme, goes to connectivity checks @@ -1502,7 +887,7 @@ void test::DRC_TEST_PROVIDER_CLEARANCE::testZones() } -std::set test::DRC_TEST_PROVIDER_CLEARANCE::GetMatchingRuleIds() const +std::set test::DRC_TEST_PROVIDER_COPPER_CLEARANCE::GetMatchingRuleIds() const { return { DRC_RULE_ID_T::DRC_RULE_ID_CLEARANCE }; } @@ -1510,5 +895,5 @@ std::set test::DRC_TEST_PROVIDER_CLEARANCE::GetMatchingRule namespace detail { - static test::DRC_REGISTER_TEST_PROVIDER dummy; + //static test::DRC_REGISTER_TEST_PROVIDER dummy; } \ No newline at end of file diff --git a/qa/drc_proto/drc_test_provider_hole_clearance.cpp b/qa/drc_proto/drc_test_provider_hole_clearance.cpp new file mode 100644 index 0000000000..4712c9214d --- /dev/null +++ b/qa/drc_proto/drc_test_provider_hole_clearance.cpp @@ -0,0 +1,244 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace test { + +class DRC_TEST_PROVIDER_HOLE_CLEARANCE : public DRC_TEST_PROVIDER_CLEARANCE_BASE +{ +public: + DRC_TEST_PROVIDER_HOLE_CLEARANCE () : + DRC_TEST_PROVIDER_CLEARANCE_BASE() + { + } + + virtual ~DRC_TEST_PROVIDER_HOLE_CLEARANCE() + { + } + + virtual bool Run() override; + + virtual const wxString GetName() const override + { + return "hole_clearance"; + }; + + virtual const wxString GetDescription() const override + { + return "Tests clearance of holes (via/pad drills)"; + } + + virtual std::set GetMatchingRuleIds() const override; + +private: + void testPadHoles(); + bool doPadToPadHoleDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, int x_limit ); + +}; + +}; + + +bool test::DRC_TEST_PROVIDER_HOLE_CLEARANCE::Run() +{ + auto bds = m_drcEngine->GetDesignSettings(); + m_board = m_drcEngine->GetBoard(); + m_largestClearance = bds->GetBiggestClearanceValue(); + + ReportStage( ("Testing pad/hole clearances"), 0, 2 ); + testPadHoles(); + + return true; +} + +void test::DRC_TEST_PROVIDER_HOLE_CLEARANCE::testPadHoles() +{ + std::vector sortedPads; + + m_board->GetSortedPadListByXthenYCoord( sortedPads ); + + if( sortedPads.empty() ) + return; + + // find the max size of the pads (used to stop the pad-to-pad tests) + int max_size = 0; + + for( D_PAD* pad : sortedPads ) + { + // GetBoundingRadius() is the radius of the minimum sized circle fully containing the pad + int radius = pad->GetBoundingRadius(); + + if( radius > max_size ) + max_size = radius; + } + + // Better to be fast than accurate; this keeps us from having to look up / calculate the + // actual clearances + max_size += m_largestClearance; + + // Upper limit of pad list (limit not included) + D_PAD** listEnd = &sortedPads[0] + sortedPads.size(); + + // Test the pads + for( auto& pad : sortedPads ) + { + int x_limit = pad->GetPosition().x + pad->GetBoundingRadius() + max_size; + drc_dbg(4,"-> %p\n", pad); + doPadToPadHoleDrc( pad, &pad, listEnd, x_limit ); + } +} + + +bool test::DRC_TEST_PROVIDER_HOLE_CLEARANCE::doPadToPadHoleDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, + int x_limit ) +{ + const static LSET all_cu = LSET::AllCuMask(); + + LSET layerMask = aRefPad->GetLayerSet() & all_cu; + + // For hole testing we use a dummy pad which is given the shape of the hole. Note that + // this pad must have a parent because some functions expect a non-null parent to find + // the pad's board. + MODULE dummymodule( m_board ); // Creates a dummy parent + D_PAD dummypad( &dummymodule ); + + // Ensure the hole is on all copper layers + dummypad.SetLayerSet( all_cu | dummypad.GetLayerSet() ); + + for( D_PAD** pad_list = aStart; pad_list %p\n", pad); + + // We can stop the test when pad->GetPosition().x > x_limit + // because the list is sorted by X values + if( pad->GetPosition().x > x_limit ) + break; + + drc_dbg(4," chk2 against -> %p ds %d %d\n", pad, pad->GetDrillSize().x, aRefPad->GetDrillSize().x ); + + // No problem if pads which are on copper layers are on different copper layers, + // (pads can be only on a technical layer, to build complex pads) + // but their hole (if any ) can create DRC error because they are on all + // copper layers, so we test them + if( ( pad->GetLayerSet() & layerMask ) == 0 && + ( pad->GetLayerSet() & all_cu ) != 0 && + ( aRefPad->GetLayerSet() & all_cu ) != 0 ) + { + drc_dbg(4," chk3 against -> %p\n", pad); + + // if holes are in the same location and have the same size and shape, + // this can be accepted + if( pad->GetPosition() == aRefPad->GetPosition() + && pad->GetDrillSize() == aRefPad->GetDrillSize() + && pad->GetDrillShape() == aRefPad->GetDrillShape() ) + { + if( aRefPad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE ) + continue; + + // for oval holes: must also have the same orientation + if( pad->GetOrientation() == aRefPad->GetOrientation() ) + continue; + } + + /* Here, we must test clearance between holes and pads + * dummy pad size and shape is adjusted to pad drill size and shape + */ + if( pad->GetDrillSize().x ) + { + drc_dbg(4,"check pad %p\n", pad ); + // pad under testing has a hole, test this hole against pad reference + dummypad.SetPosition( pad->GetPosition() ); + dummypad.SetSize( pad->GetDrillSize() ); + dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? + PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); + dummypad.SetOrientation( pad->GetOrientation() ); + + auto rule = m_drcEngine->EvalRulesForItems( test::DRC_RULE_ID_T::DRC_RULE_ID_HOLE_CLEARANCE, aRefPad, &dummypad ); + auto minClearance = rule->GetConstraint().GetValue().Min(); + int actual; + + if( !checkClearancePadToPad( aRefPad, &dummypad, minClearance, &actual ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_HOLE_NEAR_PAD ); + + wxString msg; + + msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ), + "", + MessageTextFromValue( userUnits(), minClearance, true ), + MessageTextFromValue( userUnits(), actual, true ) ); + + drcItem->SetErrorMessage( msg ); + drcItem->SetItems( pad, aRefPad ); + drcItem->SetViolatingRule( rule ); + + ReportWithMarker( drcItem, pad->GetPosition() ); + return false; + } + } + + if( aRefPad->GetDrillSize().x ) // pad reference has a hole + { + drc_dbg(4,"check refpad %p\n", pad ); + dummypad.SetPosition( aRefPad->GetPosition() ); + dummypad.SetSize( aRefPad->GetDrillSize() ); + dummypad.SetShape( aRefPad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? + PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); + dummypad.SetOrientation( aRefPad->GetOrientation() ); + + auto rule = m_drcEngine->EvalRulesForItems( test::DRC_RULE_ID_T::DRC_RULE_ID_HOLE_CLEARANCE, aRefPad, &dummypad ); + auto minClearance = rule->GetConstraint().GetValue().Min(); + int actual; + + if( !checkClearancePadToPad( pad, &dummypad, minClearance, &actual ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_HOLE_NEAR_PAD ); + wxString msg; + + msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ), + "", + MessageTextFromValue( userUnits(), minClearance, true ), + MessageTextFromValue( userUnits(), actual, true ) ); + + drcItem->SetErrorMessage( msg ); + drcItem->SetItems( aRefPad, pad ); + drcItem->SetViolatingRule( rule ); + + ReportWithMarker( drcItem, pad->GetPosition() ); + return false; + } + } + } + } + + return true; +} + +std::set test::DRC_TEST_PROVIDER_HOLE_CLEARANCE::GetMatchingRuleIds() const +{ + return { DRC_RULE_ID_T::DRC_RULE_ID_HOLE_CLEARANCE }; +} + + +namespace detail +{ + static test::DRC_REGISTER_TEST_PROVIDER dummy; +} \ No newline at end of file