kicad/pcbnew/drc/drc_clearance_test_function...

546 lines
21 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>
#include <trigo.h>
#include <drc/drc.h>
#include <class_board.h>
#include <class_track.h>
#include <class_drawsegment.h>
#include <class_marker_pcb.h>
#include <geometry/polygon_test_point_inside.h>
2020-04-26 14:08:11 +00:00
#include <geometry/shape_rect.h>
#include <geometry/shape_segment.h>
#include <convert_basic_shapes_to_polygon.h>
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();
SHAPE_SEGMENT refSeg( aRefSeg->GetStart(), aRefSeg->GetEnd(), aRefSeg->GetWidth() );
PCB_LAYER_ID refLayer = aRefSeg->GetLayer();
LSET refLayerSet = aRefSeg->GetLayerSet();
2020-05-18 00:20:16 +00:00
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( refvia->GetLayer(), &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 = DRC_ITEM::Create( DRCE_VIA_ANNULUS );
m_msg.Printf( _( "Via annulus too small (%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 = DRC_ITEM::Create( 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 = DRC_ITEM::Create( DRCE_VIA_ANNULUS );
2020-05-11 19:39:30 +00:00
2020-07-06 11:47:42 +00:00
m_msg.Printf( _( "Via annulus too small (%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 = DRC_ITEM::Create( 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 = DRC_ITEM::Create( 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 = DRC_ITEM::Create( DRCE_ALLOWED_ITEMS );
2020-05-01 17:48:36 +00:00
m_msg.Printf( _( "Microvia not allowed (board design rule constraints)" ) );
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::BLIND_BURIED && !bds.m_BlindBuriedViaAllowed )
{
DRC_ITEM* drcItem = DRC_ITEM::Create( DRCE_ALLOWED_ITEMS );
2020-05-01 17:48:36 +00:00
m_msg.Printf( _( "Blind/buried via not allowed (board design rule constraints)" ) );
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 );
}
// 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 = DRC_ITEM::Create( DRCE_PADSTACK );
m_msg.Printf( _( "Microvia through too many layers (%s and %s not adjacent)" ),
2020-05-18 00:20:16 +00:00
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_TRACK_WIDTH;
constraintWidth = minWidth;
}
else if( refSegWidth > maxWidth )
{
errorCode = DRCE_TRACK_WIDTH;
constraintWidth = maxWidth;
}
if( errorCode )
{
wxPoint refsegMiddle = ( aRefSeg->GetStart() + aRefSeg->GetEnd() ) / 2;
DRC_ITEM* drcItem = DRC_ITEM::Create( 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 )
{
const SHAPE_SEGMENT* slot = pad->GetEffectiveHoleShape();
const DRC_CONSTRAINT* constraint = GetConstraint( aRefSeg, pad,
DRC_RULE_ID_CLEARANCE, refLayer,
&m_clearanceSource );
int minClearance;
int actual;
if( constraint )
{
m_clearanceSource = wxString::Format( _( "'%s' rule" ), m_clearanceSource );
minClearance = constraint->m_Value.Min();
}
else
{
minClearance = aRefSeg->GetClearance( refLayer, nullptr,
&m_clearanceSource );
}
if( slot->Collide( &refSeg, minClearance + bds.GetDRCEpsilon(), &actual ) )
{
DRC_ITEM* drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
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 );
MARKER_PCB* marker = new MARKER_PCB( drcItem, pad->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
if( !m_reportAllTrackErrors )
return;
}
}
int minClearance = aRefSeg->GetClearance( aRefSeg->GetLayer(), pad,
&m_clearanceSource );
2020-05-18 00:20:16 +00:00
int actual;
if( pad->Collide( &refSeg, minClearance - bds.GetDRCEpsilon(), &actual ) )
{
DRC_ITEM* drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
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 );
MARKER_PCB* marker = new MARKER_PCB( drcItem, pad->GetPosition() );
2020-05-18 00:20:16 +00:00
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;
int minClearance = aRefSeg->GetClearance( aRefSeg->GetLayer(), track,
&m_clearanceSource );
int actual;
SHAPE_SEGMENT trackSeg( track->GetStart(), track->GetEnd(), track->GetWidth() );
// Check two tracks crossing first as it reports a DRCE without distances
if( OPT_VECTOR2I intersection = refSeg.GetSeg().Intersect( trackSeg.GetSeg() ) )
{
DRC_ITEM* drcItem = DRC_ITEM::Create( 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( refSeg.Collide( &trackSeg, minClearance, &actual ) )
{
wxPoint pos = GetLocation( aRefSeg, trackSeg.GetSeg() );
DRC_ITEM* drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
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 );
MARKER_PCB* marker = new MARKER_PCB( drcItem, pos );
2020-05-18 00:20:16 +00:00
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( !( refLayerSet & zone->GetLayerSet() ).any() || zone->GetIsKeepout() )
continue;
for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
{
if( zone->GetFilledPolysList( layer ).IsEmpty() )
continue;
if( zone->GetNetCode() && zone->GetNetCode() == aRefSeg->GetNetCode() )
continue;
// 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 )
int minClearance = aRefSeg->GetClearance( aRefSeg->GetLayer(), zone,
&m_clearanceSource );
int widths = refSegWidth / 2;
int allowedDist = minClearance + widths + THRESHOLD_DIST;
int actual;
if( zone->GetFilledPolysList( layer ).Collide( testSeg, allowedDist, &actual ) )
{
actual = std::max( 0, actual - widths );
DRC_ITEM* drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), actual, true ) );
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( aRefSeg, zone );
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 );
aRefSeg->GetRuleClearance( &dummyEdge, aRefSeg->GetLayer(), &minClearance,
&m_clearanceSource );
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 );
DRC_ITEM* drcItem = DRC_ITEM::Create( DRCE_COPPER_EDGE_CLEARANCE );
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 );
}
}
}
}