Retire insideArea in favour of intersectsArea and enclosedByArea.

This commit is contained in:
Jeff Young 2022-08-28 17:41:39 +01:00
parent abc7335fd7
commit 667a54ad86
7 changed files with 171 additions and 111 deletions

View File

@ -219,8 +219,8 @@ void BOARD::IncrementTimeStamp()
{
std::unique_lock<std::mutex> cacheLock( m_CachesMutex );
m_InsideAreaCache.clear();
m_EntirelyInsideAreaCache.clear();
m_IntersectsAreaCache.clear();
m_EnclosedByAreaCache.clear();
m_InsideCourtyardCache.clear();
m_InsideFCourtyardCache.clear();
m_InsideBCourtyardCache.clear();

View File

@ -1148,8 +1148,8 @@ public:
std::unordered_map<PTR_PTR_CACHE_KEY, bool> m_InsideCourtyardCache;
std::unordered_map<PTR_PTR_CACHE_KEY, bool> m_InsideFCourtyardCache;
std::unordered_map<PTR_PTR_CACHE_KEY, bool> m_InsideBCourtyardCache;
std::unordered_map<PTR_PTR_LAYER_CACHE_KEY, bool> m_InsideAreaCache;
std::unordered_map<PTR_PTR_LAYER_CACHE_KEY, bool> m_EntirelyInsideAreaCache;
std::unordered_map<PTR_PTR_LAYER_CACHE_KEY, bool> m_IntersectsAreaCache;
std::unordered_map<PTR_PTR_LAYER_CACHE_KEY, bool> m_EnclosedByAreaCache;
std::unordered_map< wxString, LSET > m_LayerExpressionCache;
std::unordered_map<ZONE*, std::unique_ptr<DRC_RTREE>> m_CopperZoneRTreeCache;
std::unique_ptr<DRC_RTREE> m_CopperItemRTreeCache;

View File

@ -452,7 +452,10 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
PCB_EXPR_BUILTIN_FUNCTIONS& functions = PCB_EXPR_BUILTIN_FUNCTIONS::Instance();
for( const wxString& funcSig : functions.GetSignatures() )
tokens += wxT( "|" ) + funcSig;
{
if( !funcSig.Contains( "DEPRECATED" ) )
tokens += wxT( "|" ) + funcSig;
}
}
else if( expr_context == STRING )
{

View File

@ -104,7 +104,7 @@ Note: `clearance` and `hole_clearance` rules are not run against items of the sa
(rule HV_unshielded
(constraint clearance (min 2mm))
(condition "A.NetClass == 'HV' && !A.insideArea('Shield*')"))
(condition "A.NetClass == 'HV' && !A.enclosedByArea('Shield*')"))
(rule heavy_thermals
@ -130,26 +130,27 @@ Use Ctrl+/ to comment or uncomment line(s).
All function parameters support simple wildcards (`*` and `?`).
<br><br>
A.insideCourtyard('<footprint_refdes>')
A.intersectsCourtyard('<footprint_refdes>')
True if any part of `A` lies within the given footprint's principal courtyard.
<br><br>
A.insideFrontCourtyard('<footprint_refdes>')
A.intersectsFrontCourtyard('<footprint_refdes>')
True if any part of `A` lies within the given footprint's front courtyard.
<br><br>
A.insideBackCourtyard('<footprint_refdes>')
A.intersectsBackCourtyard('<footprint_refdes>')
True if any part of `A` lies within the given footprint's back courtyard.
<br><br>
A.insideArea('<zone_name>')
A.intersectsArea('<zone_name>')
True if any part of `A` lies within the given zone's outline.
<br><br>
A.entirelyInsideArea('<zone_name>')
A.enclosedByArea('<zone_name>')
True if all of `A` lies within the given zone's outline.
NB: this is potentially a more expensive call than `insideArea()`.
NB: this is potentially a more expensive call than `intersectsArea()`. Use `intersectsArea()`
where possible.
<br><br>
A.isPlated()
@ -157,7 +158,7 @@ True if `A` has a hole which is plated.
<br><br>
A.inDiffPair('<net_name>')
True if `A` has net that is part of the specified differential pair.
True if `A` has a net that is part of the specified differential pair.
`<net_name>` is the base name of the differential pair. For example, `inDiffPair('/CLK')`
matches items in the `/CLK_P` and `/CLK_N` nets.
<br><br>
@ -178,19 +179,35 @@ the canonical name (ie: `F.Cu`).
NB: this returns true if `A` is on the given layer, independently
of whether or not the rule is being evaluated for that layer.
For the latter use a `(layer "layer_name")` clause in the rule.
<br><br>
!!! A.insideCourtyard('<footprint_refdes>') !!!
Deprecated; use `intersectsCourtyard()` instead.
<br><br>
!!! A.insideFrontCourtyard('<footprint_refdes>') !!!
Deprecated; use `intersectsFrontCourtyard()` instead.
<br><br>
!!! A.insideBackCourtyard('<footprint_refdes>') !!!
Deprecated; use `intersectsBackCourtyard()` instead.
<br><br>
!!! A.insideArea('<zone_name>') !!!
Deprecated; use `intersectsArea()` instead.
<br><br><br>
### More Examples
(rule "copper keepout"
(constraint disallow track via zone)
(condition "A.insideArea('zone3')"))
(condition "A.intersectsArea('zone3')"))
(rule "BGA neckdown"
(constraint track_width (min 0.2mm) (opt 0.25mm))
(constraint clearance (min 0.05mm) (opt 0.08mm))
(condition "A.insideCourtyard('U3')"))
(condition "A.intersectsCourtyard('U3')"))
# prevent silk over tented vias

