diff --git a/pcbnew/board.cpp b/pcbnew/board.cpp index 06d90fe543..7c1a5357c7 100644 --- a/pcbnew/board.cpp +++ b/pcbnew/board.cpp @@ -266,7 +266,7 @@ void BOARD::IncrementTimeStamp() if( !m_IntersectsAreaCache.empty() || !m_EnclosedByAreaCache.empty() || !m_IntersectsCourtyardCache.empty() || !m_IntersectsFCourtyardCache.empty() || !m_IntersectsBCourtyardCache.empty() || !m_LayerExpressionCache.empty() || !m_ZoneBBoxCache.empty() || m_CopperItemRTreeCache - || m_maxClearanceValue.has_value() || !m_itemByIdCache.empty() ) + || m_maxClearanceValue.has_value() || !m_itemByIdCache.empty() || !m_ItemNetclassCache.empty() ) { m_IntersectsAreaCache.clear(); m_EnclosedByAreaCache.clear(); @@ -274,6 +274,7 @@ void BOARD::IncrementTimeStamp() m_IntersectsFCourtyardCache.clear(); m_IntersectsBCourtyardCache.clear(); m_LayerExpressionCache.clear(); + m_ItemNetclassCache.clear(); m_ZoneBBoxCache.clear(); diff --git a/pcbnew/board.h b/pcbnew/board.h index 4bc156d7bd..16082720cf 100644 --- a/pcbnew/board.h +++ b/pcbnew/board.h @@ -1449,6 +1449,8 @@ public: mutable std::unordered_map m_ZoneBBoxCache; mutable std::optional m_maxClearanceValue; + mutable std::unordered_map m_ItemNetclassCache; + // ------------ DRC caches ------------- std::vector m_DRCZones; std::vector m_DRCCopperZones; diff --git a/pcbnew/board_connected_item.cpp b/pcbnew/board_connected_item.cpp index 768bbbc81c..55d67bc94c 100644 --- a/pcbnew/board_connected_item.cpp +++ b/pcbnew/board_connected_item.cpp @@ -34,6 +34,7 @@ #include #include #include +#include using namespace std::placeholders; @@ -101,8 +102,13 @@ bool BOARD_CONNECTED_ITEM::SetNetCode( int aNetCode, bool aNoAssert ) // Invalidate clearance cache since net can affect clearance rules if( board ) + { board->InvalidateClearanceCache( m_Uuid ); + std::unique_lock writeLock( board->m_CachesMutex ); + board->m_ItemNetclassCache.erase( this ); + } + return ( m_netinfo != nullptr ); } diff --git a/pcbnew/drc/drc_engine.cpp b/pcbnew/drc/drc_engine.cpp index 7727d60d06..e763cb3bf2 100644 --- a/pcbnew/drc/drc_engine.cpp +++ b/pcbnew/drc/drc_engine.cpp @@ -279,6 +279,7 @@ void DRC_ENGINE::loadImplicitRules() DRC_CONSTRAINT constraint( CLEARANCE_CONSTRAINT ); constraint.Value().SetMin( nc->GetClearance() ); netclassRule->AddConstraint( constraint ); + m_netclassClearances[nc->GetName()] = nc->GetClearance(); } if( nc->HasTrackWidth() ) @@ -346,6 +347,8 @@ void DRC_ENGINE::loadImplicitRules() DRC_CONSTRAINT min_clearanceConstraint( CLEARANCE_CONSTRAINT ); min_clearanceConstraint.Value().SetMin( nc->GetDiffPairGap() ); netclassRule->AddConstraint( min_clearanceConstraint ); + + m_hasDiffPairClearanceOverrides = true; } } @@ -725,6 +728,23 @@ void DRC_ENGINE::compileRules() m_constraintMap[ constraint.m_Type ]->push_back( engineConstraint ); } } + + m_hasExplicitClearanceRules = false; + m_explicitConstraints.clear(); + + for( auto& [constraintType, ruleList] : m_constraintMap ) + { + for( DRC_ENGINE_CONSTRAINT* c : *ruleList ) + { + if( c->parentRule && !c->parentRule->IsImplicit() ) + { + m_explicitConstraints[constraintType].push_back( c ); + + if( constraintType == CLEARANCE_CONSTRAINT ) + m_hasExplicitClearanceRules = true; + } + } + } } @@ -753,6 +773,10 @@ void DRC_ENGINE::InitEngine( const wxFileName& aRulePath ) m_constraintMap.clear(); m_ownClearanceCache.clear(); + m_netclassClearances.clear(); + m_hasExplicitClearanceRules = false; + m_hasDiffPairClearanceOverrides = false; + m_explicitConstraints.clear(); m_board->IncrementTimeStamp(); // Clear board-level caches @@ -1614,7 +1638,49 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO } }; - if( m_constraintMap.count( aConstraintType ) ) + // Fast-path for netclass clearance when no explicit or diff pair override rules exist + if( aConstraintType == CLEARANCE_CONSTRAINT + && !m_hasExplicitClearanceRules + && !m_hasDiffPairClearanceOverrides + && !aReporter + && !a_is_non_copper + && ( !b || !b_is_non_copper ) ) + { + int clearance = 0; + + if( ac ) + { + NETCLASS* ncA = ac->GetEffectiveNetClass(); + + if( ncA ) + { + auto it = m_netclassClearances.find( ncA->GetName() ); + + if( it != m_netclassClearances.end() ) + clearance = it->second; + } + } + + if( bc ) + { + NETCLASS* ncB = bc->GetEffectiveNetClass(); + + if( ncB ) + { + auto it = m_netclassClearances.find( ncB->GetName() ); + + if( it != m_netclassClearances.end() ) + clearance = std::max( clearance, it->second ); + } + } + + if( clearance > 0 ) + { + constraint.m_Value.SetMin( clearance ); + constraint.m_ImplicitMin = true; + } + } + else if( m_constraintMap.count( aConstraintType ) ) { std::vector* ruleset = m_constraintMap[ aConstraintType ]; @@ -1852,6 +1918,41 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO } +DRC_CLEARANCE_BATCH DRC_ENGINE::EvalClearanceBatch( const BOARD_ITEM* a, const BOARD_ITEM* b, + PCB_LAYER_ID aLayer ) +{ + DRC_CLEARANCE_BATCH result; + DRC_CONSTRAINT c; + + c = EvalRules( CLEARANCE_CONSTRAINT, a, b, aLayer ); + + if( c.m_Value.HasMin() ) + result.clearance = c.m_Value.Min(); + + c = EvalRules( HOLE_CLEARANCE_CONSTRAINT, a, b, aLayer ); + + if( c.m_Value.HasMin() ) + result.holeClearance = c.m_Value.Min(); + + c = EvalRules( HOLE_TO_HOLE_CONSTRAINT, a, b, aLayer ); + + if( c.m_Value.HasMin() ) + result.holeToHole = c.m_Value.Min(); + + c = EvalRules( EDGE_CLEARANCE_CONSTRAINT, a, b, aLayer ); + + if( c.m_Value.HasMin() ) + result.edgeClearance = c.m_Value.Min(); + + c = EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, a, b, aLayer ); + + if( c.m_Value.HasMin() ) + result.physicalClearance = c.m_Value.Min(); + + return result; +} + + void DRC_ENGINE::ProcessAssertions( const BOARD_ITEM* a, std::function aFailureHandler, REPORTER* aReporter ) diff --git a/pcbnew/drc/drc_engine.h b/pcbnew/drc/drc_engine.h index 22bc029ed5..3342c27374 100644 --- a/pcbnew/drc/drc_engine.h +++ b/pcbnew/drc/drc_engine.h @@ -103,6 +103,20 @@ typedef std::function& aItem, const VECTOR2I& aPos, int aLayer, const std::function& aPathGenerator )> DRC_VIOLATION_HANDLER; + +/** + * Batch result for clearance-related constraints to reduce per-query overhead during PNS routing. + */ +struct DRC_CLEARANCE_BATCH +{ + int clearance = 0; + int holeClearance = 0; + int holeToHole = 0; + int edgeClearance = 0; + int physicalClearance = 0; +}; + + /** * Design Rule Checker object that performs all the DRC tests. * @@ -196,6 +210,18 @@ public: DRC_CONSTRAINT EvalZoneConnection( const BOARD_ITEM* a, const BOARD_ITEM* b, PCB_LAYER_ID aLayer, REPORTER* aReporter = nullptr ); + /** + * Evaluate all clearance-related constraints in a single batch call. + * This reduces per-call overhead during interactive PNS routing. + * + * @param a First board item + * @param b Second board item (may be nullptr) + * @param aLayer Layer to evaluate constraints on + * @return DRC_CLEARANCE_BATCH containing all clearance constraint values + */ + DRC_CLEARANCE_BATCH EvalClearanceBatch( const BOARD_ITEM* a, const BOARD_ITEM* b, + PCB_LAYER_ID aLayer ); + /** * Get the cached own clearance for an item on a specific layer. * @@ -346,4 +372,9 @@ protected: // Cache for GetOwnClearance lookups to improve rendering performance. // Key is (UUID, layer), value is clearance in internal units. std::unordered_map m_ownClearanceCache; + + std::unordered_map m_netclassClearances; // netclass name -> clearance + bool m_hasExplicitClearanceRules = false; + bool m_hasDiffPairClearanceOverrides = false; + std::map> m_explicitConstraints; }; diff --git a/pcbnew/pcbexpr_functions.cpp b/pcbnew/pcbexpr_functions.cpp index 6a982a21cb..6f4375e1f1 100644 --- a/pcbnew/pcbexpr_functions.cpp +++ b/pcbnew/pcbexpr_functions.cpp @@ -1351,12 +1351,34 @@ static void hasExactNetclassFunc( LIBEVAL::CONTEXT* aCtx, void* self ) return 0.0; BOARD_CONNECTED_ITEM* bcItem = static_cast( item ); - NETCLASS* netclass = bcItem->GetEffectiveNetClass(); + BOARD* board = bcItem->GetBoard(); + wxString netclassName; - if( netclass && netclass->GetName() == arg->AsString() ) - return 1.0; + if( board && ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 ) + { + std::shared_lock readLock( board->m_CachesMutex ); - return 0.0; + auto it = board->m_ItemNetclassCache.find( item ); + + if( it != board->m_ItemNetclassCache.end() ) + netclassName = it->second; + } + + if( netclassName.empty() ) + { + NETCLASS* netclass = bcItem->GetEffectiveNetClass(); + + if( netclass ) + netclassName = netclass->GetName(); + + if( board && !netclassName.empty() && ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 ) + { + std::unique_lock writeLock( board->m_CachesMutex ); + board->m_ItemNetclassCache[item] = netclassName; + } + } + + return ( netclassName == arg->AsString() ) ? 1.0 : 0.0; } ); } diff --git a/pcbnew/python/swig/board.i b/pcbnew/python/swig/board.i index 6e6fbd5685..c599b569da 100644 --- a/pcbnew/python/swig/board.i +++ b/pcbnew/python/swig/board.i @@ -82,6 +82,7 @@ HANDLE_EXCEPTIONS(BOARD::TracksInNetBetweenPoints) %ignore BOARD::m_LayerExpressionCache; %ignore BOARD::m_CopperZoneRTreeCache; %ignore BOARD::m_CopperItemRTreeCache; +%ignore BOARD::m_ItemNetclassCache; %ignore BOARD::m_DRCZones; %ignore BOARD::m_DRCCopperZones; %ignore BOARD::m_DRCMaxClearance;