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
This commit is contained in:
Seth Hillbrand 2018-11-28 05:11:43 -07:00
parent 5eb5b5ce58
commit 26765161c1
11 changed files with 135 additions and 16 deletions

View File

@ -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 );

View File

@ -35,10 +35,8 @@ typedef OPT<VECTOR2I> 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()
*

View File

@ -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 );

View File

@ -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

View File

@ -1445,7 +1445,7 @@ double MODULE::CoverageRatio( const GENERAL_COLLECTOR& aCollector ) const
// see convert_drawsegment_list_to_polygon.cpp:
extern bool ConvertOutlineToPolygon( std::vector<DRAWSEGMENT*>& aSegList, SHAPE_POLY_SET& aPolygons,
wxString* aErrorText, unsigned int aTolerance );
wxString* aErrorText, unsigned int aTolerance, wxPoint* aErrorLocation = nullptr );
bool MODULE::BuildPolyCourtyard()
{

View File

@ -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<DRAWSEGMENT*>& 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<DRAWSEGMENT*>& aSegList, SHAPE_POLY_SE
*aErrorText << msg << "\n";
}
if( aErrorLocation )
*aErrorLocation = graphic->GetPosition();
return false;
}
@ -443,6 +447,9 @@ bool ConvertOutlineToPolygon( std::vector<DRAWSEGMENT*>& aSegList, SHAPE_POLY_SE
*aErrorText << msg << "\n";
}
if( aErrorLocation )
*aErrorLocation = prevPt;
return false;
}
break;
@ -594,6 +601,9 @@ bool ConvertOutlineToPolygon( std::vector<DRAWSEGMENT*>& aSegList, SHAPE_POLY_SE
*aErrorText << msg << "\n";
}
if( aErrorLocation )
*aErrorLocation = graphic->GetPosition();
return false;
}
@ -622,6 +632,9 @@ bool ConvertOutlineToPolygon( std::vector<DRAWSEGMENT*>& aSegList, SHAPE_POLY_SE
*aErrorText << msg << "\n";
}
if( aErrorLocation )
*aErrorLocation = prevPt;
return false;
}
break;
@ -641,7 +654,7 @@ bool ConvertOutlineToPolygon( std::vector<DRAWSEGMENT*>& 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() )
{

View File

@ -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 )
{

View File

@ -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();

View File

@ -32,6 +32,7 @@
#include <vector>
#include <memory>
#include <geometry/seg.h>
#include <geometry/shape_poly_set.h>
#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();
//-----<single "item" tests>-----------------------------------------
bool doNetClass( const std::shared_ptr<NETCLASS>& aNetClass, wxString& msg );

View File

@ -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();

View File

@ -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 &lt; since this is text ultimately embedded in HTML
case DRCE_NETCLASS_TRACKWIDTH: