From a170e86fbd688e18e4d08aec5e7b8b0e24cd7d15 Mon Sep 17 00:00:00 2001 From: Jon Evans Date: Sat, 9 Nov 2024 10:33:23 -0500 Subject: [PATCH] Support padstacks in annular width and edge clearance DRC --- pcbnew/drc/drc_rtree.h | 4 + .../drc/drc_test_provider_annular_width.cpp | 300 ++++++++++-------- .../drc/drc_test_provider_edge_clearance.cpp | 69 ++-- pcbnew/pad.cpp | 7 + pcbnew/padstack.cpp | 44 ++- pcbnew/padstack.h | 2 + 6 files changed, 266 insertions(+), 160 deletions(-) diff --git a/pcbnew/drc/drc_rtree.h b/pcbnew/drc/drc_rtree.h index ac93e71022..2d745c6f48 100644 --- a/pcbnew/drc/drc_rtree.h +++ b/pcbnew/drc/drc_rtree.h @@ -124,6 +124,8 @@ public: std::vector subshapes; std::shared_ptr shape = aItem->GetEffectiveShape( aRefLayer ); + wxCHECK2_MSG( shape, return, wxT( "Item does not have a valid shape for this layer" ) ); + if( shape->HasIndexableSubshapes() ) shape->GetIndexableSubshapes( subshapes ); else @@ -259,6 +261,8 @@ public: if( filtered ) return true; + wxCHECK( aItem->shape, false ); + if( refShape->Collide( aItem->shape, aClearance ) ) { collidingCompounds.insert( aItem->parent ); diff --git a/pcbnew/drc/drc_test_provider_annular_width.cpp b/pcbnew/drc/drc_test_provider_annular_width.cpp index 0050f1c7a4..b9050adce7 100644 --- a/pcbnew/drc/drc_test_provider_annular_width.cpp +++ b/pcbnew/drc/drc_test_provider_annular_width.cpp @@ -41,7 +41,6 @@ Todo: - check pad holes too. - - pad stack support (different IAR/OAR values depending on layer) */ /** @@ -193,7 +192,7 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() int maxError = m_drcEngine->GetBoard()->GetDesignSettings().m_MaxError; auto calcEffort = - []( BOARD_ITEM* item ) + []( BOARD_ITEM* item ) -> size_t { switch( item->Type() ) { @@ -202,34 +201,42 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() case PCB_PAD_T: { - // TODO(JE) padstacks PAD* pad = static_cast( item ); if( !pad->HasHole() || pad->GetAttribute() != PAD_ATTRIB::PTH ) return 0; - if( pad->GetOffset( PADSTACK::ALL_LAYERS ) == VECTOR2I( 0, 0 ) ) - { - switch( pad->GetShape( PADSTACK::ALL_LAYERS ) ) + size_t effort = 0; + + pad->Padstack().ForEachUniqueLayer( + [&pad, &effort]( PCB_LAYER_ID aLayer ) { - case PAD_SHAPE::CHAMFERED_RECT: - if( pad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) > 0.30 ) - break; + if( pad->GetOffset( aLayer ) == VECTOR2I( 0, 0 ) ) + { + switch( pad->GetShape( aLayer ) ) + { + case PAD_SHAPE::CHAMFERED_RECT: + if( pad->GetChamferRectRatio( aLayer ) > 0.30 ) + break; - KI_FALLTHROUGH; + KI_FALLTHROUGH; - case PAD_SHAPE::CIRCLE: - case PAD_SHAPE::OVAL: - case PAD_SHAPE::RECTANGLE: - case PAD_SHAPE::ROUNDRECT: - return 1; + case PAD_SHAPE::CIRCLE: + case PAD_SHAPE::OVAL: + case PAD_SHAPE::RECTANGLE: + case PAD_SHAPE::ROUNDRECT: + effort += 1; + break; - default: - break; - } - } + default: + break; + } + } - return 5; + effort += 5; + } ); + + return effort; } default: @@ -243,11 +250,11 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() if( m_drcEngine->IsErrorLimitExceeded( DRCE_ANNULAR_WIDTH ) ) return false; - // PADSTACKS TODO: once we have padstacks we'll need to run this per-layer.... auto constraint = m_drcEngine->EvalRules( ANNULAR_WIDTH_CONSTRAINT, item, nullptr, UNDEFINED_LAYER ); - int annularWidth = 0; + int minAnnularWidth = INT_MAX; + int maxAnnularWidth = 0; int v_min = 0; int v_max = 0; bool fail_min = false; @@ -259,8 +266,15 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() case PCB_VIA_T: { PCB_VIA* via = static_cast( item ); - // TODO(JE) padstacks - annularWidth = ( via->GetWidth( PADSTACK::ALL_LAYERS ) - via->GetDrillValue() ) / 2; + int drill = via->GetDrillValue(); + + via->Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + int layerWidth = ( via->GetWidth( aLayer ) - drill ) / 2; + minAnnularWidth = std::min( minAnnularWidth, layerWidth ); + maxAnnularWidth = std::max( maxAnnularWidth, layerWidth ); + } ); break; } @@ -279,124 +293,142 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() if( fp ) sameNumPads = fp->GetPads( pad->GetNumber(), pad ); - if( pad->GetOffset( PADSTACK::ALL_LAYERS ) == VECTOR2I( 0, 0 ) ) - { - switch( pad->GetShape( PADSTACK::ALL_LAYERS ) ) + pad->Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) { - case PAD_SHAPE::CIRCLE: - annularWidth = ( pad->GetSizeX() - pad->GetDrillSizeX() ) / 2; + int annularWidth = 0; - // If there are more pads with the same number. Check to see if the - // pad is embedded inside another pad with the same number below. - if( sameNumPads.empty() ) - handled = true; - - break; - - case PAD_SHAPE::CHAMFERED_RECT: - if( pad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) > 0.30 ) - break; - - KI_FALLTHROUGH; - - case PAD_SHAPE::OVAL: - case PAD_SHAPE::RECTANGLE: - case PAD_SHAPE::ROUNDRECT: - annularWidth = std::min( pad->GetSizeX() - pad->GetDrillSizeX(), - pad->GetSizeY() - pad->GetDrillSizeY() ) / 2; - - // If there are more pads with the same number. Check to see if the - // pad is embedded inside another pad with the same number below. - if( sameNumPads.empty() ) - handled = true; - - break; - - default: - break; - } - } - - if( !handled ) - { - // Slow (but general purpose) method. - SEG::ecoord dist_sq; - SHAPE_POLY_SET padOutline; - std::shared_ptr slot = pad->GetEffectiveHoleShape(); - - // TODO(JE) padstacks - pad->TransformShapeToPolygon( padOutline, PADSTACK::ALL_LAYERS, 0, maxError, - ERROR_INSIDE ); - - if( sameNumPads.empty() ) - { - if( !padOutline.Collide( pad->GetPosition() ) ) + if( pad->GetOffset( aLayer ) == VECTOR2I( 0, 0 ) ) { - // Hole outside pad - annularWidth = 0; - } - else - { - // Disable is-inside test in SquaredDistance - padOutline.Outline( 0 ).SetClosed( false ); + VECTOR2I padSize = pad->GetSize( aLayer ); - dist_sq = padOutline.SquaredDistanceToSeg( slot->GetSeg() ); - annularWidth = sqrt( dist_sq ) - slot->GetWidth() / 2; - } - } - else if( constraint.Value().HasMin() - && ( annularWidth < constraint.Value().Min() ) ) - { - SHAPE_POLY_SET otherPadOutline; - SHAPE_POLY_SET slotPolygon; - - slot->TransformToPolygon( slotPolygon, 0, ERROR_INSIDE ); - - for( const PAD* sameNumPad : sameNumPads ) - { - // Construct the full pad with outline and hole. - sameNumPad->TransformShapeToPolygon( otherPadOutline, - PADSTACK::ALL_LAYERS, 0, maxError, - ERROR_OUTSIDE ); - - sameNumPad->TransformHoleToPolygon( otherPadOutline, 0, maxError, - ERROR_INSIDE ); - - - // If the pad hole under test intersects with another pad outline, - // the annular width calculated above is used. - bool intersects = false; - - for( int i = 0; i < otherPadOutline.OutlineCount() && !intersects; i++ ) + switch( pad->GetShape( aLayer ) ) { - intersects |= slotPolygon.COutline( 0 ).Intersects( otherPadOutline.COutline( i ) ); - if( intersects ) - continue; + case PAD_SHAPE::CIRCLE: + annularWidth = ( padSize.x - pad->GetDrillSizeX() ) / 2; - for( int j = 0; j < otherPadOutline.HoleCount( i ) && !intersects; j++ ) + // If there are more pads with the same number. Check to see if the + // pad is embedded inside another pad with the same number below. + if( sameNumPads.empty() ) + handled = true; + + break; + + case PAD_SHAPE::CHAMFERED_RECT: + if( pad->GetChamferRectRatio( aLayer ) > 0.30 ) + break; + + KI_FALLTHROUGH; + + case PAD_SHAPE::OVAL: + case PAD_SHAPE::RECTANGLE: + case PAD_SHAPE::ROUNDRECT: + annularWidth = std::min( padSize.x - pad->GetDrillSizeX(), + padSize.y - pad->GetDrillSizeY() ) / 2; + + // If there are more pads with the same number. Check to see if the + // pad is embedded inside another pad with the same number below. + if( sameNumPads.empty() ) + handled = true; + + break; + + default: + break; + } + } + + if( !handled ) + { + // Slow (but general purpose) method. + SEG::ecoord dist_sq; + SHAPE_POLY_SET padOutline; + std::shared_ptr slot = pad->GetEffectiveHoleShape(); + + pad->TransformShapeToPolygon( padOutline, aLayer, 0, maxError, + ERROR_INSIDE ); + + if( sameNumPads.empty() ) + { + if( !padOutline.Collide( pad->GetPosition() ) ) { - intersects |= slotPolygon.COutline( 0 ).Intersects( otherPadOutline.CHole( i, j ) ); - if( intersects ) - continue; + // Hole outside pad + annularWidth = 0; + } + else + { + // Disable is-inside test in SquaredDistance + padOutline.Outline( 0 ).SetClosed( false ); + + dist_sq = padOutline.SquaredDistanceToSeg( slot->GetSeg() ); + annularWidth = sqrt( dist_sq ) - slot->GetWidth() / 2; } } - - if( intersects ) - continue; - - // Determine the effective annular width if the pad hole under - // test lies withing the boundary of another pad outline. - int effectiveWidth = std::numeric_limits::max(); - - if( collide( otherPadOutline, slotPolygon.Outline( 0 ), - effectiveWidth, &effectiveWidth ) ) + else if( constraint.Value().HasMin() + && ( minAnnularWidth < constraint.Value().Min() ) ) { - if( effectiveWidth > annularWidth ) - annularWidth = effectiveWidth; + SHAPE_POLY_SET otherPadOutline; + SHAPE_POLY_SET slotPolygon; + + slot->TransformToPolygon( slotPolygon, 0, ERROR_INSIDE ); + + for( const PAD* sameNumPad : sameNumPads ) + { + // Construct the full pad with outline and hole. + sameNumPad->TransformShapeToPolygon( + otherPadOutline, PADSTACK::ALL_LAYERS, 0, maxError, + ERROR_OUTSIDE ); + + sameNumPad->TransformHoleToPolygon( + otherPadOutline, 0, maxError, ERROR_INSIDE ); + + + // If the pad hole under test intersects with another pad outline, + // the annular width calculated above is used. + bool intersects = false; + + for( int i = 0; + i < otherPadOutline.OutlineCount() && !intersects; + i++ ) + { + intersects |= slotPolygon.COutline( 0 ).Intersects( + otherPadOutline.COutline( i ) ); + + if( intersects ) + break; + + for( int j = 0; + j < otherPadOutline.HoleCount( i ) && !intersects; + j++ ) + { + intersects |= slotPolygon.COutline( 0 ).Intersects( + otherPadOutline.CHole( i, j ) ); + + if( intersects ) + break; + } + } + + if( intersects ) + continue; + + // Determine the effective annular width if the pad hole under + // test lies withing the boundary of another pad outline. + int effectiveWidth = std::numeric_limits::max(); + + if( collide( otherPadOutline, slotPolygon.Outline( 0 ), + effectiveWidth, &effectiveWidth ) ) + { + if( effectiveWidth > annularWidth ) + annularWidth = effectiveWidth; + } + } } } - } - } + + maxAnnularWidth = std::max( maxAnnularWidth, annularWidth ); + minAnnularWidth = std::min( minAnnularWidth, annularWidth ); + } ); break; } @@ -411,13 +443,13 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() if( constraint.Value().HasMin() ) { v_min = constraint.Value().Min(); - fail_min = annularWidth < v_min; + fail_min = minAnnularWidth < v_min; } if( constraint.Value().HasMax() ) { v_max = constraint.Value().Max(); - fail_max = annularWidth > v_max; + fail_max = maxAnnularWidth > v_max; } if( fail_min || fail_max ) @@ -430,7 +462,7 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() msg = formatMsg( _( "(%s min annular width %s; actual %s)" ), constraint.GetName(), v_min, - annularWidth ); + minAnnularWidth ); } if( fail_max ) @@ -438,7 +470,7 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() msg = formatMsg( _( "(%s max annular width %s; actual %s)" ), constraint.GetName(), v_max, - annularWidth ); + maxAnnularWidth ); } drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); diff --git a/pcbnew/drc/drc_test_provider_edge_clearance.cpp b/pcbnew/drc/drc_test_provider_edge_clearance.cpp index 9e4dec8497..85f8635ef3 100644 --- a/pcbnew/drc/drc_test_provider_edge_clearance.cpp +++ b/pcbnew/drc/drc_test_provider_edge_clearance.cpp @@ -304,38 +304,57 @@ bool DRC_TEST_PROVIDER_EDGE_CLEARANCE::Run() } } - const std::shared_ptr& itemShape = item->GetEffectiveShape(); + std::vector layersToTest; - for( PCB_LAYER_ID testLayer : { Edge_Cuts, Margin } ) + switch( item->Type() ) { - if( testCopper && item->IsOnCopperLayer() ) - { - edgesTree.QueryColliding( item, UNDEFINED_LAYER, testLayer, nullptr, - [&]( BOARD_ITEM* edge ) -> bool - { - return testAgainstEdge( item, itemShape.get(), edge, - EDGE_CLEARANCE_CONSTRAINT, - DRCE_EDGE_CLEARANCE ); - }, - m_largestEdgeClearance ); - } + case PCB_PAD_T: + layersToTest = static_cast( item )->Padstack().UniqueLayers(); + break; - if( testSilk && ( item->IsOnLayer( F_SilkS ) || item->IsOnLayer( B_SilkS ) ) ) + case PCB_VIA_T: + layersToTest = static_cast( item )->Padstack().UniqueLayers(); + break; + + default: + layersToTest = { UNDEFINED_LAYER }; + } + + for( PCB_LAYER_ID shapeLayer : layersToTest ) + { + const std::shared_ptr& itemShape = item->GetEffectiveShape( shapeLayer ); + + for( PCB_LAYER_ID testLayer : { Edge_Cuts, Margin } ) { - if( edgesTree.QueryColliding( item, UNDEFINED_LAYER, testLayer, nullptr, - [&]( BOARD_ITEM* edge ) -> bool - { - return testAgainstEdge( item, itemShape.get(), edge, - SILK_CLEARANCE_CONSTRAINT, - DRCE_SILK_EDGE_CLEARANCE ); - }, - m_largestEdgeClearance ) ) + if( testCopper && item->IsOnCopperLayer() ) { - // violations reported during QueryColliding + edgesTree.QueryColliding( item, shapeLayer, testLayer, nullptr, + [&]( BOARD_ITEM* edge ) -> bool + { + return testAgainstEdge( item, itemShape.get(), edge, + EDGE_CLEARANCE_CONSTRAINT, + DRCE_EDGE_CLEARANCE ); + }, + m_largestEdgeClearance ); } - else + + if( testSilk && ( item->IsOnLayer( F_SilkS ) || item->IsOnLayer( B_SilkS ) ) ) { - // TODO: check postion being outside board boundary + if( edgesTree.QueryColliding( item, shapeLayer, testLayer, nullptr, + [&]( BOARD_ITEM* edge ) -> bool + { + return testAgainstEdge( item, itemShape.get(), edge, + SILK_CLEARANCE_CONSTRAINT, + DRCE_SILK_EDGE_CLEARANCE ); + }, + m_largestEdgeClearance ) ) + { + // violations reported during QueryColliding + } + else + { + // TODO: check postion being outside board boundary + } } } } diff --git a/pcbnew/pad.cpp b/pcbnew/pad.cpp index 07e424b70a..311d5a6cc0 100644 --- a/pcbnew/pad.cpp +++ b/pcbnew/pad.cpp @@ -533,6 +533,9 @@ std::shared_ptr PAD::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING fla aLayer = Padstack().EffectiveLayerFor( aLayer ); + wxASSERT_MSG( m_effectiveShapes.contains( aLayer ) && m_effectiveShapes.at( aLayer ), + wxT( "Null shape in PAD::GetEffectiveShape!" ) ); + return m_effectiveShapes[aLayer]; } @@ -1620,6 +1623,10 @@ void PAD::ViewGetLayers( int aLayers[], int& aCount ) const LSET cuLayers = ( m_padStack.LayerSet() & LSET::AllCuMask() ); + // Don't spend cycles rendering layers that aren't visible + if( const BOARD* board = GetBoard() ) + cuLayers &= board->GetEnabledLayers(); + if( cuLayers.count() > 1 ) { // Multi layer pad diff --git a/pcbnew/padstack.cpp b/pcbnew/padstack.cpp index cf10580ee6..c622c95d9d 100644 --- a/pcbnew/padstack.cpp +++ b/pcbnew/padstack.cpp @@ -872,6 +872,31 @@ void PADSTACK::ForEachUniqueLayer( const std::function& aM } +std::vector PADSTACK::UniqueLayers() const +{ + switch( Mode() ) + { + default: + case MODE::NORMAL: + return { F_Cu }; + + case MODE::FRONT_INNER_BACK: + return { F_Cu, INNER_LAYERS, B_Cu }; + + case MODE::CUSTOM: + { + std::vector layers; + int layerCount = m_parent ? m_parent->BoardCopperLayerCount() : MAX_CU_LAYERS; + + for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, layerCount ) ) + layers.push_back( layer ); + + return layers; + } + } +} + + PCB_LAYER_ID PADSTACK::EffectiveLayerFor( PCB_LAYER_ID aLayer ) const { switch( static_cast( aLayer ) ) @@ -911,7 +936,24 @@ PCB_LAYER_ID PADSTACK::EffectiveLayerFor( PCB_LAYER_ID aLayer ) const wxString::Format( wxT( "Unhandled layer %d in PADSTACK::EffectiveLayerFor" ), aLayer ) ); - return Mode() == MODE::CUSTOM ? aLayer : INNER_LAYERS; + if( Mode() == MODE::FRONT_INNER_BACK ) + return INNER_LAYERS; + + // Custom padstack: Clamp to parent board's stackup if present + if( m_parent ) + { + LSET boardCopper = m_parent->BoardLayerSet() & LSET::AllCuMask(); + + if( boardCopper.Contains( aLayer ) ) + return aLayer; + + // We're asked for an inner copper layer not present in the board. There is no right + // answer here, so fall back on the front shape + return ALL_LAYERS; + } + + // No parent, just pass through + return aLayer; } case MODE::NORMAL: diff --git a/pcbnew/padstack.h b/pcbnew/padstack.h index ec0f39220b..17f799efe2 100644 --- a/pcbnew/padstack.h +++ b/pcbnew/padstack.h @@ -334,6 +334,8 @@ public: */ void ForEachUniqueLayer( const std::function& aMethod ) const; + std::vector UniqueLayers() const; + /** * Determines which geometry layer should be used for the given input layer. * For example, for MODE::NORMAL, this will always be F_Cu, and for MODE::FRONT_INNER_BACK,