Cache netclass clearances

Importing Altium boards can generate hundreds of netclasses.  Our O(N)
search was choking on the overload
This commit is contained in:
Seth Hillbrand
2026-01-09 16:15:20 -08:00
parent 03215844fd
commit e741ee2e6c
7 changed files with 170 additions and 6 deletions
+2 -1
View File
@@ -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();
+2
View File
@@ -1449,6 +1449,8 @@ public:
mutable std::unordered_map<const ZONE*, BOX2I> m_ZoneBBoxCache;
mutable std::optional<int> m_maxClearanceValue;
mutable std::unordered_map<const BOARD_ITEM*, wxString> m_ItemNetclassCache;
// ------------ DRC caches -------------
std::vector<ZONE*> m_DRCZones;
std::vector<ZONE*> m_DRCCopperZones;
+6
View File
@@ -34,6 +34,7 @@
#include <i18n_utility.h>
#include <netinfo.h>
#include <api/board/board_types.pb.h>
#include <shared_mutex>
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<std::shared_mutex> writeLock( board->m_CachesMutex );
board->m_ItemNetclassCache.erase( this );
}
return ( m_netinfo != nullptr );
}
+102 -1
View File
@@ -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<DRC_ENGINE_CONSTRAINT*>* 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<void( const DRC_CONSTRAINT* )> aFailureHandler,
REPORTER* aReporter )
+31
View File
@@ -103,6 +103,20 @@ typedef std::function<void( const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I& aPos, int aLayer,
const std::function<void( PCB_MARKER* )>& 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<DRC_OWN_CLEARANCE_CACHE_KEY, int> m_ownClearanceCache;
std::unordered_map<wxString, int> m_netclassClearances; // netclass name -> clearance
bool m_hasExplicitClearanceRules = false;
bool m_hasDiffPairClearanceOverrides = false;
std::map<DRC_CONSTRAINT_T, std::vector<DRC_ENGINE_CONSTRAINT*>> m_explicitConstraints;
};
+26 -4
View File
@@ -1351,12 +1351,34 @@ static void hasExactNetclassFunc( LIBEVAL::CONTEXT* aCtx, void* self )
return 0.0;
BOARD_CONNECTED_ITEM* bcItem = static_cast<BOARD_CONNECTED_ITEM*>( 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<std::shared_mutex> 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<std::shared_mutex> writeLock( board->m_CachesMutex );
board->m_ItemNetclassCache[item] = netclassName;
}
}
return ( netclassName == arg->AsString() ) ? 1.0 : 0.0;
} );
}
+1
View File
@@ -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;