From f99761e5bd485c71df08b8b753ba16aa96cd20f5 Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Thu, 25 Aug 2022 13:20:36 +0100 Subject: [PATCH] entirelyInsideArea() prototype. --- pcbnew/board.cpp | 1 + pcbnew/board.h | 1 + pcbnew/dialogs/panel_setup_rules_help.md | 6 + pcbnew/dialogs/panel_setup_rules_help_md.h | 6 + pcbnew/pcb_expr_evaluator.cpp | 264 +++++++++++++-------- pcbnew/python/swig/board.i | 1 + 6 files changed, 185 insertions(+), 94 deletions(-) diff --git a/pcbnew/board.cpp b/pcbnew/board.cpp index 23fdcb0a68..8e924df013 100644 --- a/pcbnew/board.cpp +++ b/pcbnew/board.cpp @@ -220,6 +220,7 @@ void BOARD::IncrementTimeStamp() { std::unique_lock cacheLock( m_CachesMutex ); m_InsideAreaCache.clear(); + m_EntirelyInsideAreaCache.clear(); m_InsideCourtyardCache.clear(); m_InsideFCourtyardCache.clear(); m_InsideBCourtyardCache.clear(); diff --git a/pcbnew/board.h b/pcbnew/board.h index 2f8ad47d9a..f0e595bc8d 100644 --- a/pcbnew/board.h +++ b/pcbnew/board.h @@ -1146,6 +1146,7 @@ public: std::unordered_map m_InsideFCourtyardCache; std::unordered_map m_InsideBCourtyardCache; std::unordered_map m_InsideAreaCache; + std::unordered_map m_EntirelyInsideAreaCache; std::unordered_map< wxString, LSET > m_LayerExpressionCache; std::unordered_map> m_CopperZoneRTreeCache; std::unique_ptr m_CopperItemRTreeCache; diff --git a/pcbnew/dialogs/panel_setup_rules_help.md b/pcbnew/dialogs/panel_setup_rules_help.md index 1ca4df4c07..3882005fc7 100644 --- a/pcbnew/dialogs/panel_setup_rules_help.md +++ b/pcbnew/dialogs/panel_setup_rules_help.md @@ -144,6 +144,12 @@ True if any part of `A` lies within the given footprint's back courtyard. A.insideArea('') True if any part of `A` lies within the given zone's outline. +

+ + A.entirelyInsideArea('') +True if all of `A` lies within the given zone's outline. + +NB: this is potentially a more expensive call than `insideArea()`.

A.isPlated() diff --git a/pcbnew/dialogs/panel_setup_rules_help_md.h b/pcbnew/dialogs/panel_setup_rules_help_md.h index defb493c5c..97736eefcc 100644 --- a/pcbnew/dialogs/panel_setup_rules_help_md.h +++ b/pcbnew/dialogs/panel_setup_rules_help_md.h @@ -147,6 +147,12 @@ _HKI( "### Top-level Clauses\n" "True if any part of `A` lies within the given zone's outline.\n" "

\n" "\n" +" A.entirelyInsideArea('')\n" +"True if all of `A` lies within the given zone's outline. \n" +"\n" +"NB: this is potentially a more expensive call than `insideArea()`.\n" +"

\n" +"\n" " A.isPlated()\n" "True if `A` has a hole which is plated.\n" "

\n" diff --git a/pcbnew/pcb_expr_evaluator.cpp b/pcbnew/pcb_expr_evaluator.cpp index cd128a1cff..b96b3c18e7 100644 --- a/pcbnew/pcb_expr_evaluator.cpp +++ b/pcbnew/pcb_expr_evaluator.cpp @@ -40,7 +40,7 @@ #include #include -bool exprFromTo( LIBEVAL::CONTEXT* aCtx, void* self ) +bool fromToFunc( LIBEVAL::CONTEXT* aCtx, void* self ) { PCB_EXPR_VAR_REF* vref = static_cast( self ); BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; @@ -74,7 +74,7 @@ bool exprFromTo( LIBEVAL::CONTEXT* aCtx, void* self ) } -static void existsOnLayer( LIBEVAL::CONTEXT* aCtx, void *self ) +static void existsOnLayerFunc( LIBEVAL::CONTEXT* aCtx, void *self ) { PCB_EXPR_VAR_REF* vref = static_cast( self ); BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; @@ -169,7 +169,7 @@ static void existsOnLayer( LIBEVAL::CONTEXT* aCtx, void *self ) } -static void isPlated( LIBEVAL::CONTEXT* aCtx, void* self ) +static void isPlatedFunc( LIBEVAL::CONTEXT* aCtx, void* self ) { LIBEVAL::VALUE* result = aCtx->AllocValue(); @@ -246,7 +246,7 @@ bool isInsideCourtyard( BOARD_ITEM* aItem, const EDA_RECT& aItemBBox, }; -static void insideCourtyard( LIBEVAL::CONTEXT* aCtx, void* self ) +static void insideCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self ) { PCB_EXPR_CONTEXT* context = static_cast( aCtx ); LIBEVAL::VALUE* arg = aCtx->Pop(); @@ -313,7 +313,7 @@ static void insideCourtyard( LIBEVAL::CONTEXT* aCtx, void* self ) } -static void insideFrontCourtyard( LIBEVAL::CONTEXT* aCtx, void* self ) +static void insideFrontCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self ) { PCB_EXPR_CONTEXT* context = static_cast( aCtx ); LIBEVAL::VALUE* arg = aCtx->Pop(); @@ -380,7 +380,7 @@ static void insideFrontCourtyard( LIBEVAL::CONTEXT* aCtx, void* self ) } -static void insideBackCourtyard( LIBEVAL::CONTEXT* aCtx, void* self ) +static void insideBackCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self ) { PCB_EXPR_CONTEXT* context = static_cast( aCtx ); LIBEVAL::VALUE* arg = aCtx->Pop(); @@ -446,11 +446,11 @@ static void insideBackCourtyard( LIBEVAL::CONTEXT* aCtx, void* self ) } -bool calcIsInsideArea( BOARD_ITEM* aItem, const EDA_RECT& aItemBBox, PCB_EXPR_CONTEXT* aCtx, +bool collidesWithArea( BOARD_ITEM* aItem, const EDA_RECT& aItemBBox, PCB_EXPR_CONTEXT* aCtx, ZONE* aArea ) { BOARD* board = aArea->GetBoard(); - EDA_RECT areaBBox = aArea->GetBoundingBox(); + EDA_RECT areaBBox = aArea->GetCachedBoundingBox(); std::shared_ptr shape; if( !areaBBox.Intersects( aItemBBox ) ) @@ -573,29 +573,132 @@ bool calcIsInsideArea( BOARD_ITEM* aItem, const EDA_RECT& aItemBBox, PCB_EXPR_CO } +bool enclosedByArea( BOARD_ITEM* aItem, const EDA_RECT& aItemBBox, PCB_LAYER_ID layer, ZONE* aArea ) +{ + BOARD* board = aItem->GetBoard(); + EDA_RECT areaBBox = aArea->GetCachedBoundingBox(); + SHAPE_POLY_SET itemShape; + + if( !areaBBox.Intersects( aItemBBox ) ) + return false; + + aItem->TransformShapeWithClearanceToPolygon( itemShape, layer, 0, + board->GetDesignSettings().m_MaxError, + ERROR_OUTSIDE ); + + itemShape.BooleanSubtract( *aArea->Outline(), SHAPE_POLY_SET::PM_FAST ); + + return itemShape.IsEmpty(); +} + + bool isInsideArea( BOARD_ITEM* aItem, const EDA_RECT& aItemBBox, PCB_EXPR_CONTEXT* aCtx, - ZONE* aArea ) + ZONE* aArea, bool aEntirely ) { if( !aArea || aArea == aItem || aArea->GetParent() == aItem ) return false; BOARD* board = aArea->GetBoard(); std::unique_lock cacheLock( board->m_CachesMutex ); + auto& cache = aEntirely ? board->m_EntirelyInsideAreaCache + : board->m_InsideAreaCache; PCB_LAYER_ID layer = aCtx->GetLayer(); PTR_PTR_LAYER_CACHE_KEY key = { aArea, aItem, layer }; - auto i = board->m_InsideAreaCache.find( key ); + auto i = cache.find( key ); - if( i != board->m_InsideAreaCache.end() ) + if( i != cache.end() ) return i->second; - bool isInside = calcIsInsideArea( aItem, aItemBBox, aCtx, aArea ); + bool isInside; + + if( aEntirely ) + isInside = enclosedByArea( aItem, aItemBBox, layer, aArea ); + else + isInside = collidesWithArea( aItem, aItemBBox, aCtx, aArea ); + + cache[ key ] = isInside; - board->m_InsideAreaCache[ key ] = isInside; return isInside; } -static void insideArea( LIBEVAL::CONTEXT* aCtx, void* self ) +bool evalInsideAreaFunc( BOARD_ITEM* aItem, const wxString& aArg, PCB_EXPR_CONTEXT* aCtx, + bool aEntirely ) +{ + BOARD* board = aItem->GetBoard(); + EDA_RECT itemBBox; + + if( aItem->Type() == PCB_ZONE_T || aItem->Type() == PCB_FP_ZONE_T ) + itemBBox = static_cast( aItem )->GetCachedBoundingBox(); + else + itemBBox = aItem->GetBoundingBox(); + + if( aArg == wxT( "A" ) ) + { + ZONE* zone = dynamic_cast( aCtx->GetItem( 0 ) ); + return isInsideArea( aItem, itemBBox, aCtx, zone, aEntirely ); + } + else if( aArg == wxT( "B" ) ) + { + ZONE* zone = dynamic_cast( aCtx->GetItem( 1 ) ); + return isInsideArea( aItem, itemBBox, aCtx, zone, aEntirely ); + } + else if( KIID::SniffTest( aArg ) ) + { + KIID target( aArg ); + + for( ZONE* area : board->Zones() ) + { + // Only a single zone can match the UUID; exit once we find a match whether + // "inside" or not + if( area->m_Uuid == target ) + return isInsideArea( aItem, itemBBox, aCtx, area, aEntirely ); + } + + for( FOOTPRINT* footprint : board->Footprints() ) + { + for( ZONE* area : footprint->Zones() ) + { + // Only a single zone can match the UUID; exit once we find a match + // whether "inside" or not + if( area->m_Uuid == target ) + return isInsideArea( aItem, itemBBox, aCtx, area, aEntirely ); + } + } + + return 0.0; + } + else // Match on zone name + { + for( ZONE* area : board->Zones() ) + { + if( area->GetZoneName().Matches( aArg ) ) + { + // Many zones can match the name; exit only when we find an "inside" + if( isInsideArea( aItem, itemBBox, aCtx, area, aEntirely ) ) + return true; + } + } + + for( FOOTPRINT* footprint : board->Footprints() ) + { + for( ZONE* area : footprint->Zones() ) + { + // Many zones can match the name; exit only when we find an "inside" + if( area->GetZoneName().Matches( aArg ) ) + { + if( isInsideArea( aItem, itemBBox, aCtx, area, aEntirely ) ) + return true; + } + } + } + + return false; + } +} + + +static void insideAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self ) { PCB_EXPR_CONTEXT* context = static_cast( aCtx ); LIBEVAL::VALUE* arg = aCtx->Pop(); @@ -625,81 +728,53 @@ static void insideArea( LIBEVAL::CONTEXT* aCtx, void* self ) result->SetDeferredEval( [item, arg, context]() -> double { - BOARD* board = item->GetBoard(); - EDA_RECT itemBBox; + if( evalInsideAreaFunc( item, arg->AsString(), context, false ) ) + return 1.0; - if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T ) - itemBBox = static_cast( item )->GetCachedBoundingBox(); - else - itemBBox = item->GetBoundingBox(); + return 0.0; + } ); +} - if( arg->AsString() == wxT( "A" ) ) - { - ZONE* zone = dynamic_cast( context->GetItem( 0 ) ); - return isInsideArea( item, itemBBox, context, zone ) ? 1.0 : 0.0; - } - else if( arg->AsString() == wxT( "B" ) ) - { - ZONE* zone = dynamic_cast( context->GetItem( 1 ) ); - return isInsideArea( item, itemBBox, context, zone ) ? 1.0 : 0.0; - } - else if( KIID::SniffTest( arg->AsString() ) ) - { - KIID target( arg->AsString()); - for( ZONE* area : board->Zones() ) - { - // Only a single zone can match the UUID; exit once we find a match whether - // "inside" or not - if( area->m_Uuid == target ) - return isInsideArea( item, itemBBox, context, area ) ? 1.0 : 0.0; - } +static void entirelyInsideAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + PCB_EXPR_CONTEXT* context = static_cast( aCtx ); + LIBEVAL::VALUE* arg = aCtx->Pop(); + LIBEVAL::VALUE* result = aCtx->AllocValue(); - for( FOOTPRINT* footprint : board->Footprints() ) - { - for( ZONE* area : footprint->Zones() ) - { - // Only a single zone can match the UUID; exit once we find a match - // whether "inside" or not - if( area->m_Uuid == target ) - return isInsideArea( item, itemBBox, context, area ) ? 1.0 : 0.0; - } - } + result->Set( 0.0 ); + aCtx->Push( result ); - return 0.0; - } - else // Match on zone name - { - for( ZONE* area : board->Zones()) - { - if( area->GetZoneName().Matches( arg->AsString() ) ) - { - // Many zones can match the name; exit only when we find an "inside" - if( isInsideArea( item, itemBBox, context, area ) ) - return 1.0; - } - } + if( !arg ) + { + if( aCtx->HasErrorCallback() ) + { + aCtx->ReportError( wxString::Format( _( "Missing rule-area identifier argument " + "(A, B, or rule-area name) to %s." ), + wxT( "entirelyInsideArea()" ) ) ); + } - for( FOOTPRINT* footprint : board->Footprints() ) - { - for( ZONE* area : footprint->Zones() ) - { - // Many zones can match the name; exit only when we find an "inside" - if( area->GetZoneName().Matches( arg->AsString() ) ) - { - if( isInsideArea( item, itemBBox, context, area ) ) - return 1.0; - } - } - } + return; + } - return 0.0; - } + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; + + if( !item ) + return; + + result->SetDeferredEval( + [item, arg, context]() -> double + { + if( evalInsideAreaFunc( item, arg->AsString(), context, true ) ) + return 1.0; + + return 0.0; } ); } -static void memberOf( LIBEVAL::CONTEXT* aCtx, void* self ) +static void memberOfFunc( LIBEVAL::CONTEXT* aCtx, void* self ) { LIBEVAL::VALUE* arg = aCtx->Pop(); LIBEVAL::VALUE* result = aCtx->AllocValue(); @@ -760,7 +835,7 @@ static void isMicroVia( LIBEVAL::CONTEXT* aCtx, void* self ) } -static void isBlindBuriedVia( LIBEVAL::CONTEXT* aCtx, void* self ) +static void isBlindBuriedViaFunc( LIBEVAL::CONTEXT* aCtx, void* self ) { PCB_EXPR_VAR_REF* vref = static_cast( self ); BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; @@ -776,7 +851,7 @@ static void isBlindBuriedVia( LIBEVAL::CONTEXT* aCtx, void* self ) } -static void isCoupledDiffPair( LIBEVAL::CONTEXT* aCtx, void* self ) +static void isCoupledDiffPairFunc( LIBEVAL::CONTEXT* aCtx, void* self ) { PCB_EXPR_CONTEXT* context = static_cast( aCtx ); BOARD_CONNECTED_ITEM* a = dynamic_cast( context->GetItem( 0 ) ); @@ -812,7 +887,7 @@ static void isCoupledDiffPair( LIBEVAL::CONTEXT* aCtx, void* self ) } -static void inDiffPair( LIBEVAL::CONTEXT* aCtx, void* self ) +static void inDiffPairFunc( LIBEVAL::CONTEXT* aCtx, void* self ) { LIBEVAL::VALUE* argv = aCtx->Pop(); PCB_EXPR_VAR_REF* vref = static_cast( self ); @@ -863,7 +938,7 @@ static void inDiffPair( LIBEVAL::CONTEXT* aCtx, void* self ) } -static void getField( LIBEVAL::CONTEXT* aCtx, void* self ) +static void getFieldFunc( LIBEVAL::CONTEXT* aCtx, void* self ) { LIBEVAL::VALUE* arg = aCtx->Pop(); PCB_EXPR_VAR_REF* vref = static_cast( self ); @@ -912,19 +987,20 @@ PCB_EXPR_BUILTIN_FUNCTIONS::PCB_EXPR_BUILTIN_FUNCTIONS() void PCB_EXPR_BUILTIN_FUNCTIONS::RegisterAllFunctions() { m_funcs.clear(); - RegisterFunc( wxT( "existsOnLayer('x')" ), existsOnLayer ); - RegisterFunc( wxT( "isPlated()" ), isPlated ); - RegisterFunc( wxT( "insideCourtyard('x')" ), insideCourtyard ); - RegisterFunc( wxT( "insideFrontCourtyard('x')" ), insideFrontCourtyard ); - RegisterFunc( wxT( "insideBackCourtyard('x')" ), insideBackCourtyard ); - RegisterFunc( wxT( "insideArea('x')" ), insideArea ); + RegisterFunc( wxT( "existsOnLayer('x')" ), existsOnLayerFunc ); + RegisterFunc( wxT( "isPlated()" ), isPlatedFunc ); + RegisterFunc( wxT( "insideCourtyard('x')" ), insideCourtyardFunc ); + RegisterFunc( wxT( "insideFrontCourtyard('x')" ), insideFrontCourtyardFunc ); + RegisterFunc( wxT( "insideBackCourtyard('x')" ), insideBackCourtyardFunc ); + RegisterFunc( wxT( "insideArea('x')" ), insideAreaFunc ); + RegisterFunc( wxT( "entirelyInsideArea('x')" ), entirelyInsideAreaFunc ); RegisterFunc( wxT( "isMicroVia()" ), isMicroVia ); - RegisterFunc( wxT( "isBlindBuriedVia()" ), isBlindBuriedVia ); - RegisterFunc( wxT( "memberOf('x')" ), memberOf ); - RegisterFunc( wxT( "fromTo('x','y')" ), exprFromTo ); - RegisterFunc( wxT( "isCoupledDiffPair()" ), isCoupledDiffPair ); - RegisterFunc( wxT( "inDiffPair('x')" ), inDiffPair ); - RegisterFunc( wxT( "getField('x')" ), getField ); + RegisterFunc( wxT( "isBlindBuriedVia()" ), isBlindBuriedViaFunc ); + RegisterFunc( wxT( "memberOf('x')" ), memberOfFunc ); + RegisterFunc( wxT( "fromTo('x','y')" ), fromToFunc ); + RegisterFunc( wxT( "isCoupledDiffPair()" ), isCoupledDiffPairFunc ); + RegisterFunc( wxT( "inDiffPair('x')" ), inDiffPairFunc ); + RegisterFunc( wxT( "getField('x')" ), getFieldFunc ); } diff --git a/pcbnew/python/swig/board.i b/pcbnew/python/swig/board.i index 5af76036f4..ae6f1254d0 100644 --- a/pcbnew/python/swig/board.i +++ b/pcbnew/python/swig/board.i @@ -77,6 +77,7 @@ HANDLE_EXCEPTIONS(BOARD::TracksInNetBetweenPoints) %ignore BOARD::m_InsideFCourtyardCache; %ignore BOARD::m_InsideBCourtyardCache; %ignore BOARD::m_InsideAreaCache; +%ignore BOARD::m_EntirelyInsideAreaCache; %ignore BOARD::m_LayerExpressionCache; %ignore BOARD::m_CopperZoneRTreeCache; %ignore BOARD::m_CopperItemRTreeCache;