diff --git a/common/string_utils.cpp b/common/string_utils.cpp index 798ab66a95..12e955a3e5 100644 --- a/common/string_utils.cpp +++ b/common/string_utils.cpp @@ -1629,6 +1629,91 @@ std::vector ExpandStackedPinNotation( const wxString& aPinName, bool* } +int CountStackedPinNotation( const wxString& aPinName, bool* aValid ) +{ + if( aValid ) + *aValid = true; + + // Fast path: if no brackets, it's a single pin + const bool hasOpenBracket = aPinName.Contains( wxT( "[" ) ); + const bool hasCloseBracket = aPinName.Contains( wxT( "]" ) ); + + if( hasOpenBracket || hasCloseBracket ) + { + if( !aPinName.StartsWith( wxT( "[" ) ) || !aPinName.EndsWith( wxT( "]" ) ) ) + { + if( aValid ) + *aValid = false; + return 1; + } + } + + if( !aPinName.StartsWith( wxT( "[" ) ) || !aPinName.EndsWith( wxT( "]" ) ) ) + return 1; + + const wxString inner = aPinName.Mid( 1, aPinName.Length() - 2 ); + + int count = 0; + size_t start = 0; + + while( start < inner.length() ) + { + size_t comma = inner.find( ',', start ); + wxString part = ( comma == wxString::npos ) ? inner.Mid( start ) : inner.Mid( start, comma - start ); + part.Trim( true ).Trim( false ); + + if( part.empty() ) + { + start = ( comma == wxString::npos ) ? inner.length() : comma + 1; + continue; + } + + int dashPos = part.Find( '-' ); + if( dashPos != wxNOT_FOUND ) + { + wxString startTxt = part.Left( dashPos ); + wxString endTxt = part.Mid( dashPos + 1 ); + startTxt.Trim( true ).Trim( false ); + endTxt.Trim( true ).Trim( false ); + + auto [startPrefix, startVal] = ParseAlphaNumericPin( startTxt ); + auto [endPrefix, endVal] = ParseAlphaNumericPin( endTxt ); + + if( startPrefix != endPrefix || startVal == -1 || endVal == -1 || startVal > endVal ) + { + if( aValid ) + *aValid = false; + + return 1; + } + + // Count pins in the range + count += static_cast( endVal - startVal + 1 ); + } + else + { + // Single pin + ++count; + } + + if( comma == wxString::npos ) + break; + + start = comma + 1; + } + + if( count == 0 ) + { + if( aValid ) + *aValid = false; + + return 1; + } + + return count; +} + + wxString GetDefaultVariantName() { return wxString( defaultVariantName ); diff --git a/eeschema/lib_symbol.cpp b/eeschema/lib_symbol.cpp index 735b0a9aa4..c7339acae9 100644 --- a/eeschema/lib_symbol.cpp +++ b/eeschema/lib_symbol.cpp @@ -916,18 +916,13 @@ int LIB_SYMBOL::GetPinCount() for( SCH_PIN* pin : GetGraphicalPins( 0 /* all units */, 1 /* single body style */ ) ) { - bool valid; - std::vector numbers = pin->GetStackedPinNumbers( &valid ); - wxLogTrace( "CVPCB_PINCOUNT", - wxString::Format( "LIB_SYMBOL::GetPinCount lib='%s' pin base='%s' shown='%s' valid=%d +%zu", - GetLibId().Format().wx_str(), pin->GetName(), - pin->GetShownNumber(), valid, numbers.size() ) ); - count += numbers.size(); - } - - wxLogTrace( "CVPCB_PINCOUNT", - wxString::Format( "LIB_SYMBOL::GetPinCount total for lib='%s' => %d", - GetLibId().Format().wx_str(), count ) ); + int pinCount = pin->GetStackedPinCount(); + count += pinCount; + } + + wxLogTrace( "CVPCB_PINCOUNT", "LIB_SYMBOL::GetPinCount total for lib='%s' => %d", GetLibId().Format().wx_str(), + count ); + return count; } diff --git a/eeschema/sch_pin.cpp b/eeschema/sch_pin.cpp index 45b3c38f40..fbdf7f9ef7 100644 --- a/eeschema/sch_pin.cpp +++ b/eeschema/sch_pin.cpp @@ -609,6 +609,14 @@ std::vector SCH_PIN::GetStackedPinNumbers( bool* aValid ) const return numbers; } + +int SCH_PIN::GetStackedPinCount( bool* aValid ) const +{ + wxString shown = GetShownNumber(); + return CountStackedPinNotation( shown, aValid ); +} + + std::optional SCH_PIN::GetSmallestLogicalNumber() const { bool valid = false; diff --git a/eeschema/sch_pin.h b/eeschema/sch_pin.h index 9e472c2ea4..de462bc9ab 100644 --- a/eeschema/sch_pin.h +++ b/eeschema/sch_pin.h @@ -124,6 +124,19 @@ public: const wxString& GetNumber() const { return m_number; } wxString GetShownNumber() const; std::vector GetStackedPinNumbers( bool* aValid = nullptr ) const; + + /** + * Return the count of logical pins represented by this pin's stacked notation. + * + * This is a fast alternative to GetStackedPinNumbers().size() that avoids + * allocating and populating a vector of strings. + * + * @param aValid Optional pointer to bool that will be set to indicate if the + * stacked notation is valid (true) or malformed (false). + * @return The number of logical pins represented (always >= 1). + */ + int GetStackedPinCount( bool* aValid = nullptr ) const; + /** * Return the smallest logical pin number if this pin uses stacked * notation and it is valid. Otherwise returns std::nullopt. diff --git a/include/string_utils.h b/include/string_utils.h index 55f6d9a7fe..81d107dcff 100644 --- a/include/string_utils.h +++ b/include/string_utils.h @@ -494,6 +494,18 @@ KICOMMON_API wxString NormalizeFileUri( const wxString& aFileUri ); KICOMMON_API std::vector ExpandStackedPinNotation( const wxString& aPinName, bool* aValid = nullptr ); +/** + * Count the number of pins represented by stacked pin notation without allocating strings. + * + * This is a fast alternative to ExpandStackedPinNotation().size() for cases where only + * the count is needed. + * + * @param aPinName is the pin name to count (may or may not use stacked notation) + * @param aValid is optionally set to indicate whether the notation was valid + * @return count of individual pins represented (always >= 1) + */ +KICOMMON_API int CountStackedPinNotation( const wxString& aPinName, bool* aValid = nullptr ); + KICOMMON_API wxString GetDefaultVariantName(); diff --git a/qa/tests/eeschema/test_stacked_pin_conversion.cpp b/qa/tests/eeschema/test_stacked_pin_conversion.cpp index 4a3ad913d8..3b2a92688e 100644 --- a/qa/tests/eeschema/test_stacked_pin_conversion.cpp +++ b/qa/tests/eeschema/test_stacked_pin_conversion.cpp @@ -772,4 +772,56 @@ BOOST_AUTO_TEST_CASE( TestDuplicatePinDetectionWithStackedNotation ) BOOST_CHECK( messages.empty() ); } + +/** + * Test that GetStackedPinCount returns the same count as GetStackedPinNumbers().size() + * but more efficiently + */ +BOOST_AUTO_TEST_CASE( TestStackedPinCountEfficiency ) +{ + // Test cases with different pin notations + struct TestCase + { + wxString notation; + int expectedCount; + bool expectedValid; + }; + + std::vector testCases = { + { wxT( "1" ), 1, true }, // Simple pin + { wxT( "[1,2,3]" ), 3, true }, // List notation + { wxT( "[5-7]" ), 3, true }, // Range notation + { wxT( "[1,3,5-7]" ), 5, true }, // Mixed notation + { wxT( "[A1-A5]" ), 5, true }, // Alphanumeric range + { wxT( "[1-10]" ), 10, true }, // Larger range + { wxT( "[PA1,PA2,PB5-PB8]" ), 6, true }, // Complex mixed + { wxT( "[1-3,10,20-22]" ), 7, true }, // Multiple ranges + { wxT( "[10" ), 1, false }, // Invalid (missing bracket) + { wxT( "10]" ), 1, false }, // Invalid (missing bracket) + { wxT( "[5-3]" ), 1, false }, // Invalid (reverse range) + }; + + for( const auto& testCase : testCases ) + { + SCH_PIN* pin = new SCH_PIN( m_symbol.get() ); + pin->SetNumber( testCase.notation ); + + bool validExpand = false; + bool validCount = false; + + std::vector expanded = pin->GetStackedPinNumbers( &validExpand ); + int count = pin->GetStackedPinCount( &validCount ); + + // Both methods should agree on validity + BOOST_CHECK_EQUAL( validExpand, validCount ); + BOOST_CHECK_EQUAL( validExpand, testCase.expectedValid ); + + // Both methods should return the same count + BOOST_CHECK_EQUAL( static_cast( expanded.size() ), count ); + BOOST_CHECK_EQUAL( count, testCase.expectedCount ); + + delete pin; + } +} + BOOST_AUTO_TEST_SUITE_END()