Move pad checking to Footprint Checker.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/18102
This commit is contained in:
Jeff Young 2024-05-29 15:23:46 +01:00
parent cc350cf279
commit 15d4e114e0
10 changed files with 283 additions and 211 deletions

View File

@ -151,7 +151,7 @@ void DIALOG_FOOTPRINT_CHECKER::runChecks()
{ 0, 0 } ); { 0, 0 } );
} ); } );
footprint->CheckPads( footprint->CheckPads( m_frame,
[&]( const PAD* aPad, int aErrorCode, const wxString& aMsg ) [&]( const PAD* aPad, int aErrorCode, const wxString& aMsg )
{ {
errorHandler( aPad, nullptr, nullptr, aErrorCode, aMsg, aPad->GetPosition() ); errorHandler( aPad, nullptr, nullptr, aErrorCode, aMsg, aPad->GetPosition() );

View File

@ -24,6 +24,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
#include <drc/drc_item.h>
#include <base_units.h> #include <base_units.h>
#include <bitmaps.h> #include <bitmaps.h>
#include <board_commit.h> #include <board_commit.h>
@ -1258,216 +1259,21 @@ void DIALOG_PAD_PROPERTIES::OnSetLayers( wxCommandEvent& event )
bool DIALOG_PAD_PROPERTIES::padValuesOK() bool DIALOG_PAD_PROPERTIES::padValuesOK()
{ {
bool error = !transferDataToPad( m_previewPad ); transferDataToPad( m_previewPad );
wxArrayString error_msgs; wxArrayString error_msgs;
wxArrayString warning_msgs; wxArrayString warning_msgs;
VECTOR2I pad_size = m_previewPad->GetSize();
VECTOR2I drill_size = m_previewPad->GetDrillSize();
if( m_previewPad->GetShape() == PAD_SHAPE::CUSTOM ) m_previewPad->CheckPad( m_parentFrame,
{ [&]( int errorCode, const wxString& msg )
pad_size = m_previewPad->GetBoundingBox().GetSize();
}
else if( m_previewPad->GetShape() == PAD_SHAPE::CIRCLE )
{
if( pad_size.x <= 0 )
error_msgs.Add( _( "Error: Pad must have a positive size." ) );
}
else
{
if( pad_size.x <= 0 || pad_size.y <= 0 )
error_msgs.Add( _( "Error: Pad must have a positive size." ) );
}
// Test hole against pad shape
if( m_previewPad->IsOnCopperLayer() && m_previewPad->GetDrillSize().x > 0 )
{
int maxError = m_board->GetDesignSettings().m_MaxError;
SHAPE_POLY_SET padOutline;
m_previewPad->TransformShapeToPolygon( padOutline, UNDEFINED_LAYER, 0, maxError,
ERROR_INSIDE );
if( !padOutline.Collide( m_previewPad->GetPosition() ) )
{
warning_msgs.Add( _( "Warning: Pad hole not inside pad shape." ) );
}
else if( m_previewPad->GetAttribute() == PAD_ATTRIB::PTH )
{
std::shared_ptr<SHAPE_SEGMENT> slot = m_previewPad->GetEffectiveHoleShape();
SHAPE_POLY_SET slotOutline;
TransformOvalToPolygon( slotOutline, slot->GetSeg().A, slot->GetSeg().B,
slot->GetWidth(), maxError, ERROR_INSIDE );
padOutline.BooleanSubtract( slotOutline, SHAPE_POLY_SET::PM_FAST );
if( padOutline.IsEmpty() )
warning_msgs.Add( _( "Warning: Pad hole will leave no copper." ) );
}
}
if( m_previewPad->GetLocalClearance().value_or( 0 ) < 0 )
warning_msgs.Add( _( "Warning: Negative local clearance values will 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
// Therefore test for minimal acceptable negative value
std::optional<int> solderMaskMargin = m_previewPad->GetLocalSolderMaskMargin();
if( solderMaskMargin.has_value() && solderMaskMargin.value() < 0 )
{
int absMargin = abs( solderMaskMargin.value() );
if( m_previewPad->GetShape() == PAD_SHAPE::CUSTOM )
{
for( const std::shared_ptr<PCB_SHAPE>& shape : m_previewPad->GetPrimitives() )
{ {
BOX2I shapeBBox = shape->GetBoundingBox(); if( errorCode == DRCE_PADSTACK_INVALID )
error_msgs.Add( _( "Error: " ) + msg );
if( absMargin > shapeBBox.GetWidth() || absMargin > shapeBBox.GetHeight() ) else if( errorCode == DRCE_PADSTACK )
{ warning_msgs.Add( _( "Warning: " ) + msg );
warning_msgs.Add( _( "Warning: Negative solder mask clearances larger than " else if( errorCode == DRCE_PAD_TH_WITH_NO_HOLE )
"some shape primitives. Results may be surprising." ) ); error_msgs.Add( _( "Error: Through hole pad has no hole." ) );
} );
break;
}
}
}
else if( absMargin > pad_size.x || absMargin > pad_size.y )
{
warning_msgs.Add( _( "Warning: Negative solder mask clearance larger than pad. No "
"solder mask will be generated." ) );
}
}
// Some pads need a positive solder paste clearance (mainly for BGA with small pads)
// However, a positive value can create issues if the resulting shape is too big.
// (like a solder paste creating a solder paste area on a neighbor pad or on the solder mask)
// So we could ask for user to confirm the choice
// For now we just check for disappearing paste
wxSize paste_size;
int paste_margin = m_previewPad->GetLocalSolderPasteMargin().value_or( 0 );
double paste_ratio = m_previewPad->GetLocalSolderPasteMarginRatio().value_or( 0 );
paste_size.x = pad_size.x + paste_margin + KiROUND( pad_size.x * paste_ratio );
paste_size.y = pad_size.y + paste_margin + KiROUND( pad_size.y * paste_ratio );
if( paste_size.x <= 0 || paste_size.y <= 0 )
{
warning_msgs.Add( _( "Warning: Negative solder paste margins larger than pad. No solder "
"paste mask will be generated." ) );
}
LSET padlayers_mask = m_previewPad->GetLayerSet();
if( padlayers_mask == 0 )
error_msgs.Add( _( "Error: pad has no layer." ) );
if( !padlayers_mask[F_Cu] && !padlayers_mask[B_Cu] )
{
if( ( drill_size.x || drill_size.y ) && m_previewPad->GetAttribute() != PAD_ATTRIB::NPTH )
{
warning_msgs.Add( _( "Warning: Plated through holes should normally have a copper pad "
"on at least one layer." ) );
}
}
if( error )
error_msgs.Add( _( "Error: Trapazoid delta is too large." ) );
switch( m_previewPad->GetAttribute() )
{
case PAD_ATTRIB::NPTH: // Not plated, but through hole, a hole is expected
case PAD_ATTRIB::PTH: // Pad through hole, a hole is also expected
if( drill_size.x <= 0
|| ( drill_size.y <= 0 && m_previewPad->GetDrillShape() == PAD_DRILL_SHAPE::OBLONG ) )
{
error_msgs.Add( _( "Error: Through hole pad has no hole." ) );
}
break;
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] )
{
warning_msgs.Add( _( "Warning: Connector pads normally have no solder paste. Use a "
"SMD pad instead." ) );
}
KI_FALLTHROUGH;
case PAD_ATTRIB::SMD: // SMD and Connector pads (One external copper layer only)
{
if( drill_size.x > 0 || drill_size.y > 0 )
{
error_msgs.Add( _( "Error: SMD pad has a hole." ) );
}
LSET innerlayers_mask = padlayers_mask & LSET::InternalCuMask();
if( ( padlayers_mask[F_Cu] && padlayers_mask[B_Cu] ) || innerlayers_mask.count() != 0 )
warning_msgs.Add( _( "Warning: SMD pad has no outer layers." ) );
}
break;
}
if( ( m_previewPad->GetProperty() == PAD_PROP::FIDUCIAL_GLBL || m_previewPad->GetProperty() == PAD_PROP::FIDUCIAL_LOCAL )
&& m_previewPad->GetAttribute() == PAD_ATTRIB::NPTH )
{
warning_msgs.Add( _( "Warning: Fiducial property makes no sense on NPTH pads." ) );
}
if( m_previewPad->GetProperty() == PAD_PROP::TESTPOINT
&& m_previewPad->GetAttribute() == PAD_ATTRIB::NPTH )
{
warning_msgs.Add( _( "Warning: Testpoint property makes no sense on NPTH pads." ) );
}
if( m_previewPad->GetProperty() == PAD_PROP::HEATSINK
&& m_previewPad->GetAttribute() == PAD_ATTRIB::NPTH )
{
warning_msgs.Add( _( "Warning: Heatsink property makes no sense of NPTH pads." ) );
}
if( m_previewPad->GetProperty() == PAD_PROP::CASTELLATED
&& m_previewPad->GetAttribute() != PAD_ATTRIB::PTH )
{
warning_msgs.Add( _( "Warning: Castellated property is for PTH pads." ) );
}
if( m_previewPad->GetProperty() == PAD_PROP::BGA
&& m_previewPad->GetAttribute() != PAD_ATTRIB::SMD )
{
warning_msgs.Add( _( "Warning: BGA property is for SMD pads." ) );
}
if( m_previewPad->GetProperty() == PAD_PROP::MECHANICAL
&& m_previewPad->GetAttribute() != PAD_ATTRIB::PTH )
{
warning_msgs.Add( _( "Warning: Mechanical property is for PTH pads." ) );
}
if( m_previewPad->GetShape() == PAD_SHAPE::ROUNDRECT
|| m_previewPad->GetShape() == PAD_SHAPE::CHAMFERED_RECT )
{
wxASSERT( m_cornerRatio.GetDoubleValue() == m_mixedCornerRatio.GetDoubleValue() );
if( m_cornerRatio.GetDoubleValue() < 0.0 )
error_msgs.Add( _( "Error: Negative corner size." ) );
else if( m_cornerRatio.GetDoubleValue() > 50.0 )
warning_msgs.Add( _( "Warning: Corner size will make pad circular." ) );
}
// PADSTACKS TODO: this will need to check each layer in the pad...
if( m_previewPad->GetShape() == PAD_SHAPE::CUSTOM )
{
SHAPE_POLY_SET mergedPolygon;
m_previewPad->MergePrimitivesAsPolygon( &mergedPolygon );
if( mergedPolygon.OutlineCount() > 1 )
error_msgs.Add( _( "Error: Custom pad shape must resolve to a single polygon." ) );
}
if( error_msgs.GetCount() || warning_msgs.GetCount() ) if( error_msgs.GetCount() || warning_msgs.GetCount() )
{ {

View File

@ -130,9 +130,13 @@ DRC_ITEM DRC_ITEM::viaDiameter( DRCE_VIA_DIAMETER,
wxT( "via_diameter" ) ); wxT( "via_diameter" ) );
DRC_ITEM DRC_ITEM::padstack( DRCE_PADSTACK, DRC_ITEM DRC_ITEM::padstack( DRCE_PADSTACK,
_( "Padstack is not valid" ), _( "" ),
wxT( "padstack" ) ); wxT( "padstack" ) );
DRC_ITEM DRC_ITEM::padstackInvalid( DRCE_PADSTACK_INVALID,
_( "Padstack is not valid" ),
wxT( "padstack_invalid" ) );
DRC_ITEM DRC_ITEM::microviaDrillTooSmall( DRCE_MICROVIA_DRILL_OUT_OF_RANGE, DRC_ITEM DRC_ITEM::microviaDrillTooSmall( DRCE_MICROVIA_DRILL_OUT_OF_RANGE,
_( "Micro via hole size out of range" ), _( "Micro via hole size out of range" ),
wxT( "microvia_drill_out_of_range" ) ); wxT( "microvia_drill_out_of_range" ) );
@ -317,6 +321,8 @@ std::vector<std::reference_wrapper<RC_ITEM>> DRC_ITEM::allItemTypes( {
DRC_ITEM::isolatedCopper, DRC_ITEM::isolatedCopper,
DRC_ITEM::footprint, DRC_ITEM::footprint,
DRC_ITEM::padstack, DRC_ITEM::padstack,
// Do not include padstackInvalid; it flags invalid states so must always be an error
// DRC_ITEM::padStackInvalid;
DRC_ITEM::pthInsideCourtyard, DRC_ITEM::pthInsideCourtyard,
DRC_ITEM::npthInsideCourtyard, DRC_ITEM::npthInsideCourtyard,
DRC_ITEM::itemOnDisabledLayer, DRC_ITEM::itemOnDisabledLayer,
@ -354,6 +360,7 @@ std::shared_ptr<DRC_ITEM> DRC_ITEM::Create( int aErrorCode )
case DRCE_DRILL_OUT_OF_RANGE: return std::make_shared<DRC_ITEM>( drillTooSmall ); case DRCE_DRILL_OUT_OF_RANGE: return std::make_shared<DRC_ITEM>( drillTooSmall );
case DRCE_VIA_DIAMETER: return std::make_shared<DRC_ITEM>( viaDiameter ); case DRCE_VIA_DIAMETER: return std::make_shared<DRC_ITEM>( viaDiameter );
case DRCE_PADSTACK: return std::make_shared<DRC_ITEM>( padstack ); case DRCE_PADSTACK: return std::make_shared<DRC_ITEM>( padstack );
case DRCE_PADSTACK_INVALID: return std::make_shared<DRC_ITEM>( padstackInvalid );
case DRCE_MICROVIA_DRILL_OUT_OF_RANGE: return std::make_shared<DRC_ITEM>( microviaDrillTooSmall ); case DRCE_MICROVIA_DRILL_OUT_OF_RANGE: return std::make_shared<DRC_ITEM>( microviaDrillTooSmall );
case DRCE_OVERLAPPING_FOOTPRINTS: return std::make_shared<DRC_ITEM>( courtyardsOverlap ); case DRCE_OVERLAPPING_FOOTPRINTS: return std::make_shared<DRC_ITEM>( courtyardsOverlap );
case DRCE_MISSING_COURTYARD: return std::make_shared<DRC_ITEM>( missingCourtyard ); case DRCE_MISSING_COURTYARD: return std::make_shared<DRC_ITEM>( missingCourtyard );

View File

@ -57,6 +57,7 @@ enum PCB_DRC_CODE {
DRCE_DRILL_OUT_OF_RANGE, // Too small via or pad drill DRCE_DRILL_OUT_OF_RANGE, // Too small via or pad drill
DRCE_VIA_DIAMETER, // Via diameter checks (min/max) DRCE_VIA_DIAMETER, // Via diameter checks (min/max)
DRCE_PADSTACK, // something is wrong with a pad or via stackup DRCE_PADSTACK, // something is wrong 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_MICROVIA_DRILL_OUT_OF_RANGE, // Too small micro via drill
DRCE_OVERLAPPING_FOOTPRINTS, // footprint courtyards overlap DRCE_OVERLAPPING_FOOTPRINTS, // footprint courtyards overlap
DRCE_MISSING_COURTYARD, // footprint has no courtyard defined DRCE_MISSING_COURTYARD, // footprint has no courtyard defined
@ -176,6 +177,7 @@ private:
static DRC_ITEM drillTooSmall; static DRC_ITEM drillTooSmall;
static DRC_ITEM viaDiameter; static DRC_ITEM viaDiameter;
static DRC_ITEM padstack; static DRC_ITEM padstack;
static DRC_ITEM padstackInvalid;
static DRC_ITEM microviaDrillTooSmall; static DRC_ITEM microviaDrillTooSmall;
static DRC_ITEM courtyardsOverlap; static DRC_ITEM courtyardsOverlap;
static DRC_ITEM missingCourtyard; static DRC_ITEM missingCourtyard;

View File

@ -34,6 +34,7 @@
- DRCE_OVERLAPPING_PADS, - DRCE_OVERLAPPING_PADS,
- DRCE_PAD_TH_WITH_NO_HOLE, - DRCE_PAD_TH_WITH_NO_HOLE,
- DRCE_PADSTACK, - DRCE_PADSTACK,
- DRCE_PADSTACK_INVALID,
- DRCE_FOOTPRINT (unknown or duplicate pads in net-tie pad groups), - DRCE_FOOTPRINT (unknown or duplicate pads in net-tie pad groups),
- DRCE_SHORTING_ITEMS - DRCE_SHORTING_ITEMS
*/ */
@ -97,7 +98,7 @@ bool DRC_TEST_PROVIDER_FOOTPRINT_CHECKS::Run()
if( !m_drcEngine->IsErrorLimitExceeded( DRCE_PAD_TH_WITH_NO_HOLE ) if( !m_drcEngine->IsErrorLimitExceeded( DRCE_PAD_TH_WITH_NO_HOLE )
|| !m_drcEngine->IsErrorLimitExceeded( DRCE_PADSTACK ) ) || !m_drcEngine->IsErrorLimitExceeded( DRCE_PADSTACK ) )
{ {
footprint->CheckPads( footprint->CheckPads( m_drcEngine,
[&]( const PAD* aPad, int aErrorCode, const wxString& aMsg ) [&]( const PAD* aPad, int aErrorCode, const wxString& aMsg )
{ {
if( !m_drcEngine->IsErrorLimitExceeded( aErrorCode ) ) if( !m_drcEngine->IsErrorLimitExceeded( aErrorCode ) )

View File

@ -3030,7 +3030,8 @@ void FOOTPRINT::CheckFootprintAttributes( const std::function<void( const wxStri
} }
void FOOTPRINT::CheckPads( const std::function<void( const PAD*, int, void FOOTPRINT::CheckPads( UNITS_PROVIDER* aUnitsProvider,
const std::function<void( const PAD*, int,
const wxString& )>& aErrorHandler ) const wxString& )>& aErrorHandler )
{ {
if( aErrorHandler == nullptr ) if( aErrorHandler == nullptr )
@ -3038,6 +3039,11 @@ void FOOTPRINT::CheckPads( const std::function<void( const PAD*, int,
for( PAD* pad: Pads() ) for( PAD* pad: Pads() )
{ {
pad->CheckPad( aUnitsProvider,
[&]( int errorCode, const wxString& msg )
{
aErrorHandler( pad, errorCode, msg );
} );
if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH ) if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
{ {
// Ensure the drill size can be handled in next calculations. // Ensure the drill size can be handled in next calculations.

View File

@ -454,7 +454,8 @@ public:
* *
* @param aErrorHandler callback to handle the error messages generated * @param aErrorHandler callback to handle the error messages generated
*/ */
void CheckPads( const std::function<void( const PAD*, int, const wxString& )>& aErrorHandler ); void CheckPads( UNITS_PROVIDER* aUnitsProvider,
const std::function<void( const PAD*, int, const wxString& )>& aErrorHandler );
/** /**
* Check for overlapping, different-numbered, non-net-tie pads. * Check for overlapping, different-numbered, non-net-tie pads.

View File

@ -58,6 +58,7 @@
#include <memory> #include <memory>
#include <macros.h> #include <macros.h>
#include <magic_enum.hpp> #include <magic_enum.hpp>
#include <drc/drc_item.h>
#include "kiface_base.h" #include "kiface_base.h"
#include "pcbnew_settings.h" #include "pcbnew_settings.h"
@ -1901,6 +1902,250 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer,
} }
void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
const std::function<void( int aErrorCode,
const wxString& aMsg )>& aErrorHandler ) const
{
VECTOR2I pad_size = GetSize();
VECTOR2I drill_size = GetDrillSize();
wxString msg;
if( GetShape() == PAD_SHAPE::CUSTOM )
pad_size = GetBoundingBox().GetSize();
else if( pad_size.x <= 0 || ( pad_size.y <= 0 && GetShape() != PAD_SHAPE::CIRCLE ) )
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Pad must have a positive size)" ) );
// Test hole against pad shape
if( IsOnCopperLayer() && GetDrillSize().x > 0 )
{
// 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( GetDrillSizeX() <= min_drill_size || GetDrillSizeY() <= min_drill_size )
{
msg.Printf( _( "(PTH pad hole size must be larger than %s)" ),
aUnitsProvider->StringFromValue( min_drill_size, true ) );
aErrorHandler( DRCE_PADSTACK_INVALID, msg );
}
int maxError = GetBoard()->GetDesignSettings().m_MaxError;
SHAPE_POLY_SET padOutline;
TransformShapeToPolygon( padOutline, UNDEFINED_LAYER, 0, maxError, ERROR_INSIDE );
if( !padOutline.Collide( GetPosition() ) )
{
aErrorHandler( DRCE_PADSTACK, _( "Pad hole not inside pad shape" ) );
}
else if( GetAttribute() == PAD_ATTRIB::PTH )
{
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" ) );
}
}
if( GetLocalClearance().value_or( 0 ) < 0 )
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
// Therefore test for minimal acceptable negative value
std::optional<int> solderMaskMargin = GetLocalSolderMaskMargin();
if( solderMaskMargin.has_value() && solderMaskMargin.value() < 0 )
{
int absMargin = abs( solderMaskMargin.value() );
if( GetShape() == PAD_SHAPE::CUSTOM )
{
for( const std::shared_ptr<PCB_SHAPE>& shape : GetPrimitives() )
{
BOX2I shapeBBox = shape->GetBoundingBox();
if( absMargin > shapeBBox.GetWidth() || absMargin > shapeBBox.GetHeight() )
{
aErrorHandler( DRCE_PADSTACK, _( "Negative solder mask clearance is larger "
"than some shape primitives; results may be "
"surprising" ) );
break;
}
}
}
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" ) );
}
}
// Some pads need a positive solder paste clearance (mainly for BGA with small pads)
// However, a positive value can create issues if the resulting shape is too big.
// (like a solder paste creating a solder paste area on a neighbor pad or on the solder mask)
// So we could ask for user to confirm the choice
// For now we just check for disappearing paste
wxSize paste_size;
int paste_margin = GetLocalSolderPasteMargin().value_or( 0 );
double paste_ratio = GetLocalSolderPasteMarginRatio().value_or( 0 );
paste_size.x = pad_size.x + paste_margin + KiROUND( pad_size.x * paste_ratio );
paste_size.y = pad_size.y + paste_margin + KiROUND( pad_size.y * paste_ratio );
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" ) );
}
LSET padlayers_mask = GetLayerSet();
if( padlayers_mask == 0 )
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Pad has no layer)" ) );
if( GetAttribute() == PAD_ATTRIB::PTH && !IsOnCopperLayer() )
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" ) );
}
}
switch( GetAttribute() )
{
case PAD_ATTRIB::NPTH: // Not plated, but through hole, a hole is expected
case PAD_ATTRIB::PTH: // Pad through hole, a hole is also expected
if( drill_size.x <= 0
|| ( drill_size.y <= 0 && GetDrillShape() == PAD_DRILL_SHAPE::OBLONG ) )
{
aErrorHandler( DRCE_PAD_TH_WITH_NO_HOLE, wxEmptyString );
}
break;
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" ) );
}
KI_FALLTHROUGH;
case PAD_ATTRIB::SMD: // SMD and Connector pads (One external copper layer only)
{
if( drill_size.x > 0 || drill_size.y > 0 )
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(SMD pad has a hole)" ) );
LSET innerlayers_mask = padlayers_mask & LSET::InternalCuMask();
if( IsOnLayer( F_Cu ) && IsOnLayer( B_Cu ) )
{
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" ) );
}
else if( IsOnLayer( B_Paste ) )
{
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" ) );
}
else if( IsOnLayer( F_Paste ) )
{
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" ) );
}
break;
}
}
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" ) );
}
if( GetProperty() == PAD_PROP::TESTPOINT && GetAttribute() == PAD_ATTRIB::NPTH )
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" ) );
if( GetProperty() == PAD_PROP::CASTELLATED && GetAttribute() != PAD_ATTRIB::PTH )
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" ) );
if( GetProperty() == PAD_PROP::MECHANICAL && GetAttribute() != PAD_ATTRIB::PTH )
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)" ) );
else if( GetRoundRectRadiusRatio() > 50.0 )
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)" ) );
else if( GetChamferRectRatio() > 50.0 )
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Corner chamfer is too large)" ) );
}
else if( GetShape() == PAD_SHAPE::TRAPEZOID )
{
if( ( GetDelta().x < 0 && GetDelta().x < -GetSize().y )
|| ( GetDelta().x > 0 && GetDelta().x > GetSize().y )
|| ( GetDelta().y < 0 && GetDelta().y < -GetSize().x )
|| ( GetDelta().y > 0 && GetDelta().y > GetSize().x ) )
{
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Trapazoid delta is too large)" ) );
}
}
// PADSTACKS TODO: this will need to check each layer in the pad...
if( GetShape() == PAD_SHAPE::CUSTOM )
{
SHAPE_POLY_SET mergedPolygon;
MergePrimitivesAsPolygon( &mergedPolygon );
if( mergedPolygon.OutlineCount() > 1 )
aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Custom pad shape must resolve to a single polygon)" ) );
}
}
bool PAD::operator==( const BOARD_ITEM& aOther ) const bool PAD::operator==( const BOARD_ITEM& aOther ) const
{ {
if( Type() != aOther.Type() ) if( Type() != aOther.Type() )

View File

@ -814,6 +814,10 @@ public:
m_zoneLayerOverrides.at( aLayer ) = aOverride; m_zoneLayerOverrides.at( aLayer ) = aOverride;
} }
void CheckPad( UNITS_PROVIDER* aUnitsProvider,
const std::function<void( int aErrorCode,
const wxString& aMsg )>& aErrorHandler ) const;
double Similarity( const BOARD_ITEM& aOther ) const override; double Similarity( const BOARD_ITEM& aOther ) const override;
bool operator==( const BOARD_ITEM& aOther ) const override; bool operator==( const BOARD_ITEM& aOther ) const override;