From 8260f0ee13b777eae203e5ea88cfa586ef06a9de Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Tue, 29 Nov 2022 14:18:44 +0000 Subject: [PATCH] Add support for unitless values to PCB_EXPR_EVALUATOR. Fixes https://gitlab.com/kicad/code/kicad/issues/13016 --- common/CMakeLists.txt | 1 + common/libeval_compiler/libeval_compiler.cpp | 17 +- pcbnew/drc/drc_rule_condition.cpp | 2 +- pcbnew/drc/drc_rule_parser.cpp | 28 +- pcbnew/drc/drc_rule_parser.h | 2 +- pcbnew/pcb_expr_evaluator.cpp | 1049 +---------------- pcbnew/pcb_expr_evaluator.h | 24 +- pcbnew/pcb_expr_functions.cpp | 985 ++++++++++++++++ .../libeval_compiler_test.cpp | 5 +- qa/unittests/pcbnew/test_libeval_compiler.cpp | 2 +- 10 files changed, 1098 insertions(+), 1017 deletions(-) create mode 100644 pcbnew/pcb_expr_functions.cpp diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index e5bd09ab2c..e57c45f419 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -526,6 +526,7 @@ set( PCB_COMMON_SRCS hash_eda.cpp ${CMAKE_SOURCE_DIR}/pcbnew/pcb_base_frame.cpp ${CMAKE_SOURCE_DIR}/pcbnew/pcb_expr_evaluator.cpp + ${CMAKE_SOURCE_DIR}/pcbnew/pcb_expr_functions.cpp ${CMAKE_SOURCE_DIR}/pcbnew/board_commit.cpp ${CMAKE_SOURCE_DIR}/pcbnew/board_connected_item.cpp ${CMAKE_SOURCE_DIR}/pcbnew/board_design_settings.cpp diff --git a/common/libeval_compiler/libeval_compiler.cpp b/common/libeval_compiler/libeval_compiler.cpp index 98ec77182a..6429047d5a 100644 --- a/common/libeval_compiler/libeval_compiler.cpp +++ b/common/libeval_compiler/libeval_compiler.cpp @@ -1003,16 +1003,25 @@ bool COMPILER::generateUCode( UCODE* aCode, CONTEXT* aPreflightContext ) } else if( son && son->op == TR_UNIT ) { + if( m_unitResolver->GetSupportedUnits().empty() ) + { + msg.Printf( _( "Unexpected units for '%s'" ), *node->value.str ); + reportError( CST_CODEGEN, msg, node->srcPos ); + } + int units = son->value.idx; value = m_unitResolver->Convert( *node->value.str, units ); son->isVisited = true; } else { - msg.Printf( _( "Missing units for '%s'| (%s)" ), - *node->value.str, - m_unitResolver->GetSupportedUnitsMessage() ); - reportError( CST_CODEGEN, msg, node->srcPos ); + if( !m_unitResolver->GetSupportedUnitsMessage().empty() ) + { + msg.Printf( _( "Missing units for '%s'| (%s)" ), + *node->value.str, + m_unitResolver->GetSupportedUnitsMessage() ); + reportError( CST_CODEGEN, msg, node->srcPos ); + } value = EDA_UNIT_UTILS::UI::DoubleValueFromString( *node->value.str ); } diff --git a/pcbnew/drc/drc_rule_condition.cpp b/pcbnew/drc/drc_rule_condition.cpp index 947d16df4f..6dce18eee4 100644 --- a/pcbnew/drc/drc_rule_condition.cpp +++ b/pcbnew/drc/drc_rule_condition.cpp @@ -88,7 +88,7 @@ bool DRC_RULE_CONDITION::EvaluateFor( const BOARD_ITEM* aItemA, const BOARD_ITEM bool DRC_RULE_CONDITION::Compile( REPORTER* aReporter, int aSourceLine, int aSourceOffset ) { - PCB_EXPR_COMPILER compiler; + PCB_EXPR_COMPILER compiler( new PCB_UNIT_RESOLVER() ); if( aReporter ) { diff --git a/pcbnew/drc/drc_rule_parser.cpp b/pcbnew/drc/drc_rule_parser.cpp index 331604f666..d31c326c46 100644 --- a/pcbnew/drc/drc_rule_parser.cpp +++ b/pcbnew/drc/drc_rule_parser.cpp @@ -345,6 +345,9 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule ) reportError( msg ); } + bool unitless = c.m_Type == VIA_COUNT_CONSTRAINT + || c.m_Type == MIN_RESOLVED_SPOKES_CONSTRAINT; + if( c.m_Type == DISALLOW_CONSTRAINT ) { for( token = NextTok(); token != T_RIGHT; token = NextTok() ) @@ -416,15 +419,10 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule ) } else if( c.m_Type == MIN_RESOLVED_SPOKES_CONSTRAINT ) { - // We don't use a min/max/opt structure here for two reasons: - // - // 1) The min/max/opt parser can't handle unitless numbers, and if we make it handle - // them then it will no longer catch the more common case of forgetting to add a unit - // and getting an ineffective rule because the distances are in nanometers. - // - // 2) Min/max/opt gives a strong implication that you could specify the optimal number - // of spokes. We don't want to open that door because the spoke generator is highly - // optimized around being able to "cheat" off of a cartesian coordinate system. + // We don't use a min/max/opt structure here because it would give a strong implication + // that you could specify the optimal number of spokes. We don't want to open that door + // because the spoke generator is highly optimized around being able to "cheat" off of a + // cartesian coordinate system. token = NextTok(); @@ -491,7 +489,7 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule ) break; } - parseValueWithUnits( FromUTF8(), value ); + parseValueWithUnits( FromUTF8(), value, unitless ); c.m_Value.SetMin( value ); if( (int) NextTok() != DSN_RIGHT ) @@ -511,7 +509,8 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule ) break; } - parseValueWithUnits( FromUTF8(), value ); + parseValueWithUnits( FromUTF8(), value, unitless ); + c.m_Value.SetMax( value ); if( (int) NextTok() != DSN_RIGHT ) @@ -531,7 +530,7 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule ) break; } - parseValueWithUnits( FromUTF8(), value ); + parseValueWithUnits( FromUTF8(), value, unitless ); c.m_Value.SetOpt( value ); if( (int) NextTok() != DSN_RIGHT ) @@ -562,7 +561,7 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule ) } -void DRC_RULES_PARSER::parseValueWithUnits( const wxString& aExpr, int& aResult ) +void DRC_RULES_PARSER::parseValueWithUnits( const wxString& aExpr, int& aResult, bool aUnitless ) { auto errorHandler = [&]( const wxString& aMessage, int aOffset ) { @@ -585,7 +584,8 @@ void DRC_RULES_PARSER::parseValueWithUnits( const wxString& aExpr, int& aResult } }; - PCB_EXPR_EVALUATOR evaluator; + PCB_EXPR_EVALUATOR evaluator( aUnitless ? (LIBEVAL::UNIT_RESOLVER*) new PCB_UNITLESS_RESOLVER() + : (LIBEVAL::UNIT_RESOLVER*) new PCB_UNIT_RESOLVER() ); evaluator.SetErrorCallback( errorHandler ); evaluator.Evaluate( aExpr ); diff --git a/pcbnew/drc/drc_rule_parser.h b/pcbnew/drc/drc_rule_parser.h index a5a3e963c7..d575f0139f 100644 --- a/pcbnew/drc/drc_rule_parser.h +++ b/pcbnew/drc/drc_rule_parser.h @@ -49,7 +49,7 @@ private: std::shared_ptr parseDRC_RULE(); void parseConstraint( DRC_RULE* aRule ); - void parseValueWithUnits( const wxString& aExpr, int& aResult ); + void parseValueWithUnits( const wxString& aExpr, int& aResult, bool aUnitless = false ); LSET parseLayer(); SEVERITY parseSeverity(); void parseUnknown(); diff --git a/pcbnew/pcb_expr_evaluator.cpp b/pcbnew/pcb_expr_evaluator.cpp index 912ac06ba5..3d7822fcb8 100644 --- a/pcbnew/pcb_expr_evaluator.cpp +++ b/pcbnew/pcb_expr_evaluator.cpp @@ -25,964 +25,13 @@ #include #include #include -#include -#include -#include -#include -#include +#include #include -#include - -#include -#include -#include - #include -bool fromToFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; - LIBEVAL::VALUE* result = aCtx->AllocValue(); - - LIBEVAL::VALUE* argTo = aCtx->Pop(); - LIBEVAL::VALUE* argFrom = aCtx->Pop(); - - result->Set(0.0); - aCtx->Push( result ); - - if(!item) - return false; - - auto ftCache = item->GetBoard()->GetConnectivity()->GetFromToCache(); - - if( !ftCache ) - { - wxLogWarning( wxT( "Attempting to call fromTo() with non-existent from-to cache." ) ); - return true; - } - - if( ftCache->IsOnFromToPath( static_cast( item ), - argFrom->AsString(), argTo->AsString() ) ) - { - result->Set(1.0); - } - - return true; -} - - -#define MISSING_LAYER_ARG( f ) wxString::Format( _( "Missing layer name argument to %s." ), f ) - -static void existsOnLayerFunc( LIBEVAL::CONTEXT* aCtx, void *self ) -{ - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; - - LIBEVAL::VALUE* arg = aCtx->Pop(); - LIBEVAL::VALUE* result = aCtx->AllocValue(); - - result->Set( 0.0 ); - aCtx->Push( result ); - - if( !item ) - return; - - if( !arg ) - { - if( aCtx->HasErrorCallback() ) - aCtx->ReportError( MISSING_LAYER_ARG( wxT( "existsOnLayer()" ) ) ); - - return; - } - - result->SetDeferredEval( - [item, arg, aCtx]() -> double - { - const wxString& layerName = arg->AsString(); - wxPGChoices& layerMap = ENUM_MAP::Instance().Choices(); - - if( aCtx->HasErrorCallback()) - { - /* - * Interpreted version - */ - - bool anyMatch = false; - - for( unsigned ii = 0; ii < layerMap.GetCount(); ++ii ) - { - wxPGChoiceEntry& entry = layerMap[ ii ]; - - if( entry.GetText().Matches( layerName )) - { - anyMatch = true; - - if( item->IsOnLayer( ToLAYER_ID( entry.GetValue()))) - return 1.0; - } - } - - if( !anyMatch ) - { - aCtx->ReportError( wxString::Format( _( "Unrecognized layer '%s'" ), - layerName ) ); - } - } - else - { - /* - * Compiled version - */ - - BOARD* board = item->GetBoard(); - std::unique_lock cacheLock( board->m_CachesMutex ); - auto i = board->m_LayerExpressionCache.find( layerName ); - LSET mask; - - if( i == board->m_LayerExpressionCache.end() ) - { - for( unsigned ii = 0; ii < layerMap.GetCount(); ++ii ) - { - wxPGChoiceEntry& entry = layerMap[ ii ]; - - if( entry.GetText().Matches( layerName ) ) - mask.set( ToLAYER_ID( entry.GetValue() ) ); - } - - board->m_LayerExpressionCache[ layerName ] = mask; - } - else - { - mask = i->second; - } - - if( ( item->GetLayerSet() & mask ).any() ) - return 1.0; - } - - return 0.0; - } ); -} - - -static void isPlatedFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - LIBEVAL::VALUE* result = aCtx->AllocValue(); - - result->Set( 0.0 ); - aCtx->Push( result ); - - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; - - if( !item ) - return; - - if( item->Type() == PCB_PAD_T && static_cast( item )->GetAttribute() == PAD_ATTRIB::PTH ) - result->Set( 1.0 ); - else if( item->Type() == PCB_VIA_T ) - result->Set( 1.0 ); -} - - -bool collidesWithCourtyard( BOARD_ITEM* aItem, std::shared_ptr& aItemShape, - PCB_EXPR_CONTEXT* aCtx, FOOTPRINT* aFootprint, PCB_LAYER_ID aSide ) -{ - SHAPE_POLY_SET footprintCourtyard; - - footprintCourtyard = aFootprint->GetCourtyard( aSide ); - - if( !aItemShape ) - { - // Since rules are used for zone filling we can't rely on the filled shapes. - // Use the zone outline instead. - if( ZONE* zone = dynamic_cast( aItem ) ) - aItemShape.reset( zone->Outline()->Clone() ); - else - aItemShape = aItem->GetEffectiveShape( aCtx->GetLayer() ); - } - - return footprintCourtyard.Collide( aItemShape.get() ); -}; - - -static bool searchFootprints( BOARD* aBoard, const wxString& aArg, PCB_EXPR_CONTEXT* aCtx, - std::function aFunc ) -{ - if( aArg == wxT( "A" ) ) - { - FOOTPRINT* fp = dynamic_cast( aCtx->GetItem( 0 ) ); - - if( fp && aFunc( fp ) ) - return 1.0; - } - else if( aArg == wxT( "B" ) ) - { - FOOTPRINT* fp = dynamic_cast( aCtx->GetItem( 1 ) ); - - if( fp && aFunc( fp ) ) - return 1.0; - } - else for( FOOTPRINT* fp : aBoard->Footprints() ) - { - if( fp->GetReference().Matches( aArg ) ) - { - if( aFunc( fp ) ) - return 1.0; - } - } - - return 0.0; -} - - -#define MISSING_FP_ARG( f ) \ - wxString::Format( _( "Missing footprint argument (A, B, or reference designator) to %s." ), f ) - -static void intersectsCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - PCB_EXPR_CONTEXT* context = static_cast( aCtx ); - LIBEVAL::VALUE* arg = context->Pop(); - LIBEVAL::VALUE* result = context->AllocValue(); - - result->Set( 0.0 ); - context->Push( result ); - - if( !arg ) - { - if( context->HasErrorCallback() ) - context->ReportError( MISSING_FP_ARG( wxT( "intersectsCourtyard()" ) ) ); - - return; - } - - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr; - - if( !item ) - return; - - result->SetDeferredEval( - [item, arg, context]() -> double - { - BOARD* board = item->GetBoard(); - std::shared_ptr itemShape; - - if( searchFootprints( board, arg->AsString(), context, - [&]( FOOTPRINT* fp ) - { - PTR_PTR_CACHE_KEY key = { fp, item }; - std::unique_lock cacheLock( board->m_CachesMutex ); - - auto i = board->m_IntersectsCourtyardCache.find( key ); - - if( i != board->m_IntersectsCourtyardCache.end() ) - return i->second; - - bool res = collidesWithCourtyard( item, itemShape, context, fp, F_Cu ) - || collidesWithCourtyard( item, itemShape, context, fp, B_Cu ); - - board->m_IntersectsCourtyardCache[ key ] = res; - return res; - } ) ) - { - return 1.0; - } - - return 0.0; - } ); -} - - -static void intersectsFrontCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - PCB_EXPR_CONTEXT* context = static_cast( aCtx ); - LIBEVAL::VALUE* arg = context->Pop(); - LIBEVAL::VALUE* result = context->AllocValue(); - - result->Set( 0.0 ); - context->Push( result ); - - if( !arg ) - { - if( context->HasErrorCallback() ) - context->ReportError( MISSING_FP_ARG( wxT( "intersectsFrontCourtyard()" ) ) ); - - return; - } - - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr; - - if( !item ) - return; - - result->SetDeferredEval( - [item, arg, context]() -> double - { - BOARD* board = item->GetBoard(); - std::shared_ptr itemShape; - - if( searchFootprints( board, arg->AsString(), context, - [&]( FOOTPRINT* fp ) - { - PTR_PTR_CACHE_KEY key = { fp, item }; - std::unique_lock cacheLock( board->m_CachesMutex ); - - auto i = board->m_IntersectsFCourtyardCache.find( key ); - - if( i != board->m_IntersectsFCourtyardCache.end() ) - return i->second; - - bool res = collidesWithCourtyard( item, itemShape, context, fp, F_Cu ); - - board->m_IntersectsFCourtyardCache[ key ] = res; - return res; - } ) ) - { - return 1.0; - } - - return 0.0; - } ); -} - - -static void intersectsBackCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - PCB_EXPR_CONTEXT* context = static_cast( aCtx ); - LIBEVAL::VALUE* arg = context->Pop(); - LIBEVAL::VALUE* result = context->AllocValue(); - - result->Set( 0.0 ); - context->Push( result ); - - if( !arg ) - { - if( context->HasErrorCallback() ) - context->ReportError( MISSING_FP_ARG( wxT( "intersectsBackCourtyard()" ) ) ); - - return; - } - - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr; - - if( !item ) - return; - - result->SetDeferredEval( - [item, arg, context]() -> double - { - BOARD* board = item->GetBoard(); - std::shared_ptr itemShape; - - if( searchFootprints( board, arg->AsString(), context, - [&]( FOOTPRINT* fp ) - { - PTR_PTR_CACHE_KEY key = { fp, item }; - std::unique_lock cacheLock( board->m_CachesMutex ); - - auto i = board->m_IntersectsBCourtyardCache.find( key ); - - if( i != board->m_IntersectsBCourtyardCache.end() ) - return i->second; - - bool res = collidesWithCourtyard( item, itemShape, context, fp, B_Cu ); - - board->m_IntersectsBCourtyardCache[ key ] = res; - return res; - } ) ) - { - return 1.0; - } - - return 0.0; - } ); -} - - -bool collidesWithArea( BOARD_ITEM* aItem, PCB_EXPR_CONTEXT* aCtx, ZONE* aArea ) -{ - BOARD* board = aArea->GetBoard(); - BOX2I areaBBox = aArea->GetBoundingBox(); - std::shared_ptr shape; - - // 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. - SHAPE_POLY_SET areaOutline = aArea->Outline()->CloneDropTriangulation(); - areaOutline.Deflate( board->GetDesignSettings().GetDRCEpsilon(), 0, - SHAPE_POLY_SET::ALLOW_ACUTE_CORNERS ); - - if( aItem->GetFlags() & HOLE_PROXY ) - { - if( aItem->Type() == PCB_PAD_T ) - { - return areaOutline.Collide( aItem->GetEffectiveHoleShape().get() ); - } - else if( aItem->Type() == PCB_VIA_T ) - { - LSET overlap = aItem->GetLayerSet() & aArea->GetLayerSet(); - - /// Avoid buried vias that don't overlap the zone's layers - if( overlap.any() ) - { - if( aCtx->GetLayer() == UNDEFINED_LAYER || overlap.Contains( aCtx->GetLayer() ) ) - return areaOutline.Collide( aItem->GetEffectiveHoleShape().get() ); - } - } - - return false; - } - - if( aItem->Type() == PCB_FOOTPRINT_T ) - { - FOOTPRINT* footprint = static_cast( aItem ); - - if( ( footprint->GetFlags() & MALFORMED_COURTYARDS ) != 0 ) - { - if( aCtx->HasErrorCallback() ) - aCtx->ReportError( _( "Footprint's courtyard is not a single, closed shape." ) ); - - return false; - } - - if( ( aArea->GetLayerSet() & LSET::FrontMask() ).any() ) - { - const SHAPE_POLY_SET& courtyard = footprint->GetCourtyard( F_CrtYd ); - - if( courtyard.OutlineCount() == 0 ) - { - if( aCtx->HasErrorCallback() ) - aCtx->ReportError( _( "Footprint has no front courtyard." ) ); - - return false; - } - else - { - return areaOutline.Collide( &courtyard.Outline( 0 ) ); - } - } - - if( ( aArea->GetLayerSet() & LSET::BackMask() ).any() ) - { - const SHAPE_POLY_SET& courtyard = footprint->GetCourtyard( B_CrtYd ); - - if( courtyard.OutlineCount() == 0 ) - { - if( aCtx->HasErrorCallback() ) - aCtx->ReportError( _( "Footprint has no back courtyard." ) ); - - return false; - } - else - { - return areaOutline.Collide( &courtyard.Outline( 0 ) ); - } - } - - return false; - } - - if( aItem->Type() == PCB_ZONE_T || aItem->Type() == PCB_FP_ZONE_T ) - { - ZONE* zone = static_cast( aItem ); - - if( !zone->IsFilled() ) - return false; - - DRC_RTREE* zoneRTree = board->m_CopperZoneRTreeCache[ zone ].get(); - - if( zoneRTree ) - { - for( PCB_LAYER_ID layer : aArea->GetLayerSet().Seq() ) - { - if( aCtx->GetLayer() == layer || aCtx->GetLayer() == UNDEFINED_LAYER ) - { - if( zoneRTree->QueryColliding( areaBBox, &areaOutline, layer ) ) - return true; - } - } - } - - return false; - } - else - { - PCB_LAYER_ID layer = aCtx->GetLayer(); - - if( layer != UNDEFINED_LAYER && !( aArea->GetLayerSet().Contains( layer ) ) ) - return false; - - if( !shape ) - shape = aItem->GetEffectiveShape( layer ); - - return areaOutline.Collide( shape.get() ); - } -} - - -bool searchAreas( BOARD* aBoard, const wxString& aArg, PCB_EXPR_CONTEXT* aCtx, - std::function aFunc ) -{ - if( aArg == wxT( "A" ) ) - { - return aFunc( dynamic_cast( aCtx->GetItem( 0 ) ) ); - } - else if( aArg == wxT( "B" ) ) - { - return aFunc( dynamic_cast( aCtx->GetItem( 1 ) ) ); - } - else if( KIID::SniffTest( aArg ) ) - { - KIID target( aArg ); - - for( ZONE* area : aBoard->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 aFunc( area ); - } - - for( FOOTPRINT* footprint : aBoard->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 aFunc( area ); - } - } - - return 0.0; - } - else // Match on zone name - { - for( ZONE* area : aBoard->Zones() ) - { - if( area->GetZoneName().Matches( aArg ) ) - { - // Many zones can match the name; exit only when we find an "inside" - if( aFunc( area ) ) - return true; - } - } - - for( FOOTPRINT* footprint : aBoard->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( aFunc( area ) ) - return true; - } - } - } - - return false; - } -} - - -#define MISSING_AREA_ARG( f ) \ - wxString::Format( _( "Missing rule-area argument (A, B, or rule-area name) to %s." ), f ) - -static void intersectsAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - PCB_EXPR_CONTEXT* context = static_cast( aCtx ); - LIBEVAL::VALUE* arg = aCtx->Pop(); - LIBEVAL::VALUE* result = aCtx->AllocValue(); - - result->Set( 0.0 ); - aCtx->Push( result ); - - if( !arg ) - { - if( aCtx->HasErrorCallback() ) - aCtx->ReportError( MISSING_AREA_ARG( wxT( "intersectsArea()" ) ) ); - - return; - } - - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr; - - if( !item ) - return; - - result->SetDeferredEval( - [item, arg, context]() -> double - { - BOARD* board = item->GetBoard(); - PCB_LAYER_ID layer = context->GetLayer(); - BOX2I itemBBox = item->GetBoundingBox(); - - if( searchAreas( board, arg->AsString(), context, - [&]( ZONE* aArea ) - { - if( !aArea || aArea == item || aArea->GetParent() == item ) - return false; - - if( !( aArea->GetLayerSet() & item->GetLayerSet() ).any() ) - return false; - - if( !aArea->GetBoundingBox().Intersects( itemBBox ) ) - return false; - - std::unique_lock 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, context, aArea ); - - board->m_IntersectsAreaCache[ key ] = collides; - - return collides; - } ) ) - { - return 1.0; - } - - return 0.0; - } ); -} - - -static void enclosedByAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - PCB_EXPR_CONTEXT* context = static_cast( aCtx ); - LIBEVAL::VALUE* arg = aCtx->Pop(); - LIBEVAL::VALUE* result = aCtx->AllocValue(); - - result->Set( 0.0 ); - aCtx->Push( result ); - - if( !arg ) - { - if( aCtx->HasErrorCallback() ) - aCtx->ReportError( MISSING_AREA_ARG( wxT( "enclosedByArea()" ) ) ); - - return; - } - - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr; - - if( !item ) - return; - - result->SetDeferredEval( - [item, arg, context]() -> double - { - BOARD* board = item->GetBoard(); - int maxError = board->GetDesignSettings().m_MaxError; - PCB_LAYER_ID layer = context->GetLayer(); - BOX2I itemBBox = item->GetBoundingBox(); - - if( searchAreas( board, arg->AsString(), context, - [&]( ZONE* aArea ) - { - if( !aArea || aArea == item || aArea->GetParent() == item ) - return false; - - if( !( aArea->GetLayerSet() & item->GetLayerSet() ).any() ) - return false; - - if( !aArea->GetBoundingBox().Intersects( itemBBox ) ) - return false; - - std::unique_lock 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; - bool enclosedByArea; - - item->TransformShapeToPolygon( itemShape, layer, 0, maxError, - ERROR_OUTSIDE ); - - if( itemShape.IsEmpty() ) - { - // If it's already empty then our test will have no meaning. - enclosedByArea = false; - } - else - { - itemShape.BooleanSubtract( *aArea->Outline(), - SHAPE_POLY_SET::PM_FAST ); - - enclosedByArea = itemShape.IsEmpty(); - } - - board->m_EnclosedByAreaCache[ key ] = enclosedByArea; - - return enclosedByArea; - } ) ) - { - return 1.0; - } - - return 0.0; - } ); -} - - -#define MISSING_GROUP_ARG( f ) \ - wxString::Format( _( "Missing group name argument to %s." ), f ) - -static void memberOfFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - LIBEVAL::VALUE* arg = aCtx->Pop(); - LIBEVAL::VALUE* result = aCtx->AllocValue(); - - result->Set( 0.0 ); - aCtx->Push( result ); - - if( !arg ) - { - if( aCtx->HasErrorCallback() ) - aCtx->ReportError( MISSING_GROUP_ARG( wxT( "memberOf()" ) ) ); - - return; - } - - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; - - if( !item ) - return; - - result->SetDeferredEval( - [item, arg]() -> double - { - PCB_GROUP* group = item->GetParentGroup(); - - if( !group && item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T ) - group = item->GetParent()->GetParentGroup(); - - while( group ) - { - if( group->GetName().Matches( arg->AsString() ) ) - return 1.0; - - group = group->GetParentGroup(); - } - - return 0.0; - } ); -} - - -static void isMicroVia( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; - LIBEVAL::VALUE* result = aCtx->AllocValue(); - - result->Set( 0.0 ); - aCtx->Push( result ); - - PCB_VIA* via = dyn_cast( item ); - - if( via && via->GetViaType() == VIATYPE::MICROVIA ) - result->Set ( 1.0 ); -} - - -static void isBlindBuriedViaFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; - LIBEVAL::VALUE* result = aCtx->AllocValue(); - - result->Set( 0.0 ); - aCtx->Push( result ); - - PCB_VIA* via = dyn_cast( item ); - - if( via && via->GetViaType() == VIATYPE::BLIND_BURIED ) - result->Set ( 1.0 ); -} - - -static void isCoupledDiffPairFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - PCB_EXPR_CONTEXT* context = static_cast( aCtx ); - BOARD_CONNECTED_ITEM* a = dynamic_cast( context->GetItem( 0 ) ); - BOARD_CONNECTED_ITEM* b = dynamic_cast( context->GetItem( 1 ) ); - LIBEVAL::VALUE* result = aCtx->AllocValue(); - - result->Set( 0.0 ); - aCtx->Push( result ); - - result->SetDeferredEval( - [a, b, context]() -> double - { - NETINFO_ITEM* netinfo = a ? a->GetNet() : nullptr; - - if( !netinfo ) - return 0.0; - - wxString coupledNet; - wxString dummy; - - if( !DRC_ENGINE::MatchDpSuffix( netinfo->GetNetname(), coupledNet, dummy ) ) - return 0.0; - - if( context->GetConstraint() == DRC_CONSTRAINT_T::LENGTH_CONSTRAINT - || context->GetConstraint() == DRC_CONSTRAINT_T::SKEW_CONSTRAINT ) - { - // DRC engine evaluates these singly, so we won't have a B item - return 1.0; - } - - return b && b->GetNetname() == coupledNet; - } ); -} - - -#define MISSING_DP_ARG( f ) \ - wxString::Format( _( "Missing diff-pair name argument to %s." ), f ) - -static void inDiffPairFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - LIBEVAL::VALUE* argv = aCtx->Pop(); - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; - LIBEVAL::VALUE* result = aCtx->AllocValue(); - - result->Set( 0.0 ); - aCtx->Push( result ); - - if( !argv ) - { - if( aCtx->HasErrorCallback() ) - aCtx->ReportError( MISSING_DP_ARG( wxT( "inDiffPair()" ) ) ); - - return; - } - - if( !item || !item->GetBoard() ) - return; - - result->SetDeferredEval( - [item, argv]() -> double - { - if( item && item->IsConnected() ) - { - NETINFO_ITEM* netinfo = static_cast( item )->GetNet(); - - wxString refName = netinfo->GetNetname(); - wxString arg = argv->AsString(); - wxString baseName, coupledNet; - int polarity = DRC_ENGINE::MatchDpSuffix( refName, coupledNet, baseName ); - - if( polarity != 0 && item->GetBoard()->FindNet( coupledNet ) ) - { - if( baseName.Matches( arg ) ) - return 1.0; - - if( baseName.EndsWith( "_" ) && baseName.BeforeLast( '_' ).Matches( arg ) ) - return 1.0; - } - } - - return 0.0; - } ); -} - - -static void getFieldFunc( LIBEVAL::CONTEXT* aCtx, void* self ) -{ - LIBEVAL::VALUE* arg = aCtx->Pop(); - PCB_EXPR_VAR_REF* vref = static_cast( self ); - BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; - LIBEVAL::VALUE* result = aCtx->AllocValue(); - - result->Set( "" ); - aCtx->Push( result ); - - if( !arg ) - { - if( aCtx->HasErrorCallback() ) - { - aCtx->ReportError( wxString::Format( _( "Missing field name argument to %s." ), - wxT( "getField()" ) ) ); - } - - return; - } - - if( !item || !item->GetBoard() ) - return; - - result->SetDeferredEval( - [item, arg]() -> wxString - { - if( item && item->Type() == PCB_FOOTPRINT_T ) - { - FOOTPRINT* fp = static_cast( item ); - - if( fp->HasProperty( arg->AsString() ) ) - return fp->GetProperty( arg->AsString() ); - } - - return ""; - } ); -} - - -PCB_EXPR_BUILTIN_FUNCTIONS::PCB_EXPR_BUILTIN_FUNCTIONS() -{ - RegisterAllFunctions(); -} - - -void PCB_EXPR_BUILTIN_FUNCTIONS::RegisterAllFunctions() -{ - m_funcs.clear(); - - RegisterFunc( wxT( "existsOnLayer('x')" ), existsOnLayerFunc ); - - RegisterFunc( wxT( "isPlated()" ), isPlatedFunc ); - - RegisterFunc( wxT( "insideCourtyard('x') DEPRECATED" ), intersectsCourtyardFunc ); - RegisterFunc( wxT( "insideFrontCourtyard('x') DEPRECATED" ), intersectsFrontCourtyardFunc ); - RegisterFunc( wxT( "insideBackCourtyard('x') DEPRECATED" ), intersectsBackCourtyardFunc ); - RegisterFunc( wxT( "intersectsCourtyard('x')" ), intersectsCourtyardFunc ); - RegisterFunc( wxT( "intersectsFrontCourtyard('x')" ), intersectsFrontCourtyardFunc ); - RegisterFunc( wxT( "intersectsBackCourtyard('x')" ), intersectsBackCourtyardFunc ); - - 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 ); -} - +/* -------------------------------------------------------------------------------------------- + * Specialized Expression References + */ BOARD_ITEM* PCB_EXPR_VAR_REF::GetObject( const LIBEVAL::CONTEXT* aCtx ) const { @@ -1236,49 +285,65 @@ BOARD* PCB_EXPR_CONTEXT::GetBoard() const } -class PCB_UNIT_RESOLVER : public LIBEVAL::UNIT_RESOLVER +/* -------------------------------------------------------------------------------------------- + * Unit Resolvers + */ + +const std::vector& PCB_UNIT_RESOLVER::GetSupportedUnits() const { -public: - virtual ~PCB_UNIT_RESOLVER() - { - } + static const std::vector pcbUnits = { wxT( "mil" ), wxT( "mm" ), wxT( "in" ) }; - virtual const std::vector& GetSupportedUnits() const override - { - static const std::vector pcbUnits = { wxT( "mil" ), wxT( "mm" ), wxT( "in" ) }; - - return pcbUnits; - } - - virtual wxString GetSupportedUnitsMessage() const override - { - return _( "must be mm, in, or mil" ); - } - - virtual double Convert( const wxString& aString, int unitId ) const override - { - double v = wxAtof( aString ); - - switch( unitId ) - { - case 0: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::MILS, aString ); - case 1: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::MILLIMETRES, aString ); - case 2: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::INCHES, aString ); - default: return v; - } - }; -}; - - -PCB_EXPR_COMPILER::PCB_EXPR_COMPILER() -{ - m_unitResolver = std::make_unique(); + return pcbUnits; } -PCB_EXPR_EVALUATOR::PCB_EXPR_EVALUATOR() : +wxString PCB_UNIT_RESOLVER::GetSupportedUnitsMessage() const +{ + return _( "must be mm, in, or mil" ); +} + + +double PCB_UNIT_RESOLVER::Convert( const wxString& aString, int unitId ) const +{ + double v = wxAtof( aString ); + + switch( unitId ) + { + case 0: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::MILS, aString ); + case 1: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::MILLIMETRES, aString ); + case 2: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::INCHES, aString ); + default: return v; + } +}; + + +const std::vector& PCB_UNITLESS_RESOLVER::GetSupportedUnits() const +{ + static const std::vector emptyUnits; + + return emptyUnits; +} + + +double PCB_UNITLESS_RESOLVER::Convert( const wxString& aString, int unitId ) const +{ + return wxAtof( aString ); +}; + + +PCB_EXPR_COMPILER::PCB_EXPR_COMPILER( LIBEVAL::UNIT_RESOLVER* aUnitResolver ) +{ + m_unitResolver.reset( aUnitResolver ); +} + + +/* -------------------------------------------------------------------------------------------- + * PCB Expression Evaluator + */ + +PCB_EXPR_EVALUATOR::PCB_EXPR_EVALUATOR( LIBEVAL::UNIT_RESOLVER* aUnitResolver ) : m_result( 0 ), - m_compiler(), + m_compiler( aUnitResolver ), m_ucode(), m_errorStatus() { diff --git a/pcbnew/pcb_expr_evaluator.h b/pcbnew/pcb_expr_evaluator.h index 3240977766..43030a3266 100644 --- a/pcbnew/pcb_expr_evaluator.h +++ b/pcbnew/pcb_expr_evaluator.h @@ -196,17 +196,37 @@ private: }; +class PCB_UNIT_RESOLVER : public LIBEVAL::UNIT_RESOLVER +{ +public: + const std::vector& GetSupportedUnits() const override; + + wxString GetSupportedUnitsMessage() const override; + + double Convert( const wxString& aString, int unitId ) const override; +}; + + +class PCB_UNITLESS_RESOLVER : public LIBEVAL::UNIT_RESOLVER +{ +public: + const std::vector& GetSupportedUnits() const override; + + double Convert( const wxString& aString, int unitId ) const override; +}; + + class PCB_EXPR_COMPILER : public LIBEVAL::COMPILER { public: - PCB_EXPR_COMPILER(); + PCB_EXPR_COMPILER( LIBEVAL::UNIT_RESOLVER* aUnitResolver ); }; class PCB_EXPR_EVALUATOR { public: - PCB_EXPR_EVALUATOR( ); + PCB_EXPR_EVALUATOR( LIBEVAL::UNIT_RESOLVER* aUnitResolver ); ~PCB_EXPR_EVALUATOR(); bool Evaluate( const wxString& aExpr ); diff --git a/pcbnew/pcb_expr_functions.cpp b/pcbnew/pcb_expr_functions.cpp new file mode 100644 index 0000000000..e05a0f577e --- /dev/null +++ b/pcbnew/pcb_expr_functions.cpp @@ -0,0 +1,985 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019-2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +bool fromToFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + LIBEVAL::VALUE* argTo = aCtx->Pop(); + LIBEVAL::VALUE* argFrom = aCtx->Pop(); + + result->Set(0.0); + aCtx->Push( result ); + + if(!item) + return false; + + auto ftCache = item->GetBoard()->GetConnectivity()->GetFromToCache(); + + if( !ftCache ) + { + wxLogWarning( wxT( "Attempting to call fromTo() with non-existent from-to cache." ) ); + return true; + } + + if( ftCache->IsOnFromToPath( static_cast( item ), + argFrom->AsString(), argTo->AsString() ) ) + { + result->Set(1.0); + } + + return true; +} + + +#define MISSING_LAYER_ARG( f ) wxString::Format( _( "Missing layer name argument to %s." ), f ) + +static void existsOnLayerFunc( LIBEVAL::CONTEXT* aCtx, void *self ) +{ + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; + + LIBEVAL::VALUE* arg = aCtx->Pop(); + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + result->Set( 0.0 ); + aCtx->Push( result ); + + if( !item ) + return; + + if( !arg ) + { + if( aCtx->HasErrorCallback() ) + aCtx->ReportError( MISSING_LAYER_ARG( wxT( "existsOnLayer()" ) ) ); + + return; + } + + result->SetDeferredEval( + [item, arg, aCtx]() -> double + { + const wxString& layerName = arg->AsString(); + wxPGChoices& layerMap = ENUM_MAP::Instance().Choices(); + + if( aCtx->HasErrorCallback()) + { + /* + * Interpreted version + */ + + bool anyMatch = false; + + for( unsigned ii = 0; ii < layerMap.GetCount(); ++ii ) + { + wxPGChoiceEntry& entry = layerMap[ ii ]; + + if( entry.GetText().Matches( layerName )) + { + anyMatch = true; + + if( item->IsOnLayer( ToLAYER_ID( entry.GetValue()))) + return 1.0; + } + } + + if( !anyMatch ) + { + aCtx->ReportError( wxString::Format( _( "Unrecognized layer '%s'" ), + layerName ) ); + } + } + else + { + /* + * Compiled version + */ + + BOARD* board = item->GetBoard(); + std::unique_lock cacheLock( board->m_CachesMutex ); + auto i = board->m_LayerExpressionCache.find( layerName ); + LSET mask; + + if( i == board->m_LayerExpressionCache.end() ) + { + for( unsigned ii = 0; ii < layerMap.GetCount(); ++ii ) + { + wxPGChoiceEntry& entry = layerMap[ ii ]; + + if( entry.GetText().Matches( layerName ) ) + mask.set( ToLAYER_ID( entry.GetValue() ) ); + } + + board->m_LayerExpressionCache[ layerName ] = mask; + } + else + { + mask = i->second; + } + + if( ( item->GetLayerSet() & mask ).any() ) + return 1.0; + } + + return 0.0; + } ); +} + + +static void isPlatedFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + result->Set( 0.0 ); + aCtx->Push( result ); + + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; + + if( !item ) + return; + + if( item->Type() == PCB_PAD_T && static_cast( item )->GetAttribute() == PAD_ATTRIB::PTH ) + result->Set( 1.0 ); + else if( item->Type() == PCB_VIA_T ) + result->Set( 1.0 ); +} + + +bool collidesWithCourtyard( BOARD_ITEM* aItem, std::shared_ptr& aItemShape, + PCB_EXPR_CONTEXT* aCtx, FOOTPRINT* aFootprint, PCB_LAYER_ID aSide ) +{ + SHAPE_POLY_SET footprintCourtyard; + + footprintCourtyard = aFootprint->GetCourtyard( aSide ); + + if( !aItemShape ) + { + // Since rules are used for zone filling we can't rely on the filled shapes. + // Use the zone outline instead. + if( ZONE* zone = dynamic_cast( aItem ) ) + aItemShape.reset( zone->Outline()->Clone() ); + else + aItemShape = aItem->GetEffectiveShape( aCtx->GetLayer() ); + } + + return footprintCourtyard.Collide( aItemShape.get() ); +}; + + +static bool searchFootprints( BOARD* aBoard, const wxString& aArg, PCB_EXPR_CONTEXT* aCtx, + std::function aFunc ) +{ + if( aArg == wxT( "A" ) ) + { + FOOTPRINT* fp = dynamic_cast( aCtx->GetItem( 0 ) ); + + if( fp && aFunc( fp ) ) + return 1.0; + } + else if( aArg == wxT( "B" ) ) + { + FOOTPRINT* fp = dynamic_cast( aCtx->GetItem( 1 ) ); + + if( fp && aFunc( fp ) ) + return 1.0; + } + else for( FOOTPRINT* fp : aBoard->Footprints() ) + { + if( fp->GetReference().Matches( aArg ) ) + { + if( aFunc( fp ) ) + return 1.0; + } + } + + return 0.0; +} + + +#define MISSING_FP_ARG( f ) \ + wxString::Format( _( "Missing footprint argument (A, B, or reference designator) to %s." ), f ) + +static void intersectsCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + PCB_EXPR_CONTEXT* context = static_cast( aCtx ); + LIBEVAL::VALUE* arg = context->Pop(); + LIBEVAL::VALUE* result = context->AllocValue(); + + result->Set( 0.0 ); + context->Push( result ); + + if( !arg ) + { + if( context->HasErrorCallback() ) + context->ReportError( MISSING_FP_ARG( wxT( "intersectsCourtyard()" ) ) ); + + return; + } + + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr; + + if( !item ) + return; + + result->SetDeferredEval( + [item, arg, context]() -> double + { + BOARD* board = item->GetBoard(); + std::shared_ptr itemShape; + + if( searchFootprints( board, arg->AsString(), context, + [&]( FOOTPRINT* fp ) + { + PTR_PTR_CACHE_KEY key = { fp, item }; + std::unique_lock cacheLock( board->m_CachesMutex ); + + auto i = board->m_IntersectsCourtyardCache.find( key ); + + if( i != board->m_IntersectsCourtyardCache.end() ) + return i->second; + + bool res = collidesWithCourtyard( item, itemShape, context, fp, F_Cu ) + || collidesWithCourtyard( item, itemShape, context, fp, B_Cu ); + + board->m_IntersectsCourtyardCache[ key ] = res; + return res; + } ) ) + { + return 1.0; + } + + return 0.0; + } ); +} + + +static void intersectsFrontCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + PCB_EXPR_CONTEXT* context = static_cast( aCtx ); + LIBEVAL::VALUE* arg = context->Pop(); + LIBEVAL::VALUE* result = context->AllocValue(); + + result->Set( 0.0 ); + context->Push( result ); + + if( !arg ) + { + if( context->HasErrorCallback() ) + context->ReportError( MISSING_FP_ARG( wxT( "intersectsFrontCourtyard()" ) ) ); + + return; + } + + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr; + + if( !item ) + return; + + result->SetDeferredEval( + [item, arg, context]() -> double + { + BOARD* board = item->GetBoard(); + std::shared_ptr itemShape; + + if( searchFootprints( board, arg->AsString(), context, + [&]( FOOTPRINT* fp ) + { + PTR_PTR_CACHE_KEY key = { fp, item }; + std::unique_lock cacheLock( board->m_CachesMutex ); + + auto i = board->m_IntersectsFCourtyardCache.find( key ); + + if( i != board->m_IntersectsFCourtyardCache.end() ) + return i->second; + + bool res = collidesWithCourtyard( item, itemShape, context, fp, F_Cu ); + + board->m_IntersectsFCourtyardCache[ key ] = res; + return res; + } ) ) + { + return 1.0; + } + + return 0.0; + } ); +} + + +static void intersectsBackCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + PCB_EXPR_CONTEXT* context = static_cast( aCtx ); + LIBEVAL::VALUE* arg = context->Pop(); + LIBEVAL::VALUE* result = context->AllocValue(); + + result->Set( 0.0 ); + context->Push( result ); + + if( !arg ) + { + if( context->HasErrorCallback() ) + context->ReportError( MISSING_FP_ARG( wxT( "intersectsBackCourtyard()" ) ) ); + + return; + } + + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr; + + if( !item ) + return; + + result->SetDeferredEval( + [item, arg, context]() -> double + { + BOARD* board = item->GetBoard(); + std::shared_ptr itemShape; + + if( searchFootprints( board, arg->AsString(), context, + [&]( FOOTPRINT* fp ) + { + PTR_PTR_CACHE_KEY key = { fp, item }; + std::unique_lock cacheLock( board->m_CachesMutex ); + + auto i = board->m_IntersectsBCourtyardCache.find( key ); + + if( i != board->m_IntersectsBCourtyardCache.end() ) + return i->second; + + bool res = collidesWithCourtyard( item, itemShape, context, fp, B_Cu ); + + board->m_IntersectsBCourtyardCache[ key ] = res; + return res; + } ) ) + { + return 1.0; + } + + return 0.0; + } ); +} + + +bool collidesWithArea( BOARD_ITEM* aItem, PCB_EXPR_CONTEXT* aCtx, ZONE* aArea ) +{ + BOARD* board = aArea->GetBoard(); + BOX2I areaBBox = aArea->GetBoundingBox(); + std::shared_ptr shape; + + // 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. + SHAPE_POLY_SET areaOutline = aArea->Outline()->CloneDropTriangulation(); + areaOutline.Deflate( board->GetDesignSettings().GetDRCEpsilon(), 0, + SHAPE_POLY_SET::ALLOW_ACUTE_CORNERS ); + + if( aItem->GetFlags() & HOLE_PROXY ) + { + if( aItem->Type() == PCB_PAD_T ) + { + return areaOutline.Collide( aItem->GetEffectiveHoleShape().get() ); + } + else if( aItem->Type() == PCB_VIA_T ) + { + LSET overlap = aItem->GetLayerSet() & aArea->GetLayerSet(); + + /// Avoid buried vias that don't overlap the zone's layers + if( overlap.any() ) + { + if( aCtx->GetLayer() == UNDEFINED_LAYER || overlap.Contains( aCtx->GetLayer() ) ) + return areaOutline.Collide( aItem->GetEffectiveHoleShape().get() ); + } + } + + return false; + } + + if( aItem->Type() == PCB_FOOTPRINT_T ) + { + FOOTPRINT* footprint = static_cast( aItem ); + + if( ( footprint->GetFlags() & MALFORMED_COURTYARDS ) != 0 ) + { + if( aCtx->HasErrorCallback() ) + aCtx->ReportError( _( "Footprint's courtyard is not a single, closed shape." ) ); + + return false; + } + + if( ( aArea->GetLayerSet() & LSET::FrontMask() ).any() ) + { + const SHAPE_POLY_SET& courtyard = footprint->GetCourtyard( F_CrtYd ); + + if( courtyard.OutlineCount() == 0 ) + { + if( aCtx->HasErrorCallback() ) + aCtx->ReportError( _( "Footprint has no front courtyard." ) ); + + return false; + } + else + { + return areaOutline.Collide( &courtyard.Outline( 0 ) ); + } + } + + if( ( aArea->GetLayerSet() & LSET::BackMask() ).any() ) + { + const SHAPE_POLY_SET& courtyard = footprint->GetCourtyard( B_CrtYd ); + + if( courtyard.OutlineCount() == 0 ) + { + if( aCtx->HasErrorCallback() ) + aCtx->ReportError( _( "Footprint has no back courtyard." ) ); + + return false; + } + else + { + return areaOutline.Collide( &courtyard.Outline( 0 ) ); + } + } + + return false; + } + + if( aItem->Type() == PCB_ZONE_T || aItem->Type() == PCB_FP_ZONE_T ) + { + ZONE* zone = static_cast( aItem ); + + if( !zone->IsFilled() ) + return false; + + DRC_RTREE* zoneRTree = board->m_CopperZoneRTreeCache[ zone ].get(); + + if( zoneRTree ) + { + for( PCB_LAYER_ID layer : aArea->GetLayerSet().Seq() ) + { + if( aCtx->GetLayer() == layer || aCtx->GetLayer() == UNDEFINED_LAYER ) + { + if( zoneRTree->QueryColliding( areaBBox, &areaOutline, layer ) ) + return true; + } + } + } + + return false; + } + else + { + PCB_LAYER_ID layer = aCtx->GetLayer(); + + if( layer != UNDEFINED_LAYER && !( aArea->GetLayerSet().Contains( layer ) ) ) + return false; + + if( !shape ) + shape = aItem->GetEffectiveShape( layer ); + + return areaOutline.Collide( shape.get() ); + } +} + + +bool searchAreas( BOARD* aBoard, const wxString& aArg, PCB_EXPR_CONTEXT* aCtx, + std::function aFunc ) +{ + if( aArg == wxT( "A" ) ) + { + return aFunc( dynamic_cast( aCtx->GetItem( 0 ) ) ); + } + else if( aArg == wxT( "B" ) ) + { + return aFunc( dynamic_cast( aCtx->GetItem( 1 ) ) ); + } + else if( KIID::SniffTest( aArg ) ) + { + KIID target( aArg ); + + for( ZONE* area : aBoard->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 aFunc( area ); + } + + for( FOOTPRINT* footprint : aBoard->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 aFunc( area ); + } + } + + return 0.0; + } + else // Match on zone name + { + for( ZONE* area : aBoard->Zones() ) + { + if( area->GetZoneName().Matches( aArg ) ) + { + // Many zones can match the name; exit only when we find an "inside" + if( aFunc( area ) ) + return true; + } + } + + for( FOOTPRINT* footprint : aBoard->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( aFunc( area ) ) + return true; + } + } + } + + return false; + } +} + + +#define MISSING_AREA_ARG( f ) \ + wxString::Format( _( "Missing rule-area argument (A, B, or rule-area name) to %s." ), f ) + +static void intersectsAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + PCB_EXPR_CONTEXT* context = static_cast( aCtx ); + LIBEVAL::VALUE* arg = aCtx->Pop(); + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + result->Set( 0.0 ); + aCtx->Push( result ); + + if( !arg ) + { + if( aCtx->HasErrorCallback() ) + aCtx->ReportError( MISSING_AREA_ARG( wxT( "intersectsArea()" ) ) ); + + return; + } + + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr; + + if( !item ) + return; + + result->SetDeferredEval( + [item, arg, context]() -> double + { + BOARD* board = item->GetBoard(); + PCB_LAYER_ID layer = context->GetLayer(); + BOX2I itemBBox = item->GetBoundingBox(); + + if( searchAreas( board, arg->AsString(), context, + [&]( ZONE* aArea ) + { + if( !aArea || aArea == item || aArea->GetParent() == item ) + return false; + + if( !( aArea->GetLayerSet() & item->GetLayerSet() ).any() ) + return false; + + if( !aArea->GetBoundingBox().Intersects( itemBBox ) ) + return false; + + std::unique_lock 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, context, aArea ); + + board->m_IntersectsAreaCache[ key ] = collides; + + return collides; + } ) ) + { + return 1.0; + } + + return 0.0; + } ); +} + + +static void enclosedByAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + PCB_EXPR_CONTEXT* context = static_cast( aCtx ); + LIBEVAL::VALUE* arg = aCtx->Pop(); + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + result->Set( 0.0 ); + aCtx->Push( result ); + + if( !arg ) + { + if( aCtx->HasErrorCallback() ) + aCtx->ReportError( MISSING_AREA_ARG( wxT( "enclosedByArea()" ) ) ); + + return; + } + + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr; + + if( !item ) + return; + + result->SetDeferredEval( + [item, arg, context]() -> double + { + BOARD* board = item->GetBoard(); + int maxError = board->GetDesignSettings().m_MaxError; + PCB_LAYER_ID layer = context->GetLayer(); + BOX2I itemBBox = item->GetBoundingBox(); + + if( searchAreas( board, arg->AsString(), context, + [&]( ZONE* aArea ) + { + if( !aArea || aArea == item || aArea->GetParent() == item ) + return false; + + if( !( aArea->GetLayerSet() & item->GetLayerSet() ).any() ) + return false; + + if( !aArea->GetBoundingBox().Intersects( itemBBox ) ) + return false; + + std::unique_lock 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; + bool enclosedByArea; + + item->TransformShapeToPolygon( itemShape, layer, 0, maxError, + ERROR_OUTSIDE ); + + if( itemShape.IsEmpty() ) + { + // If it's already empty then our test will have no meaning. + enclosedByArea = false; + } + else + { + itemShape.BooleanSubtract( *aArea->Outline(), + SHAPE_POLY_SET::PM_FAST ); + + enclosedByArea = itemShape.IsEmpty(); + } + + board->m_EnclosedByAreaCache[ key ] = enclosedByArea; + + return enclosedByArea; + } ) ) + { + return 1.0; + } + + return 0.0; + } ); +} + + +#define MISSING_GROUP_ARG( f ) \ + wxString::Format( _( "Missing group name argument to %s." ), f ) + +static void memberOfFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + LIBEVAL::VALUE* arg = aCtx->Pop(); + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + result->Set( 0.0 ); + aCtx->Push( result ); + + if( !arg ) + { + if( aCtx->HasErrorCallback() ) + aCtx->ReportError( MISSING_GROUP_ARG( wxT( "memberOf()" ) ) ); + + return; + } + + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; + + if( !item ) + return; + + result->SetDeferredEval( + [item, arg]() -> double + { + PCB_GROUP* group = item->GetParentGroup(); + + if( !group && item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T ) + group = item->GetParent()->GetParentGroup(); + + while( group ) + { + if( group->GetName().Matches( arg->AsString() ) ) + return 1.0; + + group = group->GetParentGroup(); + } + + return 0.0; + } ); +} + + +static void isMicroVia( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + result->Set( 0.0 ); + aCtx->Push( result ); + + PCB_VIA* via = dyn_cast( item ); + + if( via && via->GetViaType() == VIATYPE::MICROVIA ) + result->Set ( 1.0 ); +} + + +static void isBlindBuriedViaFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + result->Set( 0.0 ); + aCtx->Push( result ); + + PCB_VIA* via = dyn_cast( item ); + + if( via && via->GetViaType() == VIATYPE::BLIND_BURIED ) + result->Set ( 1.0 ); +} + + +static void isCoupledDiffPairFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + PCB_EXPR_CONTEXT* context = static_cast( aCtx ); + BOARD_CONNECTED_ITEM* a = dynamic_cast( context->GetItem( 0 ) ); + BOARD_CONNECTED_ITEM* b = dynamic_cast( context->GetItem( 1 ) ); + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + result->Set( 0.0 ); + aCtx->Push( result ); + + result->SetDeferredEval( + [a, b, context]() -> double + { + NETINFO_ITEM* netinfo = a ? a->GetNet() : nullptr; + + if( !netinfo ) + return 0.0; + + wxString coupledNet; + wxString dummy; + + if( !DRC_ENGINE::MatchDpSuffix( netinfo->GetNetname(), coupledNet, dummy ) ) + return 0.0; + + if( context->GetConstraint() == DRC_CONSTRAINT_T::LENGTH_CONSTRAINT + || context->GetConstraint() == DRC_CONSTRAINT_T::SKEW_CONSTRAINT ) + { + // DRC engine evaluates these singly, so we won't have a B item + return 1.0; + } + + return b && b->GetNetname() == coupledNet; + } ); +} + + +#define MISSING_DP_ARG( f ) \ + wxString::Format( _( "Missing diff-pair name argument to %s." ), f ) + +static void inDiffPairFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + LIBEVAL::VALUE* argv = aCtx->Pop(); + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + result->Set( 0.0 ); + aCtx->Push( result ); + + if( !argv ) + { + if( aCtx->HasErrorCallback() ) + aCtx->ReportError( MISSING_DP_ARG( wxT( "inDiffPair()" ) ) ); + + return; + } + + if( !item || !item->GetBoard() ) + return; + + result->SetDeferredEval( + [item, argv]() -> double + { + if( item && item->IsConnected() ) + { + NETINFO_ITEM* netinfo = static_cast( item )->GetNet(); + + wxString refName = netinfo->GetNetname(); + wxString arg = argv->AsString(); + wxString baseName, coupledNet; + int polarity = DRC_ENGINE::MatchDpSuffix( refName, coupledNet, baseName ); + + if( polarity != 0 && item->GetBoard()->FindNet( coupledNet ) ) + { + if( baseName.Matches( arg ) ) + return 1.0; + + if( baseName.EndsWith( "_" ) && baseName.BeforeLast( '_' ).Matches( arg ) ) + return 1.0; + } + } + + return 0.0; + } ); +} + + +static void getFieldFunc( LIBEVAL::CONTEXT* aCtx, void* self ) +{ + LIBEVAL::VALUE* arg = aCtx->Pop(); + PCB_EXPR_VAR_REF* vref = static_cast( self ); + BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr; + LIBEVAL::VALUE* result = aCtx->AllocValue(); + + result->Set( "" ); + aCtx->Push( result ); + + if( !arg ) + { + if( aCtx->HasErrorCallback() ) + { + aCtx->ReportError( wxString::Format( _( "Missing field name argument to %s." ), + wxT( "getField()" ) ) ); + } + + return; + } + + if( !item || !item->GetBoard() ) + return; + + result->SetDeferredEval( + [item, arg]() -> wxString + { + if( item && item->Type() == PCB_FOOTPRINT_T ) + { + FOOTPRINT* fp = static_cast( item ); + + if( fp->HasProperty( arg->AsString() ) ) + return fp->GetProperty( arg->AsString() ); + } + + return ""; + } ); +} + + +PCB_EXPR_BUILTIN_FUNCTIONS::PCB_EXPR_BUILTIN_FUNCTIONS() +{ + RegisterAllFunctions(); +} + + +void PCB_EXPR_BUILTIN_FUNCTIONS::RegisterAllFunctions() +{ + m_funcs.clear(); + + RegisterFunc( wxT( "existsOnLayer('x')" ), existsOnLayerFunc ); + + RegisterFunc( wxT( "isPlated()" ), isPlatedFunc ); + + RegisterFunc( wxT( "insideCourtyard('x') DEPRECATED" ), intersectsCourtyardFunc ); + RegisterFunc( wxT( "insideFrontCourtyard('x') DEPRECATED" ), intersectsFrontCourtyardFunc ); + RegisterFunc( wxT( "insideBackCourtyard('x') DEPRECATED" ), intersectsBackCourtyardFunc ); + RegisterFunc( wxT( "intersectsCourtyard('x')" ), intersectsCourtyardFunc ); + RegisterFunc( wxT( "intersectsFrontCourtyard('x')" ), intersectsFrontCourtyardFunc ); + RegisterFunc( wxT( "intersectsBackCourtyard('x')" ), intersectsBackCourtyardFunc ); + + 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 ); +} + + diff --git a/qa/tools/libeval_compiler/libeval_compiler_test.cpp b/qa/tools/libeval_compiler/libeval_compiler_test.cpp index 6eadce5a69..4459a94e83 100644 --- a/qa/tools/libeval_compiler/libeval_compiler_test.cpp +++ b/qa/tools/libeval_compiler/libeval_compiler_test.cpp @@ -13,9 +13,10 @@ #include -bool testEvalExpr( const std::string expr, LIBEVAL::VALUE expectedResult, bool expectError = false, BOARD_ITEM* itemA = nullptr, BOARD_ITEM* itemB = nullptr ) +bool testEvalExpr( const std::string expr, LIBEVAL::VALUE expectedResult, bool expectError = false, + BOARD_ITEM* itemA = nullptr, BOARD_ITEM* itemB = nullptr ) { - PCB_EXPR_COMPILER compiler; + PCB_EXPR_COMPILER compiler( new PCB_UNIT_RESOLVER() ); PCB_EXPR_UCODE ucode; bool ok = true; diff --git a/qa/unittests/pcbnew/test_libeval_compiler.cpp b/qa/unittests/pcbnew/test_libeval_compiler.cpp index eea7c96d23..6216288bd6 100644 --- a/qa/unittests/pcbnew/test_libeval_compiler.cpp +++ b/qa/unittests/pcbnew/test_libeval_compiler.cpp @@ -90,7 +90,7 @@ static bool testEvalExpr( const wxString& expr, const LIBEVAL::VALUE& expectedRe bool expectError = false, BOARD_ITEM* itemA = nullptr, BOARD_ITEM* itemB = nullptr ) { - PCB_EXPR_COMPILER compiler; + PCB_EXPR_COMPILER compiler( new PCB_UNIT_RESOLVER() ); PCB_EXPR_UCODE ucode; PCB_EXPR_CONTEXT context( NULL_CONSTRAINT, UNDEFINED_LAYER ); PCB_EXPR_CONTEXT preflightContext( NULL_CONSTRAINT, UNDEFINED_LAYER );