View File

@ -105,7 +105,7 @@ _HKI( "### Top-level Clauses\n"
"\n"
" (rule HV_unshielded\n"
" (constraint clearance (min 2mm))\n"
" (condition \"A.NetClass == 'HV' && !A.insideArea('Shield*')\"))\n"
" (condition \"A.NetClass == 'HV' && !A.enclosedByArea('Shield*')\"))\n"
"\n"
"\n"
" (rule heavy_thermals\n"
@ -131,26 +131,27 @@ _HKI( "### Top-level Clauses\n"
"All function parameters support simple wildcards (`*` and `?`).\n"
"<br><br>\n"
"\n"
" A.insideCourtyard('<footprint_refdes>')\n"
" A.intersectsCourtyard('<footprint_refdes>')\n"
"True if any part of `A` lies within the given footprint's principal courtyard.\n"
"<br><br>\n"
"\n"
" A.insideFrontCourtyard('<footprint_refdes>')\n"
" A.intersectsFrontCourtyard('<footprint_refdes>')\n"
"True if any part of `A` lies within the given footprint's front courtyard.\n"
"<br><br>\n"
"\n"
" A.insideBackCourtyard('<footprint_refdes>')\n"
" A.intersectsBackCourtyard('<footprint_refdes>')\n"
"True if any part of `A` lies within the given footprint's back courtyard.\n"
"<br><br>\n"
"\n"
" A.insideArea('<zone_name>')\n"
" A.intersectsArea('<zone_name>')\n"
"True if any part of `A` lies within the given zone's outline.\n"
"<br><br>\n"
"\n"
" A.entirelyInsideArea('<zone_name>')\n"
" A.enclosedByArea('<zone_name>')\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"
"NB: this is potentially a more expensive call than `intersectsArea()`. Use `intersectsArea()` \n"
"where possible.\n"
"<br><br>\n"
"\n"
" A.isPlated()\n"
@ -158,7 +159,7 @@ _HKI( "### Top-level Clauses\n"
"<br><br>\n"
"\n"
" A.inDiffPair('<net_name>')\n"
"True if `A` has net that is part of the specified differential pair.\n"
"True if `A` has a net that is part of the specified differential pair.\n"
"`<net_name>` is the base name of the differential pair. For example, `inDiffPair('/CLK')`\n"
"matches items in the `/CLK_P` and `/CLK_N` nets.\n"
"<br><br>\n"
@ -179,19 +180,35 @@ _HKI( "### Top-level Clauses\n"
"NB: this returns true if `A` is on the given layer, independently\n"
"of whether or not the rule is being evaluated for that layer.\n"
"For the latter use a `(layer \"layer_name\")` clause in the rule.\n"
"<br><br>\n"
"\n"
" !!! A.insideCourtyard('<footprint_refdes>') !!!\n"
"Deprecated; use `intersectsCourtyard()` instead.\n"
"<br><br>\n"
"\n"
" !!! A.insideFrontCourtyard('<footprint_refdes>') !!!\n"
"Deprecated; use `intersectsFrontCourtyard()` instead.\n"
"<br><br>\n"
"\n"
" !!! A.insideBackCourtyard('<footprint_refdes>') !!!\n"
"Deprecated; use `intersectsBackCourtyard()` instead.\n"
"<br><br>\n"
"\n"
" !!! A.insideArea('<zone_name>') !!!\n"
"Deprecated; use `intersectsArea()` instead.\n"
"<br><br><br>\n"
"\n"
"### More Examples\n"
"\n"
" (rule \"copper keepout\"\n"
" (constraint disallow track via zone)\n"
" (condition \"A.insideArea('zone3')\"))\n"
" (condition \"A.intersectsArea('zone3')\"))\n"
"\n"
"\n"
" (rule \"BGA neckdown\"\n"
" (constraint track_width (min 0.2mm) (opt 0.25mm))\n"
" (constraint clearance (min 0.05mm) (opt 0.08mm))\n"
" (condition \"A.insideCourtyard('U3')\"))\n"
" (condition \"A.intersectsCourtyard('U3')\"))\n"
"\n"
"\n"
" # prevent silk over tented vias\n"

View File

@ -154,7 +154,7 @@ bool DRC_TEST_PROVIDER_DISALLOW::Run()
{
std::unique_lock<std::mutex> cacheLock( board->m_CachesMutex );
board->m_InsideAreaCache[ key ] = isInside;
board->m_IntersectsAreaCache[ key ] = isInside;
}
done.fetch_add( 1 );

View File

@ -38,7 +38,6 @@
#include <connectivity/from_to_cache.h>
#include <drc/drc_engine.h>
#include <geometry/shape_circle.h>
bool fromToFunc( LIBEVAL::CONTEXT* aCtx, void* self )
{
@ -453,9 +452,6 @@ bool collidesWithArea( BOARD_ITEM* aItem, const EDA_RECT& aItemBBox, PCB_EXPR_CO
EDA_RECT areaBBox = aArea->GetCachedBoundingBox();
std::shared_ptr<SHAPE> shape;
if( !areaBBox.Intersects( aItemBBox ) )
return false;
// Collisions include touching, so we need to deflate outline by enough to exclude it.
// This is particularly important for detecting copper fills as they will be exactly
// touching along the entire exclusion border.
@ -491,9 +487,7 @@ bool collidesWithArea( BOARD_ITEM* aItem, const EDA_RECT& aItemBBox, PCB_EXPR_CO
if( ( footprint->GetFlags() & MALFORMED_COURTYARDS ) != 0 )
{
if( aCtx->HasErrorCallback() )
{
aCtx->ReportError( _( "Footprint's courtyard is not a single, closed shape." ) );
}
return false;
}
@ -573,75 +567,18 @@ bool collidesWithArea( 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 )
bool searchAreas( BOARD_ITEM* aItem, const wxString& aArg, PCB_EXPR_CONTEXT* aCtx,
std::function<bool( ZONE* )> aFunc )
{
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, bool aEntirely )
{
if( !aArea || aArea == aItem || aArea->GetParent() == aItem )
return false;
BOARD* board = aArea->GetBoard();
std::unique_lock<std::mutex> 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 = cache.find( key );
if( i != cache.end() )
return i->second;
bool isInside;
if( aEntirely )
isInside = enclosedByArea( aItem, aItemBBox, layer, aArea );
else
isInside = collidesWithArea( aItem, aItemBBox, aCtx, aArea );
cache[ key ] = isInside;
return isInside;
}
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<ZONE*>( aItem )->GetCachedBoundingBox();
else
itemBBox = aItem->GetBoundingBox();
BOARD* board = aItem->GetBoard();
if( aArg == wxT( "A" ) )
{
ZONE* zone = dynamic_cast<ZONE*>( aCtx->GetItem( 0 ) );
return isInsideArea( aItem, itemBBox, aCtx, zone, aEntirely );
return aFunc( dynamic_cast<ZONE*>( aCtx->GetItem( 0 ) ) );
}
else if( aArg == wxT( "B" ) )
{
ZONE* zone = dynamic_cast<ZONE*>( aCtx->GetItem( 1 ) );
return isInsideArea( aItem, itemBBox, aCtx, zone, aEntirely );
return aFunc( dynamic_cast<ZONE*>( aCtx->GetItem( 1 ) ) );
}
else if( KIID::SniffTest( aArg ) )
{
@ -652,7 +589,7 @@ bool evalInsideAreaFunc( BOARD_ITEM* aItem, const wxString& aArg, PCB_EXPR_CONTE
// 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 aFunc( area );
}
for( FOOTPRINT* footprint : board->Footprints() )
@ -662,7 +599,7 @@ bool evalInsideAreaFunc( BOARD_ITEM* aItem, const wxString& aArg, PCB_EXPR_CONTE
// 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 aFunc( area );
}
}
@ -675,7 +612,7 @@ bool evalInsideAreaFunc( BOARD_ITEM* aItem, const wxString& aArg, PCB_EXPR_CONTE
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 ) )
if( aFunc( area ) )
return true;
}
}
@ -687,7 +624,7 @@ bool evalInsideAreaFunc( BOARD_ITEM* aItem, const wxString& aArg, PCB_EXPR_CONTE
// 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 ) )
if( aFunc( area ) )
return true;
}
}
@ -698,7 +635,7 @@ bool evalInsideAreaFunc( BOARD_ITEM* aItem, const wxString& aArg, PCB_EXPR_CONTE
}
static void insideAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self )
static void intersectsAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self )
{
PCB_EXPR_CONTEXT* context = static_cast<PCB_EXPR_CONTEXT*>( aCtx );
LIBEVAL::VALUE* arg = aCtx->Pop();
@ -713,14 +650,14 @@ static void insideAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self )
{
aCtx->ReportError( wxString::Format( _( "Missing rule-area identifier argument "
"(A, B, or rule-area name) to %s." ),
wxT( "insideArea()" ) ) );
wxT( "intersectsArea()" ) ) );
}
return;
}
PCB_EXPR_VAR_REF* vref = static_cast<PCB_EXPR_VAR_REF*>( self );
BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr;
if( !item )
return;
@ -728,15 +665,48 @@ static void insideAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self )
result->SetDeferredEval(
[item, arg, context]() -> double
{
if( evalInsideAreaFunc( item, arg->AsString(), context, false ) )
BOARD* board = item->GetBoard();
PCB_LAYER_ID layer = context->GetLayer();
EDA_RECT itemBBox;
if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
itemBBox = static_cast<ZONE*>( item )->GetCachedBoundingBox();
else
itemBBox = item->GetBoundingBox();
if( searchAreas( item, arg->AsString(), context,
[&]( ZONE* aArea )
{
if( !aArea || aArea == item || aArea->GetParent() == item )
return false;
if( !aArea->GetCachedBoundingBox().Intersects( itemBBox ) )
return false;
std::unique_lock<std::mutex> cacheLock( board->m_CachesMutex );
PTR_PTR_LAYER_CACHE_KEY key = { aArea, item, layer };
auto i = board->m_IntersectsAreaCache.find( key );
if( i != board->m_IntersectsAreaCache.end() )
return i->second;
bool collides = collidesWithArea( item, itemBBox, context, aArea );
board->m_IntersectsAreaCache[ key ] = collides;
return collides;
} ) )
{
return 1.0;
}
return 0.0;
} );
}
static void entirelyInsideAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self )
static void enclosedByAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self )
{
PCB_EXPR_CONTEXT* context = static_cast<PCB_EXPR_CONTEXT*>( aCtx );
LIBEVAL::VALUE* arg = aCtx->Pop();
@ -751,14 +721,14 @@ static void entirelyInsideAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self )
{
aCtx->ReportError( wxString::Format( _( "Missing rule-area identifier argument "
"(A, B, or rule-area name) to %s." ),
wxT( "entirelyInsideArea()" ) ) );
wxT( "eclosedByArea()" ) ) );
}
return;
}
PCB_EXPR_VAR_REF* vref = static_cast<PCB_EXPR_VAR_REF*>( self );
BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr;
if( !item )
return;
@ -766,8 +736,49 @@ static void entirelyInsideAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self )
result->SetDeferredEval(
[item, arg, context]() -> double
{
if( evalInsideAreaFunc( item, arg->AsString(), context, true ) )
BOARD* board = item->GetBoard();
int maxError = board->GetDesignSettings().m_MaxError;
PCB_LAYER_ID layer = context->GetLayer();
EDA_RECT itemBBox;
if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
itemBBox = static_cast<ZONE*>( item )->GetCachedBoundingBox();
else
itemBBox = item->GetBoundingBox();
if( searchAreas( item, arg->AsString(), context,
[&]( ZONE* aArea )
{
if( !aArea || aArea == item || aArea->GetParent() == item )
return false;
if( !aArea->GetCachedBoundingBox().Intersects( itemBBox ) )
return false;
std::unique_lock<std::mutex> cacheLock( board->m_CachesMutex );
PTR_PTR_LAYER_CACHE_KEY key = { aArea, item, layer };
auto i = board->m_EnclosedByAreaCache.find( key );
if( i != board->m_EnclosedByAreaCache.end() )
return i->second;
SHAPE_POLY_SET itemShape;
item->TransformShapeWithClearanceToPolygon( itemShape, layer, 0,
maxError, ERROR_OUTSIDE );
itemShape.BooleanSubtract( *aArea->Outline(), SHAPE_POLY_SET::PM_FAST );
bool enclosedByArea = itemShape.IsEmpty();
board->m_EnclosedByAreaCache[ key ] = enclosedByArea;
return enclosedByArea;
} ) )
{
return 1.0;
}
return 0.0;
} );
@ -987,19 +998,31 @@ PCB_EXPR_BUILTIN_FUNCTIONS::PCB_EXPR_BUILTIN_FUNCTIONS()
void PCB_EXPR_BUILTIN_FUNCTIONS::RegisterAllFunctions()
{
m_funcs.clear();
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( "insideCourtyard('x') DEPRECATED" ), insideCourtyardFunc );
RegisterFunc( wxT( "insideFrontCourtyard('x') DEPRECATED" ), insideFrontCourtyardFunc );
RegisterFunc( wxT( "insideBackCourtyard('x') DEPRECATED" ), insideBackCourtyardFunc );
RegisterFunc( wxT( "intersectsCourtyard('x')" ), insideCourtyardFunc );
RegisterFunc( wxT( "intersectsFrontCourtyard('x')" ), insideFrontCourtyardFunc );
RegisterFunc( wxT( "intersectsBackCourtyard('x')" ), insideBackCourtyardFunc );
RegisterFunc( wxT( "insideArea('x') DEPRECATED" ), intersectsAreaFunc );
RegisterFunc( wxT( "intersectsArea('x')" ), intersectsAreaFunc );
RegisterFunc( wxT( "enclosedByArea('x')" ), enclosedByAreaFunc );
RegisterFunc( wxT( "isMicroVia()" ), isMicroVia );
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 );
}