7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-20 00:21:31 +00:00

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
This commit is contained in:
Jeff Young 2025-01-07 14:39:06 +00:00
parent db601c58cc
commit a41a9ea51f

View File

@ -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 ) )
[&]( PCB_LAYER_ID aLayer )
{
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 )
{
// 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;
}