Move pad checking to Footprint Checker.
Fixes https://gitlab.com/kicad/code/kicad/-/issues/18102
This commit is contained in:
parent
cc350cf279
commit
15d4e114e0
|
@ -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() );
|
||||||
|
|
|
@ -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( absMargin > shapeBBox.GetWidth() || absMargin > shapeBBox.GetHeight() )
|
|
||||||
{
|
|
||||||
warning_msgs.Add( _( "Warning: Negative solder mask clearances larger than "
|
|
||||||
"some shape primitives. Results may be surprising." ) );
|
|
||||||
|
|
||||||
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 ) )
|
|
||||||
{
|
{
|
||||||
|
if( errorCode == DRCE_PADSTACK_INVALID )
|
||||||
|
error_msgs.Add( _( "Error: " ) + msg );
|
||||||
|
else if( errorCode == DRCE_PADSTACK )
|
||||||
|
warning_msgs.Add( _( "Warning: " ) + msg );
|
||||||
|
else if( errorCode == DRCE_PAD_TH_WITH_NO_HOLE )
|
||||||
error_msgs.Add( _( "Error: Through hole pad has no hole." ) );
|
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() )
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 ) )
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
245
pcbnew/pad.cpp
245
pcbnew/pad.cpp
|
@ -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() )
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue