Browse Source

Simplify testing of multiple pads with same number.

We now test each hole against the combined copper of all pads
with the same number minus the other holes.

Also fixes a bug with "handled" flag not getting reset between
layers of padstacks.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/19521
pcb_db
Jeff Young 10 months ago
parent
commit
a41a9ea51f
  1. 266
      pcbnew/drc/drc_test_provider_annular_width.cpp

266
pcbnew/drc/drc_test_provider_annular_width.cpp

@ -88,8 +88,6 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run()
if( !reportPhase( _( "Checking pad & via annular rings..." ) ) )
return false; // DRC cancelled
int maxError = m_drcEngine->GetBoard()->GetDesignSettings().m_MaxError;
auto calcEffort =
[]( BOARD_ITEM* item ) -> size_t
{
@ -143,6 +141,120 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run()
}
};
auto checkPadAnnularWidth =
[]( PAD* pad, PCB_LAYER_ID aLayer, DRC_CONSTRAINT& constraint,
const std::vector<const PAD*>& sameNumPads,
int* aMinAnnularWidth, int* aMaxAnnularWidth )
{
int annularWidth = 0;
bool handled = false;
if( pad->GetOffset( aLayer ) == VECTOR2I( 0, 0 ) )
{
VECTOR2I padSize = pad->GetSize( aLayer );
switch( pad->GetShape( aLayer ) )
{
case PAD_SHAPE::CIRCLE:
annularWidth = ( padSize.x - pad->GetDrillSizeX() ) / 2;
// If there are more pads with the same number then we'll still need to
// run the more generalised checks below.
handled = sameNumPads.empty();
break;
case PAD_SHAPE::CHAMFERED_RECT:
if( pad->GetChamferRectRatio( aLayer ) > 0.30 )
break;
KI_FALLTHROUGH;
case PAD_SHAPE::OVAL:
case PAD_SHAPE::RECTANGLE:
case PAD_SHAPE::ROUNDRECT:
annularWidth = std::min( padSize.x - pad->GetDrillSizeX(),
padSize.y - pad->GetDrillSizeY() ) / 2;
// If there are more pads with the same number then we'll still need to
// run the more generalised checks below.
handled = sameNumPads.empty();
break;
default:
break;
}
}
if( !handled )
{
// Slow (but general purpose) method.
int maxError = pad->GetBoard()->GetDesignSettings().m_MaxError;
SEG::ecoord dist_sq;
SHAPE_POLY_SET padOutline;
std::shared_ptr<SHAPE_SEGMENT> slot = pad->GetEffectiveHoleShape();
pad->TransformShapeToPolygon( padOutline, aLayer, 0, maxError, ERROR_INSIDE );
if( sameNumPads.empty() )
{
if( !padOutline.Collide( pad->GetPosition() ) )
{
// Hole outside pad
annularWidth = 0;
}
else
{
// Disable is-inside test in SquaredDistance
padOutline.Outline( 0 ).SetClosed( false );
dist_sq = padOutline.SquaredDistanceToSeg( slot->GetSeg() );
annularWidth = sqrt( dist_sq ) - slot->GetWidth() / 2;
}
}
else if( constraint.Value().HasMin()
&& ( annularWidth < constraint.Value().Min() ) )
{
SHAPE_POLY_SET aggregatePadOutline = padOutline;
SHAPE_POLY_SET otherPadHoles;
SHAPE_POLY_SET slotPolygon;
slot->TransformToPolygon( slotPolygon, 0, ERROR_INSIDE );
for( const PAD* sameNumPad : sameNumPads )
{
// Construct the full pad with outline and hole.
sameNumPad->TransformShapeToPolygon( aggregatePadOutline,
PADSTACK::ALL_LAYERS, 0,
maxError, ERROR_OUTSIDE );
sameNumPad->TransformHoleToPolygon( otherPadHoles, 0, maxError,
ERROR_INSIDE );
}
aggregatePadOutline.BooleanSubtract( otherPadHoles );
if( !aggregatePadOutline.Collide( pad->GetPosition() ) )
{
// Hole outside pad
annularWidth = 0;
}
else
{
// Disable is-inside test in SquaredDistance
aggregatePadOutline.Outline( 0 ).SetClosed( false );
dist_sq = aggregatePadOutline.SquaredDistanceToSeg( slot->GetSeg() );
annularWidth = sqrt( dist_sq ) - slot->GetWidth() / 2;
}
}
}
*aMaxAnnularWidth = std::max( *aMaxAnnularWidth, annularWidth );
*aMinAnnularWidth = std::min( *aMinAnnularWidth, annularWidth );
};
auto checkAnnularWidth =
[&]( BOARD_ITEM* item ) -> bool
{
@ -159,167 +271,41 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run()
bool fail_min = false;
bool fail_max = false;
switch( item->Type() )
{
case PCB_VIA_T:
{
PCB_VIA* via = static_cast<PCB_VIA*>( item );
int drill = via->GetDrillValue();
int drill = via->GetDrillValue();
via->Padstack().ForEachUniqueLayer(
[&]( PCB_LAYER_ID aLayer )
{
int layerWidth = ( via->GetWidth( aLayer ) - drill ) / 2;
minAnnularWidth = std::min( minAnnularWidth, layerWidth );
maxAnnularWidth = std::max( maxAnnularWidth, layerWidth );
} );
[&]( PCB_LAYER_ID aLayer )
{
int layerWidth = ( via->GetWidth( aLayer ) - drill ) / 2;
minAnnularWidth = std::min( minAnnularWidth, layerWidth );
maxAnnularWidth = std::max( maxAnnularWidth, layerWidth );
} );
break;
}
case PCB_PAD_T:
{
PAD* pad = static_cast<PAD*>( item );
bool handled = false;
if( !pad->HasHole() || pad->GetAttribute() != PAD_ATTRIB::PTH )
return true;
std::vector<const PAD*> sameNumPads;
const FOOTPRINT* fp = static_cast<const FOOTPRINT*>( pad->GetParent() );
if( fp )
if( const FOOTPRINT* fp = static_cast<const FOOTPRINT*>( pad->GetParent() ) )
sameNumPads = fp->GetPads( pad->GetNumber(), pad );
pad->Padstack().ForEachUniqueLayer(
[&]( PCB_LAYER_ID aLayer )
{
int annularWidth = 0;
if( pad->GetOffset( aLayer ) == VECTOR2I( 0, 0 ) )
{
VECTOR2I padSize = pad->GetSize( aLayer );
switch( pad->GetShape( aLayer ) )
{
case PAD_SHAPE::CIRCLE:
annularWidth = ( padSize.x - pad->GetDrillSizeX() ) / 2;
// If there are more pads with the same number. Check to see if the
// pad is embedded inside another pad with the same number below.
if( sameNumPads.empty() )
handled = true;
break;
case PAD_SHAPE::CHAMFERED_RECT:
if( pad->GetChamferRectRatio( aLayer ) > 0.30 )
break;
KI_FALLTHROUGH;
case PAD_SHAPE::OVAL:
case PAD_SHAPE::RECTANGLE:
case PAD_SHAPE::ROUNDRECT:
annularWidth = std::min( padSize.x - pad->GetDrillSizeX(),
padSize.y - pad->GetDrillSizeY() ) / 2;
// If there are more pads with the same number. Check to see if the
// pad is embedded inside another pad with the same number below.
if( sameNumPads.empty() )
handled = true;
break;
default:
break;
}
}
if( !handled )
[&]( PCB_LAYER_ID aLayer )
{
// Slow (but general purpose) method.
SEG::ecoord dist_sq;
SHAPE_POLY_SET padOutline;
std::shared_ptr<SHAPE_SEGMENT> slot = pad->GetEffectiveHoleShape();
pad->TransformShapeToPolygon( padOutline, aLayer, 0, maxError,
ERROR_INSIDE );
if( sameNumPads.empty() )
{
if( !padOutline.Collide( pad->GetPosition() ) )
{
// Hole outside pad
annularWidth = 0;
}
else
{
// Disable is-inside test in SquaredDistance
padOutline.Outline( 0 ).SetClosed( false );
dist_sq = padOutline.SquaredDistanceToSeg( slot->GetSeg() );
annularWidth = sqrt( dist_sq ) - slot->GetWidth() / 2;
}
}
else if( constraint.Value().HasMin()
&& ( annularWidth < constraint.Value().Min() ) )
{
SHAPE_POLY_SET otherPadOutline;
SHAPE_POLY_SET otherPadHoles;
SHAPE_POLY_SET slotPolygon;
slot->TransformToPolygon( slotPolygon, 0, ERROR_INSIDE );
for( const PAD* sameNumPad : sameNumPads )
{
// Construct the full pad with outline and hole.
sameNumPad->TransformShapeToPolygon(
otherPadOutline, PADSTACK::ALL_LAYERS, 0, maxError,
ERROR_OUTSIDE );
sameNumPad->TransformHoleToPolygon(
otherPadHoles, 0, maxError, ERROR_INSIDE );
}
otherPadOutline.BooleanSubtract( otherPadHoles );
// If the pad hole under test intersects with another pad outline,
// the annular width calculated above is used.
bool intersects = false;
for( int i = 0; i < otherPadOutline.OutlineCount() && !intersects; i++ )
{
intersects |= slotPolygon.COutline( 0 ).Intersects( otherPadOutline.COutline( i ) );
for( int j = 0; j < otherPadOutline.HoleCount( i ) && !intersects; j++ )
{
intersects |= slotPolygon.COutline( 0 ).Intersects( otherPadOutline.CHole( i, j ) );
}
}
if( !intersects )
{
// Determine the effective annular width if the pad hole under
// test lies withing the boundary of another pad outline.
int effectiveWidth = std::numeric_limits<int>::max();
for( int ii = 0; ii < otherPadOutline.OutlineCount(); ii++ )
{
if( slot->Collide( &otherPadOutline.Outline( ii ), 0 ) )
{
if( effectiveWidth > annularWidth )
annularWidth = effectiveWidth;
}
}
}
}
}
maxAnnularWidth = std::max( maxAnnularWidth, annularWidth );
minAnnularWidth = std::min( minAnnularWidth, annularWidth );
} );
checkPadAnnularWidth( pad, aLayer, constraint, sameNumPads,
&minAnnularWidth, &maxAnnularWidth );
} );
break;
}

Loading…
Cancel
Save