7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-21 18:03:45 +00:00

Give padstack warning violation a name.

Also regularises capitalisation & parens in some error
messages.

Also de-duplicates hole testing for PTH pads, and SMD
rationality testing.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/18334
This commit is contained in:
Jeff Young 2024-07-07 14:04:22 +01:00
parent e9d376c912
commit 8b948e7b9c
5 changed files with 77 additions and 152 deletions

View File

@ -652,9 +652,8 @@ bool TestBoardOutlinesGraphicItems( BOARD* aBoard, int aMinDist,
if( aErrorHandler )
{
(*aErrorHandler)( wxString::Format( _( "(Rectangle has null or very small "
"size: %d nm)" ),
dim ),
(*aErrorHandler)( wxString::Format( _( "(rectangle has null or very small "
"size: %d nm)" ), dim ),
graphic, nullptr, graphic->GetStart() );
}
}
@ -663,15 +662,16 @@ bool TestBoardOutlinesGraphicItems( BOARD* aBoard, int aMinDist,
case SHAPE_T::CIRCLE:
{
if( graphic->GetRadius() <= min_dist )
int r = graphic->GetRadius();
if( r <= min_dist )
{
success = false;
if( aErrorHandler )
{
(*aErrorHandler)( wxString::Format( _( "(Circle has null or very small "
"radius: %d nm)" ),
(int)graphic->GetRadius() ),
(*aErrorHandler)( wxString::Format( _( "(circle has null or very small "
"radius: %d nm)" ), r ),
graphic, nullptr, graphic->GetStart() );
}
}
@ -689,7 +689,7 @@ bool TestBoardOutlinesGraphicItems( BOARD* aBoard, int aMinDist,
if( aErrorHandler )
{
(*aErrorHandler)( wxString::Format( _( "(Segment has null or very small "
(*aErrorHandler)( wxString::Format( _( "(segment has null or very small "
"length: %d nm)" ), dim ),
graphic, nullptr, graphic->GetStart() );
}
@ -712,7 +712,8 @@ bool TestBoardOutlinesGraphicItems( BOARD* aBoard, int aMinDist,
if( aErrorHandler )
{
(*aErrorHandler)( wxString::Format( _( "(Arc has null or very small size: %d nm)" ), dim ),
(*aErrorHandler)( wxString::Format( _( "(arc has null or very small size: "
"%d nm)" ), dim ),
graphic, nullptr, graphic->GetStart() );
}
}

View File

@ -131,7 +131,7 @@ DRC_ITEM DRC_ITEM::viaDiameter( DRCE_VIA_DIAMETER,
wxT( "via_diameter" ) );
DRC_ITEM DRC_ITEM::padstack( DRCE_PADSTACK,
wxT( "" ),
wxT( "Padstack is questionable" ),
wxT( "padstack" ) );
DRC_ITEM DRC_ITEM::padstackInvalid( DRCE_PADSTACK_INVALID,

View File

@ -56,7 +56,7 @@ enum PCB_DRC_CODE {
DRCE_CONNECTION_WIDTH, // Net connection too small
DRCE_DRILL_OUT_OF_RANGE, // Too small via or pad drill
DRCE_VIA_DIAMETER, // Via diameter checks (min/max)
DRCE_PADSTACK, // something is wrong with a pad or via stackup
DRCE_PADSTACK, // something is questionable with a pad or via stackup
DRCE_PADSTACK_INVALID, // something is invalid with a pad or via stackup
DRCE_MICROVIA_DRILL_OUT_OF_RANGE, // Too small micro via drill
DRCE_OVERLAPPING_FOOTPRINTS, // footprint courtyards overlap

View File

@ -3068,93 +3068,6 @@ void FOOTPRINT::CheckPads( UNITS_PROVIDER* aUnitsProvider,
{
aErrorHandler( pad, errorCode, msg );
} );
if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
{
// Ensure the drill size can be handled in next calculations.
// Use min size = 4 IU to be able to build a polygon from a hole shape
const int min_drill_size = 4;
if( pad->GetDrillSizeX() <= min_drill_size || pad->GetDrillSizeY() <= min_drill_size )
{
(aErrorHandler)( pad, DRCE_PAD_TH_WITH_NO_HOLE,
_( "(PTH pad's hole size is very small or null)" ) );
}
}
if( pad->GetAttribute() == PAD_ATTRIB::PTH )
{
if( !pad->IsOnCopperLayer() )
{
(aErrorHandler)( pad, DRCE_PADSTACK, _( "(PTH pad has no copper layers)" ) );
}
else
{
// Ensure the pad has a copper area.
// min drill size is already tested and converting shapes to polygon can be made
LSET lset = pad->GetLayerSet() & LSET::AllCuMask();
PCB_LAYER_ID layer = lset.Seq().at( 0 );
SHAPE_POLY_SET padOutline;
pad->TransformShapeToPolygon( padOutline, layer, 0, ARC_HIGH_DEF, ERROR_INSIDE );
std::shared_ptr<SHAPE_SEGMENT> hole = pad->GetEffectiveHoleShape();
SHAPE_POLY_SET holeOutline;
TransformOvalToPolygon( holeOutline, hole->GetSeg().A, hole->GetSeg().B,
hole->GetWidth(), ARC_HIGH_DEF, ERROR_OUTSIDE );
// Test if there is copper area outside hole
SHAPE_POLY_SET padOutlineCopy = padOutline;
padOutline.BooleanSubtract( holeOutline, SHAPE_POLY_SET::POLYGON_MODE::PM_FAST );
if( padOutline.IsEmpty() )
aErrorHandler( pad, DRCE_PADSTACK, _( "(PTH pad's hole leaves no copper)" ) );
else
{
// Test if the pad hole is fully inside the copper area
holeOutline.BooleanSubtract( padOutlineCopy, SHAPE_POLY_SET::POLYGON_MODE::PM_FAST );
if( !holeOutline.IsEmpty() )
aErrorHandler( pad, DRCE_PADSTACK,
_( "(PTH pad's hole non fully inside copper)" ) );
}
}
}
if( pad->GetAttribute() == PAD_ATTRIB::SMD )
{
if( pad->IsOnLayer( F_Cu ) && pad->IsOnLayer( B_Cu ) )
{
aErrorHandler( pad, DRCE_PADSTACK,
_( "(SMD pad appears on both front and back copper)" ) );
}
else if( pad->IsOnLayer( F_Cu ) )
{
if( pad->IsOnLayer( B_Mask ) )
{
aErrorHandler( pad, DRCE_PADSTACK,
_( "(SMD pad copper and mask layers don't match)" ) );
}
else if( pad->IsOnLayer( B_Paste ) )
{
aErrorHandler( pad, DRCE_PADSTACK,
_( "(SMD pad copper and paste layers don't match)" ) );
}
}
else if( pad->IsOnLayer( B_Cu ) )
{
if( pad->IsOnLayer( F_Mask ) )
{
aErrorHandler( pad, DRCE_PADSTACK,
_( "(SMD pad copper and mask layers don't match)" ) );
}
else if( pad->IsOnLayer( F_Paste ) )
{
aErrorHandler( pad, DRCE_PADSTACK,
_( "(SMD pad copper and paste layers don't match)" ) );
}
}
}
}
}
@ -3342,9 +3255,7 @@ void FOOTPRINT::CheckNetTiePadGroups( const std::function<void( const wxString&
std::set<wxString> padNumbers;
wxString msg;
auto ret = MapPadNumbersToNetTieGroups();
for( auto [ padNumber, _ ] : ret )
for( const auto& [ padNumber, _ ] : MapPadNumbersToNetTieGroups() )
{
const PAD* pad = FindPadByNumber( padNumber );
@ -3458,9 +3369,8 @@ double FOOTPRINT::Similarity( const BOARD_ITEM& aOther ) const
double similarity = 1.0;
for( size_t ii = 0; ii < m_pads.size(); ++ii )
for( const PAD* pad : m_pads)
{
const PAD* pad = m_pads[ii];
const PAD* otherPad = other.FindPadByNumber( pad->GetNumber() );
if( !otherPad )
@ -3615,8 +3525,8 @@ bool FOOTPRINT::cmp_padstack::operator()( const PAD* aFirst, const PAD* aSecond
if( aFirst->GetLocalSolderMaskMargin() != aSecond->GetLocalSolderMaskMargin() )
return aFirst->GetLocalSolderMaskMargin() < aSecond->GetLocalSolderMaskMargin();
std::shared_ptr<SHAPE_POLY_SET> firstShape = aFirst->GetEffectivePolygon( ERROR_INSIDE );
std::shared_ptr<SHAPE_POLY_SET> secondShape = aSecond->GetEffectivePolygon( ERROR_INSIDE );
const std::shared_ptr<SHAPE_POLY_SET>& firstShape = aFirst->GetEffectivePolygon( ERROR_INSIDE );
const std::shared_ptr<SHAPE_POLY_SET>& secondShape = aSecond->GetEffectivePolygon( ERROR_INSIDE );
if( firstShape->VertexCount() != secondShape->VertexCount() )
return firstShape->VertexCount() < secondShape->VertexCount();

View File

@ -2089,27 +2089,41 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
TransformShapeToPolygon( padOutline, UNDEFINED_LAYER, 0, maxError, ERROR_INSIDE );
if( !padOutline.Collide( GetPosition() ) )
if( GetAttribute() == PAD_ATTRIB::PTH )
{
aErrorHandler( DRCE_PADSTACK, _( "Pad hole not inside pad shape" ) );
// Test if there is copper area outside hole
std::shared_ptr<SHAPE_SEGMENT> hole = GetEffectiveHoleShape();
SHAPE_POLY_SET holeOutline;
TransformOvalToPolygon( holeOutline, hole->GetSeg().A, hole->GetSeg().B,
hole->GetWidth(), ARC_HIGH_DEF, ERROR_OUTSIDE );
SHAPE_POLY_SET copper = padOutline;
copper.BooleanSubtract( holeOutline, SHAPE_POLY_SET::POLYGON_MODE::PM_FAST );
if( copper.IsEmpty() )
{
aErrorHandler( DRCE_PADSTACK, _( "(PTH pad hole leaves no copper)" ) );
}
else
{
// Test if the pad hole is fully inside the copper area
holeOutline.BooleanSubtract( padOutline, SHAPE_POLY_SET::POLYGON_MODE::PM_FAST );
if( !holeOutline.IsEmpty() )
aErrorHandler( DRCE_PADSTACK, _( "(PTH pad hole non fully inside copper)" ) );
}
}
else if( GetAttribute() == PAD_ATTRIB::PTH )
else
{
std::shared_ptr<SHAPE_SEGMENT> slot = GetEffectiveHoleShape();
SHAPE_POLY_SET slotOutline;
TransformOvalToPolygon( slotOutline, slot->GetSeg().A, slot->GetSeg().B,
slot->GetWidth(), maxError, ERROR_OUTSIDE );
padOutline.BooleanSubtract( slotOutline, SHAPE_POLY_SET::PM_FAST );
if( padOutline.IsEmpty() )
aErrorHandler( DRCE_PADSTACK, _( "Pad hole will leave no copper" ) );
// Test only if the pad hole's centre is inside the copper area
if( !padOutline.Collide( GetPosition() ) )
aErrorHandler( DRCE_PADSTACK, _( "(pad hole not inside pad shape)" ) );
}
}
if( GetLocalClearance().value_or( 0 ) < 0 )
aErrorHandler( DRCE_PADSTACK, _( "Negative local clearance values have no effect" ) );
aErrorHandler( DRCE_PADSTACK, _( "(negative local clearance values have no effect)" ) );
// Some pads need a negative solder mask clearance (mainly for BGA with small pads)
// However the negative solder mask clearance must not create negative mask size
@ -2128,9 +2142,9 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
if( absMargin > shapeBBox.GetWidth() || absMargin > shapeBBox.GetHeight() )
{
aErrorHandler( DRCE_PADSTACK, _( "Negative solder mask clearance is larger "
aErrorHandler( DRCE_PADSTACK, _( "(negative solder mask clearance is larger "
"than some shape primitives; results may be "
"surprising" ) );
"surprising)" ) );
break;
}
@ -2138,8 +2152,8 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
}
else if( absMargin > pad_size.x || absMargin > pad_size.y )
{
aErrorHandler( DRCE_PADSTACK, _( "Negative solder mask clearance is larger than pad; "
"no solder mask will be generated" ) );
aErrorHandler( DRCE_PADSTACK, _( "(negative solder mask clearance is larger than pad; "
"no solder mask will be generated)" ) );
}
}
@ -2157,8 +2171,8 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
if( paste_size.x <= 0 || paste_size.y <= 0 )
{
aErrorHandler( DRCE_PADSTACK, _( "Negative solder paste margin is larger than pad; "
"no solder paste mask will be generated" ) );
aErrorHandler( DRCE_PADSTACK, _( "(negative solder paste margin is larger than pad; "
"no solder paste mask will be generated)" ) );
}
LSET padlayers_mask = GetLayerSet();
@ -2167,14 +2181,14 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Pad has no layer)" ) );
if( GetAttribute() == PAD_ATTRIB::PTH && !IsOnCopperLayer() )
aErrorHandler( DRCE_PADSTACK, _( "PTH pad has no copper layers" ) );
aErrorHandler( DRCE_PADSTACK, _( "(PTH pad has no copper layers)" ) );
if( !padlayers_mask[F_Cu] && !padlayers_mask[B_Cu] )
{
if( ( drill_size.x || drill_size.y ) && GetAttribute() != PAD_ATTRIB::NPTH )
{
aErrorHandler( DRCE_PADSTACK, _( "Plated through holes normally have a copper pad on "
"at least one layer" ) );
aErrorHandler( DRCE_PADSTACK, _( "(plated through holes normally have a copper pad on "
"at least one layer)" ) );
}
}
@ -2192,8 +2206,8 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
case PAD_ATTRIB::CONN: // Connector pads are smd pads, just they do not have solder paste.
if( padlayers_mask[B_Paste] || padlayers_mask[F_Paste] )
{
aErrorHandler( DRCE_PADSTACK, _( "Connector pads normally have no solder paste; use a "
"SMD pad instead" ) );
aErrorHandler( DRCE_PADSTACK, _( "(connector pads normally have no solder paste; use a "
"SMD pad instead)" ) );
}
KI_FALLTHROUGH;
@ -2206,37 +2220,37 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
if( IsOnLayer( F_Cu ) && IsOnLayer( B_Cu ) )
{
aErrorHandler( DRCE_PADSTACK, _( "SMD pad has copper on both sides of the board" ) );
aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has copper on both sides of the board)" ) );
}
else if( IsOnLayer( F_Cu ) )
{
if( IsOnLayer( B_Mask ) )
{
aErrorHandler( DRCE_PADSTACK, _( "SMD pad has copper and mask layers on different "
"sides of the board" ) );
aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has copper and mask layers on different "
"sides of the board)" ) );
}
else if( IsOnLayer( B_Paste ) )
{
aErrorHandler( DRCE_PADSTACK, _( "SMD pad has copper and paste layers on different "
"sides of the board" ) );
aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has copper and paste layers on different "
"sides of the board)" ) );
}
}
else if( IsOnLayer( B_Cu ) )
{
if( IsOnLayer( F_Mask ) )
{
aErrorHandler( DRCE_PADSTACK, _( "SMD pad has copper and mask layers on different "
"sides of the board" ) );
aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has copper and mask layers on different "
"sides of the board)" ) );
}
else if( IsOnLayer( F_Paste ) )
{
aErrorHandler( DRCE_PADSTACK, _( "SMD pad has copper and paste layers on different "
"sides of the board" ) );
aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has copper and paste layers on different "
"sides of the board)" ) );
}
}
else if( innerlayers_mask.count() != 0 )
{
aErrorHandler( DRCE_PADSTACK, _( "SMD pad has no outer layers" ) );
aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has no outer layers)" ) );
}
break;
@ -2246,37 +2260,37 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
if( ( GetProperty() == PAD_PROP::FIDUCIAL_GLBL || GetProperty() == PAD_PROP::FIDUCIAL_LOCAL )
&& GetAttribute() == PAD_ATTRIB::NPTH )
{
aErrorHandler( DRCE_PADSTACK, _( "Fiducial property makes no sense on NPTH pads" ) );
aErrorHandler( DRCE_PADSTACK, _( "('fiducial' property makes no sense on NPTH pads)" ) );
}
if( GetProperty() == PAD_PROP::TESTPOINT && GetAttribute() == PAD_ATTRIB::NPTH )
aErrorHandler( DRCE_PADSTACK, _( "Testpoint property makes no sense on NPTH pads" ) );
aErrorHandler( DRCE_PADSTACK, _( "('testpoint' property makes no sense on NPTH pads)" ) );
if( GetProperty() == PAD_PROP::HEATSINK && GetAttribute() == PAD_ATTRIB::NPTH )
aErrorHandler( DRCE_PADSTACK, _( "Heatsink property makes no sense of NPTH pads" ) );
aErrorHandler( DRCE_PADSTACK, _( "('heatsink' property makes no sense of NPTH pads)" ) );
if( GetProperty() == PAD_PROP::CASTELLATED && GetAttribute() != PAD_ATTRIB::PTH )
aErrorHandler( DRCE_PADSTACK, _( "Castellated property is for PTH pads" ) );
aErrorHandler( DRCE_PADSTACK, _( "('castellated' property is for PTH pads)" ) );
if( GetProperty() == PAD_PROP::BGA && GetAttribute() != PAD_ATTRIB::SMD )
aErrorHandler( DRCE_PADSTACK, _( "BGA property is for SMD pads" ) );
aErrorHandler( DRCE_PADSTACK, _( "('BGA' property is for SMD pads)" ) );
if( GetProperty() == PAD_PROP::MECHANICAL && GetAttribute() != PAD_ATTRIB::PTH )
aErrorHandler( DRCE_PADSTACK, _( "Mechanical property is for PTH pads" ) );
aErrorHandler( DRCE_PADSTACK, _( "('mechanical' property is for PTH pads)" ) );
if( GetShape() == PAD_SHAPE::ROUNDRECT )
{
if( GetRoundRectRadiusRatio() < 0.0 )
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Negative corner radius is not allowed)" ) );
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(negative corner radius is not allowed)" ) );
else if( GetRoundRectRadiusRatio() > 50.0 )
aErrorHandler( DRCE_PADSTACK, _( "Corner size will make pad circular" ) );
aErrorHandler( DRCE_PADSTACK, _( "(corner size will make pad circular)" ) );
}
else if( GetShape() == PAD_SHAPE::CHAMFERED_RECT )
{
if( GetChamferRectRatio() < 0.0 )
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Negative corner chamfer is not allowed)" ) );
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(negative corner chamfer is not allowed)" ) );
else if( GetChamferRectRatio() > 50.0 )
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Corner chamfer is too large)" ) );
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(corner chamfer is too large)" ) );
}
else if( GetShape() == PAD_SHAPE::TRAPEZOID )
{
@ -2285,7 +2299,7 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
|| ( GetDelta().y < 0 && GetDelta().y < -GetSize().x )
|| ( GetDelta().y > 0 && GetDelta().y > GetSize().x ) )
{
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Trapazoid delta is too large)" ) );
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(trapazoid delta is too large)" ) );
}
}
@ -2296,7 +2310,7 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
MergePrimitivesAsPolygon( &mergedPolygon );
if( mergedPolygon.OutlineCount() > 1 )
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Custom pad shape must resolve to a single polygon)" ) );
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(custom pad shape must resolve to a single polygon)" ) );
}
}