diff --git a/pcbnew/board.cpp b/pcbnew/board.cpp index 7398fb7473..e3f58f6def 100644 --- a/pcbnew/board.cpp +++ b/pcbnew/board.cpp @@ -30,6 +30,7 @@ #include +#include #include #include #include @@ -1090,6 +1091,13 @@ void BOARD::SetDesignSettings( const BOARD_DESIGN_SETTINGS& aSettings ) } +void BOARD::InvalidateClearanceCache( const KIID& aUuid ) +{ + if( m_designSettings && m_designSettings->m_DRCEngine ) + m_designSettings->m_DRCEngine->InvalidateClearanceCache( aUuid ); +} + + int BOARD::GetMaxClearanceValue() const { if( !m_maxClearanceValue.has_value() ) diff --git a/pcbnew/board.h b/pcbnew/board.h index 4d51b5bde8..0569d1ff8b 100644 --- a/pcbnew/board.h +++ b/pcbnew/board.h @@ -778,6 +778,15 @@ public: BOARD_DESIGN_SETTINGS& GetDesignSettings() const; void SetDesignSettings( const BOARD_DESIGN_SETTINGS& aSettings ); + /** + * Invalidate the clearance cache for a specific item. + * + * Called by items when properties that could affect clearance change. + * + * @param aUuid the UUID of the item to invalidate. + */ + void InvalidateClearanceCache( const KIID& aUuid ); + BOARD_STACKUP GetStackupOrDefault() const; const PAGE_INFO& GetPageSettings() const { return m_paper; } diff --git a/pcbnew/board_connected_item.cpp b/pcbnew/board_connected_item.cpp index de54479ea0..768bbbc81c 100644 --- a/pcbnew/board_connected_item.cpp +++ b/pcbnew/board_connected_item.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,16 @@ BOARD_CONNECTED_ITEM::BOARD_CONNECTED_ITEM( BOARD_ITEM* aParent, KICAD_T idtype } +void BOARD_CONNECTED_ITEM::SetLayer( PCB_LAYER_ID aLayer ) +{ + BOARD_ITEM::SetLayer( aLayer ); + + // Invalidate clearance cache since layer can affect clearance rules + if( BOARD* board = GetBoard() ) + board->InvalidateClearanceCache( m_Uuid ); +} + + void BOARD_CONNECTED_ITEM::UnpackNet( const kiapi::board::types::Net& aProto ) { if( BOARD* board = GetBoard() ) @@ -88,27 +99,20 @@ bool BOARD_CONNECTED_ITEM::SetNetCode( int aNetCode, bool aNoAssert ) if( !aNoAssert ) wxASSERT( m_netinfo ); + // Invalidate clearance cache since net can affect clearance rules + if( board ) + board->InvalidateClearanceCache( m_Uuid ); + return ( m_netinfo != nullptr ); } int BOARD_CONNECTED_ITEM::GetOwnClearance( PCB_LAYER_ID aLayer, wxString* aSource ) const { - DRC_CONSTRAINT constraint; - if( GetBoard() && GetBoard()->GetDesignSettings().m_DRCEngine ) { BOARD_DESIGN_SETTINGS& bds = GetBoard()->GetDesignSettings(); - - constraint = bds.m_DRCEngine->EvalRules( CLEARANCE_CONSTRAINT, this, nullptr, aLayer ); - } - - if( constraint.Value().HasMin() ) - { - if( aSource ) - *aSource = constraint.GetName(); - - return constraint.Value().Min(); + return bds.m_DRCEngine->GetCachedOwnClearance( this, aLayer, aSource ); } return 0; @@ -215,7 +219,7 @@ static struct BOARD_CONNECTED_ITEM_DESC // Replace layer property as the properties panel will set a restriction for copper layers // only for BOARD_CONNECTED_ITEM that we don't want to apply to BOARD_ITEM - auto layer = new PROPERTY_ENUM( + auto layer = new PROPERTY_ENUM( _HKI( "Layer" ), &BOARD_CONNECTED_ITEM::SetLayer, &BOARD_CONNECTED_ITEM::GetLayer ); layer->SetChoices( layerEnum.Choices() ); diff --git a/pcbnew/board_connected_item.h b/pcbnew/board_connected_item.h index e0f3638ee5..d574b00fb7 100644 --- a/pcbnew/board_connected_item.h +++ b/pcbnew/board_connected_item.h @@ -47,6 +47,9 @@ class BOARD_CONNECTED_ITEM : public BOARD_ITEM public: BOARD_CONNECTED_ITEM( BOARD_ITEM* aParent, KICAD_T idtype ); + void SetLayer( PCB_LAYER_ID aLayer ) override; + PCB_LAYER_ID GetLayer() const override { return BOARD_ITEM::GetLayer(); } + // Do not create a copy constructor & operator=. // The ones generated by the compiler are adequate. diff --git a/pcbnew/drc/drc_engine.cpp b/pcbnew/drc/drc_engine.cpp index b45ceceaec..d859ad2629 100644 --- a/pcbnew/drc/drc_engine.cpp +++ b/pcbnew/drc/drc_engine.cpp @@ -752,6 +752,7 @@ void DRC_ENGINE::InitEngine( const wxFileName& aRulePath ) } m_constraintMap.clear(); + m_ownClearanceCache.clear(); m_board->IncrementTimeStamp(); // Clear board-level caches @@ -2221,3 +2222,68 @@ std::vector DRC_ENGINE::GetItemsMatchingCondition( const wxString& return matches; } + + +int DRC_ENGINE::GetCachedOwnClearance( const BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, + wxString* aSource ) +{ + DRC_OWN_CLEARANCE_CACHE_KEY key{ aItem->m_Uuid, aLayer }; + + auto it = m_ownClearanceCache.find( key ); + + if( it != m_ownClearanceCache.end() ) + { + // Cache hit. We don't cache the source string since it's rarely requested + // and caching it would add complexity. + return it->second; + } + + // Cache miss - evaluate the constraint + DRC_CONSTRAINT_T constraintType = CLEARANCE_CONSTRAINT; + + if( aItem->Type() == PCB_PAD_T ) + { + const PAD* pad = static_cast( aItem ); + + if( pad->GetAttribute() == PAD_ATTRIB::NPTH ) + constraintType = HOLE_CLEARANCE_CONSTRAINT; + } + + DRC_CONSTRAINT constraint = EvalRules( constraintType, aItem, nullptr, aLayer ); + + int clearance = 0; + + if( constraint.Value().HasMin() ) + { + clearance = constraint.Value().Min(); + + if( aSource ) + *aSource = constraint.GetName(); + } + + // Store in cache + m_ownClearanceCache[key] = clearance; + + return clearance; +} + + +void DRC_ENGINE::InvalidateClearanceCache( const KIID& aUuid ) +{ + // Remove all entries for this item (across all layers) + auto it = m_ownClearanceCache.begin(); + + while( it != m_ownClearanceCache.end() ) + { + if( it->first.m_uuid == aUuid ) + it = m_ownClearanceCache.erase( it ); + else + ++it; + } +} + + +void DRC_ENGINE::ClearClearanceCache() +{ + m_ownClearanceCache.clear(); +} diff --git a/pcbnew/drc/drc_engine.h b/pcbnew/drc/drc_engine.h index fb2db076ca..be5be5da53 100644 --- a/pcbnew/drc/drc_engine.h +++ b/pcbnew/drc/drc_engine.h @@ -27,12 +27,46 @@ #include #include +#include +#include #include #include #include #include +/** + * Cache key for own clearance lookups, combining item UUID and layer. + */ +struct DRC_OWN_CLEARANCE_CACHE_KEY +{ + KIID m_uuid; + PCB_LAYER_ID m_layer; + + bool operator==( const DRC_OWN_CLEARANCE_CACHE_KEY& aOther ) const + { + return m_uuid == aOther.m_uuid && m_layer == aOther.m_layer; + } +}; + + +namespace std +{ + template <> + struct hash + { + std::size_t operator()( const DRC_OWN_CLEARANCE_CACHE_KEY& aKey ) const + { + std::size_t seed = 0xa82de1c0; + seed ^= std::hash{}( aKey.m_uuid ) + 0x9e3779b9 + ( seed << 6 ) + ( seed >> 2 ); + seed ^= std::hash{}( static_cast( aKey.m_layer ) ) + 0x9e3779b9 + + ( seed << 6 ) + ( seed >> 2 ); + return seed; + } + }; +} + + class BOARD_COMMIT; class BOARD_DESIGN_SETTINGS; class DRC_TEST_PROVIDER; @@ -162,6 +196,36 @@ public: DRC_CONSTRAINT EvalZoneConnection( const BOARD_ITEM* a, const BOARD_ITEM* b, PCB_LAYER_ID aLayer, REPORTER* aReporter = nullptr ); + /** + * Get the cached own clearance for an item on a specific layer. + * + * This is used by BOARD_CONNECTED_ITEM::GetOwnClearance() to avoid re-evaluating + * DRC rules on every paint refresh. + * + * @param aItem the item to get clearance for. + * @param aLayer the layer in question. + * @param aSource optionally reports the source as a user-readable string. + * @return the clearance in internal units. + */ + int GetCachedOwnClearance( const BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, + wxString* aSource = nullptr ); + + /** + * Invalidate the clearance cache for a specific item. + * + * Called when item properties that could affect clearance (net, type, layer) change. + * + * @param aUuid the UUID of the item to invalidate. + */ + void InvalidateClearanceCache( const KIID& aUuid ); + + /** + * Clear the entire clearance cache. + * + * Called when DRC rules change or board design settings change. + */ + void ClearClearanceCache(); + void ProcessAssertions( const BOARD_ITEM* a, std::function aFailureHandler, REPORTER* aReporter = nullptr ); @@ -270,4 +334,8 @@ protected: PROGRESS_REPORTER* m_progressReporter; std::shared_ptr m_debugOverlay; + + // Cache for GetOwnClearance lookups to improve rendering performance. + // Key is (UUID, layer), value is clearance in internal units. + std::unordered_map m_ownClearanceCache; }; diff --git a/pcbnew/pad.cpp b/pcbnew/pad.cpp index ce4d2fe852..d50eb76070 100644 --- a/pcbnew/pad.cpp +++ b/pcbnew/pad.cpp @@ -1355,6 +1355,10 @@ void PAD::SetAttribute( PAD_ATTRIB aAttribute ) SetNetCode( NETINFO_LIST::UNCONNECTED ); break; } + + // Invalidate clearance cache since pad type affects constraint evaluation + if( BOARD* board = GetBoard() ) + board->InvalidateClearanceCache( m_Uuid ); } SetDirty(); @@ -1581,29 +1585,21 @@ std::optional PAD::GetClearanceOverrides( wxString* aSource ) const } +void PAD::SetLayerSet( const LSET& aLayers ) +{ + m_padStack.SetLayerSet( aLayers ); + SetDirty(); + + // Invalidate clearance cache since layer set can affect clearance rules + if( BOARD* board = GetBoard() ) + board->InvalidateClearanceCache( m_Uuid ); +} + + int PAD::GetOwnClearance( PCB_LAYER_ID aLayer, wxString* aSource ) const { - DRC_CONSTRAINT c; - - if( GetBoard() && GetBoard()->GetDesignSettings().m_DRCEngine ) - { - BOARD_DESIGN_SETTINGS& bds = GetBoard()->GetDesignSettings(); - - if( GetAttribute() == PAD_ATTRIB::NPTH ) - c = bds.m_DRCEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, this, nullptr, aLayer ); - else - c = bds.m_DRCEngine->EvalRules( CLEARANCE_CONSTRAINT, this, nullptr, aLayer ); - } - - if( c.Value().HasMin() ) - { - if( aSource ) - *aSource = c.GetName(); - - return c.Value().Min(); - } - - return 0; + // The NPTH vs regular pad logic is handled in DRC_ENGINE::GetCachedOwnClearance + return BOARD_CONNECTED_ITEM::GetOwnClearance( aLayer, aSource ); } diff --git a/pcbnew/pad.h b/pcbnew/pad.h index b2ebdf8abd..47f137bb21 100644 --- a/pcbnew/pad.h +++ b/pcbnew/pad.h @@ -556,7 +556,7 @@ public: m_polyDirty[ERROR_OUTSIDE] = true; } - void SetLayerSet( const LSET& aLayers ) override { m_padStack.SetLayerSet( aLayers ); SetDirty(); } + void SetLayerSet( const LSET& aLayers ) override; LSET GetLayerSet() const override { return m_padStack.LayerSet(); } void SetAttribute( PAD_ATTRIB aAttribute ); diff --git a/pcbnew/pcb_track.cpp b/pcbnew/pcb_track.cpp index dad4830c80..4016f6d49a 100644 --- a/pcbnew/pcb_track.cpp +++ b/pcbnew/pcb_track.cpp @@ -1640,6 +1640,10 @@ void PCB_VIA::SetLayerPair( PCB_LAYER_ID aTopLayer, PCB_LAYER_ID aBottomLayer ) Padstack().Drill().start = aTopLayer; Padstack().Drill().end = aBottomLayer; SanitizeLayers(); + + // Invalidate clearance cache since layer can affect clearance rules + if( BOARD* board = GetBoard() ) + board->InvalidateClearanceCache( m_Uuid ); } @@ -1651,6 +1655,10 @@ void PCB_VIA::SetTopLayer( PCB_LAYER_ID aLayer ) Padstack().Drill().start = aLayer; SanitizeLayers(); + + // Invalidate clearance cache since layer can affect clearance rules + if( BOARD* board = GetBoard() ) + board->InvalidateClearanceCache( m_Uuid ); } @@ -1662,6 +1670,10 @@ void PCB_VIA::SetBottomLayer( PCB_LAYER_ID aLayer ) Padstack().Drill().end = aLayer; SanitizeLayers(); + + // Invalidate clearance cache since layer can affect clearance rules + if( BOARD* board = GetBoard() ) + board->InvalidateClearanceCache( m_Uuid ); }