From 6f3b6e3ad8c7f06a384274beb7263db85c15e124 Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Wed, 29 May 2024 15:23:46 +0100 Subject: [PATCH] Move pad checking to Footprint Checker. Fixes https://gitlab.com/kicad/code/kicad/-/issues/18102 (cherry picked from commit 15d4e114e0ed89a26268d97864169d18d8d21eea) --- pcbnew/dialogs/dialog_footprint_checker.cpp | 2 +- pcbnew/dialogs/dialog_pad_properties.cpp | 208 +-------------- pcbnew/drc/drc_item.cpp | 9 +- pcbnew/drc/drc_item.h | 2 + .../drc_test_provider_footprint_checks.cpp | 3 +- pcbnew/footprint.cpp | 8 +- pcbnew/footprint.h | 3 +- pcbnew/initpcb.cpp | 14 + pcbnew/pad.cpp | 245 ++++++++++++++++++ pcbnew/pad.h | 4 + 10 files changed, 296 insertions(+), 202 deletions(-) diff --git a/pcbnew/dialogs/dialog_footprint_checker.cpp b/pcbnew/dialogs/dialog_footprint_checker.cpp index 3c1108ed8f..6532da4405 100644 --- a/pcbnew/dialogs/dialog_footprint_checker.cpp +++ b/pcbnew/dialogs/dialog_footprint_checker.cpp @@ -151,7 +151,7 @@ void DIALOG_FOOTPRINT_CHECKER::runChecks() { 0, 0 } ); } ); - footprint->CheckPads( + footprint->CheckPads( m_frame, [&]( const PAD* aPad, int aErrorCode, const wxString& aMsg ) { errorHandler( aPad, nullptr, nullptr, aErrorCode, aMsg, aPad->GetPosition() ); diff --git a/pcbnew/dialogs/dialog_pad_properties.cpp b/pcbnew/dialogs/dialog_pad_properties.cpp index 660809de20..5450009c7f 100644 --- a/pcbnew/dialogs/dialog_pad_properties.cpp +++ b/pcbnew/dialogs/dialog_pad_properties.cpp @@ -24,6 +24,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ +#include #include #include #include @@ -1217,208 +1218,21 @@ void DIALOG_PAD_PROPERTIES::OnSetLayers( wxCommandEvent& event ) bool DIALOG_PAD_PROPERTIES::padValuesOK() { - bool error = !transferDataToPad( m_previewPad ); + transferDataToPad( m_previewPad ); wxArrayString error_msgs; wxArrayString warning_msgs; - VECTOR2I pad_size = m_previewPad->GetSize(); - VECTOR2I drill_size = m_previewPad->GetDrillSize(); - if( m_previewPad->GetShape() == PAD_SHAPE::CUSTOM ) - { - 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 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() < 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 - if( m_previewPad->GetLocalSolderMaskMargin() < 0 ) - { - int absMargin = abs( m_previewPad->GetLocalSolderMaskMargin() ); - - if( m_previewPad->GetShape() == PAD_SHAPE::CUSTOM ) - { - for( const std::shared_ptr& shape : m_previewPad->GetPrimitives() ) + m_previewPad->CheckPad( m_parentFrame, + [&]( int errorCode, const wxString& msg ) { - 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(); - double paste_ratio = m_previewPad->GetLocalSolderPasteMarginRatio(); - - 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->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( 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." ) ); + } ); if( error_msgs.GetCount() || warning_msgs.GetCount() ) { diff --git a/pcbnew/drc/drc_item.cpp b/pcbnew/drc/drc_item.cpp index bca36b03db..0752dbcac7 100644 --- a/pcbnew/drc/drc_item.cpp +++ b/pcbnew/drc/drc_item.cpp @@ -130,9 +130,13 @@ DRC_ITEM DRC_ITEM::viaDiameter( DRCE_VIA_DIAMETER, wxT( "via_diameter" ) ); DRC_ITEM DRC_ITEM::padstack( DRCE_PADSTACK, - _( "Padstack is not valid" ), + _( "" ), 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, _( "Micro via hole size out of range" ), wxT( "microvia_drill_out_of_range" ) ); @@ -317,6 +321,8 @@ std::vector> DRC_ITEM::allItemTypes( { DRC_ITEM::isolatedCopper, DRC_ITEM::footprint, 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::npthInsideCourtyard, DRC_ITEM::itemOnDisabledLayer, @@ -354,6 +360,7 @@ std::shared_ptr DRC_ITEM::Create( int aErrorCode ) case DRCE_DRILL_OUT_OF_RANGE: return std::make_shared( drillTooSmall ); case DRCE_VIA_DIAMETER: return std::make_shared( viaDiameter ); case DRCE_PADSTACK: return std::make_shared( padstack ); + case DRCE_PADSTACK_INVALID: return std::make_shared( padstackInvalid ); case DRCE_MICROVIA_DRILL_OUT_OF_RANGE: return std::make_shared( microviaDrillTooSmall ); case DRCE_OVERLAPPING_FOOTPRINTS: return std::make_shared( courtyardsOverlap ); case DRCE_MISSING_COURTYARD: return std::make_shared( missingCourtyard ); diff --git a/pcbnew/drc/drc_item.h b/pcbnew/drc/drc_item.h index e919292c2a..ab4bc22bb5 100644 --- a/pcbnew/drc/drc_item.h +++ b/pcbnew/drc/drc_item.h @@ -57,6 +57,7 @@ enum PCB_DRC_CODE { 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_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 DRCE_MISSING_COURTYARD, // footprint has no courtyard defined @@ -176,6 +177,7 @@ private: static DRC_ITEM drillTooSmall; static DRC_ITEM viaDiameter; static DRC_ITEM padstack; + static DRC_ITEM padstackInvalid; static DRC_ITEM microviaDrillTooSmall; static DRC_ITEM courtyardsOverlap; static DRC_ITEM missingCourtyard; diff --git a/pcbnew/drc/drc_test_provider_footprint_checks.cpp b/pcbnew/drc/drc_test_provider_footprint_checks.cpp index bcbe53199b..1b836370d1 100644 --- a/pcbnew/drc/drc_test_provider_footprint_checks.cpp +++ b/pcbnew/drc/drc_test_provider_footprint_checks.cpp @@ -34,6 +34,7 @@ - DRCE_OVERLAPPING_PADS, - DRCE_PAD_TH_WITH_NO_HOLE, - DRCE_PADSTACK, + - DRCE_PADSTACK_INVALID, - DRCE_FOOTPRINT (unknown or duplicate pads in net-tie pad groups), - DRCE_SHORTING_ITEMS */ @@ -97,7 +98,7 @@ bool DRC_TEST_PROVIDER_FOOTPRINT_CHECKS::Run() if( !m_drcEngine->IsErrorLimitExceeded( DRCE_PAD_TH_WITH_NO_HOLE ) || !m_drcEngine->IsErrorLimitExceeded( DRCE_PADSTACK ) ) { - footprint->CheckPads( + footprint->CheckPads( m_drcEngine, [&]( const PAD* aPad, int aErrorCode, const wxString& aMsg ) { if( !m_drcEngine->IsErrorLimitExceeded( aErrorCode ) ) diff --git a/pcbnew/footprint.cpp b/pcbnew/footprint.cpp index 9b486214ba..db65cb0213 100644 --- a/pcbnew/footprint.cpp +++ b/pcbnew/footprint.cpp @@ -2782,7 +2782,8 @@ void FOOTPRINT::CheckFootprintAttributes( const std::function& aErrorHandler ) { if( aErrorHandler == nullptr ) @@ -2790,6 +2791,11 @@ void FOOTPRINT::CheckPads( const std::functionCheckPad( aUnitsProvider, + [&]( int errorCode, const wxString& msg ) + { + 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. diff --git a/pcbnew/footprint.h b/pcbnew/footprint.h index 17d9f3f3ec..01b611958f 100644 --- a/pcbnew/footprint.h +++ b/pcbnew/footprint.h @@ -432,7 +432,8 @@ public: * * @param aErrorHandler callback to handle the error messages generated */ - void CheckPads( const std::function& aErrorHandler ); + void CheckPads( UNITS_PROVIDER* aUnitsProvider, + const std::function& aErrorHandler ); /** * Check for overlapping, different-numbered, non-net-tie pads. diff --git a/pcbnew/initpcb.cpp b/pcbnew/initpcb.cpp index 4ebcc3b8f4..0a59ecea14 100644 --- a/pcbnew/initpcb.cpp +++ b/pcbnew/initpcb.cpp @@ -141,6 +141,20 @@ bool FOOTPRINT_EDIT_FRAME::Clear_Pcb( bool doAskAboutUnsavedChanges ) // This board will only be used to hold a footprint for editing GetBoard()->SetBoardUse( BOARD_USE::FPHOLDER ); + // Setup our own severities for the Footprint Checker. + // These are not (at present) user-editable. + std::map& drcSeverities = GetBoard()->GetDesignSettings().m_DRCSeverities; + + for( int errorCode = DRCE_FIRST; errorCode <= DRCE_LAST; ++errorCode ) + drcSeverities[ errorCode ] = RPT_SEVERITY_ERROR; + + drcSeverities[ DRCE_DRILLED_HOLES_COLOCATED ] = RPT_SEVERITY_WARNING; + drcSeverities[ DRCE_DRILLED_HOLES_TOO_CLOSE ] = RPT_SEVERITY_WARNING; + + drcSeverities[ DRCE_PADSTACK ] = RPT_SEVERITY_WARNING; + + drcSeverities[ DRCE_FOOTPRINT_TYPE_MISMATCH ] = RPT_SEVERITY_WARNING; + // clear filename, to avoid overwriting an old file GetBoard()->SetFileName( wxEmptyString ); diff --git a/pcbnew/pad.cpp b/pcbnew/pad.cpp index 1fac7ce53f..3f62059fe3 100644 --- a/pcbnew/pad.cpp +++ b/pcbnew/pad.cpp @@ -53,6 +53,7 @@ #include #include +#include #include "kiface_base.h" #include "pcbnew_settings.h" @@ -1725,6 +1726,250 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, } +void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider, + const std::function& 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 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 solderMaskMargin = GetLocalSolderMaskMargin(); + + if( solderMaskMargin.has_value() && solderMaskMargin.value() < 0 ) + { + int absMargin = abs( solderMaskMargin.value() ); + + if( GetShape() == PAD_SHAPE::CUSTOM ) + { + for( const std::shared_ptr& 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 { if( Type() != aOther.Type() ) diff --git a/pcbnew/pad.h b/pcbnew/pad.h index 247cc34314..f049204b2c 100644 --- a/pcbnew/pad.h +++ b/pcbnew/pad.h @@ -721,6 +721,10 @@ public: m_zoneLayerOverrides.at( aLayer ) = aOverride; } + void CheckPad( UNITS_PROVIDER* aUnitsProvider, + const std::function& aErrorHandler ) const; + double Similarity( const BOARD_ITEM& aOther ) const override; bool operator==( const BOARD_ITEM& aOther ) const override;