kicad/pcbnew/drc/drc_clearance_test_function...

996 lines
39 KiB
C++
Raw Normal View History

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2004-2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 2007 Dick Hollenbeck, dick@softplc.com
2019-06-04 10:45:43 +00:00
* Copyright (C) 2019 KiCad Developers, see AUTHORS.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
*/
#include <fctsys.h>
2018-01-29 20:58:58 +00:00
#include <pcb_edit_frame.h>
#include <trigo.h>
#include <pcbnew.h>
#include <drc/drc.h>
#include <class_board.h>
#include <class_module.h>
#include <class_track.h>
#include <class_zone.h>
#include <class_drawsegment.h>
#include <class_marker_pcb.h>
#include <math_for_graphics.h>
#include <geometry/polygon_test_point_inside.h>
2016-04-06 18:15:49 +00:00
#include <convert_basic_shapes_to_polygon.h>
#include <board_commit.h>
#include <math/util.h> // for KiROUND
2020-04-26 14:08:11 +00:00
#include <geometry/shape_rect.h>
#include <macros.h>
/**
* 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 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.
2016-04-06 18:15:49 +00:00
* This case is not covered by the following check if one polygone is
* completely contained in the other (because edges don't intersect)!
*/
2019-06-04 10:45:43 +00:00
if( TestPointInsidePolygon( aTref, aTrefCount, aTtest[0] ) )
{
*actualDist = 0;
return false;
}
2019-06-04 10:45:43 +00:00
if( TestPointInsidePolygon( aTtest, aTtestCount, aTref[0] ) )
{
*actualDist = 0;
return false;
}
2016-04-06 18:15:49 +00:00
for( int ii = 0, jj = aTrefCount - 1; ii < aTrefCount; jj = ii, ii++ )
2019-06-04 10:45:43 +00:00
{
// for all edges in aTref
for( int kk = 0, ll = aTtestCount - 1; kk < aTtestCount; ll = kk, kk++ )
{
// for all edges in aTtest
double d;
2016-04-06 18:15:49 +00:00
int intersect = TestForIntersectionOfStraightLineSegments(
2019-06-04 10:45:43 +00:00
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 );
2016-04-06 18:15:49 +00:00
if( intersect )
{
*actualDist = 0;
return false;
}
if( d < aAllowedDist )
{
*actualDist = KiROUND( d );
return false;
}
}
}
return true;
}
2019-06-04 10:45:43 +00:00
/*
* compare a trapezoid (can be rectangle) and a segment and return true if distance > aDist
*/
bool 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)!
*/
2016-04-06 18:15:49 +00:00
if( TestPointInsidePolygon( aTref, aTrefCount, aSegStart ) )
{
*aActual = 0;
return false;
}
2016-04-06 18:15:49 +00:00
for( int ii = 0, jj = aTrefCount-1; ii < aTrefCount; jj = ii, ii++ )
{ // for all edges in polygon
double d;
2016-04-06 18:15:49 +00:00
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;
}
2020-05-18 00:20:16 +00:00
void DRC::doTrackDrc( BOARD_COMMIT& aCommit, TRACK* aRefSeg, TRACKS::iterator aStartIt,
TRACKS::iterator aEndIt, bool aTestZones )
{
BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings();
2020-05-18 00:20:16 +00:00
SEG refSeg( aRefSeg->GetStart(), aRefSeg->GetEnd() );
PCB_LAYER_ID refLayer = aRefSeg->GetLayer();
LSET refLayerSet = aRefSeg->GetLayerSet();
EDA_RECT refSegBB = aRefSeg->GetBoundingBox();
int refSegWidth = aRefSeg->GetWidth();
/******************************************/
/* Phase 0 : via DRC tests : */
/******************************************/
if( aRefSeg->Type() == PCB_VIA_T )
{
2020-05-18 00:20:16 +00:00
VIA *refvia = static_cast<VIA*>( aRefSeg );
int viaAnnulus = ( refvia->GetWidth() - refvia->GetDrill() ) / 2;
int minAnnulus = refvia->GetMinAnnulus( &m_clearanceSource );
// test if the via size is smaller than minimum
2019-12-28 00:55:11 +00:00
if( refvia->GetViaType() == VIATYPE::MICROVIA )
{
if( viaAnnulus < minAnnulus )
{
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TOO_SMALL_VIA_ANNULUS );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s %s; actual %s)" ),
2020-05-18 00:20:16 +00:00
m_clearanceSource,
MessageTextFromValue( userUnits(), minAnnulus, true ),
MessageTextFromValue( userUnits(), viaAnnulus, true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( refvia );
MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
if( refvia->GetWidth() < bds.m_MicroViasMinSize )
{
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TOO_SMALL_MICROVIA );
2020-05-18 00:20:16 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( " (board minimum %s; actual %s)" ),
MessageTextFromValue( userUnits(), bds.m_MicroViasMinSize, true ),
MessageTextFromValue( userUnits(), refvia->GetWidth(), true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( refvia );
MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
}
else
{
if( bds.m_ViasMinAnnulus > minAnnulus )
{
minAnnulus = bds.m_ViasMinAnnulus;
m_clearanceSource = _( "board minimum" );
}
2020-05-11 19:39:30 +00:00
if( viaAnnulus < minAnnulus )
2020-05-11 19:39:30 +00:00
{
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TOO_SMALL_VIA_ANNULUS );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s %s; actual %s)" ),
2020-05-18 00:20:16 +00:00
m_clearanceSource,
MessageTextFromValue( userUnits(), minAnnulus, true ),
MessageTextFromValue( userUnits(), viaAnnulus, true ) );
2020-05-11 19:39:30 +00:00
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
2020-05-11 19:39:30 +00:00
drcItem->SetItems( refvia );
MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
2020-05-11 19:39:30 +00:00
}
if( refvia->GetWidth() < bds.m_ViasMinSize )
{
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TOO_SMALL_VIA );
2020-05-18 00:20:16 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( " (board minimum %s; actual %s)" ),
MessageTextFromValue( userUnits(), bds.m_ViasMinSize, true ),
MessageTextFromValue( userUnits(), refvia->GetWidth(), true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( refvia );
MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
}
// test if via's hole is bigger than its diameter
// This test is necessary since the via hole size and width can be modified
// and a default via hole can be bigger than some vias sizes
if( refvia->GetDrillValue() > refvia->GetWidth() )
{
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_VIA_HOLE_BIGGER );
2020-05-18 00:20:16 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( " (diameter %s; drill %s)" ),
MessageTextFromValue( userUnits(), refvia->GetWidth(), true ),
MessageTextFromValue( userUnits(), refvia->GetDrillValue(), true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( refvia );
MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
// test if the type of via is allowed due to design rules
if( refvia->GetViaType() == VIATYPE::MICROVIA && !bds.m_MicroViasAllowed )
{
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_MICROVIA_NOT_ALLOWED );
2020-05-01 17:48:36 +00:00
2020-05-18 00:20:16 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( " (board design rule constraints)" ) );
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( refvia );
MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
// test if the type of via is allowed due to design rules
if( refvia->GetViaType() == VIATYPE::BLIND_BURIED && !bds.m_BlindBuriedViaAllowed )
{
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_BURIED_VIA_NOT_ALLOWED );
2020-05-01 17:48:36 +00:00
2020-05-18 00:20:16 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( " (board design rule constraints)" ) );
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( refvia );
MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
// For microvias: test if they are blind vias and only between 2 layers
// because they are used for very small drill size and are drill by laser
// and **only one layer** can be drilled
2019-12-28 00:55:11 +00:00
if( refvia->GetViaType() == VIATYPE::MICROVIA )
{
PCB_LAYER_ID layer1, layer2;
bool err = true;
refvia->LayerPair( &layer1, &layer2 );
if( layer1 > layer2 )
std::swap( layer1, layer2 );
if( layer2 == B_Cu && layer1 == bds.GetCopperLayerCount() - 2 )
err = false;
else if( layer1 == F_Cu && layer2 == In1_Cu )
err = false;
if( err )
{
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_MICROVIA_TOO_MANY_LAYERS );
2020-05-18 00:20:16 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( " (%s and %s not adjacent)" ),
m_pcb->GetLayerName( layer1 ),
m_pcb->GetLayerName( layer2 ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( refvia );
MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
}
}
else // This is a track segment
{
int minWidth, maxWidth;
aRefSeg->GetWidthConstraints( &minWidth, &maxWidth, &m_clearanceSource );
int errorCode = 0;
int constraintWidth;
if( refSegWidth < minWidth )
{
errorCode = DRCE_TOO_SMALL_TRACK_WIDTH;
constraintWidth = minWidth;
}
else if( refSegWidth > maxWidth )
{
errorCode = DRCE_TOO_LARGE_TRACK_WIDTH;
constraintWidth = maxWidth;
}
if( errorCode )
{
wxPoint refsegMiddle = ( aRefSeg->GetStart() + aRefSeg->GetEnd() ) / 2;
DRC_ITEM* drcItem = new DRC_ITEM( errorCode );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s %s; actual %s)" ),
2020-05-18 00:20:16 +00:00
m_clearanceSource,
MessageTextFromValue( userUnits(), constraintWidth, true ),
2020-05-18 00:20:16 +00:00
MessageTextFromValue( userUnits(), refSegWidth, true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( aRefSeg );
MARKER_PCB* marker = new MARKER_PCB( drcItem, refsegMiddle );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
}
/******************************************/
/* Phase 1 : test DRC track to pads : */
/******************************************/
// Compute the min distance to pads
for( MODULE* mod : m_pcb->Modules() )
{
// Don't preflight at the module level. Getting a module's bounding box goes
// through all its pads anyway (so it's no faster), and also all its drawings
// (so it's in fact slower).
for( D_PAD* pad : mod->Pads() )
{
// Preflight based on bounding boxes.
EDA_RECT inflatedBB = refSegBB;
2020-05-18 00:20:16 +00:00
inflatedBB.Inflate( pad->GetBoundingRadius() + m_largestClearance );
if( !inflatedBB.Contains( pad->GetPosition() ) )
continue;
2020-05-18 00:20:16 +00:00
if( !( pad->GetLayerSet() & refLayerSet ).any() )
2020-04-27 17:03:38 +00:00
continue;
// No need to check pads with the same net as the refSeg.
if( pad->GetNetCode() && aRefSeg->GetNetCode() == pad->GetNetCode() )
continue;
if( pad->GetDrillSize().x > 0 )
{
// For hole testing we use a dummy pad which is a copy of the current pad
// shrunk down to nothing but its hole.
D_PAD dummypad( *pad );
dummypad.SetSize( pad->GetDrillSize() );
dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ?
PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE );
// Ensure the hole is on all copper layers
const static LSET all_cu = LSET::AllCuMask();
dummypad.SetLayerSet( all_cu | dummypad.GetLayerSet() );
int minClearance;
DRC_RULE* rule = GetRule( aRefSeg, &dummypad, CLEARANCE_CONSTRAINT );
if( rule )
{
m_clearanceSource = wxString::Format( _( "'%s' rule" ), rule->m_Name );
minClearance = rule->m_Clearance.Min;
}
else
{
minClearance = aRefSeg->GetClearance( nullptr, &m_clearanceSource );
}
/* Treat an oval hole as a line segment along the hole's major axis,
* shortened by half its minor axis.
* A circular hole is just a degenerate case of an oval hole.
*/
wxPoint slotStart, slotEnd;
int slotWidth;
pad->GetOblongGeometry( pad->GetDrillSize(), &slotStart, &slotEnd, &slotWidth );
slotStart += pad->GetPosition();
slotEnd += pad->GetPosition();
2012-02-19 04:02:19 +00:00
SEG slotSeg( slotStart, slotEnd );
int widths = ( slotWidth + refSegWidth ) / 2;
int center2centerAllowed = minClearance + widths;
// Avoid square-roots if possible (for performance)
SEG::ecoord center2center_squared = refSeg.SquaredDistance( slotSeg );
if( center2center_squared < SEG::Square( center2centerAllowed ) )
{
int actual = std::max( 0.0, sqrt( center2center_squared ) - widths );
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TRACK_NEAR_HOLE );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
2020-05-18 00:20:16 +00:00
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), actual, true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( aRefSeg, pad );
2020-05-18 00:20:16 +00:00
MARKER_PCB* marker = new MARKER_PCB( drcItem, GetLocation( aRefSeg, slotSeg ) );
addMarkerToPcb( aCommit, marker );
if( !m_reportAllTrackErrors )
return;
}
}
2020-05-18 00:20:16 +00:00
int minClearance = aRefSeg->GetClearance( pad, &m_clearanceSource );
int actual;
if( !checkClearanceSegmToPad( refSeg, refSegWidth, pad, minClearance, &actual ) )
{
2020-05-18 00:20:16 +00:00
actual = std::max( 0, actual );
SEG padSeg( pad->GetPosition(), pad->GetPosition() );
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TRACK_NEAR_PAD );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
2020-05-18 00:20:16 +00:00
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), actual, true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( aRefSeg, pad );
2020-05-18 00:20:16 +00:00
MARKER_PCB* marker = new MARKER_PCB( drcItem, GetLocation( aRefSeg, padSeg ) );
addMarkerToPcb( aCommit, marker );
if( !m_reportAllTrackErrors )
return;
}
}
}
/***********************************************/
/* Phase 2: test DRC with other track segments */
/***********************************************/
// Test the reference segment with other track segments
2019-05-31 02:30:28 +00:00
for( auto it = aStartIt; it != aEndIt; it++ )
{
TRACK* track = *it;
// No problem if segments have the same net code:
if( aRefSeg->GetNetCode() == track->GetNetCode() )
continue;
// No problem if tracks are on different layers:
2020-05-18 00:20:16 +00:00
// Note that while the general case of GetLayerSet intersection always works,
// the others are much faster.
bool sameLayers;
if( aRefSeg->Type() == PCB_VIA_T )
{
if( track->Type() == PCB_VIA_T )
sameLayers = ( refLayerSet & track->GetLayerSet() ).any();
else
sameLayers = refLayerSet.test( track->GetLayer() );
}
else
{
if( track->Type() == PCB_VIA_T )
sameLayers = track->GetLayerSet().test( refLayer );
else
sameLayers = track->GetLayer() == refLayer;
}
if( !sameLayers )
continue;
2020-05-18 00:20:16 +00:00
// Preflight based on worst-case inflated bounding boxes:
EDA_RECT trackBB = track->GetBoundingBox();
2020-05-18 00:20:16 +00:00
trackBB.Inflate( m_largestClearance );
if( !trackBB.Intersects( refSegBB ) )
continue;
2020-05-18 00:20:16 +00:00
int minClearance = aRefSeg->GetClearance( track, &m_clearanceSource );
SEG trackSeg( track->GetStart(), track->GetEnd() );
int widths = ( refSegWidth + track->GetWidth() ) / 2;
int center2centerAllowed = minClearance + widths;
// Avoid square-roots if possible (for performance)
SEG::ecoord center2center_squared = refSeg.SquaredDistance( trackSeg );
OPT_VECTOR2I intersection = refSeg.Intersect( trackSeg );
// Check two tracks crossing first as it reports a DRCE without distances
if( intersection )
{
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TRACKS_CROSSING );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( aRefSeg, track );
MARKER_PCB* marker = new MARKER_PCB( drcItem, (wxPoint) intersection.get() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
if( !m_reportAllTrackErrors )
return;
}
else if( center2center_squared < SEG::Square( center2centerAllowed ) )
{
int errorCode = DRCE_TRACK_ENDS;
if( aRefSeg->Type() == PCB_VIA_T && track->Type() == PCB_VIA_T )
errorCode = DRCE_VIA_NEAR_VIA;
else if( aRefSeg->Type() == PCB_VIA_T || track->Type() == PCB_VIA_T )
errorCode = DRCE_VIA_NEAR_TRACK;
else if( refSeg.ApproxParallel( trackSeg ) )
errorCode = DRCE_TRACK_SEGMENTS_TOO_CLOSE;
int actual = std::max( 0.0, sqrt( center2center_squared ) - widths );
DRC_ITEM* drcItem = new DRC_ITEM( errorCode );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
2020-05-18 00:20:16 +00:00
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), actual, true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( aRefSeg, track );
2020-05-18 00:20:16 +00:00
MARKER_PCB* marker = new MARKER_PCB( drcItem, GetLocation( aRefSeg, trackSeg ) );
addMarkerToPcb( aCommit, marker );
if( !m_reportAllTrackErrors )
return;
}
}
/***************************************/
/* Phase 3: test DRC with copper zones */
/***************************************/
// Can be *very* time consumming.
if( aTestZones )
{
SEG testSeg( aRefSeg->GetStart(), aRefSeg->GetEnd() );
for( ZONE_CONTAINER* zone : m_pcb->Zones() )
{
if( zone->GetFilledPolysList().IsEmpty() || zone->GetIsKeepout() )
continue;
2020-05-18 00:20:16 +00:00
if( !( refLayerSet & zone->GetLayerSet() ).any() )
continue;
if( zone->GetNetCode() && zone->GetNetCode() == aRefSeg->GetNetCode() )
continue;
2020-05-18 00:20:16 +00:00
int minClearance = aRefSeg->GetClearance( zone, &m_clearanceSource );
int widths = refSegWidth / 2;
int center2centerAllowed = minClearance + widths;
SHAPE_POLY_SET* outline = const_cast<SHAPE_POLY_SET*>( &zone->GetFilledPolysList() );
SEG::ecoord center2center_squared = outline->SquaredDistance( testSeg );
// to avoid false positive, due to rounding issues and approxiamtions
// in distance and clearance calculations, use a small threshold for distance
// (1 micron)
#define THRESHOLD_DIST Millimeter2iu( 0.001 )
if( center2center_squared + THRESHOLD_DIST < SEG::Square( center2centerAllowed ) )
{
int actual = std::max( 0.0, sqrt( center2center_squared ) - widths );
DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TRACK_NEAR_ZONE );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
2020-05-18 00:20:16 +00:00
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), actual, true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( aRefSeg, zone );
2020-05-18 00:20:16 +00:00
MARKER_PCB* marker = new MARKER_PCB( drcItem, GetLocation( aRefSeg, zone ) );
addMarkerToPcb( aCommit, marker );
}
}
}
/***********************************************/
/* Phase 4: test DRC with to board edge */
/***********************************************/
if( m_board_outline_valid )
{
int minClearance = bds.m_CopperEdgeClearance;
m_clearanceSource = _( "board edge" );
static DRAWSEGMENT dummyEdge;
dummyEdge.SetLayer( Edge_Cuts );
if( aRefSeg->GetRuleClearance( &dummyEdge, &minClearance, &m_clearanceSource ) )
/* minClearance and m_clearanceSource set in GetRuleClearance() */;
SEG testSeg( aRefSeg->GetStart(), aRefSeg->GetEnd() );
int halfWidth = refSegWidth / 2;
int center2centerAllowed = minClearance + halfWidth;
for( auto it = m_board_outlines.IterateSegmentsWithHoles(); it; it++ )
{
SEG::ecoord center2center_squared = testSeg.SquaredDistance( *it );
if( center2center_squared < SEG::Square( center2centerAllowed ) )
{
VECTOR2I pt = testSeg.NearestPoint( *it );
KICAD_T types[] = { PCB_LINE_T, EOT };
DRAWSEGMENT* edge = nullptr;
INSPECTOR_FUNC inspector =
[&] ( EDA_ITEM* item, void* testData )
{
DRAWSEGMENT* test_edge = dynamic_cast<DRAWSEGMENT*>( item );
if( !test_edge || test_edge->GetLayer() != Edge_Cuts )
return SEARCH_RESULT::CONTINUE;
if( test_edge->HitTest( (wxPoint) pt, minClearance + halfWidth ) )
{
edge = test_edge;
return SEARCH_RESULT::QUIT;
}
return SEARCH_RESULT::CONTINUE;
};
// Best-efforts search for edge segment
BOARD::IterateForward<BOARD_ITEM*>( m_pcb->Drawings(), inspector, nullptr, types );
int actual = std::max( 0.0, sqrt( center2center_squared ) - halfWidth );
int errorCode = ( aRefSeg->Type() == PCB_VIA_T ) ? DRCE_VIA_NEAR_EDGE
: DRCE_TRACK_NEAR_EDGE;
DRC_ITEM* drcItem = new DRC_ITEM( errorCode );
2020-05-18 00:20:16 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), actual, true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( aRefSeg, edge );
MARKER_PCB* marker = new MARKER_PCB( drcItem, (wxPoint) pt );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
}
}
}
bool DRC::checkClearancePadToPad( D_PAD* aRefPad, D_PAD* aPad, int aMinClearance, int* aActual )
{
// relativePadPos is the aPad shape position relative to the aRefPad shape position
* KIWAY Milestone A): Make major modules into DLL/DSOs. ! The initial testing of this commit should be done using a Debug build so that all the wxASSERT()s are enabled. Also, be sure and keep enabled the USE_KIWAY_DLLs option. The tree won't likely build without it. Turning it off is senseless anyways. If you want stable code, go back to a prior version, the one tagged with "stable". * Relocate all functionality out of the wxApp derivative into more finely targeted purposes: a) DLL/DSO specific b) PROJECT specific c) EXE or process specific d) configuration file specific data e) configuration file manipulations functions. All of this functionality was blended into an extremely large wxApp derivative and that was incompatible with the desire to support multiple concurrently loaded DLL/DSO's ("KIFACE")s and multiple concurrently open projects. An amazing amount of organization come from simply sorting each bit of functionality into the proper box. * Switch to wxConfigBase from wxConfig everywhere except instantiation. * Add classes KIWAY, KIFACE, KIFACE_I, SEARCH_STACK, PGM_BASE, PGM_KICAD, PGM_SINGLE_TOP, * Remove "Return" prefix on many function names. * Remove obvious comments from CMakeLists.txt files, and from else() and endif()s. * Fix building boost for use in a DSO on linux. * Remove some of the assumptions in the CMakeLists.txt files that windows had to be the host platform when building windows binaries. * Reduce the number of wxStrings being constructed at program load time via static construction. * Pass wxConfigBase* to all SaveSettings() and LoadSettings() functions so that these functions are useful even when the wxConfigBase comes from another source, as is the case in the KICAD_MANAGER_FRAME. * Move the setting of the KIPRJMOD environment variable into class PROJECT, so that it can be moved into a project variable soon, and out of FP_LIB_TABLE. * Add the KIWAY_PLAYER which is associated with a particular PROJECT, and all its child wxFrames and wxDialogs now have a Kiway() member function which returns a KIWAY& that that window tree branch is in support of. This is like wxWindows DNA in that child windows get this member with proper value at time of construction. * Anticipate some of the needs for milestones B) and C) and make code adjustments now in an effort to reduce work in those milestones. * No testing has been done for python scripting, since milestone C) has that being largely reworked and re-thought-out.
2014-03-20 00:42:08 +00:00
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
2016-04-06 18:15:49 +00:00
// 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:
2016-04-06 18:15:49 +00:00
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;
2016-04-06 18:15:49 +00:00
if( aRefPad->GetShape() == PAD_SHAPE_ROUNDRECT )
{
int padRadius = aRefPad->GetRoundRectCornerRadius();
dist_extra = padRadius;
GetRoundRectCornerCenters( polyref, padRadius, wxPoint( 0, 0 ), aRefPad->GetSize(),
2019-06-04 10:45:43 +00:00
aRefPad->GetOrientation() );
2016-04-06 18:15:49 +00:00
}
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(),
2019-06-04 10:45:43 +00:00
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 ),
2019-06-04 10:45:43 +00:00
aRefPad->GetOrientation() );
}
2016-04-06 18:15:49 +00:00
else
{
// BuildPadPolygon has meaning for rect a trapeziod shapes and returns the 4 corners.
2016-04-06 18:15:49 +00:00
aRefPad->BuildPadPolygon( polyref, wxSize( 0, 0 ), aRefPad->GetOrientation() );
}
2016-04-06 18:15:49 +00:00
// corners of aPad (used only for rect/roundrect/trap pad)
wxPoint polycompare[4];
// corners of aPad (used only custom pad)
SHAPE_POLY_SET polysetcompare;
2016-04-06 18:15:49 +00:00
switch( aPad->GetShape() )
{
case PAD_SHAPE_ROUNDRECT:
case PAD_SHAPE_RECT:
case PAD_SHAPE_CHAMFERED_RECT:
2016-04-06 18:15:49 +00:00
case PAD_SHAPE_TRAPEZOID:
case PAD_SHAPE_CUSTOM:
2016-04-06 18:15:49 +00:00
if( aPad->GetShape() == PAD_SHAPE_ROUNDRECT )
{
int padRadius = aPad->GetRoundRectCornerRadius();
dist_extra = padRadius;
GetRoundRectCornerCenters( polycompare, padRadius, relativePadPos, aPad->GetSize(),
2019-06-04 10:45:43 +00:00
aPad->GetOrientation() );
2016-04-06 18:15:49 +00:00
}
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,
2019-06-04 10:45:43 +00:00
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,
2019-06-04 10:45:43 +00:00
aPad->GetOrientation() );
}
2016-04-06 18:15:49 +00:00
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)
2016-04-06 18:15:49 +00:00
{
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;
}
2016-04-06 18:15:49 +00:00
}
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;
}
2019-06-04 10:45:43 +00:00
}
else
{
if( !poly2polyDRC( polyref, 4, polycompare, 4, aMinClearance + dist_extra, aActual ) )
{
*aActual = std::max( 0, *aActual - dist_extra );
diag = false;
}
}
2016-04-06 18:15:49 +00:00
break;
default:
wxLogDebug( wxT( "DRC::checkClearancePadToPad: unexpected pad shape %d" ), aPad->GetShape() );
break;
}
}
return diag;
}
/*
* Test if distance between a segment and a pad is > minClearance. Return the actual
* distance if it is less.
*/
bool DRC::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 ) )
{
2020-05-18 00:20:16 +00:00
*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 ) )
{
2020-05-18 00:20:16 +00:00
*aActualDist = std::max( 0, actual - widths );
return false;
}
}
return true;
}