From 26765161c1c0693d3a490a84004ab00ef68013d2 Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Wed, 28 Nov 2018 05:11:43 -0700 Subject: [PATCH] drc: Add board outline and edge crossing This adds a check for contiguous board outlines to the DRC. It also uses the calculated outline to ensure that traces are not crossing the outlines. Fixes: lp:1648055 * https://bugs.launchpad.net/kicad/+bug/1648055 --- common/geometry/seg.cpp | 33 +++++++++++++++++++ include/geometry/seg.h | 10 ++++-- pcbnew/class_board.cpp | 16 ++++++--- pcbnew/class_board.h | 9 +++-- pcbnew/class_module.cpp | 2 +- .../convert_drawsegment_list_to_polygon.cpp | 19 +++++++++-- pcbnew/dialogs/dialog_export_step.cpp | 3 +- pcbnew/drc.cpp | 20 ++++++++++- pcbnew/drc.h | 9 +++++ pcbnew/drc_clearance_test_functions.cpp | 26 +++++++++++++++ pcbnew/drc_item.cpp | 4 +++ 11 files changed, 135 insertions(+), 16 deletions(-) diff --git a/common/geometry/seg.cpp b/common/geometry/seg.cpp index f1796b64cf..a0f8a56405 100644 --- a/common/geometry/seg.cpp +++ b/common/geometry/seg.cpp @@ -96,6 +96,39 @@ SEG::ecoord SEG::SquaredDistance( const SEG& aSeg ) const } +const VECTOR2I SEG::NearestPoint( const SEG& aSeg ) const +{ + if( auto p = Intersect( aSeg ) ) + return *p; + + const VECTOR2I pts_origin[4] = + { + aSeg.NearestPoint( A ), + aSeg.NearestPoint( B ), + NearestPoint( aSeg.A ), + NearestPoint( aSeg.B ) + }; + + const ecoord pts_dist[4] = + { + ( pts_origin[0] - A ).SquaredEuclideanNorm(), + ( pts_origin[1] - B ).SquaredEuclideanNorm(), + ( pts_origin[2] - aSeg.A ).SquaredEuclideanNorm(), + ( pts_origin[3] - aSeg.B ).SquaredEuclideanNorm() + }; + + int min_i = 0; + + for( int i = 0; i < 4; i++ ) + { + if( pts_dist[i] < pts_dist[min_i] ) + min_i = i; + } + + return pts_origin[min_i]; +} + + OPT_VECTOR2I SEG::Intersect( const SEG& aSeg, bool aIgnoreEndpoints, bool aLines ) const { const VECTOR2I e( B - A ); diff --git a/include/geometry/seg.h b/include/geometry/seg.h index 792b00c663..ece5511384 100644 --- a/include/geometry/seg.h +++ b/include/geometry/seg.h @@ -35,10 +35,8 @@ typedef OPT OPT_VECTOR2I; class SEG { -private: - typedef VECTOR2I::extended_type ecoord; - public: + using ecoord = VECTOR2I::extended_type; friend inline std::ostream& operator<<( std::ostream& aStream, const SEG& aSeg ); /* Start and the of the segment. Public, to make access simpler. @@ -156,6 +154,12 @@ public: */ const VECTOR2I NearestPoint( const VECTOR2I &aP ) const; + /** + * Computes a point on the segment (this) that is closest to any point on aSeg. + * @return the nearest point + */ + const VECTOR2I NearestPoint( const SEG &aSeg ) const; + /** * Function Intersect() * diff --git a/pcbnew/class_board.cpp b/pcbnew/class_board.cpp index 05c219008f..66b8488b97 100644 --- a/pcbnew/class_board.cpp +++ b/pcbnew/class_board.cpp @@ -171,8 +171,6 @@ void BOARD::BuildConnectivity() const wxPoint BOARD::GetPosition() const { - wxLogWarning( wxT( "This should not be called on the BOARD object") ); - return ZeroOffset; } @@ -1021,6 +1019,12 @@ void BOARD::Remove( BOARD_ITEM* aBoardItem ) } +wxString BOARD::GetSelectMenuText( EDA_UNITS_T aUnits ) const +{ + return wxString::Format( _( "PCB" ) ); +} + + void BOARD::DeleteMARKERs() { // the vector does not know how to delete the MARKER_PCB, it holds pointers @@ -2949,12 +2953,14 @@ BOARD_ITEM* BOARD::Duplicate( const BOARD_ITEM* aItem, * return true if success, false if a contour is not valid */ extern bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, - wxString* aErrorText, unsigned int aTolerance ); + wxString* aErrorText, unsigned int aTolerance, + wxPoint* aErrorLocation = nullptr ); -bool BOARD::GetBoardPolygonOutlines( SHAPE_POLY_SET& aOutlines, wxString* aErrorText ) +bool BOARD::GetBoardPolygonOutlines( SHAPE_POLY_SET& aOutlines, wxString* aErrorText, wxPoint* aErrorLocation ) { - bool success = BuildBoardPolygonOutlines( this, aOutlines, aErrorText, Millimeter2iu( 0.05 ) ); + bool success = BuildBoardPolygonOutlines( this, aOutlines, aErrorText, + Millimeter2iu( 0.05 ), aErrorLocation ); // Make polygon strictly simple to avoid issues (especially in 3D viewer) aOutlines.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); diff --git a/pcbnew/class_board.h b/pcbnew/class_board.h index 7074abf497..0761556399 100644 --- a/pcbnew/class_board.h +++ b/pcbnew/class_board.h @@ -562,6 +562,8 @@ public: const ZONE_SETTINGS& GetZoneSettings() const { return m_zoneSettings; } void SetZoneSettings( const ZONE_SETTINGS& aSettings ) { m_zoneSettings = aSettings; } + wxString GetSelectMenuText( EDA_UNITS_T aUnits ) const override; + /** * Function GetColorSettings * @return the current COLORS_DESIGN_SETTINGS in use @@ -585,11 +587,14 @@ public: * @param aOutlines The SHAPE_POLY_SET to fill in with outlines/holes. * @param aErrorText = a wxString reference to display an error message * with the coordinate of the point which creates the error - * (default = NULL , no message returned on error) + * (default = nullptr , no message returned on error) + * @param aErrorLocation = a wxPoint giving the location of the Error message on the board + * if left null (default), no location is returned + * * @return true if success, false if a contour is not valid */ bool GetBoardPolygonOutlines( SHAPE_POLY_SET& aOutlines, - wxString* aErrorText = NULL ); + wxString* aErrorText = nullptr, wxPoint* aErrorLocation = nullptr ); /** * Function ConvertBrdLayerToPolygonalContours diff --git a/pcbnew/class_module.cpp b/pcbnew/class_module.cpp index 9f658e60e9..17a1b5b739 100644 --- a/pcbnew/class_module.cpp +++ b/pcbnew/class_module.cpp @@ -1445,7 +1445,7 @@ double MODULE::CoverageRatio( const GENERAL_COLLECTOR& aCollector ) const // see convert_drawsegment_list_to_polygon.cpp: extern bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& aPolygons, - wxString* aErrorText, unsigned int aTolerance ); + wxString* aErrorText, unsigned int aTolerance, wxPoint* aErrorLocation = nullptr ); bool MODULE::BuildPolyCourtyard() { diff --git a/pcbnew/convert_drawsegment_list_to_polygon.cpp b/pcbnew/convert_drawsegment_list_to_polygon.cpp index d39904cb30..ac73a27b97 100644 --- a/pcbnew/convert_drawsegment_list_to_polygon.cpp +++ b/pcbnew/convert_drawsegment_list_to_polygon.cpp @@ -177,9 +177,10 @@ static DRAWSEGMENT* findPoint( const wxPoint& aPoint, std::vector< DRAWSEGMENT* * @param aPolygons will contain the complex polygon. * @param aTolerance is the max distance between points that is still accepted as connected (internal units) * @param aErrorText is a wxString to return error message. + * @param aErrorLocation is the optional position of the error in the outline */ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& aPolygons, - wxString* aErrorText, unsigned int aTolerance ) + wxString* aErrorText, unsigned int aTolerance, wxPoint* aErrorLocation ) { if( aSegList.size() == 0 ) return true; @@ -415,6 +416,9 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SE *aErrorText << msg << "\n"; } + if( aErrorLocation ) + *aErrorLocation = graphic->GetPosition(); + return false; } @@ -443,6 +447,9 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SE *aErrorText << msg << "\n"; } + if( aErrorLocation ) + *aErrorLocation = prevPt; + return false; } break; @@ -594,6 +601,9 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SE *aErrorText << msg << "\n"; } + if( aErrorLocation ) + *aErrorLocation = graphic->GetPosition(); + return false; } @@ -622,6 +632,9 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SE *aErrorText << msg << "\n"; } + if( aErrorLocation ) + *aErrorLocation = prevPt; + return false; } break; @@ -641,7 +654,7 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SE * All contours should be closed, i.e. valid closed polygon vertices */ bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, - wxString* aErrorText, unsigned int aTolerance ) + wxString* aErrorText, unsigned int aTolerance, wxPoint* aErrorLocation ) { PCB_TYPE_COLLECTOR items; @@ -659,7 +672,7 @@ bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, segList.push_back( static_cast< DRAWSEGMENT* >( items[ii] ) ); } - bool success = ConvertOutlineToPolygon( segList, aOutlines, aErrorText, aTolerance ); + bool success = ConvertOutlineToPolygon( segList, aOutlines, aErrorText, aTolerance, aErrorLocation ); if( !success || !aOutlines.OutlineCount() ) { diff --git a/pcbnew/dialogs/dialog_export_step.cpp b/pcbnew/dialogs/dialog_export_step.cpp index e198ccd9d9..550fdf4a6b 100644 --- a/pcbnew/dialogs/dialog_export_step.cpp +++ b/pcbnew/dialogs/dialog_export_step.cpp @@ -221,7 +221,8 @@ void DIALOG_EXPORT_STEP::onUpdateYPos( wxUpdateUIEvent& aEvent ) } extern bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, - wxString* aErrorText, unsigned int aTolerance ); + wxString* aErrorText, unsigned int aTolerance, + wxPoint* aErrorLocation = nullptr ); void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent ) { diff --git a/pcbnew/drc.cpp b/pcbnew/drc.cpp index 2c3e8be896..e0aced286e 100644 --- a/pcbnew/drc.cpp +++ b/pcbnew/drc.cpp @@ -393,8 +393,15 @@ void DRC::RunTests( wxTextCtrl* aMessages ) // ( the board can be reloaded ) m_pcb = m_pcbEditorFrame->GetBoard(); - // someone should have cleared the two lists before calling this. + if( aMessages ) + { + aMessages->AppendText( _( "Board Outline...\n" ) ); + wxSafeYield(); + } + testOutline(); + + // someone should have cleared the two lists before calling this. if( !testNetClasses() ) { // testing the netclasses is a special case because if the netclasses @@ -1165,6 +1172,17 @@ void DRC::testCopperTextItem( BOARD_ITEM* aTextItem ) } +void DRC::testOutline() +{ + wxPoint error_loc( m_pcb->GetBoardEdgesBoundingBox().GetPosition() ); + if( !m_pcb->GetBoardPolygonOutlines( m_board_outlines, nullptr, &error_loc ) ) + { + addMarkerToPcb( newMarker( error_loc, m_pcb, DRCE_INVALID_OUTLINE ) ); + return; + } +} + + void DRC::testDisabledLayers() { BOARD* board = m_pcbEditorFrame->GetBoard(); diff --git a/pcbnew/drc.h b/pcbnew/drc.h index ffd378de62..b20600c9c4 100644 --- a/pcbnew/drc.h +++ b/pcbnew/drc.h @@ -32,6 +32,7 @@ #include #include #include +#include #define OK_DRC 0 #define BAD_DRC 1 @@ -94,6 +95,8 @@ #define DRCE_BURIED_VIA_NOT_ALLOWED 49 ///< buried vias are not allowed #define DRCE_DISABLED_LAYER_ITEM 50 ///< item on a disabled layer #define DRCE_DRILLED_HOLES_TOO_CLOSE 51 ///< overlapping drilled holes break drill bits +#define DRCE_TRACK_NEAR_EDGE 53 ///< track too close to board edge +#define DRCE_INVALID_OUTLINE 54 ///< invalid board outline class EDA_DRAW_PANEL; @@ -220,6 +223,7 @@ private: PCB_EDIT_FRAME* m_pcbEditorFrame; ///< The pcb frame editor which owns the board BOARD* m_pcb; + SHAPE_POLY_SET m_board_outlines; ///< The board outline including cutouts DIALOG_DRC_CONTROL* m_drcDialog; DRC_LIST m_unconnected; ///< list of unconnected pads, as DRC_ITEMs @@ -314,6 +318,11 @@ private: ///> Tests for items placed on disabled layers (causing false connections). void testDisabledLayers(); + /** + * Test that the board outline is contiguous and composed of valid elements + */ + void testOutline(); + //---------------------------------------------- bool doNetClass( const std::shared_ptr& aNetClass, wxString& msg ); diff --git a/pcbnew/drc_clearance_test_functions.cpp b/pcbnew/drc_clearance_test_functions.cpp index aac4a5387a..41540f31af 100644 --- a/pcbnew/drc_clearance_test_functions.cpp +++ b/pcbnew/drc_clearance_test_functions.cpp @@ -704,6 +704,32 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads ) addMarkerToPcb( newMarker( aRefSeg, zone, DRCE_TRACK_NEAR_ZONE ) ); } + /***********************************************/ + /* Phase 4: test DRC with to board edge */ + /***********************************************/ + { + SEG test_seg( aRefSeg->GetStart(), aRefSeg->GetEnd() ); + + // the minimum distance = clearance plus half the reference track + // width. Board edges do not have width or clearance values, so we + // look for simple crossing. + SEG::ecoord w_dist = aRefSeg->GetClearance() + aRefSeg->GetWidth() / 2; + w_dist *= w_dist; + + for( auto it = m_board_outlines.IterateSegmentsWithHoles(); it; it++ ) + { + if( test_seg.SquaredDistance( *it ) < w_dist ) + { + auto pt = test_seg.NearestPoint( *it ); + markers.push_back( newMarker( wxPoint( pt.x, pt.y ), aRefSeg, DRCE_TRACK_NEAR_EDGE ) ); + + if( !handleNewMarker() ) + return false; + } + } + } + + if( markers.size() > 0 ) { commitMarkers(); diff --git a/pcbnew/drc_item.cpp b/pcbnew/drc_item.cpp index 6c71010476..1b5f0adb75 100644 --- a/pcbnew/drc_item.cpp +++ b/pcbnew/drc_item.cpp @@ -101,6 +101,10 @@ wxString DRC_ITEM::GetErrorText() const return wxString( _( "Micro via drill too small" ) ); case DRCE_DRILLED_HOLES_TOO_CLOSE: return wxString( _( "Drilled holes too close together" ) ); + case DRCE_TRACK_NEAR_EDGE: + return wxString( _( "Track too close to board edge" ) ); + case DRCE_INVALID_OUTLINE: + return wxString( _( "Board outline does not form a closed polygon" ) ); // use < since this is text ultimately embedded in HTML case DRCE_NETCLASS_TRACKWIDTH: