mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-13 17:53:11 +02:00
ADDED: Stacked pin notation support
Implement bracket notation for stacked pins ([1,2,3], [1-4], [1,3,5-7]). Automatic net naming proceeds based on the smallest logical pin number in stacked groups. Provide explode/reform commands in symbol editor for conversion. Supports arbitrary ranges including BGA alphanum ranges like [AA1-AA3,CD14-CD22] Adds some additional QA and trace logging Fixes https://gitlab.com/kicad/code/kicad/-/issues/2004
This commit is contained in:
parent
ac1b44715b
commit
f66cbaf43a
@ -38,6 +38,7 @@
|
||||
#include <fmt/chrono.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/regex.h>
|
||||
#include <wx/tokenzr.h>
|
||||
#include "locale_io.h"
|
||||
|
||||
|
||||
@ -1503,3 +1504,124 @@ wxString NormalizeFileUri( const wxString& aFileUri )
|
||||
|
||||
return retv;
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
// Extract (prefix, numericValue) where numericValue = -1 if no numeric suffix
|
||||
std::pair<wxString, long> ParseAlphaNumericPin( const wxString& pinNum )
|
||||
{
|
||||
wxString prefix;
|
||||
long numValue = -1;
|
||||
|
||||
size_t numStart = pinNum.length();
|
||||
for( int i = static_cast<int>( pinNum.length() ) - 1; i >= 0; --i )
|
||||
{
|
||||
if( !wxIsdigit( pinNum[i] ) )
|
||||
{
|
||||
numStart = i + 1;
|
||||
break;
|
||||
}
|
||||
if( i == 0 )
|
||||
numStart = 0; // all digits
|
||||
}
|
||||
|
||||
if( numStart < pinNum.length() )
|
||||
{
|
||||
prefix = pinNum.Left( numStart );
|
||||
wxString numericPart = pinNum.Mid( numStart );
|
||||
numericPart.ToLong( &numValue );
|
||||
}
|
||||
|
||||
return { prefix, numValue };
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<wxString> ExpandStackedPinNotation( const wxString& aPinName, bool* aValid )
|
||||
{
|
||||
if( aValid )
|
||||
*aValid = true;
|
||||
|
||||
std::vector<wxString> expanded;
|
||||
|
||||
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;
|
||||
expanded.push_back( aPinName );
|
||||
return expanded;
|
||||
}
|
||||
}
|
||||
|
||||
if( !aPinName.StartsWith( wxT( "[" ) ) || !aPinName.EndsWith( wxT( "]" ) ) )
|
||||
{
|
||||
expanded.push_back( aPinName );
|
||||
return expanded;
|
||||
}
|
||||
|
||||
const wxString inner = aPinName.Mid( 1, aPinName.Length() - 2 );
|
||||
|
||||
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;
|
||||
expanded.clear();
|
||||
expanded.push_back( aPinName );
|
||||
return expanded;
|
||||
}
|
||||
|
||||
for( long ii = startVal; ii <= endVal; ++ii )
|
||||
{
|
||||
if( startPrefix.IsEmpty() )
|
||||
expanded.emplace_back( wxString::Format( wxT( "%ld" ), ii ) );
|
||||
else
|
||||
expanded.emplace_back( wxString::Format( wxT( "%s%ld" ), startPrefix, ii ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
expanded.push_back( part );
|
||||
}
|
||||
|
||||
if( comma == wxString::npos )
|
||||
break;
|
||||
start = comma + 1;
|
||||
}
|
||||
|
||||
if( expanded.empty() )
|
||||
{
|
||||
expanded.push_back( aPinName );
|
||||
if( aValid )
|
||||
*aValid = false;
|
||||
}
|
||||
|
||||
return expanded;
|
||||
}
|
||||
|
@ -62,6 +62,7 @@
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/ffile.h>
|
||||
|
||||
|
||||
CVPCB_MAINFRAME::CVPCB_MAINFRAME( KIWAY* aKiway, wxWindow* aParent ) :
|
||||
@ -796,7 +797,12 @@ void CVPCB_MAINFRAME::DisplayStatus()
|
||||
msg.Empty();
|
||||
|
||||
if( symbol )
|
||||
msg = wxString::Format( wxT( "%i" ), symbol->GetPinCount() );
|
||||
{
|
||||
int pc = symbol->GetPinCount();
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "DisplayStatus: selected '%s' pinCount=%d" ),
|
||||
symbol->GetReference(), pc );
|
||||
msg = wxString::Format( wxT( "%i" ), pc );
|
||||
}
|
||||
|
||||
if( !filters.IsEmpty() )
|
||||
filters += wxT( ", " );
|
||||
@ -957,6 +963,12 @@ int CVPCB_MAINFRAME::readSchematicNetlist( const std::string& aNetlist )
|
||||
|
||||
m_netlist.Clear();
|
||||
|
||||
// Trace basic payload characteristics to verify libparts are present and visible here
|
||||
wxLogTrace( "CVPCB_PINCOUNT",
|
||||
wxT( "readSchematicNetlist: payload size=%zu has_libparts=%d has_libpart=%d" ),
|
||||
aNetlist.size(), aNetlist.find( "(libparts" ) != std::string::npos,
|
||||
aNetlist.find( "(libpart" ) != std::string::npos );
|
||||
|
||||
try
|
||||
{
|
||||
netlistReader.LoadNetlist();
|
||||
|
@ -132,7 +132,13 @@ void FOOTPRINTS_LISTBOX::SetFootprints( FOOTPRINT_LIST& aList, const wxString& a
|
||||
filter.FilterByFootprintFilters( aComponent->GetFootprintFilters() );
|
||||
|
||||
if( aFilterType & FILTERING_BY_PIN_COUNT && aComponent )
|
||||
filter.FilterByPinCount( aComponent->GetPinCount() );
|
||||
{
|
||||
int pc = aComponent->GetPinCount();
|
||||
wxLogTrace( "CVPCB_PINCOUNT",
|
||||
wxT( "FOOTPRINTS_LISTBOX::SetFootprints: ref='%s' pinCount filter=%d" ),
|
||||
aComponent->GetReference(), pc );
|
||||
filter.FilterByPinCount( pc );
|
||||
}
|
||||
|
||||
if( aFilterType & FILTERING_BY_LIBRARY )
|
||||
filter.FilterByLibrary( aLibName );
|
||||
|
@ -175,11 +175,28 @@ DIALOG_FIELD_PROPERTIES::DIALOG_FIELD_PROPERTIES( SCH_BASE_FRAME* aParent, const
|
||||
wxString netlist;
|
||||
wxArrayString pins;
|
||||
|
||||
for( SCH_PIN* pin : symbol->GetPins( 0 /* all units */, 1 /* single bodyStyle */ ) )
|
||||
for( SCH_PIN* pin : symbol->GetGraphicalPins( 0 /* all units */, 1 /* single bodyStyle */ ) )
|
||||
{
|
||||
bool valid = false;
|
||||
auto expanded = pin->GetStackedPinNumbers( &valid );
|
||||
|
||||
if( valid && !expanded.empty() )
|
||||
{
|
||||
for( const wxString& num : expanded )
|
||||
pins.push_back( num + ' ' + pin->GetShownName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
pins.push_back( pin->GetNumber() + ' ' + pin->GetShownName() );
|
||||
}
|
||||
}
|
||||
|
||||
if( !pins.IsEmpty() )
|
||||
netlist << EscapeString( wxJoin( pins, '\t' ), CTX_LINE );
|
||||
{
|
||||
wxString dbg = wxJoin( pins, '\t' );
|
||||
wxLogTrace( "FOOTPRINT_CHOOSER", wxS( "Chooser payload pins (LIB_SYMBOL): %s" ), dbg );
|
||||
netlist << EscapeString( dbg, CTX_LINE );
|
||||
}
|
||||
|
||||
netlist << wxS( "\r" );
|
||||
|
||||
@ -213,12 +230,28 @@ DIALOG_FIELD_PROPERTIES::DIALOG_FIELD_PROPERTIES( SCH_BASE_FRAME* aParent, const
|
||||
|
||||
if( lib_symbol )
|
||||
{
|
||||
for( SCH_PIN* pin : lib_symbol->GetPins( 0 /* all units */, 1 /* single bodyStyle */ ) )
|
||||
for( SCH_PIN* pin : lib_symbol->GetGraphicalPins( 0 /* all units */, 1 /* single bodyStyle */ ) )
|
||||
{
|
||||
bool valid = false;
|
||||
auto expanded = pin->GetStackedPinNumbers( &valid );
|
||||
if( valid && !expanded.empty() )
|
||||
{
|
||||
for( const wxString& num : expanded )
|
||||
pins.push_back( num + ' ' + pin->GetShownName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
pins.push_back( pin->GetNumber() + ' ' + pin->GetShownName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( !pins.IsEmpty() )
|
||||
netlist << EscapeString( wxJoin( pins, '\t' ), CTX_LINE );
|
||||
{
|
||||
wxString dbg = wxJoin( pins, '\t' );
|
||||
wxLogTrace( "FOOTPRINT_CHOOSER", wxS( "Chooser payload pins (SCH_SYMBOL): %s" ), dbg );
|
||||
netlist << EscapeString( dbg, CTX_LINE );
|
||||
}
|
||||
|
||||
netlist << wxS( "\r" );
|
||||
|
||||
|
@ -402,7 +402,7 @@ void DIALOG_LIB_FIELDS_TABLE::SetupColumnProperties( int aCol )
|
||||
LIB_SYMBOL* symbol = m_symbolsList[0];
|
||||
wxArrayString pins;
|
||||
|
||||
for( SCH_PIN* pin : symbol->GetPins( 0 /* all units */, 1 /* single bodyStyle */ ) )
|
||||
for( SCH_PIN* pin : symbol->GetGraphicalPins( 0 /* all units */, 1 /* single bodyStyle */ ) )
|
||||
pins.push_back( pin->GetNumber() + ' ' + pin->GetShownName() );
|
||||
|
||||
if( !pins.IsEmpty() )
|
||||
|
@ -631,7 +631,7 @@ int ERC_TESTER::TestMissingUnits()
|
||||
}
|
||||
}
|
||||
|
||||
for( SCH_PIN* pin : libSymbol->GetPins( missing_unit, bodyStyle ) )
|
||||
for( SCH_PIN* pin : libSymbol->GetGraphicalPins( missing_unit, bodyStyle ) )
|
||||
{
|
||||
switch( pin->GetType() )
|
||||
{
|
||||
@ -1295,6 +1295,44 @@ int ERC_TESTER::TestGroundPins()
|
||||
}
|
||||
|
||||
|
||||
int ERC_TESTER::TestStackedPinNotation()
|
||||
{
|
||||
int warnings = 0;
|
||||
|
||||
for( const SCH_SHEET_PATH& sheet : m_sheetList )
|
||||
{
|
||||
SCH_SCREEN* screen = sheet.LastScreen();
|
||||
|
||||
for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
|
||||
{
|
||||
SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
|
||||
|
||||
for( SCH_PIN* pin : symbol->GetPins( &sheet ) )
|
||||
{
|
||||
bool valid;
|
||||
pin->GetStackedPinNumbers( &valid );
|
||||
|
||||
if( !valid )
|
||||
{
|
||||
std::shared_ptr<ERC_ITEM> ercItem =
|
||||
ERC_ITEM::Create( ERCE_STACKED_PIN_SYNTAX );
|
||||
ercItem->SetItems( pin );
|
||||
ercItem->SetSheetSpecificPath( sheet );
|
||||
ercItem->SetItemsSheetPaths( sheet );
|
||||
|
||||
SCH_MARKER* marker =
|
||||
new SCH_MARKER( std::move( ercItem ), pin->GetPosition() );
|
||||
screen->Append( marker );
|
||||
warnings++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
|
||||
int ERC_TESTER::TestSameLocalGlobalLabel()
|
||||
{
|
||||
int errCount = 0;
|
||||
@ -1961,6 +1999,9 @@ void ERC_TESTER::RunTests( DS_PROXY_VIEW_ITEM* aDrawingSheet, SCH_EDIT_FRAME* aE
|
||||
if( m_settings.IsTestEnabled( ERCE_GROUND_PIN_NOT_GROUND ) )
|
||||
TestGroundPins();
|
||||
|
||||
if( m_settings.IsTestEnabled( ERCE_STACKED_PIN_SYNTAX ) )
|
||||
TestStackedPinNotation();
|
||||
|
||||
// Test similar labels (i;e. labels which are identical when
|
||||
// using case insensitive comparisons)
|
||||
if( m_settings.IsTestEnabled( ERCE_SIMILAR_LABELS )
|
||||
|
@ -118,6 +118,12 @@ public:
|
||||
*/
|
||||
int TestGroundPins();
|
||||
|
||||
/**
|
||||
* Checks for pin numbers that resemble stacked pin notation but are invalid.
|
||||
* @return warning count
|
||||
*/
|
||||
int TestStackedPinNotation();
|
||||
|
||||
/**
|
||||
* Checks for global and local labels with the same name
|
||||
* @return the error count
|
||||
|
@ -161,6 +161,10 @@ ERC_ITEM ERC_ITEM::groundPinNotGround( ERCE_GROUND_PIN_NOT_GROUND,
|
||||
_HKI( "Ground pin not connected to ground net" ),
|
||||
wxT( "ground_pin_not_ground" ) );
|
||||
|
||||
ERC_ITEM ERC_ITEM::stackedPinName( ERCE_STACKED_PIN_SYNTAX,
|
||||
_HKI( "Pin name resembles stacked pin" ),
|
||||
wxT( "stacked_pin_name" ) );
|
||||
|
||||
ERC_ITEM ERC_ITEM::unresolvedVariable( ERCE_UNRESOLVED_VARIABLE,
|
||||
_HKI( "Unresolved text variable" ),
|
||||
wxT( "unresolved_variable" ) );
|
||||
@ -268,6 +272,7 @@ std::vector<std::reference_wrapper<RC_ITEM>> ERC_ITEM::allItemTypes(
|
||||
ERC_ITEM::groundPinNotGround,
|
||||
|
||||
ERC_ITEM::heading_misc,
|
||||
ERC_ITEM::stackedPinName,
|
||||
ERC_ITEM::unannotated,
|
||||
ERC_ITEM::unresolvedVariable,
|
||||
ERC_ITEM::undefinedNetclass,
|
||||
@ -351,6 +356,7 @@ std::shared_ptr<ERC_ITEM> ERC_ITEM::Create( int aErrorCode )
|
||||
case ERCE_MISSING_POWER_INPUT_PIN: return std::make_shared<ERC_ITEM>( missingPowerInputPin );
|
||||
case ERCE_MISSING_BIDI_PIN: return std::make_shared<ERC_ITEM>( missingBidiPin );
|
||||
case ERCE_UNCONNECTED_WIRE_ENDPOINT: return std::make_shared<ERC_ITEM>( unconnectedWireEndpoint );
|
||||
case ERCE_STACKED_PIN_SYNTAX: return std::make_shared<ERC_ITEM>( stackedPinName );
|
||||
case ERCE_UNSPECIFIED:
|
||||
default:
|
||||
wxFAIL_MSG( wxS( "Unknown ERC error code" ) );
|
||||
|
@ -227,6 +227,7 @@ private:
|
||||
static ERC_ITEM busToBusConflict;
|
||||
static ERC_ITEM busToNetConflict;
|
||||
static ERC_ITEM groundPinNotGround;
|
||||
static ERC_ITEM stackedPinName;
|
||||
static ERC_ITEM unresolvedVariable;
|
||||
static ERC_ITEM undefinedNetclass;
|
||||
static ERC_ITEM simulationModelIssues;
|
||||
|
@ -120,6 +120,7 @@ ERC_SETTINGS::ERC_SETTINGS( JSON_SETTINGS* aParent, const std::string& aPath ) :
|
||||
m_ERCSeverities[ERCE_FOUR_WAY_JUNCTION] = RPT_SEVERITY_IGNORE;
|
||||
m_ERCSeverities[ERCE_LABEL_MULTIPLE_WIRES] = RPT_SEVERITY_WARNING;
|
||||
m_ERCSeverities[ERCE_UNCONNECTED_WIRE_ENDPOINT] = RPT_SEVERITY_WARNING;
|
||||
m_ERCSeverities[ERCE_STACKED_PIN_SYNTAX] = RPT_SEVERITY_WARNING;
|
||||
|
||||
m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "rule_severities",
|
||||
[&]() -> nlohmann::json
|
||||
|
@ -87,8 +87,9 @@ enum ERCE_T
|
||||
ERCE_FOUR_WAY_JUNCTION, ///< A four-way junction was found.
|
||||
ERCE_LABEL_MULTIPLE_WIRES, ///< A label is connected to more than one wire.
|
||||
ERCE_UNCONNECTED_WIRE_ENDPOINT, ///< A label is connected to more than one wire.
|
||||
ERCE_STACKED_PIN_SYNTAX, ///< Pin name resembles stacked pin notation.
|
||||
|
||||
ERCE_LAST = ERCE_UNCONNECTED_WIRE_ENDPOINT,
|
||||
ERCE_LAST = ERCE_STACKED_PIN_SYNTAX,
|
||||
|
||||
ERCE_DUPLICATE_PIN_ERROR,
|
||||
ERCE_PIN_TO_PIN_WARNING, // pin connected to an other pin: warning level
|
||||
|
@ -79,12 +79,29 @@ static wxString netList( SCH_SYMBOL* aSymbol, SCH_SHEET_PATH& aSheetPath )
|
||||
|
||||
if( lib_symbol )
|
||||
{
|
||||
for( SCH_PIN* pin : lib_symbol->GetPins( 0 /* all units */, 1 /* single bodyStyle */ ) )
|
||||
for( SCH_PIN* pin : lib_symbol->GetGraphicalPins( 0 /* all units */, 1 /* single bodyStyle */ ) )
|
||||
{
|
||||
bool valid = false;
|
||||
std::vector<wxString> expanded = pin->GetStackedPinNumbers( &valid );
|
||||
|
||||
if( valid && !expanded.empty() )
|
||||
{
|
||||
for( const wxString& num : expanded )
|
||||
pins.push_back( num + ' ' + pin->GetShownName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
pins.push_back( pin->GetNumber() + ' ' + pin->GetShownName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( !pins.IsEmpty() )
|
||||
netlist << EscapeString( wxJoin( pins, '\t' ), CTX_LINE );
|
||||
{
|
||||
wxString dbg = wxJoin( pins, '\t' );
|
||||
wxLogTrace( "FOOTPRINT_CHOOSER", wxS( "Chooser payload pins: %s" ), dbg );
|
||||
netlist << EscapeString( dbg, CTX_LINE );
|
||||
}
|
||||
|
||||
netlist << wxS( "\r" );
|
||||
|
||||
@ -112,11 +129,28 @@ static wxString netList( LIB_SYMBOL* aSymbol )
|
||||
wxString netlist;
|
||||
wxArrayString pins;
|
||||
|
||||
for( SCH_PIN* pin : aSymbol->GetPins( 0 /* all units */, 1 /* single bodyStyle */ ) )
|
||||
for( SCH_PIN* pin : aSymbol->GetGraphicalPins( 0 /* all units */, 1 /* single bodyStyle */ ) )
|
||||
{
|
||||
bool valid = false;
|
||||
std::vector<wxString> expanded = pin->GetStackedPinNumbers( &valid );
|
||||
|
||||
if( valid && !expanded.empty() )
|
||||
{
|
||||
for( const wxString& num : expanded )
|
||||
pins.push_back( num + ' ' + pin->GetShownName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
pins.push_back( pin->GetNumber() + ' ' + pin->GetShownName() );
|
||||
}
|
||||
}
|
||||
|
||||
if( !pins.IsEmpty() )
|
||||
netlist << EscapeString( wxJoin( pins, '\t' ), CTX_LINE );
|
||||
{
|
||||
wxString dbg = wxJoin( pins, '\t' );
|
||||
wxLogTrace( "FOOTPRINT_CHOOSER", wxS( "Chooser payload pins: %s" ), dbg );
|
||||
netlist << EscapeString( dbg, CTX_LINE );
|
||||
}
|
||||
|
||||
netlist << wxS( "\r" );
|
||||
|
||||
|
@ -782,7 +782,7 @@ void LIB_SYMBOL::AddDrawItem( SCH_ITEM* aItem, bool aSort )
|
||||
}
|
||||
|
||||
|
||||
std::vector<SCH_PIN*> LIB_SYMBOL::GetPins( int aUnit, int aBodyStyle ) const
|
||||
std::vector<SCH_PIN*> LIB_SYMBOL::GetGraphicalPins( int aUnit, int aBodyStyle ) const
|
||||
{
|
||||
std::vector<SCH_PIN*> pins;
|
||||
|
||||
@ -807,28 +807,75 @@ std::vector<SCH_PIN*> LIB_SYMBOL::GetPins( int aUnit, int aBodyStyle ) const
|
||||
continue;
|
||||
|
||||
// TODO: get rid of const_cast. (It used to be a C-style cast so was less noticeable.)
|
||||
pins.push_back( const_cast<SCH_PIN*>( static_cast<const SCH_PIN*>( &item ) ) );
|
||||
SCH_PIN* pin = const_cast<SCH_PIN*>( static_cast<const SCH_PIN*>( &item ) );
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "GetGraphicalPins: lib='%s' unit=%d body=%d -> include pin name='%s' number='%s' shownNum='%s'",
|
||||
GetLibId().Format().wx_str(), aUnit, aBodyStyle,
|
||||
pin->GetName(), pin->GetNumber(), pin->GetShownNumber() ) );
|
||||
pins.push_back( pin );
|
||||
}
|
||||
|
||||
return pins;
|
||||
}
|
||||
|
||||
|
||||
std::vector<SCH_PIN*> LIB_SYMBOL::GetPins() const
|
||||
std::vector<LIB_SYMBOL::LOGICAL_PIN> LIB_SYMBOL::GetLogicalPins( int aUnit, int aBodyStyle ) const
|
||||
{
|
||||
return GetPins( 0, 0 );
|
||||
std::vector<LOGICAL_PIN> out;
|
||||
|
||||
for( SCH_PIN* pin : GetGraphicalPins( aUnit, aBodyStyle ) )
|
||||
{
|
||||
bool valid = false;
|
||||
std::vector<wxString> expanded = pin->GetStackedPinNumbers( &valid );
|
||||
|
||||
if( valid && !expanded.empty() )
|
||||
{
|
||||
for( const wxString& num : expanded )
|
||||
{
|
||||
out.push_back( LOGICAL_PIN{ pin, num } );
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "GetLogicalPins: base='%s' -> '%s'",
|
||||
pin->GetShownNumber(), num ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
out.push_back( LOGICAL_PIN{ pin, pin->GetShownNumber() } );
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "GetLogicalPins: base='%s' (no expansion)",
|
||||
pin->GetShownNumber() ) );
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
int LIB_SYMBOL::GetPinCount()
|
||||
{
|
||||
return (int) GetPins( 0 /* all units */, 1 /* single body style */ ).size();
|
||||
int count = 0;
|
||||
|
||||
for( SCH_PIN* pin : GetGraphicalPins( 0 /* all units */, 1 /* single body style */ ) )
|
||||
{
|
||||
bool valid;
|
||||
std::vector<wxString> 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 ) );
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
SCH_PIN* LIB_SYMBOL::GetPin( const wxString& aNumber, int aUnit, int aBodyStyle ) const
|
||||
{
|
||||
for( SCH_PIN* pin : GetPins( aUnit, aBodyStyle ) )
|
||||
for( SCH_PIN* pin : GetGraphicalPins( aUnit, aBodyStyle ) )
|
||||
{
|
||||
if( aNumber == pin->GetNumber() )
|
||||
return pin;
|
||||
@ -841,12 +888,12 @@ SCH_PIN* LIB_SYMBOL::GetPin( const wxString& aNumber, int aUnit, int aBodyStyle
|
||||
bool LIB_SYMBOL::PinsConflictWith( const LIB_SYMBOL& aOtherPart, bool aTestNums, bool aTestNames,
|
||||
bool aTestType, bool aTestOrientation, bool aTestLength ) const
|
||||
{
|
||||
for( const SCH_PIN* pin : GetPins() )
|
||||
for( const SCH_PIN* pin : GetGraphicalPins() )
|
||||
{
|
||||
wxASSERT( pin );
|
||||
bool foundMatch = false;
|
||||
|
||||
for( const SCH_PIN* otherPin : aOtherPart.GetPins() )
|
||||
for( const SCH_PIN* otherPin : aOtherPart.GetGraphicalPins() )
|
||||
{
|
||||
wxASSERT( otherPin );
|
||||
|
||||
@ -899,6 +946,12 @@ bool LIB_SYMBOL::PinsConflictWith( const LIB_SYMBOL& aOtherPart, bool aTestNums,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<SCH_PIN*> LIB_SYMBOL::GetPins() const
|
||||
{
|
||||
// Back-compat shim: return graphical pins for all units/body styles
|
||||
return GetGraphicalPins( 0, 0 );
|
||||
}
|
||||
|
||||
|
||||
const BOX2I LIB_SYMBOL::GetUnitBoundingBox( int aUnit, int aBodyStyle,
|
||||
bool aIgnoreHiddenFields,
|
||||
|
@ -416,21 +416,32 @@ public:
|
||||
void RemoveField( SCH_FIELD* aField ) { RemoveDrawItem( aField ); }
|
||||
|
||||
/**
|
||||
* Return a list of pin object pointers from the draw item list.
|
||||
* Graphical pins: Return schematic pin objects as drawn (unexpanded), filtered by unit/body.
|
||||
*
|
||||
* Note pin objects are owned by the draw list of the symbol. Deleting any of the objects
|
||||
* will leave list in a unstable state and will likely segfault when the list is destroyed.
|
||||
* Note: pin objects are owned by the symbol's draw list; do not delete them.
|
||||
*
|
||||
* @param aUnit - Unit number of pins to collect. Set to 0 to get pins from all symbol units.
|
||||
* @param aBodyStyle - Symbol alternate body style of pins to collect. Set to 0 to get pins
|
||||
* from all body styles.
|
||||
* @param aUnit Unit number to collect; 0 = all units
|
||||
* @param aBodyStyle Alternate body style to collect; 0 = all body styles
|
||||
*/
|
||||
std::vector<SCH_PIN*> GetPins( int aUnit, int aBodyStyle ) const;
|
||||
std::vector<SCH_PIN*> GetGraphicalPins( int aUnit = 0, int aBodyStyle = 0 ) const;
|
||||
|
||||
/**
|
||||
* Return a list of pin pointers for all units / converts. Used primarily for SPICE where
|
||||
* we want to treat all unit as a single part.
|
||||
* Logical pins: Return expanded logical pins based on stacked-pin notation.
|
||||
* Each returned item pairs a base graphical pin with a single expanded logical number.
|
||||
*/
|
||||
struct LOGICAL_PIN
|
||||
{
|
||||
SCH_PIN* pin; ///< pointer to the base graphical pin
|
||||
wxString number; ///< expanded logical pin number
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all logical pins (expanded) filtered by unit/body.
|
||||
* For non-stacked pins, the single logical pin's number equals the base pin number.
|
||||
*/
|
||||
std::vector<LOGICAL_PIN> GetLogicalPins( int aUnit, int aBodyStyle ) const;
|
||||
|
||||
// Deprecated: use GetGraphicalPins(). This override remains to satisfy SYMBOL's pure virtual.
|
||||
std::vector<SCH_PIN*> GetPins() const override;
|
||||
|
||||
/**
|
||||
|
67
eeschema/multiline_pin_text.cpp
Normal file
67
eeschema/multiline_pin_text.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers
|
||||
*
|
||||
* 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 3
|
||||
* 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 at
|
||||
* http://www.gnu.org/licenses/
|
||||
*/
|
||||
|
||||
|
||||
#include "multiline_pin_text.h"
|
||||
|
||||
#include <wx/tokenzr.h>
|
||||
|
||||
MULTILINE_PIN_TEXT_LAYOUT ComputeMultiLinePinNumberLayout( const wxString& aText,
|
||||
const VECTOR2D& aAnchorPos, const TEXT_ATTRIBUTES& aAttrs )
|
||||
{
|
||||
MULTILINE_PIN_TEXT_LAYOUT layout;
|
||||
layout.m_StartPos = aAnchorPos;
|
||||
|
||||
if( !( aText.StartsWith( "[" ) && aText.EndsWith( "]" ) && aText.Contains( "\n" ) ) )
|
||||
return layout; // not multi-line stacked
|
||||
|
||||
wxString content = aText.Mid( 1, aText.Length() - 2 );
|
||||
wxArrayString lines; wxStringSplit( content, lines, '\n' );
|
||||
if( lines.size() <= 1 )
|
||||
return layout;
|
||||
|
||||
layout.m_IsMultiLine = true;
|
||||
|
||||
layout.m_Lines = lines;
|
||||
for( size_t i = 0; i < layout.m_Lines.size(); ++i )
|
||||
layout.m_Lines[i].Trim( true ).Trim( false );
|
||||
|
||||
layout.m_LineSpacing = KiROUND( aAttrs.m_Size.y * 1.3 );
|
||||
|
||||
// Apply alignment-dependent origin shift identical to sch_painter logic
|
||||
if( aAttrs.m_Angle == ANGLE_VERTICAL )
|
||||
{
|
||||
int totalWidth = ( (int) layout.m_Lines.size() - 1 ) * layout.m_LineSpacing;
|
||||
if( aAttrs.m_Halign == GR_TEXT_H_ALIGN_RIGHT )
|
||||
layout.m_StartPos.x -= totalWidth;
|
||||
else if( aAttrs.m_Halign == GR_TEXT_H_ALIGN_CENTER )
|
||||
layout.m_StartPos.x -= totalWidth / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
int totalHeight = ( (int) layout.m_Lines.size() - 1 ) * layout.m_LineSpacing;
|
||||
if( aAttrs.m_Valign == GR_TEXT_V_ALIGN_BOTTOM )
|
||||
layout.m_StartPos.y -= totalHeight;
|
||||
else if( aAttrs.m_Valign == GR_TEXT_V_ALIGN_CENTER )
|
||||
layout.m_StartPos.y -= totalHeight / 2;
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
40
eeschema/multiline_pin_text.h
Normal file
40
eeschema/multiline_pin_text.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers
|
||||
*
|
||||
* 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 3
|
||||
* 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 at
|
||||
* http://www.gnu.org/licenses/
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <wx/arrstr.h>
|
||||
#include <math/vector2d.h>
|
||||
#include <font/text_attributes.h>
|
||||
|
||||
struct MULTILINE_PIN_TEXT_LAYOUT
|
||||
{
|
||||
bool m_IsMultiLine = false; // true if brace-wrapped multi-line stacked list
|
||||
wxArrayString m_Lines; // individual numbered lines (trimmed)
|
||||
VECTOR2D m_StartPos; // position used for line index 0 after alignment shift
|
||||
int m_LineSpacing = 0; // inter-line spacing in IU (along secondary axis)
|
||||
};
|
||||
|
||||
// Compute layout for a (possibly) multi-line stacked pin number string. If not multi-line, the
|
||||
// returned layout has m_IsMultiLine=false and no further adjustments are required.
|
||||
MULTILINE_PIN_TEXT_LAYOUT ComputeMultiLinePinNumberLayout( const wxString& aText,
|
||||
const VECTOR2D& aAnchorPos, const TEXT_ATTRIBUTES& aAttrs );
|
@ -176,7 +176,21 @@ std::vector<PIN_INFO> NETLIST_EXPORTER_BASE::CreatePinList( SCH_SYMBOL* aSymbol,
|
||||
continue;
|
||||
}
|
||||
|
||||
pins.emplace_back( pin->GetShownNumber(), netName );
|
||||
bool valid;
|
||||
std::vector<wxString> numbers = pin->GetStackedPinNumbers( &valid );
|
||||
wxString baseName = pin->GetShownName();
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "CreatePinList(single): ref='%s' pinNameBase='%s' shownNum='%s' net='%s' "
|
||||
"valid=%d expand=%zu",
|
||||
ref, baseName, pin->GetShownNumber(), netName, valid, numbers.size() ) );
|
||||
|
||||
for( const wxString& num : numbers )
|
||||
{
|
||||
wxString pinName = baseName.IsEmpty() ? num : baseName + wxT( "_" ) + num;
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( " -> emit pin num='%s' name='%s' net='%s'", num, pinName, netName ) );
|
||||
pins.emplace_back( num, netName, pinName );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -266,7 +280,20 @@ void NETLIST_EXPORTER_BASE::findAllUnitsOfSymbol( SCH_SYMBOL* aSchSymbol,
|
||||
continue;
|
||||
}
|
||||
|
||||
aPins.emplace_back( pin->GetShownNumber(), netName );
|
||||
bool valid;
|
||||
std::vector<wxString> numbers = pin->GetStackedPinNumbers( &valid );
|
||||
wxString baseName = pin->GetShownName();
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "CreatePinList(multi): ref='%s' pinNameBase='%s' shownNum='%s' net='%s' valid=%d expand=%zu",
|
||||
ref2, baseName, pin->GetShownNumber(), netName, valid, numbers.size() ) );
|
||||
|
||||
for( const wxString& num : numbers )
|
||||
{
|
||||
wxString pinName = baseName.IsEmpty() ? num : baseName + wxT( "_" ) + num;
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( " -> emit pin num='%s' name='%s' net='%s'", num, pinName, netName ) );
|
||||
aPins.emplace_back( num, netName, pinName );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,13 +71,15 @@ struct LIB_SYMBOL_LESS_THAN
|
||||
|
||||
struct PIN_INFO
|
||||
{
|
||||
PIN_INFO( const wxString& aPinNumber, const wxString& aNetName ) :
|
||||
PIN_INFO( const wxString& aPinNumber, const wxString& aNetName, const wxString& aPinName ) :
|
||||
num( aPinNumber ),
|
||||
netName( aNetName )
|
||||
netName( aNetName ),
|
||||
pinName( aPinName )
|
||||
{}
|
||||
|
||||
wxString num;
|
||||
wxString netName;
|
||||
wxString pinName;
|
||||
};
|
||||
|
||||
|
||||
|
@ -495,7 +495,8 @@ XNODE* NETLIST_EXPORTER_XML::makeGroups()
|
||||
XNODE* xcomps = node( wxT( "groups" ) );
|
||||
|
||||
m_referencesAlreadyFound.Clear();
|
||||
m_libParts.clear();
|
||||
// Do not clear m_libParts here: it is populated in makeSymbols() and used later by
|
||||
// makeLibParts() to emit the libparts section for CvPcb and other consumers.
|
||||
|
||||
SCH_SHEET_PATH currentSheet = m_schematic->CurrentSheet();
|
||||
SCH_SHEET_LIST sheetList = m_schematic->Hierarchy();
|
||||
@ -759,7 +760,9 @@ XNODE* NETLIST_EXPORTER_XML::makeLibParts()
|
||||
}
|
||||
|
||||
//----- show the pins here ------------------------------------
|
||||
std::vector<SCH_PIN*> pinList = lcomp->GetPins( 0, 0 );
|
||||
// NOTE: Expand stacked-pin notation into individual pins so downstream
|
||||
// tools (e.g. CvPcb) see the actual number of footprint pins.
|
||||
std::vector<SCH_PIN*> pinList = lcomp->GetGraphicalPins( 0, 0 );
|
||||
|
||||
/*
|
||||
* We must erase redundant Pins references in pinList
|
||||
@ -780,6 +783,10 @@ XNODE* NETLIST_EXPORTER_XML::makeLibParts()
|
||||
}
|
||||
}
|
||||
|
||||
wxLogTrace( "CVPCB_PINCOUNT",
|
||||
wxString::Format( "makeLibParts: lib='%s' part='%s' pinList(size)=%zu",
|
||||
libNickname, lcomp->GetName(), pinList.size() ) );
|
||||
|
||||
if( pinList.size() )
|
||||
{
|
||||
XNODE* pins;
|
||||
@ -788,12 +795,40 @@ XNODE* NETLIST_EXPORTER_XML::makeLibParts()
|
||||
|
||||
for( unsigned i=0; i<pinList.size(); ++i )
|
||||
{
|
||||
XNODE* pin;
|
||||
SCH_PIN* basePin = pinList[i];
|
||||
|
||||
bool stackedValid = false;
|
||||
std::vector<wxString> expandedNums = basePin->GetStackedPinNumbers( &stackedValid );
|
||||
|
||||
// If stacked notation detected and valid, emit one libparts pin per expanded number.
|
||||
if( stackedValid && !expandedNums.empty() )
|
||||
{
|
||||
for( const wxString& num : expandedNums )
|
||||
{
|
||||
XNODE* pin;
|
||||
pins->AddChild( pin = node( wxT( "pin" ) ) );
|
||||
pin->AddAttribute( wxT( "num" ), pinList[i]->GetShownNumber() );
|
||||
pin->AddAttribute( wxT( "name" ), pinList[i]->GetShownName() );
|
||||
pin->AddAttribute( wxT( "type" ), pinList[i]->GetCanonicalElectricalTypeName() );
|
||||
pin->AddAttribute( wxT( "num" ), num );
|
||||
pin->AddAttribute( wxT( "name" ), basePin->GetShownName() );
|
||||
pin->AddAttribute( wxT( "type" ), basePin->GetCanonicalElectricalTypeName() );
|
||||
|
||||
wxLogTrace( "CVPCB_PINCOUNT",
|
||||
wxString::Format( "makeLibParts: -> pin num='%s' name='%s' (expanded)",
|
||||
num, basePin->GetShownName() ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
XNODE* pin;
|
||||
pins->AddChild( pin = node( wxT( "pin" ) ) );
|
||||
pin->AddAttribute( wxT( "num" ), basePin->GetShownNumber() );
|
||||
pin->AddAttribute( wxT( "name" ), basePin->GetShownName() );
|
||||
pin->AddAttribute( wxT( "type" ), basePin->GetCanonicalElectricalTypeName() );
|
||||
|
||||
wxLogTrace( "CVPCB_PINCOUNT",
|
||||
wxString::Format( "makeLibParts: -> pin num='%s' name='%s'",
|
||||
basePin->GetShownNumber(),
|
||||
basePin->GetShownName() ) );
|
||||
}
|
||||
|
||||
// caution: construction work site here, drive slowly
|
||||
}
|
||||
@ -956,7 +991,6 @@ XNODE* NETLIST_EXPORTER_XML::makeListOfNets( unsigned aCtl )
|
||||
for( const NET_NODE& netNode : net_record->m_Nodes )
|
||||
{
|
||||
wxString refText = netNode.m_Pin->GetParentSymbol()->GetRef( &netNode.m_Sheet );
|
||||
wxString pinText = netNode.m_Pin->GetShownNumber();
|
||||
|
||||
// Skip power symbols and virtual symbols
|
||||
if( refText[0] == wxChar( '#' ) )
|
||||
@ -974,21 +1008,39 @@ XNODE* NETLIST_EXPORTER_XML::makeListOfNets( unsigned aCtl )
|
||||
added = true;
|
||||
}
|
||||
|
||||
xnet->AddChild( xnode = node( wxT( "node" ) ) );
|
||||
xnode->AddAttribute( wxT( "ref" ), refText );
|
||||
xnode->AddAttribute( wxT( "pin" ), pinText );
|
||||
|
||||
wxString pinName = netNode.m_Pin->GetShownName();
|
||||
std::vector<wxString> nums = netNode.m_Pin->GetStackedPinNumbers();
|
||||
wxString baseName = netNode.m_Pin->GetShownName();
|
||||
wxString pinType = netNode.m_Pin->GetCanonicalElectricalTypeName();
|
||||
|
||||
if( !pinName.IsEmpty() )
|
||||
xnode->AddAttribute( wxT( "pinfunction" ), pinName );
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "XML: net='%s' ref='%s' base='%s' shownNum='%s' expand=%zu",
|
||||
net_record->m_Name, refText, baseName,
|
||||
netNode.m_Pin->GetShownNumber(), nums.size() ) );
|
||||
|
||||
for( const wxString& num : nums )
|
||||
{
|
||||
xnet->AddChild( xnode = node( wxT( "node" ) ) );
|
||||
xnode->AddAttribute( wxT( "ref" ), refText );
|
||||
xnode->AddAttribute( wxT( "pin" ), num );
|
||||
|
||||
wxString fullName = baseName.IsEmpty() ? num : baseName + wxT( "_" ) + num;
|
||||
|
||||
if( !baseName.IsEmpty() || nums.size() > 1 )
|
||||
xnode->AddAttribute( wxT( "pinfunction" ), fullName );
|
||||
|
||||
wxString typeAttr = pinType;
|
||||
|
||||
if( net_record->m_HasNoConnect
|
||||
&& ( net_record->m_Nodes.size() == 1 || allNetPinsStacked ) )
|
||||
pinType += wxT( "+no_connect" );
|
||||
{
|
||||
typeAttr += wxT( "+no_connect" );
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "XML: marking node ref='%s' pin='%s' as no_connect",
|
||||
refText, num ) );
|
||||
}
|
||||
|
||||
xnode->AddAttribute( wxT( "pintype" ), pinType );
|
||||
xnode->AddAttribute( wxT( "pintype" ), typeAttr );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
||||
* Copyright The KiCad Developers
|
||||
*
|
||||
* 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
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
@ -14,60 +14,149 @@
|
||||
* 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
|
||||
* along with this program; if not, you may find one at
|
||||
* http://www.gnu.org/licenses/
|
||||
*/
|
||||
|
||||
#include "pin_layout_cache.h"
|
||||
|
||||
#include <geometry/direction45.h>
|
||||
#include <pgm_base.h>
|
||||
#include <settings/settings_manager.h>
|
||||
#include <sch_symbol.h>
|
||||
#include <eeschema_settings.h>
|
||||
#include <schematic_settings.h>
|
||||
|
||||
#include <string_utils.h>
|
||||
#include <geometry/shape_utils.h>
|
||||
|
||||
// Small margin in internal units between the pin text and the pin line
|
||||
static const int PIN_TEXT_MARGIN = 4;
|
||||
|
||||
namespace
|
||||
// Forward declaration for helper implemented in sch_pin.cpp
|
||||
wxString FormatStackedPinForDisplay( const wxString& aPinNumber, int aPinLength, int aTextSize,
|
||||
KIFONT::FONT* aFont, const KIFONT::METRICS& aFontMetrics );
|
||||
|
||||
std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> PIN_LAYOUT_CACHE::GetPinNumberInfo( int aShadowWidth )
|
||||
{
|
||||
recomputeCaches();
|
||||
|
||||
// small margin in internal units between the pin text and the pin line
|
||||
const int PIN_TEXT_MARGIN = 4;
|
||||
wxString number = m_pin.GetShownNumber();
|
||||
if( number.IsEmpty() || !m_pin.GetParentSymbol()->GetShowPinNumbers() )
|
||||
return std::nullopt;
|
||||
|
||||
struct EXTENTS_CACHE
|
||||
{
|
||||
KIFONT::FONT* m_Font = nullptr;
|
||||
int m_FontSize = 0;
|
||||
VECTOR2I m_Extents;
|
||||
};
|
||||
// Format stacked representation if necessary
|
||||
EESCHEMA_SETTINGS* cfg = GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" );
|
||||
KIFONT::FONT* font = KIFONT::FONT::GetFont( cfg ? cfg->m_Appearance.default_font : wxString( "" ) );
|
||||
const KIFONT::METRICS& metrics = m_pin.GetFontMetrics();
|
||||
wxString formatted = FormatStackedPinForDisplay( number, m_pin.GetLength(), m_pin.GetNumberTextSize(), font, metrics );
|
||||
|
||||
/// Utility for getting the size of the 'external' pin decorators (as a radius)
|
||||
// i.e. the negation circle, the polarity 'slopes' and the nonlogic
|
||||
// marker
|
||||
int externalPinDecoSize( const SCHEMATIC_SETTINGS* aSettings, const SCH_PIN& aPin )
|
||||
std::optional<TEXT_INFO> info = TEXT_INFO();
|
||||
info->m_Text = formatted;
|
||||
info->m_TextSize = m_pin.GetNumberTextSize();
|
||||
info->m_Thickness = m_numberThickness;
|
||||
info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
|
||||
info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
|
||||
|
||||
PIN_ORIENTATION orient = m_pin.PinDrawOrient( DefaultTransform );
|
||||
|
||||
auto estimateQABox = [&]( const wxString& txt, int size, bool isVertical ) -> VECTOR2I
|
||||
{
|
||||
int h = size;
|
||||
int w = (int) ( txt.Length() * size * 0.6 );
|
||||
if( txt.Contains( '\n' ) )
|
||||
{
|
||||
wxArrayString lines; wxStringSplit( txt, lines, '\n' );
|
||||
if( isVertical )
|
||||
{
|
||||
int lineSpacing = KiROUND( size * 1.3 );
|
||||
w = (int) lines.size() * lineSpacing;
|
||||
size_t maxLen = 0; for( const wxString& l : lines ) maxLen = std::max( maxLen, l.Length() );
|
||||
h = (int) ( maxLen * size * 0.6 );
|
||||
}
|
||||
else
|
||||
{
|
||||
int lineSpacing = KiROUND( size * 1.3 );
|
||||
h = (int) lines.size() * lineSpacing;
|
||||
size_t maxLen = 0; for( const wxString& l : lines ) maxLen = std::max( maxLen, l.Length() );
|
||||
w = (int) ( maxLen * size * 0.6 );
|
||||
}
|
||||
}
|
||||
return VECTOR2I( w, h );
|
||||
};
|
||||
|
||||
// Pass 1: determine maximum perpendicular half span among all pin numbers to ensure
|
||||
// a single distance from the pin center that avoids overlap for every pin.
|
||||
const SYMBOL* parentSym = m_pin.GetParentSymbol();
|
||||
int maxHalfHeight = 0; // vertical half span across all numbers
|
||||
int maxHalfWidth = 0; // horizontal half span across all numbers (for vertical pins overlap avoidance)
|
||||
int maxFullHeight = 0; // full height (for dynamic clearance)
|
||||
if( parentSym )
|
||||
{
|
||||
for( const SCH_PIN* p : parentSym->GetPins() )
|
||||
{
|
||||
wxString raw = p->GetShownNumber();
|
||||
if( raw.IsEmpty() )
|
||||
continue;
|
||||
wxString fmt = FormatStackedPinForDisplay( raw, p->GetLength(), p->GetNumberTextSize(), font, p->GetFontMetrics() );
|
||||
// Determine true max height regardless of rotation: use isVertical=false path for multiline height
|
||||
VECTOR2I box = estimateQABox( fmt, p->GetNumberTextSize(), false );
|
||||
maxHalfHeight = std::max( maxHalfHeight, box.y / 2 );
|
||||
maxFullHeight = std::max( maxFullHeight, box.y );
|
||||
maxHalfWidth = std::max( maxHalfWidth, box.x / 2 );
|
||||
}
|
||||
}
|
||||
int clearance = getPinTextOffset() + schIUScale.MilsToIU( PIN_TEXT_MARGIN );
|
||||
VECTOR2I pinPos = m_pin.GetPosition();
|
||||
bool verticalOrient = ( orient == PIN_ORIENTATION::PIN_UP || orient == PIN_ORIENTATION::PIN_DOWN );
|
||||
|
||||
// We need the per-pin bounding width for vertical placement (rotated text). For vertical
|
||||
// pins we anchor by the RIGHT edge of the text box so the gap from the pin to text is
|
||||
// constant (clearance) independent of text width (multi-line vs single-line).
|
||||
auto currentBox = estimateQABox( formatted, info->m_TextSize, verticalOrient );
|
||||
|
||||
if( verticalOrient )
|
||||
{
|
||||
// Vertical pins: text is placed to the LEFT (negative X) and rotated vertical so that it
|
||||
// reads bottom->top when the schematic is in its canonical orientation. We right-edge
|
||||
// align the text box at (pin.x - clearance) to keep a constant gap regardless of text width.
|
||||
int boxWidth = currentBox.x;
|
||||
int centerX = pinPos.x - clearance - boxWidth / 2;
|
||||
info->m_TextPosition.x = centerX;
|
||||
info->m_TextPosition.y = pinPos.y;
|
||||
info->m_Angle = ANGLE_VERTICAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Horizontal pins: "above" means negative Y direction. All numbers are centered on the
|
||||
// pin X and share a Y offset derived from the maximum half height across all numbers so
|
||||
// that multi-line and single-line numbers align cleanly.
|
||||
int centerY = pinPos.y - ( maxHalfHeight + clearance );
|
||||
info->m_TextPosition.x = pinPos.x; // centered horizontally on pin origin
|
||||
info->m_TextPosition.y = centerY;
|
||||
info->m_Angle = ANGLE_HORIZONTAL;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
// (Removed duplicate license & namespace with second PIN_TEXT_MARGIN to avoid ambiguity)
|
||||
|
||||
// NOTE: The real implementation of FormatStackedPinForDisplay lives in sch_pin.cpp.
|
||||
// The accidental, partial duplicate that was here has been removed.
|
||||
|
||||
// Reintroduce small helper functions (previously inside an anonymous namespace) needed later.
|
||||
static int externalPinDecoSize( const SCHEMATIC_SETTINGS* aSettings, const SCH_PIN& aPin )
|
||||
{
|
||||
if( aSettings && aSettings->m_PinSymbolSize )
|
||||
return aSettings->m_PinSymbolSize;
|
||||
|
||||
return aPin.GetNumberTextSize() / 2;
|
||||
}
|
||||
|
||||
|
||||
int internalPinDecoSize( const SCHEMATIC_SETTINGS* aSettings, const SCH_PIN& aPin )
|
||||
static int internalPinDecoSize( const SCHEMATIC_SETTINGS* aSettings, const SCH_PIN& aPin )
|
||||
{
|
||||
if( aSettings && aSettings->m_PinSymbolSize > 0 )
|
||||
return aSettings->m_PinSymbolSize;
|
||||
|
||||
return aPin.GetNameTextSize() != 0 ? aPin.GetNameTextSize() / 2 : aPin.GetNumberTextSize() / 2;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
PIN_LAYOUT_CACHE::PIN_LAYOUT_CACHE( const SCH_PIN& aPin ) :
|
||||
m_pin( aPin ), m_schSettings( nullptr ), m_dirtyFlags( DIRTY_FLAGS::ALL )
|
||||
@ -131,6 +220,42 @@ void PIN_LAYOUT_CACHE::recomputeExtentsCache( bool aDefinitelyDirty, KIFONT::FON
|
||||
VECTOR2D fontSize( aSize, aSize );
|
||||
int penWidth = GetPenSizeForNormal( aSize );
|
||||
|
||||
// Handle multi-line text bounds properly
|
||||
if( aText.StartsWith( "[" ) && aText.EndsWith( "]" ) && aText.Contains( "\n" ) )
|
||||
{
|
||||
// Extract content between braces and split into lines
|
||||
wxString content = aText.Mid( 1, aText.Length() - 2 );
|
||||
wxArrayString lines;
|
||||
wxStringSplit( content, lines, '\n' );
|
||||
|
||||
if( lines.size() > 1 )
|
||||
{
|
||||
int lineSpacing = KiROUND( aSize * 1.3 ); // Same as drawMultiLineText
|
||||
int maxWidth = 0;
|
||||
|
||||
// Find the widest line
|
||||
for( const wxString& line : lines )
|
||||
{
|
||||
wxString trimmedLine = line;
|
||||
trimmedLine.Trim( true ).Trim( false );
|
||||
VECTOR2I lineExtents = aFont->StringBoundaryLimits( trimmedLine, fontSize, penWidth, false, false, aFontMetrics );
|
||||
maxWidth = std::max( maxWidth, lineExtents.x );
|
||||
}
|
||||
|
||||
// Calculate total dimensions - width is max line width, height accounts for all lines
|
||||
int totalHeight = aSize + ( lines.size() - 1 ) * lineSpacing;
|
||||
|
||||
// Add space for braces
|
||||
int braceWidth = aSize / 3;
|
||||
maxWidth += braceWidth * 2; // Space for braces on both sides
|
||||
totalHeight += aSize / 3; // Extra height for brace extensions
|
||||
|
||||
aCache.m_Extents = VECTOR2I( maxWidth, totalHeight );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Single line text (normal case)
|
||||
aCache.m_Extents = aFont->StringBoundaryLimits( aText, fontSize, penWidth, false, false, aFontMetrics );
|
||||
}
|
||||
|
||||
@ -221,35 +346,38 @@ void PIN_LAYOUT_CACHE::transformBoxForPin( BOX2I& aBox ) const
|
||||
|
||||
void PIN_LAYOUT_CACHE::transformTextForPin( TEXT_INFO& aInfo ) const
|
||||
{
|
||||
// Now, calculate boundary box corners position for the actual pin orientation
|
||||
// Local nominal position for a PIN_RIGHT orientation.
|
||||
const VECTOR2I baseLocal = aInfo.m_TextPosition;
|
||||
|
||||
// We apply a rotation/mirroring depending on the pin orientation so that the text anchor
|
||||
// maintains a constant perpendicular offset from the pin origin regardless of rotation.
|
||||
VECTOR2I rotated = baseLocal;
|
||||
EDA_ANGLE finalAngle = aInfo.m_Angle;
|
||||
|
||||
switch( m_pin.PinDrawOrient( DefaultTransform ) )
|
||||
{
|
||||
case PIN_ORIENTATION::PIN_RIGHT: // identity
|
||||
break;
|
||||
case PIN_ORIENTATION::PIN_LEFT:
|
||||
{
|
||||
aInfo.m_HAlign = GetFlippedAlignment( aInfo.m_HAlign );
|
||||
aInfo.m_TextPosition.x = -aInfo.m_TextPosition.x;
|
||||
break;
|
||||
}
|
||||
case PIN_ORIENTATION::PIN_UP:
|
||||
{
|
||||
aInfo.m_Angle = ANGLE_VERTICAL;
|
||||
aInfo.m_TextPosition = { aInfo.m_TextPosition.y, -aInfo.m_TextPosition.x };
|
||||
break;
|
||||
}
|
||||
case PIN_ORIENTATION::PIN_DOWN:
|
||||
{
|
||||
aInfo.m_Angle = ANGLE_VERTICAL;
|
||||
aInfo.m_TextPosition = { aInfo.m_TextPosition.y, aInfo.m_TextPosition.x };
|
||||
rotated.x = -rotated.x;
|
||||
rotated.y = -rotated.y;
|
||||
aInfo.m_HAlign = GetFlippedAlignment( aInfo.m_HAlign );
|
||||
break;
|
||||
case PIN_ORIENTATION::PIN_UP: // rotate +90 (x,y)->(y,-x) and vertical text
|
||||
rotated = { baseLocal.y, -baseLocal.x };
|
||||
finalAngle = ANGLE_VERTICAL;
|
||||
break;
|
||||
case PIN_ORIENTATION::PIN_DOWN: // rotate -90 (x,y)->(-y,x) and vertical text, flip h-align
|
||||
rotated = { -baseLocal.y, baseLocal.x };
|
||||
finalAngle = ANGLE_VERTICAL;
|
||||
aInfo.m_HAlign = GetFlippedAlignment( aInfo.m_HAlign );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case PIN_ORIENTATION::PIN_RIGHT:
|
||||
// Already in this form
|
||||
break;
|
||||
}
|
||||
|
||||
aInfo.m_TextPosition += m_pin.GetPosition();
|
||||
aInfo.m_TextPosition = rotated + m_pin.GetPosition();
|
||||
aInfo.m_Angle = finalAngle;
|
||||
}
|
||||
|
||||
|
||||
@ -643,50 +771,49 @@ std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> PIN_LAYOUT_CACHE::GetPinNameInfo( int
|
||||
info->m_VAlign = GR_TEXT_V_ALIGN_BOTTOM;
|
||||
}
|
||||
|
||||
transformTextForPin( *info );
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> PIN_LAYOUT_CACHE::GetPinNumberInfo( int aShadowWidth )
|
||||
{
|
||||
recomputeCaches();
|
||||
|
||||
wxString number = m_pin.GetShownNumber();
|
||||
if( number.IsEmpty() || !m_pin.GetParentSymbol()->GetShowPinNumbers() )
|
||||
return std::nullopt;
|
||||
|
||||
std::optional<TEXT_INFO> info;
|
||||
|
||||
info = TEXT_INFO();
|
||||
info->m_Text = std::move( number );
|
||||
info->m_TextSize = m_pin.GetNumberTextSize();
|
||||
info->m_Thickness = m_numberThickness;
|
||||
info->m_Angle = ANGLE_HORIZONTAL;
|
||||
info->m_TextPosition = { m_pin.GetLength() / 2, 0 };
|
||||
info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
|
||||
|
||||
// The pin number is above the pin if there's no name, or the name is inside
|
||||
const bool numAbove =
|
||||
m_pin.GetParentSymbol()->GetPinNameOffset() > 0
|
||||
|| ( m_pin.GetShownName().empty() || !m_pin.GetParentSymbol()->GetShowPinNames() );
|
||||
|
||||
if( numAbove )
|
||||
// New policy: names follow same positioning semantics as numbers.
|
||||
const SYMBOL* parentSym = m_pin.GetParentSymbol();
|
||||
if( parentSym )
|
||||
{
|
||||
info->m_TextPosition.y -= getPinTextOffset() + info->m_Thickness / 2;
|
||||
info->m_VAlign = GR_TEXT_V_ALIGN_BOTTOM;
|
||||
int maxHalfHeight = 0;
|
||||
for( const SCH_PIN* p : parentSym->GetPins() )
|
||||
{
|
||||
wxString n = p->GetShownName();
|
||||
if( n.IsEmpty() )
|
||||
continue;
|
||||
maxHalfHeight = std::max( maxHalfHeight, p->GetNameTextSize() / 2 );
|
||||
}
|
||||
int clearance = getPinTextOffset() + schIUScale.MilsToIU( PIN_TEXT_MARGIN );
|
||||
VECTOR2I pinPos = m_pin.GetPosition();
|
||||
PIN_ORIENTATION orient = m_pin.PinDrawOrient( DefaultTransform );
|
||||
bool verticalOrient = ( orient == PIN_ORIENTATION::PIN_UP || orient == PIN_ORIENTATION::PIN_DOWN );
|
||||
|
||||
if( verticalOrient )
|
||||
{
|
||||
// Vertical pins: name mirrors number placement (left + rotated) for visual consistency.
|
||||
int boxWidth = info->m_TextSize * (int) info->m_Text.Length() * 0.6; // heuristic width
|
||||
int centerX = pinPos.x - clearance - boxWidth / 2;
|
||||
info->m_TextPosition = { centerX, pinPos.y };
|
||||
info->m_Angle = ANGLE_VERTICAL;
|
||||
info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
|
||||
info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
|
||||
}
|
||||
else
|
||||
{
|
||||
info->m_TextPosition.y += getPinTextOffset() + info->m_Thickness / 2;
|
||||
info->m_VAlign = GR_TEXT_V_ALIGN_TOP;
|
||||
// Horizontal pins: name above (negative Y) aligned to same Y offset logic as numbers.
|
||||
info->m_TextPosition = { pinPos.x, pinPos.y - ( maxHalfHeight + clearance ) };
|
||||
info->m_Angle = ANGLE_HORIZONTAL;
|
||||
info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
|
||||
info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
|
||||
}
|
||||
}
|
||||
|
||||
transformTextForPin( *info );
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
// (Removed duplicate later GetPinNumberInfo – earlier definition retained at top of file.)
|
||||
|
||||
|
||||
std::optional<PIN_LAYOUT_CACHE::TEXT_INFO>
|
||||
PIN_LAYOUT_CACHE::GetPinElectricalTypeInfo( int aShadowWidth )
|
||||
{
|
||||
|
@ -1,11 +1,11 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
||||
* Copyright The KiCad Developers
|
||||
*
|
||||
* 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
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
@ -14,13 +14,11 @@
|
||||
* 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
|
||||
* along with this program; if not, you may find one at
|
||||
* http://www.gnu.org/licenses/
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
@ -55,7 +55,8 @@
|
||||
//#define SEXPR_SYMBOL_LIB_FILE_VERSION 20241209 // Private flags for SCH_FIELDs
|
||||
//#define SEXPR_SYMBOL_LIB_FILE_VERSION 20250318 // ~ no longer means empty text
|
||||
//#define SEXPR_SYMBOL_LIB_FILE_VERSION 20250324 // Jumper pin groups
|
||||
#define SEXPR_SYMBOL_LIB_FILE_VERSION 20250829 // Rounded Rectangles
|
||||
//#define SEXPR_SYMBOL_LIB_FILE_VERSION 20250829 // Rounded Rectangles
|
||||
#define SEXPR_SYMBOL_LIB_FILE_VERSION 20250901 // Stacked Pin notation
|
||||
|
||||
/**
|
||||
* Schematic file version.
|
||||
@ -126,4 +127,5 @@
|
||||
//#define SEXPR_SCHEMATIC_FILE_VERSION 20250513 // Groups can have design block lib_id
|
||||
//#define SEXPR_SCHEMATIC_FILE_VERSION 20250610 // DNP, etc. flags for rule areas
|
||||
//#define SEXPR_SCHEMATIC_FILE_VERSION 20250827 // Custom body styles
|
||||
#define SEXPR_SCHEMATIC_FILE_VERSION 20250829 // Rounded Rectangles
|
||||
//#define SEXPR_SCHEMATIC_FILE_VERSION 20250829 // Rounded Rectangles
|
||||
#define SEXPR_SCHEMATIC_FILE_VERSION 20250901 // Stacked Pin notation
|
||||
|
@ -3300,7 +3300,7 @@ void CADSTAR_SCH_ARCHIVE_LOADER::fixUpLibraryPins( LIB_SYMBOL* aSymbolToFix, int
|
||||
}
|
||||
}
|
||||
|
||||
for( SCH_PIN* pin : aSymbolToFix->GetPins( aGateNumber, 0 ) )
|
||||
for( SCH_PIN* pin : aSymbolToFix->GetGraphicalPins( aGateNumber, 0 ) )
|
||||
{
|
||||
auto setPinOrientation =
|
||||
[&]( const EDA_ANGLE& aAngle )
|
||||
|
@ -64,6 +64,7 @@
|
||||
#include <advanced_config.h>
|
||||
#include <settings/settings_manager.h>
|
||||
#include <stroke_params.h>
|
||||
#include <string_utils.h>
|
||||
#include "sch_painter.h"
|
||||
#include "common.h"
|
||||
|
||||
@ -1182,6 +1183,449 @@ void SCH_PAINTER::draw( const SCH_PIN* aPin, int aLayer, bool aDimmed )
|
||||
return aTextSize * aGal.GetWorldScale() < BITMAP_FONT_SIZE_THRESHOLD;
|
||||
};
|
||||
|
||||
// Helper function for drawing braces around multi-line text
|
||||
const auto drawBrace =
|
||||
[&]( KIGFX::GAL& aGal, const VECTOR2D& aTop, const VECTOR2D& aBottom,
|
||||
int aBraceWidth, bool aLeftBrace, const TEXT_ATTRIBUTES& aAttrs )
|
||||
{
|
||||
// Draw a simple brace using line segments, accounting for text rotation
|
||||
VECTOR2D mid = ( aTop + aBottom ) / 2.0;
|
||||
|
||||
aGal.SetLineWidth( aAttrs.m_StrokeWidth );
|
||||
aGal.SetIsFill( false );
|
||||
aGal.SetIsStroke( true );
|
||||
|
||||
// Calculate brace points in text coordinate system
|
||||
VECTOR2D p1 = aTop;
|
||||
VECTOR2D p2 = aTop;
|
||||
VECTOR2D p3 = mid;
|
||||
VECTOR2D p4 = aBottom;
|
||||
VECTOR2D p5 = aBottom;
|
||||
|
||||
// Apply brace offset based on text orientation
|
||||
if( aAttrs.m_Angle == ANGLE_VERTICAL )
|
||||
{
|
||||
// For vertical text, braces extend in the Y direction
|
||||
// "Left" brace is actually towards negative Y, "right" towards positive Y
|
||||
double braceOffset = aLeftBrace ? -aBraceWidth : aBraceWidth;
|
||||
p2.y += braceOffset / 2;
|
||||
p3.y += braceOffset;
|
||||
p4.y += braceOffset / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For horizontal text, braces extend in the X direction
|
||||
double braceOffset = aLeftBrace ? -aBraceWidth : aBraceWidth;
|
||||
p2.x += braceOffset / 2;
|
||||
p3.x += braceOffset;
|
||||
p4.x += braceOffset / 2;
|
||||
}
|
||||
|
||||
// Draw the brace segments
|
||||
aGal.DrawLine( p1, p2 );
|
||||
aGal.DrawLine( p2, p3 );
|
||||
aGal.DrawLine( p3, p4 );
|
||||
aGal.DrawLine( p4, p5 );
|
||||
};
|
||||
|
||||
const auto drawBracesAroundText =
|
||||
[&]( KIGFX::GAL& aGal, const wxArrayString& aLines, const VECTOR2D& aStartPos,
|
||||
int aLineSpacing, const TEXT_ATTRIBUTES& aAttrs )
|
||||
{
|
||||
if( aLines.size() <= 1 )
|
||||
return;
|
||||
|
||||
// Calculate brace dimensions
|
||||
int braceWidth = aAttrs.m_Size.x / 3; // Make braces a bit larger
|
||||
|
||||
// Find the maximum line width to position braces
|
||||
int maxLineWidth = 0;
|
||||
KIFONT::FONT* font = aAttrs.m_Font;
|
||||
if( !font )
|
||||
font = KIFONT::FONT::GetFont( eeconfig()->m_Appearance.default_font );
|
||||
|
||||
for( const wxString& line : aLines )
|
||||
{
|
||||
wxString trimmedLine = line;
|
||||
trimmedLine.Trim( true ).Trim( false );
|
||||
VECTOR2I lineExtents = font->StringBoundaryLimits( trimmedLine, aAttrs.m_Size,
|
||||
aAttrs.m_StrokeWidth, false, false,
|
||||
KIFONT::METRICS() );
|
||||
maxLineWidth = std::max( maxLineWidth, lineExtents.x );
|
||||
}
|
||||
|
||||
// Calculate brace positions based on text vertical alignment and rotation
|
||||
VECTOR2D braceStart = aStartPos;
|
||||
VECTOR2D braceEnd = aStartPos;
|
||||
|
||||
// Extend braces beyond the text bounds
|
||||
int textHeight = aAttrs.m_Size.y;
|
||||
int extraHeight = textHeight / 3; // Extend braces by 1/3 of text height beyond text
|
||||
|
||||
if( aAttrs.m_Angle == ANGLE_VERTICAL )
|
||||
{
|
||||
// For vertical text, lines are spaced horizontally and braces are horizontal
|
||||
braceEnd.x += ( aLines.size() - 1 ) * aLineSpacing;
|
||||
|
||||
// Extend braces horizontally to encompass all lines plus extra space
|
||||
braceStart.x -= 2 * extraHeight;
|
||||
|
||||
// Position braces in the perpendicular direction (Y) with proper spacing
|
||||
int braceSpacing = maxLineWidth / 2 + braceWidth;
|
||||
|
||||
VECTOR2D topBraceStart = braceStart;
|
||||
topBraceStart.y -= braceSpacing;
|
||||
|
||||
VECTOR2D topBraceEnd = braceEnd;
|
||||
topBraceEnd.y -= braceSpacing;
|
||||
|
||||
drawBrace( aGal, topBraceStart, topBraceEnd, braceWidth, true, aAttrs );
|
||||
|
||||
VECTOR2D bottomBraceStart = braceStart;
|
||||
bottomBraceStart.y += braceSpacing;
|
||||
|
||||
VECTOR2D bottomBraceEnd = braceEnd;
|
||||
bottomBraceEnd.y += braceSpacing;
|
||||
|
||||
drawBrace( aGal, bottomBraceStart, bottomBraceEnd, braceWidth, false, aAttrs );
|
||||
}
|
||||
else
|
||||
{
|
||||
// For horizontal text, lines are spaced vertically and braces are vertical
|
||||
braceEnd.y += ( aLines.size() - 1 ) * aLineSpacing;
|
||||
|
||||
// Extend braces vertically to encompass all lines plus extra space
|
||||
braceStart.y -= 2 * extraHeight;
|
||||
|
||||
// Position braces in the perpendicular direction (X) with proper spacing
|
||||
int braceSpacing = maxLineWidth / 2 + braceWidth;
|
||||
|
||||
// Draw left brace
|
||||
VECTOR2D leftTop = braceStart;
|
||||
leftTop.x -= braceSpacing;
|
||||
|
||||
VECTOR2D leftBottom = braceEnd;
|
||||
leftBottom.x -= braceSpacing;
|
||||
|
||||
drawBrace( aGal, leftTop, leftBottom, braceWidth, true, aAttrs );
|
||||
|
||||
// Draw right brace
|
||||
VECTOR2D rightTop = braceStart;
|
||||
rightTop.x += braceSpacing;
|
||||
|
||||
VECTOR2D rightBottom = braceEnd;
|
||||
rightBottom.x += braceSpacing;
|
||||
|
||||
drawBrace( aGal, rightTop, rightBottom, braceWidth, false, aAttrs );
|
||||
}
|
||||
};
|
||||
|
||||
const auto drawBracesAroundTextBitmap =
|
||||
[&]( KIGFX::GAL& aGal, const wxArrayString& aLines, const VECTOR2D& aStartPos,
|
||||
int aLineSpacing, const TEXT_ATTRIBUTES& aAttrs )
|
||||
{
|
||||
// Simplified brace drawing for bitmap text
|
||||
if( aLines.size() <= 1 )
|
||||
return;
|
||||
|
||||
int braceWidth = aAttrs.m_Size.x / 4;
|
||||
|
||||
// Estimate max line width (less precise for bitmap text)
|
||||
int maxLineWidth = aAttrs.m_Size.x * 4; // Conservative estimate
|
||||
|
||||
// Calculate brace positions based on rotation
|
||||
VECTOR2D braceStart = aStartPos;
|
||||
VECTOR2D braceEnd = aStartPos;
|
||||
|
||||
int textHalfHeight = aAttrs.m_Size.y / 2;
|
||||
|
||||
if( aAttrs.m_Angle == ANGLE_VERTICAL )
|
||||
{
|
||||
// For vertical text, lines are spaced horizontally
|
||||
braceEnd.x += ( aLines.size() - 1 ) * aLineSpacing;
|
||||
|
||||
VECTOR2D leftStart = braceStart;
|
||||
leftStart.y -= maxLineWidth / 2 + braceWidth / 2;
|
||||
|
||||
VECTOR2D leftEnd = braceEnd;
|
||||
leftEnd.y -= maxLineWidth / 2 + braceWidth / 2;
|
||||
|
||||
drawBrace( aGal, leftStart, leftEnd, braceWidth, true, aAttrs );
|
||||
|
||||
VECTOR2D rightStart = braceStart;
|
||||
rightStart.y += maxLineWidth / 2 + braceWidth / 2;
|
||||
|
||||
VECTOR2D rightEnd = braceEnd;
|
||||
rightEnd.y += maxLineWidth / 2 + braceWidth / 2;
|
||||
|
||||
drawBrace( aGal, rightStart, rightEnd, braceWidth, false, aAttrs );
|
||||
}
|
||||
else
|
||||
{
|
||||
// For horizontal text, lines are spaced vertically
|
||||
braceEnd.y += ( aLines.size() - 1 ) * aLineSpacing;
|
||||
|
||||
VECTOR2D braceTop = braceStart;
|
||||
braceTop.y -= textHalfHeight;
|
||||
|
||||
VECTOR2D braceBottom = braceEnd;
|
||||
braceBottom.y += textHalfHeight;
|
||||
|
||||
VECTOR2D leftTop = braceTop;
|
||||
leftTop.x -= maxLineWidth / 2 + braceWidth / 2;
|
||||
|
||||
VECTOR2D leftBottom = braceBottom;
|
||||
leftBottom.x -= maxLineWidth / 2 + braceWidth / 2;
|
||||
|
||||
drawBrace( aGal, leftTop, leftBottom, braceWidth, true, aAttrs );
|
||||
|
||||
VECTOR2D rightTop = braceTop;
|
||||
rightTop.x += maxLineWidth / 2 + braceWidth / 2;
|
||||
|
||||
VECTOR2D rightBottom = braceBottom;
|
||||
rightBottom.x += maxLineWidth / 2 + braceWidth / 2;
|
||||
|
||||
drawBrace( aGal, rightTop, rightBottom, braceWidth, false, aAttrs );
|
||||
}
|
||||
};
|
||||
|
||||
// Helper functions for drawing multi-line pin text with braces
|
||||
const auto drawMultiLineText =
|
||||
[&]( KIGFX::GAL& aGal, const wxString& aText, const VECTOR2D& aPosition,
|
||||
const TEXT_ATTRIBUTES& aAttrs, const KIFONT::METRICS& aFontMetrics )
|
||||
{
|
||||
// Check if this is multi-line stacked pin text with braces
|
||||
if( aText.StartsWith( "[" ) && aText.EndsWith( "]" ) && aText.Contains( "\n" ) )
|
||||
{
|
||||
// Extract content between braces and split into lines
|
||||
wxString content = aText.Mid( 1, aText.Length() - 2 );
|
||||
wxArrayString lines;
|
||||
wxStringSplit( content, lines, '\n' );
|
||||
|
||||
if( lines.size() > 1 )
|
||||
{
|
||||
// Calculate line spacing (similar to EDA_TEXT::GetInterline)
|
||||
int lineSpacing = KiROUND( aAttrs.m_Size.y * 1.3 ); // 130% of text height
|
||||
|
||||
// Calculate positioning based on text alignment and rotation
|
||||
VECTOR2D startPos = aPosition;
|
||||
|
||||
if( aAttrs.m_Angle == ANGLE_VERTICAL )
|
||||
{
|
||||
// For vertical text, lines are spaced horizontally
|
||||
// Adjust start position based on horizontal alignment
|
||||
if( aAttrs.m_Halign == GR_TEXT_H_ALIGN_RIGHT )
|
||||
{
|
||||
int totalWidth = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.x -= totalWidth;
|
||||
}
|
||||
else if( aAttrs.m_Halign == GR_TEXT_H_ALIGN_CENTER )
|
||||
{
|
||||
int totalWidth = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.x -= totalWidth / 2;
|
||||
}
|
||||
|
||||
// Draw each line
|
||||
for( size_t i = 0; i < lines.size(); i++ )
|
||||
{
|
||||
VECTOR2D linePos = startPos;
|
||||
linePos.x += i * lineSpacing;
|
||||
|
||||
wxString line = lines[i];
|
||||
line.Trim( true ).Trim( false );
|
||||
|
||||
strokeText( aGal, line, linePos, aAttrs, aFontMetrics );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For horizontal text, lines are spaced vertically
|
||||
// Adjust start position based on vertical alignment
|
||||
if( aAttrs.m_Valign == GR_TEXT_V_ALIGN_BOTTOM )
|
||||
{
|
||||
int totalHeight = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.y -= totalHeight;
|
||||
}
|
||||
else if( aAttrs.m_Valign == GR_TEXT_V_ALIGN_CENTER )
|
||||
{
|
||||
int totalHeight = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.y -= totalHeight / 2;
|
||||
}
|
||||
|
||||
// Draw each line
|
||||
for( size_t i = 0; i < lines.size(); i++ )
|
||||
{
|
||||
VECTOR2D linePos = startPos;
|
||||
linePos.y += i * lineSpacing;
|
||||
|
||||
wxString line = lines[i];
|
||||
line.Trim( true ).Trim( false );
|
||||
|
||||
strokeText( aGal, line, linePos, aAttrs, aFontMetrics );
|
||||
}
|
||||
}
|
||||
|
||||
// Draw braces around the text
|
||||
drawBracesAroundText( aGal, lines, startPos, lineSpacing, aAttrs );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to regular single-line text
|
||||
strokeText( aGal, aText, aPosition, aAttrs, aFontMetrics );
|
||||
};
|
||||
|
||||
const auto drawMultiLineTextBox =
|
||||
[&]( KIGFX::GAL& aGal, const wxString& aText, const VECTOR2D& aPosition,
|
||||
const TEXT_ATTRIBUTES& aAttrs, const KIFONT::METRICS& aFontMetrics )
|
||||
{
|
||||
// Similar to drawMultiLineText but uses boxText for outline fonts
|
||||
if( aText.StartsWith( "[" ) && aText.EndsWith( "]" ) && aText.Contains( "\n" ) )
|
||||
{
|
||||
wxString content = aText.Mid( 1, aText.Length() - 2 );
|
||||
wxArrayString lines;
|
||||
wxStringSplit( content, lines, '\n' );
|
||||
|
||||
if( lines.size() > 1 )
|
||||
{
|
||||
int lineSpacing = KiROUND( aAttrs.m_Size.y * 1.3 );
|
||||
VECTOR2D startPos = aPosition;
|
||||
|
||||
if( aAttrs.m_Angle == ANGLE_VERTICAL )
|
||||
{
|
||||
// For vertical text, lines are spaced horizontally
|
||||
if( aAttrs.m_Halign == GR_TEXT_H_ALIGN_RIGHT )
|
||||
{
|
||||
int totalWidth = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.x -= totalWidth;
|
||||
}
|
||||
else if( aAttrs.m_Halign == GR_TEXT_H_ALIGN_CENTER )
|
||||
{
|
||||
int totalWidth = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.x -= totalWidth / 2;
|
||||
}
|
||||
|
||||
for( size_t i = 0; i < lines.size(); i++ )
|
||||
{
|
||||
VECTOR2D linePos = startPos;
|
||||
linePos.x += i * lineSpacing;
|
||||
|
||||
wxString line = lines[i];
|
||||
line.Trim( true ).Trim( false );
|
||||
|
||||
boxText( aGal, line, linePos, aAttrs, aFontMetrics );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For horizontal text, lines are spaced vertically
|
||||
if( aAttrs.m_Valign == GR_TEXT_V_ALIGN_BOTTOM )
|
||||
{
|
||||
int totalHeight = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.y -= totalHeight;
|
||||
}
|
||||
else if( aAttrs.m_Valign == GR_TEXT_V_ALIGN_CENTER )
|
||||
{
|
||||
int totalHeight = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.y -= totalHeight / 2;
|
||||
}
|
||||
|
||||
for( size_t i = 0; i < lines.size(); i++ )
|
||||
{
|
||||
VECTOR2D linePos = startPos;
|
||||
linePos.y += i * lineSpacing;
|
||||
|
||||
wxString line = lines[i];
|
||||
line.Trim( true ).Trim( false );
|
||||
|
||||
boxText( aGal, line, linePos, aAttrs, aFontMetrics );
|
||||
}
|
||||
}
|
||||
|
||||
drawBracesAroundText( aGal, lines, startPos, lineSpacing, aAttrs );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
boxText( aGal, aText, aPosition, aAttrs, aFontMetrics );
|
||||
};
|
||||
|
||||
const auto drawMultiLineBitmapText =
|
||||
[&]( KIGFX::GAL& aGal, const wxString& aText, const VECTOR2D& aPosition,
|
||||
const TEXT_ATTRIBUTES& aAttrs )
|
||||
{
|
||||
// Similar to drawMultiLineText but uses bitmapText
|
||||
if( aText.StartsWith( "[" ) && aText.EndsWith( "]" ) && aText.Contains( "\n" ) )
|
||||
{
|
||||
wxString content = aText.Mid( 1, aText.Length() - 2 );
|
||||
wxArrayString lines;
|
||||
wxStringSplit( content, lines, '\n' );
|
||||
|
||||
if( lines.size() > 1 )
|
||||
{
|
||||
int lineSpacing = KiROUND( aAttrs.m_Size.y * 1.3 );
|
||||
VECTOR2D startPos = aPosition;
|
||||
|
||||
if( aAttrs.m_Angle == ANGLE_VERTICAL )
|
||||
{
|
||||
// For vertical text, lines are spaced horizontally
|
||||
if( aAttrs.m_Halign == GR_TEXT_H_ALIGN_RIGHT )
|
||||
{
|
||||
int totalWidth = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.x -= totalWidth;
|
||||
}
|
||||
else if( aAttrs.m_Halign == GR_TEXT_H_ALIGN_CENTER )
|
||||
{
|
||||
int totalWidth = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.x -= totalWidth / 2;
|
||||
}
|
||||
|
||||
for( size_t i = 0; i < lines.size(); i++ )
|
||||
{
|
||||
VECTOR2D linePos = startPos;
|
||||
linePos.x += i * lineSpacing;
|
||||
|
||||
wxString line = lines[i];
|
||||
line.Trim( true ).Trim( false );
|
||||
|
||||
bitmapText( aGal, line, linePos, aAttrs );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For horizontal text, lines are spaced vertically
|
||||
if( aAttrs.m_Valign == GR_TEXT_V_ALIGN_BOTTOM )
|
||||
{
|
||||
int totalHeight = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.y -= totalHeight;
|
||||
}
|
||||
else if( aAttrs.m_Valign == GR_TEXT_V_ALIGN_CENTER )
|
||||
{
|
||||
int totalHeight = ( lines.size() - 1 ) * lineSpacing;
|
||||
startPos.y -= totalHeight / 2;
|
||||
}
|
||||
|
||||
for( size_t i = 0; i < lines.size(); i++ )
|
||||
{
|
||||
VECTOR2D linePos = startPos;
|
||||
linePos.y += i * lineSpacing;
|
||||
|
||||
wxString line = lines[i];
|
||||
line.Trim( true ).Trim( false );
|
||||
|
||||
bitmapText( aGal, line, linePos, aAttrs );
|
||||
}
|
||||
}
|
||||
|
||||
// Draw braces with bitmap text (simplified version)
|
||||
drawBracesAroundTextBitmap( aGal, lines, startPos, lineSpacing, aAttrs );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bitmapText( aGal, aText, aPosition, aAttrs );
|
||||
};
|
||||
|
||||
const auto drawTextInfo =
|
||||
[&]( const PIN_LAYOUT_CACHE::TEXT_INFO& aTextInfo, const COLOR4D& aColor )
|
||||
{
|
||||
@ -1206,23 +1650,23 @@ void SCH_PAINTER::draw( const SCH_PIN* aPin, int aLayer, bool aDimmed )
|
||||
|
||||
if( !attrs.m_Font->IsOutline() )
|
||||
{
|
||||
strokeText( *m_gal, aTextInfo.m_Text, aTextInfo.m_TextPosition, attrs,
|
||||
drawMultiLineText( *m_gal, aTextInfo.m_Text, aTextInfo.m_TextPosition, attrs,
|
||||
aPin->GetFontMetrics() );
|
||||
}
|
||||
else
|
||||
{
|
||||
boxText( *m_gal, aTextInfo.m_Text, aTextInfo.m_TextPosition, attrs,
|
||||
drawMultiLineTextBox( *m_gal, aTextInfo.m_Text, aTextInfo.m_TextPosition, attrs,
|
||||
aPin->GetFontMetrics() );
|
||||
}
|
||||
}
|
||||
else if( nonCached( aPin ) && renderTextAsBitmap )
|
||||
{
|
||||
bitmapText( *m_gal, aTextInfo.m_Text, aTextInfo.m_TextPosition, attrs );
|
||||
drawMultiLineBitmapText( *m_gal, aTextInfo.m_Text, aTextInfo.m_TextPosition, attrs );
|
||||
const_cast<SCH_PIN*>( aPin )->SetFlags( IS_SHOWN_AS_BITMAP );
|
||||
}
|
||||
else
|
||||
{
|
||||
strokeText( *m_gal, aTextInfo.m_Text, aTextInfo.m_TextPosition, attrs,
|
||||
drawMultiLineText( *m_gal, aTextInfo.m_Text, aTextInfo.m_TextPosition, attrs,
|
||||
aPin->GetFontMetrics() );
|
||||
const_cast<SCH_PIN*>( aPin )->SetFlags( IS_SHOWN_AS_BITMAP );
|
||||
}
|
||||
@ -2251,11 +2695,11 @@ void SCH_PAINTER::draw( const SCH_SYMBOL* aSymbol, int aLayer )
|
||||
// Use dummy symbol if the actual couldn't be found (or couldn't be locked).
|
||||
LIB_SYMBOL* originalSymbol =
|
||||
aSymbol->GetLibSymbolRef() ? aSymbol->GetLibSymbolRef().get() : LIB_SYMBOL::GetDummy();
|
||||
std::vector<SCH_PIN*> originalPins = originalSymbol->GetPins( unit, bodyStyle );
|
||||
std::vector<SCH_PIN*> originalPins = originalSymbol->GetGraphicalPins( unit, bodyStyle );
|
||||
|
||||
// Copy the source so we can re-orient and translate it.
|
||||
LIB_SYMBOL tempSymbol( *originalSymbol );
|
||||
std::vector<SCH_PIN*> tempPins = tempSymbol.GetPins( unit, bodyStyle );
|
||||
std::vector<SCH_PIN*> tempPins = tempSymbol.GetGraphicalPins( unit, bodyStyle );
|
||||
|
||||
tempSymbol.SetFlags( aSymbol->GetFlags() );
|
||||
|
||||
|
@ -39,6 +39,49 @@
|
||||
#include <trigo.h>
|
||||
#include <string_utils.h>
|
||||
|
||||
wxString FormatStackedPinForDisplay( const wxString& aPinNumber, int aPinLength, int aTextSize, KIFONT::FONT* aFont,
|
||||
const KIFONT::METRICS& aFontMetrics )
|
||||
{
|
||||
// Check if this is stacked pin notation: [A,B,C]
|
||||
if( !aPinNumber.StartsWith( "[" ) || !aPinNumber.EndsWith( "]" ) )
|
||||
return aPinNumber;
|
||||
|
||||
const int minPinTextWidth = schIUScale.MilsToIU( 50 );
|
||||
const int maxPinTextWidth = std::max( aPinLength, minPinTextWidth );
|
||||
|
||||
VECTOR2D fontSize( aTextSize, aTextSize );
|
||||
int penWidth = GetPenSizeForNormal( aTextSize );
|
||||
VECTOR2I textExtents = aFont->StringBoundaryLimits( aPinNumber, fontSize, penWidth, false, false, aFontMetrics );
|
||||
|
||||
if( textExtents.x <= maxPinTextWidth )
|
||||
return aPinNumber; // Fits already
|
||||
|
||||
// Strip brackets and split by comma
|
||||
wxString inner = aPinNumber.Mid( 1, aPinNumber.Length() - 2 );
|
||||
wxArrayString parts;
|
||||
wxStringSplit( inner, parts, ',' );
|
||||
|
||||
if( parts.empty() )
|
||||
return aPinNumber; // malformed; fallback
|
||||
|
||||
// Build multi-line representation inside braces, each line trimmed
|
||||
wxString result = "[";
|
||||
|
||||
for( size_t i = 0; i < parts.size(); ++i )
|
||||
{
|
||||
wxString line = parts[i];
|
||||
line.Trim( true ).Trim( false );
|
||||
|
||||
if( i > 0 )
|
||||
result += "\n";
|
||||
|
||||
result += line;
|
||||
}
|
||||
|
||||
result += "]";
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// small margin in internal units between the pin text and the pin line
|
||||
#define PIN_TEXT_MARGIN 4
|
||||
@ -431,15 +474,24 @@ void SCH_PIN::SetIsDangling( bool aIsDangling )
|
||||
|
||||
bool SCH_PIN::IsStacked( const SCH_PIN* aPin ) const
|
||||
{
|
||||
bool isConnectableType_a = GetType() == ELECTRICAL_PINTYPE::PT_PASSIVE
|
||||
|| GetType() == ELECTRICAL_PINTYPE::PT_NIC;
|
||||
bool isConnectableType_b = aPin->GetType() == ELECTRICAL_PINTYPE::PT_PASSIVE
|
||||
|| aPin->GetType() == ELECTRICAL_PINTYPE::PT_NIC;
|
||||
const auto isPassiveOrNic = []( ELECTRICAL_PINTYPE t )
|
||||
{
|
||||
return t == ELECTRICAL_PINTYPE::PT_PASSIVE || t == ELECTRICAL_PINTYPE::PT_NIC;
|
||||
};
|
||||
|
||||
return m_parent == aPin->GetParent()
|
||||
&& GetPosition() == aPin->GetPosition()
|
||||
&& GetName() == aPin->GetName()
|
||||
&& ( GetType() == aPin->GetType() || isConnectableType_a || isConnectableType_b );
|
||||
const bool sameParent = m_parent == aPin->GetParent();
|
||||
const bool samePos = GetPosition() == aPin->GetPosition();
|
||||
const bool sameName = GetName() == aPin->GetName();
|
||||
const bool typeCompat = GetType() == aPin->GetType()
|
||||
|| isPassiveOrNic( GetType() )
|
||||
|| isPassiveOrNic( aPin->GetType() );
|
||||
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "IsStacked: this='%s/%s' other='%s/%s' sameParent=%d samePos=%d sameName=%d typeCompat=%d",
|
||||
GetName(), GetNumber(), aPin->GetName(), aPin->GetNumber(), sameParent,
|
||||
samePos, sameName, typeCompat ) );
|
||||
|
||||
return sameParent && samePos && sameName && typeCompat;
|
||||
}
|
||||
|
||||
|
||||
@ -538,6 +590,46 @@ wxString SCH_PIN::GetShownNumber() const
|
||||
}
|
||||
|
||||
|
||||
std::vector<wxString> SCH_PIN::GetStackedPinNumbers( bool* aValid ) const
|
||||
{
|
||||
wxString shown = GetShownNumber();
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "GetStackedPinNumbers: shown='%s'", shown ) );
|
||||
|
||||
std::vector<wxString> numbers = ExpandStackedPinNotation( shown, aValid );
|
||||
|
||||
// Log the expansion for debugging
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "Expanded '%s' to %zu pins", shown, numbers.size() ) );
|
||||
for( const wxString& num : numbers )
|
||||
{
|
||||
wxLogTrace( "KICAD_STACKED_PINS", wxString::Format( " -> '%s'", num ) );
|
||||
}
|
||||
|
||||
return numbers;
|
||||
}
|
||||
|
||||
std::optional<wxString> SCH_PIN::GetSmallestLogicalNumber() const
|
||||
{
|
||||
bool valid = false;
|
||||
auto numbers = GetStackedPinNumbers( &valid );
|
||||
|
||||
if( valid && !numbers.empty() )
|
||||
return numbers.front(); // Already in ascending order
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
wxString SCH_PIN::GetEffectivePadNumber() const
|
||||
{
|
||||
if( auto smallest = GetSmallestLogicalNumber() )
|
||||
return *smallest;
|
||||
|
||||
return GetShownNumber();
|
||||
}
|
||||
|
||||
|
||||
void SCH_PIN::SetNumber( const wxString& aNumber )
|
||||
{
|
||||
if( m_number == aNumber )
|
||||
@ -778,12 +870,17 @@ void SCH_PIN::PlotPinTexts( PLOTTER *aPlotter, const VECTOR2I &aPinPos, PIN_ORIE
|
||||
wxString name = GetShownName();
|
||||
wxString number = GetShownNumber();
|
||||
|
||||
// Apply stacked pin display formatting (reuse helper from pin_layout_cache)
|
||||
if( aDrawPinNum && !number.IsEmpty() )
|
||||
{
|
||||
const KIFONT::METRICS& metrics = GetFontMetrics();
|
||||
number = FormatStackedPinForDisplay( number, GetLength(), GetNumberTextSize(), font, metrics );
|
||||
}
|
||||
|
||||
if( name.IsEmpty() || m_nameTextSize == 0 )
|
||||
aDrawPinName = false;
|
||||
|
||||
if( number.IsEmpty() || m_numTextSize == 0 )
|
||||
aDrawPinNum = false;
|
||||
|
||||
if( !aDrawPinNum && !aDrawPinName )
|
||||
return;
|
||||
|
||||
@ -792,169 +889,257 @@ void SCH_PIN::PlotPinTexts( PLOTTER *aPlotter, const VECTOR2I &aPinPos, PIN_ORIE
|
||||
int name_offset = schIUScale.MilsToIU( PIN_TEXT_MARGIN ) + namePenWidth;
|
||||
int num_offset = schIUScale.MilsToIU( PIN_TEXT_MARGIN ) + numPenWidth;
|
||||
|
||||
/* Get the num and name colors */
|
||||
COLOR4D nameColor = settings->GetLayerColor( LAYER_PINNAM );
|
||||
COLOR4D numColor = settings->GetLayerColor( LAYER_PINNUM );
|
||||
COLOR4D bg = settings->GetBackgroundColor();
|
||||
|
||||
if( bg == COLOR4D::UNSPECIFIED || !aPlotter->GetColorMode() )
|
||||
bg = COLOR4D::WHITE;
|
||||
|
||||
if( aDimmed )
|
||||
{
|
||||
nameColor.Desaturate( );
|
||||
numColor.Desaturate( );
|
||||
nameColor.Desaturate();
|
||||
numColor.Desaturate();
|
||||
nameColor = nameColor.Mix( bg, 0.5f );
|
||||
numColor = numColor.Mix( bg, 0.5f );
|
||||
}
|
||||
|
||||
int x1 = aPinPos.x;
|
||||
int y1 = aPinPos.y;
|
||||
|
||||
switch( aPinOrient )
|
||||
{
|
||||
case PIN_ORIENTATION::PIN_UP: y1 -= GetLength(); break;
|
||||
case PIN_ORIENTATION::PIN_DOWN: y1 += GetLength(); break;
|
||||
case PIN_ORIENTATION::PIN_LEFT: x1 -= GetLength(); break;
|
||||
case PIN_ORIENTATION::PIN_RIGHT: x1 += GetLength(); break;
|
||||
case PIN_ORIENTATION::INHERIT: wxFAIL_MSG( wxS( "aPinOrient must be resolved!" ) ); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
auto plotName =
|
||||
[&]( int x, int y, const EDA_ANGLE& angle, GR_TEXT_H_ALIGN_T hJustify,
|
||||
GR_TEXT_V_ALIGN_T vJustify )
|
||||
auto plotSimpleText = [&]( int x, int y, const EDA_ANGLE& angle, GR_TEXT_H_ALIGN_T hJustify,
|
||||
GR_TEXT_V_ALIGN_T vJustify, const wxString& txt, int size,
|
||||
int penWidth, const COLOR4D& col )
|
||||
{
|
||||
TEXT_ATTRIBUTES attrs;
|
||||
attrs.m_StrokeWidth = namePenWidth;
|
||||
attrs.m_StrokeWidth = penWidth;
|
||||
attrs.m_Angle = angle;
|
||||
attrs.m_Size = VECTOR2I( GetNameTextSize(), GetNameTextSize() );
|
||||
attrs.m_Size = VECTOR2I( size, size );
|
||||
attrs.m_Halign = hJustify;
|
||||
attrs.m_Valign = vJustify;
|
||||
attrs.m_Multiline = false;
|
||||
|
||||
aPlotter->PlotText( VECTOR2I( x, y ), nameColor, name, attrs, font,
|
||||
GetFontMetrics() );
|
||||
attrs.m_Multiline = false; // we'll manage multi-line manually
|
||||
aPlotter->PlotText( VECTOR2I( x, y ), col, txt, attrs, font, GetFontMetrics() );
|
||||
};
|
||||
|
||||
auto plotNum =
|
||||
[&]( int x, int y, const EDA_ANGLE& angle, GR_TEXT_H_ALIGN_T hJustify,
|
||||
GR_TEXT_V_ALIGN_T vJustify )
|
||||
auto plotMultiLineWithBraces = [&]( int anchorX, int anchorY, bool vertical, bool /*numberBlock*/ )
|
||||
{
|
||||
TEXT_ATTRIBUTES attrs;
|
||||
attrs.m_StrokeWidth = numPenWidth;
|
||||
attrs.m_Angle = angle;
|
||||
attrs.m_Size = VECTOR2I( GetNumberTextSize(), GetNumberTextSize() );
|
||||
attrs.m_Halign = hJustify;
|
||||
attrs.m_Valign = vJustify;
|
||||
attrs.m_Multiline = false;
|
||||
// If not multi-line formatted, just plot single line centered.
|
||||
if( !number.StartsWith( "[" ) || !number.EndsWith( "]" ) || !number.Contains( "\n" ) )
|
||||
{
|
||||
plotSimpleText( anchorX, anchorY, vertical ? ANGLE_VERTICAL : ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM, number,
|
||||
GetNumberTextSize(), numPenWidth, numColor );
|
||||
return;
|
||||
}
|
||||
|
||||
aPlotter->PlotText( VECTOR2I( x, y ), numColor, number, attrs, font,
|
||||
GetFontMetrics() );
|
||||
wxString content = number.Mid( 1, number.Length() - 2 );
|
||||
wxArrayString lines;
|
||||
wxStringSplit( content, lines, '\n' );
|
||||
|
||||
if( lines.size() <= 1 )
|
||||
{
|
||||
plotSimpleText( anchorX, anchorY, vertical ? ANGLE_VERTICAL : ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM, content,
|
||||
GetNumberTextSize(), numPenWidth, numColor );
|
||||
return;
|
||||
}
|
||||
|
||||
int textSize = GetNumberTextSize();
|
||||
int lineSpacing = KiROUND( textSize * 1.3 );
|
||||
const KIFONT::METRICS& metrics = GetFontMetrics();
|
||||
|
||||
// Measure line widths for brace spacing
|
||||
int maxLineWidth = 0;
|
||||
for( const wxString& rawLine : lines )
|
||||
{
|
||||
wxString trimmed = rawLine; trimmed.Trim(true).Trim(false);
|
||||
VECTOR2I ext = font->StringBoundaryLimits( trimmed, VECTOR2D( textSize, textSize ),
|
||||
GetPenSizeForNormal( textSize ), false, false, metrics );
|
||||
if( ext.x > maxLineWidth )
|
||||
maxLineWidth = ext.x;
|
||||
}
|
||||
|
||||
// Determine starting position
|
||||
int startX = anchorX;
|
||||
int startY = anchorY;
|
||||
|
||||
if( vertical )
|
||||
{
|
||||
int totalWidth = ( (int) lines.size() - 1 ) * lineSpacing;
|
||||
startX -= totalWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
int totalHeight = ( (int) lines.size() - 1 ) * lineSpacing;
|
||||
startY -= totalHeight;
|
||||
}
|
||||
|
||||
for( size_t i = 0; i < lines.size(); ++i )
|
||||
{
|
||||
wxString l = lines[i]; l.Trim( true ).Trim( false );
|
||||
int lx = startX + ( vertical ? (int) i * lineSpacing : 0 );
|
||||
int ly = startY + ( vertical ? 0 : (int) i * lineSpacing );
|
||||
plotSimpleText( lx, ly, vertical ? ANGLE_VERTICAL : ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM, l,
|
||||
textSize, numPenWidth, numColor );
|
||||
}
|
||||
|
||||
// Now draw braces emulating SCH_PAINTER brace geometry
|
||||
auto plotBrace = [&]( const VECTOR2I& top, const VECTOR2I& bottom, bool leftOrTop, bool isVerticalText )
|
||||
{
|
||||
// Build 4 small segments approximating curly brace
|
||||
VECTOR2I mid = ( top + bottom ) / 2;
|
||||
int braceWidth = textSize / 3; // same scale as painter
|
||||
VECTOR2I p1 = top;
|
||||
VECTOR2I p5 = bottom;
|
||||
VECTOR2I p2 = top;
|
||||
VECTOR2I p3 = mid;
|
||||
VECTOR2I p4 = bottom;
|
||||
int offset = leftOrTop ? -braceWidth : braceWidth;
|
||||
|
||||
if( isVerticalText )
|
||||
{
|
||||
// Text vertical => brace extends in Y (horizontal brace lines across X axis set)
|
||||
// For vertical orientation we offset Y for p2/p3/p4
|
||||
p2.y += offset / 2;
|
||||
p3.y += offset;
|
||||
p4.y += offset / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Horizontal text => brace extends in X
|
||||
p2.x += offset / 2;
|
||||
p3.x += offset;
|
||||
p4.x += offset / 2;
|
||||
}
|
||||
|
||||
aPlotter->MoveTo( p1 ); aPlotter->FinishTo( p2 );
|
||||
aPlotter->MoveTo( p2 ); aPlotter->FinishTo( p3 );
|
||||
aPlotter->MoveTo( p3 ); aPlotter->FinishTo( p4 );
|
||||
aPlotter->MoveTo( p4 ); aPlotter->FinishTo( p5 );
|
||||
};
|
||||
|
||||
// Draw the text inside, but the pin numbers outside.
|
||||
aPlotter->SetCurrentLineWidth( numPenWidth );
|
||||
int braceWidth = textSize / 3;
|
||||
int extraHeight = textSize / 3; // extend beyond text block
|
||||
|
||||
if( vertical )
|
||||
{
|
||||
// Lines spaced horizontally, braces horizontal (above & below)
|
||||
int totalWidth = ( (int) lines.size() - 1 ) * lineSpacing;
|
||||
VECTOR2I braceStart( startX - 2 * extraHeight, anchorY );
|
||||
VECTOR2I braceEnd( startX + totalWidth + extraHeight, anchorY );
|
||||
int braceSpacing = maxLineWidth / 2 + braceWidth;
|
||||
|
||||
VECTOR2I topStart = braceStart; topStart.y -= braceSpacing;
|
||||
VECTOR2I topEnd = braceEnd; topEnd.y -= braceSpacing;
|
||||
VECTOR2I bottomStart = braceStart; bottomStart.y += braceSpacing;
|
||||
VECTOR2I bottomEnd = braceEnd; bottomEnd.y += braceSpacing;
|
||||
|
||||
plotBrace( topStart, topEnd, true, true ); // leftOrTop=true
|
||||
plotBrace( bottomStart, bottomEnd, false, true );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Lines spaced vertically, braces vertical (left & right)
|
||||
int totalHeight = ( (int) lines.size() - 1 ) * lineSpacing;
|
||||
VECTOR2I braceStart( anchorX, startY - 2 * extraHeight );
|
||||
VECTOR2I braceEnd( anchorX, startY + totalHeight + extraHeight );
|
||||
int braceSpacing = maxLineWidth / 2 + braceWidth;
|
||||
|
||||
VECTOR2I leftTop = braceStart; leftTop.x -= braceSpacing;
|
||||
VECTOR2I leftBot = braceEnd; leftBot.x -= braceSpacing;
|
||||
VECTOR2I rightTop = braceStart; rightTop.x += braceSpacing;
|
||||
VECTOR2I rightBot = braceEnd; rightBot.x += braceSpacing;
|
||||
|
||||
plotBrace( leftTop, leftBot, true, false );
|
||||
plotBrace( rightTop, rightBot, false, false );
|
||||
}
|
||||
};
|
||||
|
||||
// Logic largely mirrors original single-line placement but calls multi-line path for numbers
|
||||
if( aTextInside )
|
||||
{
|
||||
if( ( aPinOrient == PIN_ORIENTATION::PIN_LEFT )
|
||||
|| ( aPinOrient == PIN_ORIENTATION::PIN_RIGHT ) ) // It's a horizontal line.
|
||||
if( ( aPinOrient == PIN_ORIENTATION::PIN_LEFT ) || ( aPinOrient == PIN_ORIENTATION::PIN_RIGHT ) )
|
||||
{
|
||||
if( aDrawPinName )
|
||||
{
|
||||
if( aPinOrient == PIN_ORIENTATION::PIN_RIGHT )
|
||||
{
|
||||
plotName( x1 + aTextInside, y1, ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_LEFT, GR_TEXT_V_ALIGN_CENTER );
|
||||
plotSimpleText( x1 + aTextInside, y1, ANGLE_HORIZONTAL, GR_TEXT_H_ALIGN_LEFT,
|
||||
GR_TEXT_V_ALIGN_CENTER, name, GetNameTextSize(), namePenWidth, nameColor );
|
||||
else
|
||||
plotSimpleText( x1 - aTextInside, y1, ANGLE_HORIZONTAL, GR_TEXT_H_ALIGN_RIGHT,
|
||||
GR_TEXT_V_ALIGN_CENTER, name, GetNameTextSize(), namePenWidth, nameColor );
|
||||
}
|
||||
else // orient == PIN_LEFT
|
||||
{
|
||||
plotName( x1 - aTextInside, y1, ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_RIGHT, GR_TEXT_V_ALIGN_CENTER );
|
||||
}
|
||||
}
|
||||
|
||||
if( aDrawPinNum )
|
||||
{
|
||||
plotNum( ( x1 + aPinPos.x) / 2, y1 - num_offset, ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM );
|
||||
plotMultiLineWithBraces( ( x1 + aPinPos.x ) / 2, y1 - num_offset, false, true );
|
||||
}
|
||||
}
|
||||
else // It's a vertical line.
|
||||
else
|
||||
{
|
||||
if( aPinOrient == PIN_ORIENTATION::PIN_DOWN )
|
||||
{
|
||||
if( aDrawPinName )
|
||||
{
|
||||
plotName( x1, y1 + aTextInside, ANGLE_VERTICAL,
|
||||
GR_TEXT_H_ALIGN_RIGHT, GR_TEXT_V_ALIGN_CENTER );
|
||||
}
|
||||
|
||||
plotSimpleText( x1, y1 + aTextInside, ANGLE_VERTICAL, GR_TEXT_H_ALIGN_RIGHT,
|
||||
GR_TEXT_V_ALIGN_CENTER, name, GetNameTextSize(), namePenWidth, nameColor );
|
||||
if( aDrawPinNum )
|
||||
{
|
||||
plotNum( x1 - num_offset, ( y1 + aPinPos.y) / 2, ANGLE_VERTICAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM );
|
||||
plotMultiLineWithBraces( x1 - num_offset, ( y1 + aPinPos.y ) / 2, true, true );
|
||||
}
|
||||
}
|
||||
else /* PIN_UP */
|
||||
else // PIN_UP
|
||||
{
|
||||
if( aDrawPinName )
|
||||
{
|
||||
plotName( x1, y1 - aTextInside, ANGLE_VERTICAL,
|
||||
GR_TEXT_H_ALIGN_LEFT, GR_TEXT_V_ALIGN_CENTER );
|
||||
}
|
||||
|
||||
plotSimpleText( x1, y1 - aTextInside, ANGLE_VERTICAL, GR_TEXT_H_ALIGN_LEFT,
|
||||
GR_TEXT_V_ALIGN_CENTER, name, GetNameTextSize(), namePenWidth, nameColor );
|
||||
if( aDrawPinNum )
|
||||
{
|
||||
plotNum( x1 - num_offset, ( y1 + aPinPos.y) / 2, ANGLE_VERTICAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM );
|
||||
plotMultiLineWithBraces( x1 - num_offset, ( y1 + aPinPos.y ) / 2, true, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // Draw num & text pin outside.
|
||||
{
|
||||
if( ( aPinOrient == PIN_ORIENTATION::PIN_LEFT )
|
||||
|| ( aPinOrient == PIN_ORIENTATION::PIN_RIGHT ) )
|
||||
{
|
||||
// It's an horizontal line.
|
||||
if( aDrawPinName && aDrawPinNum )
|
||||
{
|
||||
plotName( ( x1 + aPinPos.x) / 2, y1 - name_offset, ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM );
|
||||
|
||||
plotNum( ( x1 + aPinPos.x) / 2, y1 + num_offset, ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_TOP );
|
||||
}
|
||||
else if( aDrawPinName )
|
||||
{
|
||||
plotName( ( x1 + aPinPos.x) / 2, y1 - name_offset, ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM );
|
||||
}
|
||||
else if( aDrawPinNum )
|
||||
{
|
||||
plotNum( ( x1 + aPinPos.x) / 2, y1 - name_offset, ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Its a vertical line.
|
||||
if( ( aPinOrient == PIN_ORIENTATION::PIN_LEFT ) || ( aPinOrient == PIN_ORIENTATION::PIN_RIGHT ) )
|
||||
{
|
||||
if( aDrawPinName && aDrawPinNum )
|
||||
{
|
||||
plotName( x1 - name_offset, ( y1 + aPinPos.y ) / 2, ANGLE_VERTICAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM );
|
||||
|
||||
plotNum( x1 + num_offset, ( y1 + aPinPos.y ) / 2, ANGLE_VERTICAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_TOP );
|
||||
plotSimpleText( ( x1 + aPinPos.x ) / 2, y1 - name_offset, ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM, name,
|
||||
GetNameTextSize(), namePenWidth, nameColor );
|
||||
plotMultiLineWithBraces( ( x1 + aPinPos.x ) / 2, y1 + num_offset, false, true );
|
||||
}
|
||||
else if( aDrawPinName )
|
||||
{
|
||||
plotName( x1 - name_offset, ( y1 + aPinPos.y ) / 2, ANGLE_VERTICAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM );
|
||||
plotSimpleText( ( x1 + aPinPos.x ) / 2, y1 - name_offset, ANGLE_HORIZONTAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM, name,
|
||||
GetNameTextSize(), namePenWidth, nameColor );
|
||||
}
|
||||
else if( aDrawPinNum )
|
||||
{
|
||||
plotNum( x1 - num_offset, ( y1 + aPinPos.y ) / 2, ANGLE_VERTICAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM );
|
||||
plotMultiLineWithBraces( ( x1 + aPinPos.x ) / 2, y1 - name_offset, false, true );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( aDrawPinName && aDrawPinNum )
|
||||
{
|
||||
plotSimpleText( x1 - name_offset, ( y1 + aPinPos.y ) / 2, ANGLE_VERTICAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM, name,
|
||||
GetNameTextSize(), namePenWidth, nameColor );
|
||||
plotMultiLineWithBraces( x1 + num_offset, ( y1 + aPinPos.y ) / 2, true, true );
|
||||
}
|
||||
else if( aDrawPinName )
|
||||
{
|
||||
plotSimpleText( x1 - name_offset, ( y1 + aPinPos.y ) / 2, ANGLE_VERTICAL,
|
||||
GR_TEXT_H_ALIGN_CENTER, GR_TEXT_V_ALIGN_BOTTOM, name,
|
||||
GetNameTextSize(), namePenWidth, nameColor );
|
||||
}
|
||||
else if( aDrawPinNum )
|
||||
{
|
||||
plotMultiLineWithBraces( x1 - num_offset, ( y1 + aPinPos.y ) / 2, true, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1256,6 +1441,14 @@ wxString SCH_PIN::GetDefaultNetName( const SCH_SHEET_PATH& aPath, bool aForceNoC
|
||||
|
||||
wxString libPinShownName = m_libPin ? m_libPin->GetShownName() : wxString( "??" );
|
||||
wxString libPinShownNumber = m_libPin ? m_libPin->GetShownNumber() : wxString( "??" );
|
||||
wxString effectivePadNumber = m_libPin ? m_libPin->GetEffectivePadNumber() : libPinShownNumber;
|
||||
|
||||
if( effectivePadNumber != libPinShownNumber )
|
||||
{
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "GetDefaultNetName: stacked pin shown='%s' -> using smallest logical='%s'",
|
||||
libPinShownNumber, effectivePadNumber ) );
|
||||
}
|
||||
|
||||
// Use timestamp for unannotated symbols
|
||||
if( symbol->GetRef( &aPath, false ).Last() == '?' )
|
||||
@ -1263,6 +1456,10 @@ wxString SCH_PIN::GetDefaultNetName( const SCH_SHEET_PATH& aPath, bool aForceNoC
|
||||
name << GetParentSymbol()->m_Uuid.AsString();
|
||||
|
||||
wxString libPinNumber = m_libPin ? m_libPin->GetNumber() : wxString( "??" );
|
||||
// Apply same smallest-logical substitution for unannotated symbols
|
||||
if( effectivePadNumber != libPinShownNumber && !effectivePadNumber.IsEmpty() )
|
||||
libPinNumber = effectivePadNumber;
|
||||
|
||||
name << "-Pad" << libPinNumber << ")";
|
||||
annotated = false;
|
||||
}
|
||||
@ -1274,7 +1471,10 @@ wxString SCH_PIN::GetDefaultNetName( const SCH_SHEET_PATH& aPath, bool aForceNoC
|
||||
name << "-" << EscapeString( libPinShownName, CTX_NETNAME );
|
||||
|
||||
if( unconnected || has_multiple )
|
||||
name << "-Pad" << EscapeString( libPinShownNumber, CTX_NETNAME );
|
||||
{
|
||||
// Use effective (possibly de-stacked) pad number in net name
|
||||
name << "-Pad" << EscapeString( effectivePadNumber, CTX_NETNAME );
|
||||
}
|
||||
|
||||
name << ")";
|
||||
}
|
||||
@ -1282,7 +1482,7 @@ wxString SCH_PIN::GetDefaultNetName( const SCH_SHEET_PATH& aPath, bool aForceNoC
|
||||
{
|
||||
// Pin numbers are unique, so we skip the unit token
|
||||
name << symbol->GetRef( &aPath, false );
|
||||
name << "-Pad" << EscapeString( libPinShownNumber, CTX_NETNAME ) << ")";
|
||||
name << "-Pad" << EscapeString( effectivePadNumber, CTX_NETNAME ) << ")";
|
||||
}
|
||||
|
||||
if( annotated )
|
||||
|
@ -24,6 +24,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <pin_type.h>
|
||||
#include <sch_item.h>
|
||||
@ -122,6 +123,19 @@ public:
|
||||
|
||||
const wxString& GetNumber() const { return m_number; }
|
||||
wxString GetShownNumber() const;
|
||||
std::vector<wxString> GetStackedPinNumbers( bool* aValid = nullptr ) const;
|
||||
/**
|
||||
* Return the smallest logical pin number if this pin uses stacked
|
||||
* notation and it is valid. Otherwise returns std::nullopt.
|
||||
*/
|
||||
std::optional<wxString> GetSmallestLogicalNumber() const;
|
||||
|
||||
/**
|
||||
* Return the pin number to be used for deterministic operations such as
|
||||
* auto‑generated net names. For stacked pins this is the smallest logical
|
||||
* number; otherwise it is the shown number.
|
||||
*/
|
||||
wxString GetEffectivePadNumber() const;
|
||||
void SetNumber( const wxString& aNumber );
|
||||
|
||||
int GetNameTextSize() const;
|
||||
|
@ -1128,7 +1128,7 @@ const SCH_PIN* SCH_SYMBOL::GetPin( const VECTOR2I& aPos ) const
|
||||
std::vector<SCH_PIN*> SCH_SYMBOL::GetLibPins() const
|
||||
{
|
||||
if( m_part )
|
||||
return m_part->GetPins( m_unit, m_bodyStyle );
|
||||
return m_part->GetGraphicalPins( m_unit, m_bodyStyle );
|
||||
|
||||
return std::vector<SCH_PIN*>();
|
||||
}
|
||||
@ -2530,11 +2530,11 @@ void SCH_SYMBOL::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS&
|
||||
|
||||
if( m_part )
|
||||
{
|
||||
std::vector<SCH_PIN*> libPins = m_part->GetPins( GetUnit(), GetBodyStyle() );
|
||||
std::vector<SCH_PIN*> libPins = m_part->GetGraphicalPins( GetUnit(), GetBodyStyle() );
|
||||
|
||||
// Copy the source so we can re-orient and translate it.
|
||||
LIB_SYMBOL tempSymbol( *m_part );
|
||||
std::vector<SCH_PIN*> tempPins = tempSymbol.GetPins( GetUnit(), GetBodyStyle() );
|
||||
std::vector<SCH_PIN*> tempPins = tempSymbol.GetGraphicalPins( GetUnit(), GetBodyStyle() );
|
||||
|
||||
// Copy the pin info from the symbol to the temp pins
|
||||
for( unsigned i = 0; i < tempPins.size(); ++ i )
|
||||
@ -2652,11 +2652,11 @@ void SCH_SYMBOL::PlotPins( PLOTTER* aPlotter ) const
|
||||
TRANSFORM savedTransform = renderSettings->m_Transform;
|
||||
renderSettings->m_Transform = GetTransform();
|
||||
|
||||
std::vector<SCH_PIN*> libPins = m_part->GetPins( GetUnit(), GetBodyStyle() );
|
||||
std::vector<SCH_PIN*> libPins = m_part->GetGraphicalPins( GetUnit(), GetBodyStyle() );
|
||||
|
||||
// Copy the source to stay const
|
||||
LIB_SYMBOL tempSymbol( *m_part );
|
||||
std::vector<SCH_PIN*> tempPins = tempSymbol.GetPins( GetUnit(), GetBodyStyle() );
|
||||
std::vector<SCH_PIN*> tempPins = tempSymbol.GetGraphicalPins( GetUnit(), GetBodyStyle() );
|
||||
SCH_PLOT_OPTS plotOpts;
|
||||
|
||||
// Copy the pin info from the symbol to the temp pins
|
||||
|
@ -935,6 +935,20 @@ TOOL_ACTION SCH_ACTIONS::pinTable( TOOL_ACTION_ARGS()
|
||||
.Tooltip( _( "Displays pin table for bulk editing of pins" ) )
|
||||
.Icon( BITMAPS::pin_table ) );
|
||||
|
||||
TOOL_ACTION SCH_ACTIONS::convertStackedPins( TOOL_ACTION_ARGS()
|
||||
.Name( "eeschema.InteractiveEdit.convertStackedPins" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Convert Stacked Pins" ) )
|
||||
.Tooltip( _( "Convert multiple pins at the same location to a single pin with stacked notation" ) )
|
||||
.Icon( BITMAPS::pin ) );
|
||||
|
||||
TOOL_ACTION SCH_ACTIONS::explodeStackedPin( TOOL_ACTION_ARGS()
|
||||
.Name( "eeschema.InteractiveEdit.explodeStackedPin" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Explode Stacked Pin" ) )
|
||||
.Tooltip( _( "Convert a pin with stacked notation to multiple individual pins" ) )
|
||||
.Icon( BITMAPS::pin ) );
|
||||
|
||||
TOOL_ACTION SCH_ACTIONS::breakWire( TOOL_ACTION_ARGS()
|
||||
.Name( "eeschema.InteractiveEdit.breakWire" )
|
||||
.Scope( AS_GLOBAL )
|
||||
|
@ -148,6 +148,8 @@ public:
|
||||
static TOOL_ACTION editSymbolLibraryLinks;
|
||||
static TOOL_ACTION symbolProperties;
|
||||
static TOOL_ACTION pinTable;
|
||||
static TOOL_ACTION convertStackedPins;
|
||||
static TOOL_ACTION explodeStackedPin;
|
||||
static TOOL_ACTION changeSymbols;
|
||||
static TOOL_ACTION updateSymbols;
|
||||
static TOOL_ACTION changeSymbol;
|
||||
|
@ -658,7 +658,7 @@ private:
|
||||
{
|
||||
std::vector<SCH_PIN*> pins;
|
||||
|
||||
for( SCH_PIN* pin : aSymbol.GetPins( aUnit, 0 ) )
|
||||
for( SCH_PIN* pin : aSymbol.GetGraphicalPins( aUnit, 0 ) )
|
||||
{
|
||||
// Figure out if the pin "connects" to the line
|
||||
const VECTOR2I pinRootPos = pin->GetPinRoot();
|
||||
|
@ -99,6 +99,70 @@ bool SYMBOL_EDITOR_EDIT_TOOL::Init()
|
||||
SCH_TABLECELL_T,
|
||||
} );
|
||||
|
||||
const auto canConvertStackedPins =
|
||||
[&]( const SELECTION& sel )
|
||||
{
|
||||
// If multiple pins are selected, check they are all at same location
|
||||
if( sel.Size() >= 2 )
|
||||
{
|
||||
std::vector<SCH_PIN*> pins;
|
||||
for( EDA_ITEM* item : sel )
|
||||
{
|
||||
if( item->Type() != SCH_PIN_T )
|
||||
return false;
|
||||
pins.push_back( static_cast<SCH_PIN*>( item ) );
|
||||
}
|
||||
|
||||
// Check that all pins are at the same location
|
||||
VECTOR2I pos = pins[0]->GetPosition();
|
||||
for( size_t i = 1; i < pins.size(); ++i )
|
||||
{
|
||||
if( pins[i]->GetPosition() != pos )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// If single pin is selected, check if there are other pins at same location
|
||||
if( sel.Size() == 1 && sel.Front()->Type() == SCH_PIN_T )
|
||||
{
|
||||
SCH_PIN* selectedPin = static_cast<SCH_PIN*>( sel.Front() );
|
||||
VECTOR2I pos = selectedPin->GetPosition();
|
||||
|
||||
// Get the symbol and check for other pins at same location
|
||||
LIB_SYMBOL* symbol = m_frame->GetCurSymbol();
|
||||
if( !symbol )
|
||||
return false;
|
||||
|
||||
int coLocatedCount = 0;
|
||||
|
||||
for( SCH_PIN* pin : symbol->GetPins() )
|
||||
{
|
||||
if( pin->GetPosition() == pos )
|
||||
{
|
||||
coLocatedCount++;
|
||||
|
||||
if( coLocatedCount >= 2 )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto canExplodeStackedPin =
|
||||
[&]( const SELECTION& sel )
|
||||
{
|
||||
if( sel.Size() != 1 || sel.Front()->Type() != SCH_PIN_T )
|
||||
return false;
|
||||
|
||||
SCH_PIN* pin = static_cast<SCH_PIN*>( sel.Front() );
|
||||
bool isValid;
|
||||
std::vector<wxString> stackedNumbers = pin->GetStackedPinNumbers( &isValid );
|
||||
return isValid && stackedNumbers.size() > 1;
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
// Add edit actions to the move tool menu
|
||||
if( moveTool )
|
||||
@ -148,6 +212,10 @@ bool SYMBOL_EDITOR_EDIT_TOOL::Init()
|
||||
selToolMenu.AddItem( SCH_ACTIONS::swap, canEdit && SELECTION_CONDITIONS::MoreThan( 1 ), 200 );
|
||||
selToolMenu.AddItem( SCH_ACTIONS::properties, canEdit && SCH_CONDITIONS::Count( 1 ), 200 );
|
||||
|
||||
selToolMenu.AddSeparator( 250 );
|
||||
selToolMenu.AddItem( SCH_ACTIONS::convertStackedPins, canEdit && canConvertStackedPins, 250 );
|
||||
selToolMenu.AddItem( SCH_ACTIONS::explodeStackedPin, canEdit && canExplodeStackedPin, 250 );
|
||||
|
||||
selToolMenu.AddSeparator( 300 );
|
||||
selToolMenu.AddItem( ACTIONS::cut, SCH_CONDITIONS::IdleSelection, 300 );
|
||||
selToolMenu.AddItem( ACTIONS::copy, SCH_CONDITIONS::IdleSelection, 300 );
|
||||
@ -712,6 +780,330 @@ int SYMBOL_EDITOR_EDIT_TOOL::PinTable( const TOOL_EVENT& aEvent )
|
||||
}
|
||||
|
||||
|
||||
int SYMBOL_EDITOR_EDIT_TOOL::ConvertStackedPins( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
SCH_COMMIT commit( m_frame );
|
||||
LIB_SYMBOL* symbol = m_frame->GetCurSymbol();
|
||||
|
||||
if( !symbol )
|
||||
return 0;
|
||||
|
||||
SCH_SELECTION_TOOL* selTool = m_toolMgr->GetTool<SCH_SELECTION_TOOL>();
|
||||
wxCHECK( selTool, -1 );
|
||||
|
||||
SCH_SELECTION& selection = selTool->GetSelection();
|
||||
|
||||
// Collect pins to convert - accept pins with any number format
|
||||
std::vector<SCH_PIN*> pinsToConvert;
|
||||
|
||||
if( selection.Size() == 1 && selection.Front()->Type() == SCH_PIN_T )
|
||||
{
|
||||
// Single pin selected - find all pins at the same location
|
||||
SCH_PIN* selectedPin = static_cast<SCH_PIN*>( selection.Front() );
|
||||
VECTOR2I pos = selectedPin->GetPosition();
|
||||
|
||||
for( SCH_PIN* pin : symbol->GetPins() )
|
||||
{
|
||||
if( pin->GetPosition() == pos )
|
||||
pinsToConvert.push_back( pin );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multiple pins selected - use them directly, accepting any pin numbers
|
||||
for( EDA_ITEM* item : selection )
|
||||
{
|
||||
if( item->Type() == SCH_PIN_T )
|
||||
pinsToConvert.push_back( static_cast<SCH_PIN*>( item ) );
|
||||
}
|
||||
}
|
||||
|
||||
if( pinsToConvert.size() < 2 )
|
||||
{
|
||||
m_frame->ShowInfoBarError( _( "At least two pins are needed to convert to stacked pins" ) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check that all pins are at the same location
|
||||
VECTOR2I pos = pinsToConvert[0]->GetPosition();
|
||||
for( size_t i = 1; i < pinsToConvert.size(); ++i )
|
||||
{
|
||||
if( pinsToConvert[i]->GetPosition() != pos )
|
||||
{
|
||||
m_frame->ShowInfoBarError( _( "All pins must be at the same location" ) );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
commit.Modify( symbol, m_frame->GetScreen() );
|
||||
|
||||
// Clear selection before modifying pins, like the Delete command does
|
||||
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
||||
|
||||
// Sort pins for consistent ordering - handle arbitrary pin number formats
|
||||
std::sort( pinsToConvert.begin(), pinsToConvert.end(),
|
||||
[]( SCH_PIN* a, SCH_PIN* b )
|
||||
{
|
||||
wxString numA = a->GetNumber();
|
||||
wxString numB = b->GetNumber();
|
||||
|
||||
// Try to convert to integers for proper numeric sorting
|
||||
long longA, longB;
|
||||
bool aIsNumeric = numA.ToLong( &longA );
|
||||
bool bIsNumeric = numB.ToLong( &longB );
|
||||
|
||||
// Both are purely numeric - sort numerically
|
||||
if( aIsNumeric && bIsNumeric )
|
||||
return longA < longB;
|
||||
|
||||
// Mixed numeric/non-numeric - numeric pins come first
|
||||
if( aIsNumeric && !bIsNumeric )
|
||||
return true;
|
||||
if( !aIsNumeric && bIsNumeric )
|
||||
return false;
|
||||
|
||||
// Both non-numeric or mixed alphanumeric - use lexicographic sorting
|
||||
return numA < numB;
|
||||
});
|
||||
|
||||
// Build the stacked notation string with range collapsing
|
||||
wxString stackedNotation = wxT("[");
|
||||
|
||||
// Helper function to collapse consecutive numbers into ranges - handles arbitrary pin formats
|
||||
auto collapseRanges = [&]() -> wxString
|
||||
{
|
||||
if( pinsToConvert.empty() )
|
||||
return wxT("");
|
||||
|
||||
wxString result;
|
||||
|
||||
// Group pins by their alphanumeric prefix for range collapsing
|
||||
std::map<wxString, std::vector<long>> prefixGroups;
|
||||
std::vector<wxString> nonNumericPins;
|
||||
|
||||
// Parse each pin number to separate prefix from numeric suffix
|
||||
for( SCH_PIN* pin : pinsToConvert )
|
||||
{
|
||||
wxString pinNumber = pin->GetNumber();
|
||||
|
||||
// Skip empty pin numbers (shouldn't happen, but be defensive)
|
||||
if( pinNumber.IsEmpty() )
|
||||
{
|
||||
nonNumericPins.push_back( wxT("(empty)") );
|
||||
continue;
|
||||
}
|
||||
|
||||
wxString prefix;
|
||||
wxString numericPart;
|
||||
|
||||
// Find where numeric part starts (scan from end)
|
||||
size_t numStart = pinNumber.length();
|
||||
for( int i = pinNumber.length() - 1; i >= 0; i-- )
|
||||
{
|
||||
if( !wxIsdigit( pinNumber[i] ) )
|
||||
{
|
||||
numStart = i + 1;
|
||||
break;
|
||||
}
|
||||
if( i == 0 ) // All digits
|
||||
numStart = 0;
|
||||
}
|
||||
|
||||
if( numStart < pinNumber.length() ) // Has numeric suffix
|
||||
{
|
||||
prefix = pinNumber.Left( numStart );
|
||||
numericPart = pinNumber.Mid( numStart );
|
||||
|
||||
long numValue;
|
||||
if( numericPart.ToLong( &numValue ) && numValue >= 0 ) // Valid non-negative number
|
||||
{
|
||||
prefixGroups[prefix].push_back( numValue );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Numeric part couldn't be parsed or is negative - treat as non-numeric
|
||||
nonNumericPins.push_back( pinNumber );
|
||||
}
|
||||
}
|
||||
else // No numeric suffix - consolidate as individual value
|
||||
{
|
||||
nonNumericPins.push_back( pinNumber );
|
||||
}
|
||||
}
|
||||
|
||||
// Process each prefix group
|
||||
for( auto& [prefix, numbers] : prefixGroups )
|
||||
{
|
||||
if( !result.IsEmpty() )
|
||||
result += wxT(",");
|
||||
|
||||
// Sort numeric values for this prefix
|
||||
std::sort( numbers.begin(), numbers.end() );
|
||||
|
||||
// Collapse consecutive ranges within this prefix
|
||||
size_t i = 0;
|
||||
while( i < numbers.size() )
|
||||
{
|
||||
if( i > 0 ) // Not first number in this prefix group
|
||||
result += wxT(",");
|
||||
|
||||
long start = numbers[i];
|
||||
long end = start;
|
||||
|
||||
// Find the end of consecutive sequence
|
||||
while( i + 1 < numbers.size() && numbers[i + 1] == numbers[i] + 1 )
|
||||
{
|
||||
i++;
|
||||
end = numbers[i];
|
||||
}
|
||||
|
||||
// Add range or single number with prefix
|
||||
if( end > start + 1 ) // Range of 3+ numbers
|
||||
result += wxString::Format( wxT("%s%ld-%s%ld"), prefix, start, prefix, end );
|
||||
else if( end == start + 1 ) // Two consecutive numbers
|
||||
result += wxString::Format( wxT("%s%ld,%s%ld"), prefix, start, prefix, end );
|
||||
else // Single number
|
||||
result += wxString::Format( wxT("%s%ld"), prefix, start );
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add non-numeric pin numbers as individual comma-separated values
|
||||
for( const wxString& nonNum : nonNumericPins )
|
||||
{
|
||||
if( !result.IsEmpty() )
|
||||
result += wxT(",");
|
||||
result += nonNum;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
stackedNotation += collapseRanges();
|
||||
stackedNotation += wxT("]");
|
||||
|
||||
// Keep the first pin and give it the stacked notation
|
||||
SCH_PIN* masterPin = pinsToConvert[0];
|
||||
masterPin->SetNumber( stackedNotation );
|
||||
|
||||
// Log information about pins being removed before we remove them
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "Converting %zu pins to stacked notation '%s'",
|
||||
pinsToConvert.size(), stackedNotation ) );
|
||||
|
||||
// Remove all other pins from the symbol that were consolidated into the stacked notation
|
||||
// Collect pins to remove first, then remove them all at once like the Delete command
|
||||
std::vector<SCH_PIN*> pinsToRemove;
|
||||
for( size_t i = 1; i < pinsToConvert.size(); ++i )
|
||||
{
|
||||
SCH_PIN* pinToRemove = pinsToConvert[i];
|
||||
|
||||
// Log the pin before removing it
|
||||
wxLogTrace( "KICAD_STACKED_PINS",
|
||||
wxString::Format( "Will remove pin '%s' at position (%d, %d)",
|
||||
pinToRemove->GetNumber(),
|
||||
pinToRemove->GetPosition().x,
|
||||
pinToRemove->GetPosition().y ) );
|
||||
|
||||
pinsToRemove.push_back( pinToRemove );
|
||||
}
|
||||
|
||||
// Remove all pins at once, like the Delete command does
|
||||
for( SCH_PIN* pin : pinsToRemove )
|
||||
{
|
||||
symbol->RemoveDrawItem( pin );
|
||||
}
|
||||
|
||||
commit.Push( wxString::Format( _( "Convert %zu Stacked Pins to '%s'" ),
|
||||
pinsToConvert.size(), stackedNotation ) );
|
||||
m_frame->RebuildView();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int SYMBOL_EDITOR_EDIT_TOOL::ExplodeStackedPin( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
SCH_COMMIT commit( m_frame );
|
||||
LIB_SYMBOL* symbol = m_frame->GetCurSymbol();
|
||||
|
||||
if( !symbol )
|
||||
return 0;
|
||||
|
||||
SCH_SELECTION_TOOL* selTool = m_toolMgr->GetTool<SCH_SELECTION_TOOL>();
|
||||
wxCHECK( selTool, -1 );
|
||||
|
||||
SCH_SELECTION& selection = selTool->GetSelection();
|
||||
|
||||
if( selection.GetSize() != 1 || selection.Front()->Type() != SCH_PIN_T )
|
||||
{
|
||||
m_frame->ShowInfoBarError( _( "Select a single pin with stacked notation to explode" ) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
SCH_PIN* pin = static_cast<SCH_PIN*>( selection.Front() );
|
||||
|
||||
// Check if the pin has stacked notation
|
||||
bool isValid;
|
||||
std::vector<wxString> stackedNumbers = pin->GetStackedPinNumbers( &isValid );
|
||||
|
||||
if( !isValid || stackedNumbers.size() <= 1 )
|
||||
{
|
||||
m_frame->ShowInfoBarError( _( "Selected pin does not have valid stacked notation" ) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
commit.Modify( symbol, m_frame->GetScreen() );
|
||||
|
||||
// Clear selection before modifying pins
|
||||
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
||||
|
||||
// Sort the stacked numbers to find the smallest one
|
||||
std::sort( stackedNumbers.begin(), stackedNumbers.end(),
|
||||
[]( const wxString& a, const wxString& b )
|
||||
{
|
||||
// Try to convert to integers for proper numeric sorting
|
||||
long numA, numB;
|
||||
if( a.ToLong( &numA ) && b.ToLong( &numB ) )
|
||||
return numA < numB;
|
||||
|
||||
// Fall back to string comparison if not numeric
|
||||
return a < b;
|
||||
});
|
||||
|
||||
// Change the original pin to use the first (smallest) number and make it visible
|
||||
pin->SetNumber( stackedNumbers[0] );
|
||||
pin->SetVisible( true );
|
||||
|
||||
// Create additional pins for the remaining numbers and make them invisible
|
||||
for( size_t i = 1; i < stackedNumbers.size(); ++i )
|
||||
{
|
||||
SCH_PIN* newPin = new SCH_PIN( symbol );
|
||||
|
||||
// Copy all properties from the original pin
|
||||
newPin->SetPosition( pin->GetPosition() );
|
||||
newPin->SetOrientation( pin->GetOrientation() );
|
||||
newPin->SetShape( pin->GetShape() );
|
||||
newPin->SetLength( pin->GetLength() );
|
||||
newPin->SetType( pin->GetType() );
|
||||
newPin->SetName( pin->GetName() );
|
||||
newPin->SetNumber( stackedNumbers[i] );
|
||||
newPin->SetNameTextSize( pin->GetNameTextSize() );
|
||||
newPin->SetNumberTextSize( pin->GetNumberTextSize() );
|
||||
newPin->SetUnit( pin->GetUnit() );
|
||||
newPin->SetBodyStyle( pin->GetBodyStyle() );
|
||||
newPin->SetVisible( false ); // Make all other pins invisible
|
||||
|
||||
// Add the new pin to the symbol
|
||||
symbol->AddDrawItem( newPin );
|
||||
}
|
||||
|
||||
commit.Push( _( "Explode Stacked Pin" ) );
|
||||
m_frame->RebuildView();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int SYMBOL_EDITOR_EDIT_TOOL::UpdateSymbolFields( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
LIB_SYMBOL* symbol = m_frame->GetCurSymbol();
|
||||
@ -1021,6 +1413,8 @@ void SYMBOL_EDITOR_EDIT_TOOL::setTransitions()
|
||||
Go( &SYMBOL_EDITOR_EDIT_TOOL::Properties, SCH_ACTIONS::properties.MakeEvent() );
|
||||
Go( &SYMBOL_EDITOR_EDIT_TOOL::Properties, SCH_ACTIONS::symbolProperties.MakeEvent() );
|
||||
Go( &SYMBOL_EDITOR_EDIT_TOOL::PinTable, SCH_ACTIONS::pinTable.MakeEvent() );
|
||||
Go( &SYMBOL_EDITOR_EDIT_TOOL::ConvertStackedPins, SCH_ACTIONS::convertStackedPins.MakeEvent() );
|
||||
Go( &SYMBOL_EDITOR_EDIT_TOOL::ExplodeStackedPin, SCH_ACTIONS::explodeStackedPin.MakeEvent() );
|
||||
Go( &SYMBOL_EDITOR_EDIT_TOOL::UpdateSymbolFields, SCH_ACTIONS::updateSymbolFields.MakeEvent() );
|
||||
// clang-format on
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ public:
|
||||
|
||||
int Properties( const TOOL_EVENT& aEvent );
|
||||
int PinTable( const TOOL_EVENT& aEvent );
|
||||
int ConvertStackedPins( const TOOL_EVENT& aEvent );
|
||||
int ExplodeStackedPin( const TOOL_EVENT& aEvent );
|
||||
int UpdateSymbolFields( const TOOL_EVENT& aEvent );
|
||||
|
||||
int Undo( const TOOL_EVENT& aEvent );
|
||||
|
@ -632,7 +632,7 @@ void PANEL_SYMBOL_CHOOSER::populateFootprintSelector( LIB_ID const& aLibId )
|
||||
|
||||
if( symbol != nullptr )
|
||||
{
|
||||
int pinCount = symbol->GetPins( 0 /* all units */, 1 /* single bodyStyle */ ).size();
|
||||
int pinCount = symbol->GetGraphicalPins( 0 /* all units */, 1 /* single bodyStyle */ ).size();
|
||||
SCH_FIELD* fp_field = symbol->GetField( FIELD_T::FOOTPRINT );
|
||||
wxString fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
|
||||
|
||||
|
@ -476,5 +476,23 @@ KICOMMON_API wxString From_UTF8( const char* cstring );
|
||||
*/
|
||||
KICOMMON_API wxString NormalizeFileUri( const wxString& aFileUri );
|
||||
|
||||
/**
|
||||
* Expand stacked pin notation like [1,2,3], [1-4], [A1-A4], or [AA1-AA3,AB4,CD12-CD14]
|
||||
* into individual pin numbers, supporting both numeric and alphanumeric pin prefixes.
|
||||
*
|
||||
* Examples:
|
||||
* "[1,2,3]" -> {"1", "2", "3"}
|
||||
* "[1-4]" -> {"1", "2", "3", "4"}
|
||||
* "[A1-A3]" -> {"A1", "A2", "A3"}
|
||||
* "[AA1-AA3,AB4]" -> {"AA1", "AA2", "AA3", "AB4"}
|
||||
* "5" -> {"5"} (non-bracketed pins returned as-is)
|
||||
*
|
||||
* @param aPinName is the pin name to expand (may or may not use stacked notation)
|
||||
* @param aValid is optionally set to indicate whether the notation was valid
|
||||
* @return vector of individual pin numbers
|
||||
*/
|
||||
KICOMMON_API std::vector<wxString> ExpandStackedPinNotation( const wxString& aPinName,
|
||||
bool* aValid = nullptr );
|
||||
|
||||
|
||||
#endif // STRING_UTILS_H
|
||||
|
@ -469,6 +469,8 @@ void FOOTPRINT_CHOOSER_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail )
|
||||
{
|
||||
case MAIL_SYMBOL_NETLIST:
|
||||
{
|
||||
wxLogTrace( "FOOTPRINT_CHOOSER", wxS( "MAIL_SYMBOL_NETLIST received: size=%zu" ),
|
||||
payload.size() );
|
||||
wxSizer* filtersSizer = m_chooserPanel->GetFiltersSizer();
|
||||
wxWindow* filtersWindow = filtersSizer->GetContainingWindow();
|
||||
wxString msg;
|
||||
@ -486,10 +488,24 @@ void FOOTPRINT_CHOOSER_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail )
|
||||
|
||||
if( strings.size() >= 1 && !strings[0].empty() )
|
||||
{
|
||||
for( const wxString& pin : wxSplit( strings[0], '\t' ) )
|
||||
wxArrayString tokens = wxSplit( strings[0], '\t' );
|
||||
|
||||
wxLogTrace( "FOOTPRINT_CHOOSER", wxS( "First line entries=%u" ), (unsigned) tokens.size() );
|
||||
|
||||
for( const wxString& pin : tokens )
|
||||
pinNames[ pin.BeforeFirst( ' ' ) ] = pin.AfterFirst( ' ' );
|
||||
|
||||
m_pinCount = (int) pinNames.size();
|
||||
|
||||
wxString pinList;
|
||||
for( const auto& kv : pinNames )
|
||||
{
|
||||
if( !pinList.IsEmpty() )
|
||||
pinList << wxS( "," );
|
||||
pinList << kv.first;
|
||||
}
|
||||
|
||||
wxLogTrace( "FOOTPRINT_CHOOSER", wxS( "Parsed pins=%d -> [%s]" ), m_pinCount, pinList );
|
||||
}
|
||||
|
||||
if( strings.size() >= 2 && !strings[1].empty() )
|
||||
@ -532,6 +548,7 @@ void FOOTPRINT_CHOOSER_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail )
|
||||
if( m_pinCount > 0 )
|
||||
{
|
||||
msg.Printf( _( "Filter by pin count (%d)" ), m_pinCount );
|
||||
wxLogTrace( "FOOTPRINT_CHOOSER", wxS( "Pin-count label: %s" ), msg );
|
||||
|
||||
if( !m_filterByPinCount )
|
||||
{
|
||||
@ -558,6 +575,8 @@ void FOOTPRINT_CHOOSER_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail )
|
||||
}
|
||||
|
||||
m_chooserPanel->GetViewerPanel()->SetPinFunctions( pinNames );
|
||||
wxLogTrace( "FOOTPRINT_CHOOSER", wxS( "SetPinFunctions called with %zu entries" ),
|
||||
pinNames.size() );
|
||||
|
||||
// Save the wxFormBuilder size of the dialog...
|
||||
if( s_dialogRect.GetSize().x == 0 || s_dialogRect.GetSize().y == 0 )
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include <netlist_reader/pcb_netlist.h>
|
||||
#include <connectivity/connectivity_data.h>
|
||||
#include <reporter.h>
|
||||
#include <wx/log.h>
|
||||
|
||||
#include "board_netlist_updater.h"
|
||||
|
||||
@ -965,14 +966,28 @@ bool BOARD_NETLIST_UPDATER::updateComponentPadConnections( FOOTPRINT* aFootprint
|
||||
{
|
||||
const COMPONENT_NET& net = aNewComponent->GetNet( pad->GetNumber() );
|
||||
|
||||
wxLogTrace( wxT( "NETLIST_UPDATE" ),
|
||||
wxT( "Processing pad %s of component %s" ),
|
||||
pad->GetNumber(),
|
||||
aNewComponent->GetReference() );
|
||||
|
||||
wxString pinFunction;
|
||||
wxString pinType;
|
||||
|
||||
if( net.IsValid() ) // i.e. the pad has a name
|
||||
{
|
||||
wxLogTrace( wxT( "NETLIST_UPDATE" ),
|
||||
wxT( " Found valid net: %s" ),
|
||||
net.GetNetName() );
|
||||
pinFunction = net.GetPinFunction();
|
||||
pinType = net.GetPinType();
|
||||
}
|
||||
else
|
||||
{
|
||||
wxLogTrace( wxT( "NETLIST_UPDATE" ),
|
||||
wxT( " No net found for pad %s" ),
|
||||
pad->GetNumber() );
|
||||
}
|
||||
|
||||
if( !m_isDryRun )
|
||||
{
|
||||
|
@ -104,6 +104,7 @@ void KICAD_NETLIST_PARSER::Parse()
|
||||
break;
|
||||
|
||||
case T_components: // The section comp starts here.
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "Parse: entering components section" ) );
|
||||
while( ( token = NextTok() ) != T_EOF )
|
||||
{
|
||||
if( token == T_RIGHT )
|
||||
@ -132,6 +133,7 @@ void KICAD_NETLIST_PARSER::Parse()
|
||||
break;
|
||||
|
||||
case T_nets: // The section nets starts here.
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "Parse: entering nets section" ) );
|
||||
while( ( token = NextTok() ) != T_EOF )
|
||||
{
|
||||
if( token == T_RIGHT )
|
||||
@ -146,6 +148,7 @@ void KICAD_NETLIST_PARSER::Parse()
|
||||
break;
|
||||
|
||||
case T_libparts: // The section libparts starts here.
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "Parse: entering libparts section" ) );
|
||||
while( ( token = NextTok() ) != T_EOF )
|
||||
{
|
||||
if( token == T_RIGHT )
|
||||
@ -384,6 +387,8 @@ void KICAD_NETLIST_PARSER::parseComponent()
|
||||
Expecting( "part, lib or description" );
|
||||
}
|
||||
}
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "parseComponent: ref='%s' libsource='%s:%s'" ),
|
||||
ref, library, name );
|
||||
break;
|
||||
|
||||
case T_property:
|
||||
@ -714,6 +719,7 @@ void KICAD_NETLIST_PARSER::parseLibPartList()
|
||||
int pinCount = 0;
|
||||
|
||||
// The last token read was libpart, so read the next token
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "parseLibPartList: begin libpart" ) );
|
||||
while( (token = NextTok() ) != T_RIGHT )
|
||||
{
|
||||
if( token == T_LEFT )
|
||||
@ -773,6 +779,8 @@ void KICAD_NETLIST_PARSER::parseLibPartList()
|
||||
break;
|
||||
|
||||
case T_pins:
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "parseLibPartList: entering pins for '%s:%s'" ),
|
||||
libName, libPartName );
|
||||
while( (token = NextTok() ) != T_RIGHT )
|
||||
{
|
||||
if( token == T_LEFT )
|
||||
@ -782,9 +790,13 @@ void KICAD_NETLIST_PARSER::parseLibPartList()
|
||||
Expecting( T_pin );
|
||||
|
||||
pinCount++;
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "parseLibPartList: pin #%d for '%s:%s'" ),
|
||||
pinCount, libName, libPartName );
|
||||
|
||||
skipCurrent();
|
||||
}
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "Parsed libpart '%s:%s' pins => pinCount=%d" ),
|
||||
libName, libPartName, pinCount );
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -795,6 +807,8 @@ void KICAD_NETLIST_PARSER::parseLibPartList()
|
||||
}
|
||||
|
||||
// Find all of the components that reference this component library part definition.
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "parseLibPartList: assigning pinCount=%d for libpart '%s:%s'" ),
|
||||
pinCount, libName, libPartName );
|
||||
for( unsigned i = 0; i < m_netlist->GetCount(); i++ )
|
||||
{
|
||||
component = m_netlist->GetComponent( i );
|
||||
@ -803,6 +817,8 @@ void KICAD_NETLIST_PARSER::parseLibPartList()
|
||||
{
|
||||
component->SetFootprintFilters( footprintFilters );
|
||||
component->SetPinCount( pinCount );
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "Assign pinCount=%d to component ref='%s' part='%s:%s'" ),
|
||||
pinCount, component->GetReference(), libName, libPartName );
|
||||
}
|
||||
|
||||
for( unsigned jj = 0; jj < aliases.GetCount(); jj++ )
|
||||
@ -811,6 +827,9 @@ void KICAD_NETLIST_PARSER::parseLibPartList()
|
||||
{
|
||||
component->SetFootprintFilters( footprintFilters );
|
||||
component->SetPinCount( pinCount );
|
||||
wxLogTrace( "CVPCB_PINCOUNT",
|
||||
wxT( "Assign pinCount=%d to component ref='%s' via alias='%s:%s'" ),
|
||||
pinCount, component->GetReference(), libName, aliases[jj] );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,8 @@
|
||||
#include <footprint.h>
|
||||
#include <richio.h>
|
||||
#include <string_utils.h>
|
||||
#include <wx/tokenzr.h>
|
||||
#include <wx/log.h>
|
||||
|
||||
|
||||
int COMPONENT_NET::Format( OUTPUTFORMATTER* aOut, int aNestLevel, int aCtl )
|
||||
@ -60,14 +62,56 @@ void COMPONENT::SetFootprint( FOOTPRINT* aFootprint )
|
||||
COMPONENT_NET COMPONENT::m_emptyNet;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const COMPONENT_NET& COMPONENT::GetNet( const wxString& aPinName ) const
|
||||
{
|
||||
wxLogTrace( wxT( "NETLIST_STACKED_PINS" ),
|
||||
wxT( "Looking for pin '%s' in component '%s'" ),
|
||||
aPinName, m_reference );
|
||||
|
||||
for( const COMPONENT_NET& net : m_nets )
|
||||
{
|
||||
wxLogTrace( wxT( "NETLIST_STACKED_PINS" ),
|
||||
wxT( " Checking net pin name '%s'" ),
|
||||
net.GetPinName() );
|
||||
|
||||
if( net.GetPinName() == aPinName )
|
||||
{
|
||||
wxLogTrace( wxT( "NETLIST_STACKED_PINS" ),
|
||||
wxT( " Found exact match for pin '%s'" ),
|
||||
aPinName );
|
||||
return net;
|
||||
}
|
||||
|
||||
// Check if this net's pin name is a stacked pin notation that expands to include aPinName
|
||||
std::vector<wxString> expandedPins = ExpandStackedPinNotation( net.GetPinName() );
|
||||
if( !expandedPins.empty() )
|
||||
{
|
||||
wxLogTrace( wxT( "NETLIST_STACKED_PINS" ),
|
||||
wxT( " Pin name '%s' expanded to %zu pins" ),
|
||||
net.GetPinName(), expandedPins.size() );
|
||||
|
||||
for( const wxString& expandedPin : expandedPins )
|
||||
{
|
||||
wxLogTrace( wxT( "NETLIST_STACKED_PINS" ),
|
||||
wxT( " Checking expanded pin '%s'" ),
|
||||
expandedPin );
|
||||
if( expandedPin == aPinName )
|
||||
{
|
||||
wxLogTrace( wxT( "NETLIST_STACKED_PINS" ),
|
||||
wxT( " Found match for pin '%s' in stacked notation '%s'" ),
|
||||
aPinName, net.GetPinName() );
|
||||
return net;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wxLogTrace( wxT( "NETLIST_STACKED_PINS" ),
|
||||
wxT( " No net found for pin '%s'" ),
|
||||
aPinName );
|
||||
return m_emptyNet;
|
||||
}
|
||||
|
||||
|
128
qa/data/eeschema/stacked_pin_nomenclature.kicad_sch
Normal file
128
qa/data/eeschema/stacked_pin_nomenclature.kicad_sch
Normal file
@ -0,0 +1,128 @@
|
||||
(kicad_sch (version 20240910) (generator eeschema)
|
||||
|
||||
(paper "A4")
|
||||
|
||||
(lib_symbols
|
||||
(symbol "Device:R"
|
||||
(pin_names (offset 0))
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(duplicate_pin_numbers_are_jumpers no)
|
||||
(property "Reference" "R1" (at 6.35 0 90)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Value" "R" (at 3.81 0 90)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Footprint" "" (at -1.778 0 90) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Datasheet" "" (at 0 0 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Description" "Resistor" (at 0 0 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "ki_keywords" "R res resistor" (at 0 0 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "ki_fp_filters" "R_*" (at 0 0 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(symbol "R_0_1"
|
||||
(rectangle (start -1.016 -2.54) (end 1.016 2.54)
|
||||
(stroke (width 0.254) (type default)) (fill (type none)) ) )
|
||||
(symbol "R_1_1"
|
||||
(pin passive line (at 0 6.35 270) (length 1.27)
|
||||
(name "" (effects (font (size 1.27 1.27))))
|
||||
(number "[1-5]" (effects (font (size 1.27 1.27)))) )
|
||||
(pin passive line (at 0 -8.636 90) (length 1.27)
|
||||
(name "" (effects (font (size 1.27 1.27))))
|
||||
(number "[6,7,9-11]" (effects (font (size 1.27 1.27)))) ) ) )
|
||||
|
||||
(symbol "power:GND"
|
||||
(power global)
|
||||
(pin_numbers (hide yes))
|
||||
(pin_names (offset 0) (hide yes))
|
||||
(exclude_from_sim no) (in_bom yes) (on_board yes)
|
||||
(duplicate_pin_numbers_are_jumpers no)
|
||||
(property "Reference" "#PWR" (at 0 -6.35 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Value" "GND" (at 0 -3.81 0)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Footprint" "" (at 0 0 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Datasheet" "" (at 0 0 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Description" "Power symbol creates a global label with name \"GND\" , ground"
|
||||
(at 0 0 0) (hide yes) (effects (font (size 1.27 1.27))) )
|
||||
(property "ki_keywords" "global power" (at 0 0 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(symbol "GND_0_1"
|
||||
(polyline (pts (xy 0 0) (xy 0 -1.27) (xy 1.27 -1.27) (xy 0 -2.54) (xy -1.27 -1.27) (xy 0 -1.27))
|
||||
(stroke (width 0) (type default)) (fill (type none)) ) )
|
||||
(symbol "GND_1_1"
|
||||
(pin power_in line (at 0 0 270) (length 0)
|
||||
(name "" (effects (font (size 1.27 1.27))))
|
||||
(number "1" (effects (font (size 1.27 1.27)))) ) ) )
|
||||
|
||||
(symbol "power:VCC"
|
||||
(power global)
|
||||
(pin_numbers (hide yes))
|
||||
(pin_names (offset 0) (hide yes))
|
||||
(exclude_from_sim no) (in_bom yes) (on_board yes)
|
||||
(duplicate_pin_numbers_are_jumpers no)
|
||||
(property "Reference" "#PWR" (at 0 -3.81 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Value" "VCC" (at 0 3.556 0)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Footprint" "" (at 0 0 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Datasheet" "" (at 0 0 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(property "Description" "Power symbol creates a global label with name \"VCC\""
|
||||
(at 0 0 0) (hide yes) (effects (font (size 1.27 1.27))) )
|
||||
(property "ki_keywords" "global power" (at 0 0 0) (hide yes)
|
||||
(effects (font (size 1.27 1.27))) )
|
||||
(symbol "VCC_0_1"
|
||||
(polyline (pts (xy -0.762 1.27) (xy 0 2.54)) (stroke (width 0) (type default)) (fill (type none)))
|
||||
(polyline (pts (xy 0 2.54) (xy 0.762 1.27)) (stroke (width 0) (type default)) (fill (type none)))
|
||||
(polyline (pts (xy 0 0) (xy 0 2.54)) (stroke (width 0) (type default)) (fill (type none))) )
|
||||
(symbol "VCC_1_1"
|
||||
(pin power_in line (at 0 0 90) (length 0)
|
||||
(name "" (effects (font (size 1.27 1.27))))
|
||||
(number "1" (effects (font (size 1.27 1.27)))) ) ) )
|
||||
)
|
||||
|
||||
(wire (pts (xy 134.62 83.82) (xy 134.62 76.2)) (stroke (width 0) (type default)))
|
||||
(wire (pts (xy 100.33 87.63) (xy 100.33 83.82)) (stroke (width 0) (type default)))
|
||||
(wire (pts (xy 100.33 83.82) (xy 109.22 83.82)) (stroke (width 0) (type default)))
|
||||
(wire (pts (xy 124.206 83.82) (xy 134.62 83.82)) (stroke (width 0) (type default)))
|
||||
|
||||
(symbol (lib_id "Device:R") (at 115.57 83.82 90) (unit 1) (body_style 1)
|
||||
(exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no) (fields_autoplaced yes)
|
||||
(uuid "2166cf4e-5b03-495b-af0a-be2718b61b04")
|
||||
(property "Reference" "R1" (at 115.57 77.47 90) (effects (font (size 1.27 1.27))))
|
||||
(property "Value" "R" (at 115.57 80.01 90) (effects (font (size 1.27 1.27))))
|
||||
(property "Footprint" "" (at 115.57 85.598 90) (hide yes) (effects (font (size 1.27 1.27))))
|
||||
(property "Datasheet" "" (at 115.57 83.82 0) (hide yes) (effects (font (size 1.27 1.27))))
|
||||
(property "Description" "Resistor" (at 115.57 83.82 0) (hide yes) (effects (font (size 1.27 1.27))))
|
||||
(pin "[6,7,9-11]" (uuid "f62cbf3f-56f0-413b-a710-93a48027efbb"))
|
||||
(pin "[1-5]" (uuid "e856ce98-c7c6-4066-97a8-60e95541ae81")) )
|
||||
|
||||
(symbol (lib_id "power:VCC") (at 134.62 76.2 0) (unit 1) (body_style 1)
|
||||
(exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no) (fields_autoplaced yes)
|
||||
(uuid "2b72c13a-dfcb-4980-a25b-daac640cdb9b")
|
||||
(property "Reference" "#PWR01" (at 134.62 80.01 0) (hide yes) (effects (font (size 1.27 1.27))))
|
||||
(property "Value" "VCC" (at 134.62 71.12 0) (effects (font (size 1.27 1.27))))
|
||||
(property "Footprint" "" (at 134.62 76.2 0) (hide yes) (effects (font (size 1.27 1.27))))
|
||||
(property "Datasheet" "" (at 134.62 76.2 0) (hide yes) (effects (font (size 1.27 1.27))))
|
||||
(property "Description" "Power symbol creates a global label with name \"VCC\""
|
||||
(at 134.62 76.2 0) (hide yes) (effects (font (size 1.27 1.27))))
|
||||
(pin "1" (uuid "ff7185fb-347f-48f5-bcf0-de5808edd725")) )
|
||||
|
||||
(symbol (lib_id "power:GND") (at 100.33 87.63 0) (unit 1) (body_style 1)
|
||||
(exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no) (fields_autoplaced yes)
|
||||
(uuid "4e64c428-4aed-4183-826f-2fa5009f4177")
|
||||
(property "Reference" "#PWR02" (at 100.33 93.98 0) (hide yes) (effects (font (size 1.27 1.27))))
|
||||
(property "Value" "GND" (at 100.33 92.71 0) (effects (font (size 1.27 1.27))))
|
||||
(property "Footprint" "" (at 100.33 87.63 0) (hide yes) (effects (font (size 1.27 1.27))))
|
||||
(property "Datasheet" "" (at 100.33 87.63 0) (hide yes) (effects (font (size 1.27 1.27))))
|
||||
(property "Description" "Power symbol creates a global label with name \"GND\" , ground"
|
||||
(at 100.33 87.63 0) (hide yes) (effects (font (size 1.27 1.27))))
|
||||
(pin "1" (uuid "b8c00030-efa1-4421-9af4-303142b1c17d")) )
|
||||
)
|
872
qa/data/pcbnew/stacked/stacked.kicad_pcb
Normal file
872
qa/data/pcbnew/stacked/stacked.kicad_pcb
Normal file
@ -0,0 +1,872 @@
|
||||
(kicad_pcb
|
||||
(version 20250901)
|
||||
(generator "pcbnew")
|
||||
(generator_version "9.99")
|
||||
(general
|
||||
(thickness 1.6)
|
||||
(legacy_teardrops no)
|
||||
)
|
||||
(paper "A4")
|
||||
(layers
|
||||
(0 "F.Cu" signal)
|
||||
(2 "B.Cu" signal)
|
||||
(9 "F.Adhes" user "F.Adhesive")
|
||||
(11 "B.Adhes" user "B.Adhesive")
|
||||
(13 "F.Paste" user)
|
||||
(15 "B.Paste" user)
|
||||
(5 "F.SilkS" user "F.Silkscreen")
|
||||
(7 "B.SilkS" user "B.Silkscreen")
|
||||
(1 "F.Mask" user)
|
||||
(3 "B.Mask" user)
|
||||
(17 "Dwgs.User" user "User.Drawings")
|
||||
(19 "Cmts.User" user "User.Comments")
|
||||
(21 "Eco1.User" user "User.Eco1")
|
||||
(23 "Eco2.User" user "User.Eco2")
|
||||
(25 "Edge.Cuts" user)
|
||||
(27 "Margin" user)
|
||||
(31 "F.CrtYd" user "F.Courtyard")
|
||||
(29 "B.CrtYd" user "B.Courtyard")
|
||||
(35 "F.Fab" user)
|
||||
(33 "B.Fab" user)
|
||||
(39 "User.1" user)
|
||||
(41 "User.2" user)
|
||||
(43 "User.3" user)
|
||||
(45 "User.4" user)
|
||||
)
|
||||
(setup
|
||||
(pad_to_mask_clearance 0)
|
||||
(allow_soldermask_bridges_in_footprints no)
|
||||
(tenting
|
||||
(front yes)
|
||||
(back yes)
|
||||
)
|
||||
(covering
|
||||
(front no)
|
||||
(back no)
|
||||
)
|
||||
(plugging
|
||||
(front no)
|
||||
(back no)
|
||||
)
|
||||
(capping no)
|
||||
(filling no)
|
||||
(pcbplotparams
|
||||
(layerselection 0x00000000_00000000_55555555_5755f5ff)
|
||||
(plot_on_all_layers_selection 0x00000000_00000000_00000000_00000000)
|
||||
(disableapertmacros no)
|
||||
(usegerberextensions no)
|
||||
(usegerberattributes yes)
|
||||
(usegerberadvancedattributes yes)
|
||||
(creategerberjobfile yes)
|
||||
(dashed_line_dash_ratio 12)
|
||||
(dashed_line_gap_ratio 3)
|
||||
(svgprecision 4)
|
||||
(plotframeref no)
|
||||
(mode 1)
|
||||
(useauxorigin no)
|
||||
(pdf_front_fp_property_popups yes)
|
||||
(pdf_back_fp_property_popups yes)
|
||||
(pdf_metadata yes)
|
||||
(pdf_single_document no)
|
||||
(dxfpolygonmode yes)
|
||||
(dxfimperialunits yes)
|
||||
(dxfusepcbnewfont yes)
|
||||
(psnegative no)
|
||||
(psa4output no)
|
||||
(plot_black_and_white yes)
|
||||
(sketchpadsonfab no)
|
||||
(plotpadnumbers no)
|
||||
(hidednponfab no)
|
||||
(sketchdnponfab yes)
|
||||
(crossoutdnponfab yes)
|
||||
(subtractmaskfromsilk no)
|
||||
(outputformat 1)
|
||||
(mirror no)
|
||||
(drillshape 1)
|
||||
(scaleselection 1)
|
||||
(outputdirectory "")
|
||||
)
|
||||
)
|
||||
(net 0 "")
|
||||
(net 1 "GND")
|
||||
(net 2 "Net-(R1-Pad6)")
|
||||
(net 3 "VCC")
|
||||
(footprint "Connector:Tag-Connect_TC2050-IDC-FP_2x05_P1.27mm_Vertical"
|
||||
(layer "F.Cu")
|
||||
(uuid "6be81952-937a-4957-9eb2-85721ef5e835")
|
||||
(at 99.15 82.8)
|
||||
(descr "Tag-Connect programming header; http://www.tag-connect.com/Materials/TC2050-IDC-430%20Datasheet.pdf")
|
||||
(tags "tag connect programming header pogo pins")
|
||||
(property "Reference" "R1"
|
||||
(at 0 5 0)
|
||||
(layer "F.SilkS")
|
||||
(uuid "a58bcfb3-ac5c-44e3-b078-f6fcbbff78fd")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "R"
|
||||
(at 0 -4.8 0)
|
||||
(layer "F.Fab")
|
||||
(uuid "e812b7b1-3211-42eb-ba2d-aa11bdc756e0")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(hide yes)
|
||||
(uuid "49f5b1e8-966f-4ec6-9eec-08f47e66d0f8")
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" "Resistor"
|
||||
(at 0 0 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(hide yes)
|
||||
(uuid "f75f79bb-0bac-443b-8a9e-c180b3d4a9f0")
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property ki_fp_filters "R_*")
|
||||
(path "/2166cf4e-5b03-495b-af0a-be2718b61b04")
|
||||
(sheetname "/")
|
||||
(sheetfile "stacked.kicad_sch")
|
||||
(attr exclude_from_pos_files)
|
||||
(duplicate_pad_numbers_are_jumpers no)
|
||||
(fp_line
|
||||
(start -3.175 1.27)
|
||||
(end -3.175 0.635)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "27aa3b0d-a092-4b0c-995a-8e3cec800dbe")
|
||||
)
|
||||
(fp_line
|
||||
(start -2.54 1.27)
|
||||
(end -3.175 1.27)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "bee2c633-f143-428a-9485-6a636fd6de5c")
|
||||
)
|
||||
(fp_line
|
||||
(start -5.5 -4.25)
|
||||
(end 4.75 -4.25)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "aae4190a-8a8d-4a1a-b456-2957c835b850")
|
||||
)
|
||||
(fp_line
|
||||
(start -5.5 4.25)
|
||||
(end -5.5 -4.25)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "921c2c67-e2cf-45e5-a409-21d7b43c6aa3")
|
||||
)
|
||||
(fp_line
|
||||
(start 4.75 -4.25)
|
||||
(end 4.75 4.25)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "f9324654-e75c-4d7f-adfb-f8bb0d60c1c9")
|
||||
)
|
||||
(fp_line
|
||||
(start 4.75 4.25)
|
||||
(end -5.5 4.25)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "bcca5d74-54fb-4c6c-a8dc-039d2882a8a4")
|
||||
)
|
||||
(fp_text user "KEEPOUT"
|
||||
(at 0 0 0)
|
||||
(layer "Cmts.User")
|
||||
(uuid "3f2c9b29-16de-427a-938d-6921987ed7d4")
|
||||
(effects
|
||||
(font
|
||||
(size 0.4 0.4)
|
||||
(thickness 0.07)
|
||||
)
|
||||
)
|
||||
)
|
||||
(fp_text user "${REFERENCE}"
|
||||
(at 0 0 0)
|
||||
(layer "F.Fab")
|
||||
(uuid "720463bd-308b-464f-83d1-706bd97b4b09")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pad "" np_thru_hole circle
|
||||
(at -3.81 -2.54)
|
||||
(size 2.3749 2.3749)
|
||||
(drill 2.3749)
|
||||
(layers "*.Cu" "*.Mask")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "e669af60-91a5-43ae-9c40-cb3349ad6cd9")
|
||||
)
|
||||
(pad "" np_thru_hole circle
|
||||
(at -3.81 0)
|
||||
(size 0.9906 0.9906)
|
||||
(drill 0.9906)
|
||||
(layers "*.Cu" "*.Mask")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "a25c573c-0aaa-4e39-8a8a-87edb26c8eba")
|
||||
)
|
||||
(pad "" np_thru_hole circle
|
||||
(at -3.81 2.54)
|
||||
(size 2.3749 2.3749)
|
||||
(drill 2.3749)
|
||||
(layers "*.Cu" "*.Mask")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "28203947-6c7f-4fda-8630-083dbbb2065b")
|
||||
)
|
||||
(pad "" np_thru_hole circle
|
||||
(at 1.905 -2.54)
|
||||
(size 2.3749 2.3749)
|
||||
(drill 2.3749)
|
||||
(layers "*.Cu" "*.Mask")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "d64457ca-c860-4e9e-8d02-773e7b597037")
|
||||
)
|
||||
(pad "" np_thru_hole circle
|
||||
(at 1.905 2.54)
|
||||
(size 2.3749 2.3749)
|
||||
(drill 2.3749)
|
||||
(layers "*.Cu" "*.Mask")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "ecc5a508-d2f3-487b-98b8-7a2095646b49")
|
||||
)
|
||||
(pad "" np_thru_hole circle
|
||||
(at 3.81 -1.016)
|
||||
(size 0.9906 0.9906)
|
||||
(drill 0.9906)
|
||||
(layers "*.Cu" "*.Mask")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "e35c5901-0bbb-48bd-bb2a-0e208b25c86d")
|
||||
)
|
||||
(pad "" np_thru_hole circle
|
||||
(at 3.81 1.016)
|
||||
(size 0.9906 0.9906)
|
||||
(drill 0.9906)
|
||||
(layers "*.Cu" "*.Mask")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "d17e6355-636f-4a2e-9a07-dac92e07dbd3")
|
||||
)
|
||||
(pad "1" connect circle
|
||||
(at -2.54 0.635)
|
||||
(size 0.7874 0.7874)
|
||||
(layers "F.Cu" "F.Mask")
|
||||
(net 1 "GND")
|
||||
(pinfunction "1")
|
||||
(pintype "passive")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "0d1d056c-2f36-4152-9a5c-117b6f43d4e3")
|
||||
)
|
||||
(pad "2" connect circle
|
||||
(at -1.27 0.635)
|
||||
(size 0.7874 0.7874)
|
||||
(layers "F.Cu" "F.Mask")
|
||||
(net 1 "GND")
|
||||
(pinfunction "2")
|
||||
(pintype "passive")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "7835c4d6-293f-470f-9732-986fc9802a90")
|
||||
)
|
||||
(pad "3" connect circle
|
||||
(at 0 0.635)
|
||||
(size 0.7874 0.7874)
|
||||
(layers "F.Cu" "F.Mask")
|
||||
(net 1 "GND")
|
||||
(pinfunction "3")
|
||||
(pintype "passive")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "ea592653-d244-4bb8-b4ed-ee92882d3777")
|
||||
)
|
||||
(pad "4" connect circle
|
||||
(at 1.27 0.635)
|
||||
(size 0.7874 0.7874)
|
||||
(layers "F.Cu" "F.Mask")
|
||||
(net 1 "GND")
|
||||
(pinfunction "4")
|
||||
(pintype "passive")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "5e78183f-956b-4f05-a129-15f4e5fe3a0c")
|
||||
)
|
||||
(pad "5" connect circle
|
||||
(at 2.54 0.635)
|
||||
(size 0.7874 0.7874)
|
||||
(layers "F.Cu" "F.Mask")
|
||||
(net 1 "GND")
|
||||
(pinfunction "5")
|
||||
(pintype "passive")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "6e86d938-6846-476f-83b5-1d8afafe7361")
|
||||
)
|
||||
(pad "6" connect circle
|
||||
(at 2.54 -0.635)
|
||||
(size 0.7874 0.7874)
|
||||
(layers "F.Cu" "F.Mask")
|
||||
(net 2 "Net-(R1-Pad6)")
|
||||
(pinfunction "6")
|
||||
(pintype "passive")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "6b67537c-a5db-4c19-a020-c8fcd9f7acf8")
|
||||
)
|
||||
(pad "7" connect circle
|
||||
(at 1.27 -0.635)
|
||||
(size 0.7874 0.7874)
|
||||
(layers "F.Cu" "F.Mask")
|
||||
(net 2 "Net-(R1-Pad6)")
|
||||
(pinfunction "7")
|
||||
(pintype "passive")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "d0255722-889d-41bc-920a-0463b6ac11fc")
|
||||
)
|
||||
(pad "8" connect circle
|
||||
(at 0 -0.635)
|
||||
(size 0.7874 0.7874)
|
||||
(layers "F.Cu" "F.Mask")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "0b67effe-459a-49bc-8c65-e7053f564265")
|
||||
)
|
||||
(pad "9" connect circle
|
||||
(at -1.27 -0.635)
|
||||
(size 0.7874 0.7874)
|
||||
(layers "F.Cu" "F.Mask")
|
||||
(net 2 "Net-(R1-Pad6)")
|
||||
(pinfunction "9")
|
||||
(pintype "passive")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "1dab4fb0-b39d-4753-ab2a-a5f4f1e8aac1")
|
||||
)
|
||||
(pad "10" connect circle
|
||||
(at -2.54 -0.635)
|
||||
(size 0.7874 0.7874)
|
||||
(layers "F.Cu" "F.Mask")
|
||||
(net 2 "Net-(R1-Pad6)")
|
||||
(pinfunction "10")
|
||||
(pintype "passive")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "46e85dda-2b66-4d09-a062-916d33de6a9e")
|
||||
)
|
||||
(zone
|
||||
(net 0)
|
||||
(net_name "")
|
||||
(layer "F.Cu")
|
||||
(uuid "8ef75162-3f7e-4efc-8b0a-4b51b175c2d3")
|
||||
(hatch full 0.508)
|
||||
(connect_pads
|
||||
(clearance 0)
|
||||
)
|
||||
(min_thickness 0.254)
|
||||
(keepout
|
||||
(tracks allowed)
|
||||
(vias not_allowed)
|
||||
(pads allowed)
|
||||
(copperpour not_allowed)
|
||||
(footprints not_allowed)
|
||||
)
|
||||
(placement
|
||||
(enabled no)
|
||||
(sheetname "")
|
||||
)
|
||||
(fill
|
||||
(thermal_gap 0.508)
|
||||
(thermal_bridge_width 0.508)
|
||||
(island_removal_mode 0)
|
||||
)
|
||||
(polygon
|
||||
(pts
|
||||
(xy 96.61 83.435) (xy 101.69 83.435) (xy 101.69 82.165) (xy 96.61 82.165)
|
||||
)
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
(footprint "Capacitor_SMD:CP_Elec_3x5.4"
|
||||
(layer "F.Cu")
|
||||
(uuid "f38d6383-ca09-42d5-84bc-8ed2ee9e1fb5")
|
||||
(at 96.5 90)
|
||||
(descr "SMD capacitor, aluminum electrolytic, Nichicon, 3.0x5.4mm")
|
||||
(tags "capacitor electrolytic")
|
||||
(property "Reference" "X1"
|
||||
(at 0 -2.7 0)
|
||||
(layer "F.SilkS")
|
||||
(uuid "8fda368e-86c3-4111-b138-69e3967c1f68")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "C"
|
||||
(at 0 2.7 0)
|
||||
(layer "F.Fab")
|
||||
(uuid "1e3a282d-c685-413d-bf0c-edca1617eef8")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(hide yes)
|
||||
(uuid "96d28378-e62a-4b33-90cc-9202b68830f3")
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" "Unpolarized capacitor"
|
||||
(at 0 0 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(hide yes)
|
||||
(uuid "78264291-21af-4cc0-b361-f72ab21292c3")
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property ki_fp_filters "C_*")
|
||||
(path "/e65020b4-b867-4d63-a46c-163f7210b6bb")
|
||||
(sheetname "/")
|
||||
(sheetfile "stacked.kicad_sch")
|
||||
(attr smd)
|
||||
(duplicate_pad_numbers_are_jumpers no)
|
||||
(fp_line
|
||||
(start -2.375 -1.435)
|
||||
(end -2 -1.435)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "7f79c690-cb60-4051-ad22-422351f6db5f")
|
||||
)
|
||||
(fp_line
|
||||
(start -2.1875 -1.6225)
|
||||
(end -2.1875 -1.2475)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "5c447aaa-d1e7-4fa3-9001-ac553d20662f")
|
||||
)
|
||||
(fp_line
|
||||
(start -1.570563 -1.06)
|
||||
(end -0.870563 -1.76)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "b111b33d-2499-4768-8eac-089403f3ec2b")
|
||||
)
|
||||
(fp_line
|
||||
(start -1.570563 1.06)
|
||||
(end -0.870563 1.76)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "edde24dd-77e1-41f4-89bb-5243e8aca75b")
|
||||
)
|
||||
(fp_line
|
||||
(start -0.870563 -1.76)
|
||||
(end 1.76 -1.76)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "d87eb71e-8e64-4d2a-a08f-3d9c164bd7b2")
|
||||
)
|
||||
(fp_line
|
||||
(start -0.870563 1.76)
|
||||
(end 1.76 1.76)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "66e18111-d83b-402d-b5bf-4272faf95588")
|
||||
)
|
||||
(fp_line
|
||||
(start 1.76 -1.76)
|
||||
(end 1.76 -1.06)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "3d5b5c44-9238-471b-8363-30d163faeaa9")
|
||||
)
|
||||
(fp_line
|
||||
(start 1.76 1.76)
|
||||
(end 1.76 1.06)
|
||||
(stroke
|
||||
(width 0.12)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.SilkS")
|
||||
(uuid "3f598ead-79d7-4fd5-8825-a2ede4af679e")
|
||||
)
|
||||
(fp_line
|
||||
(start -2.85 -1.05)
|
||||
(end -2.85 1.05)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "334da6ac-b694-478b-9cd1-8c83468d7391")
|
||||
)
|
||||
(fp_line
|
||||
(start -2.85 1.05)
|
||||
(end -1.78 1.05)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "31c9fc6d-fb51-4a4c-b881-c2c07c346518")
|
||||
)
|
||||
(fp_line
|
||||
(start -1.78 -1.05)
|
||||
(end -2.85 -1.05)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "a00caec7-86a6-4c68-a3fc-934a3d99d2d4")
|
||||
)
|
||||
(fp_line
|
||||
(start -1.78 -1.05)
|
||||
(end -0.93 -1.9)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "322cbb81-71f3-413e-b949-ccee52c5fbb0")
|
||||
)
|
||||
(fp_line
|
||||
(start -1.78 1.05)
|
||||
(end -0.93 1.9)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "e530520a-ddcd-42a3-9c5d-4d2a1f3154d3")
|
||||
)
|
||||
(fp_line
|
||||
(start -0.93 -1.9)
|
||||
(end 1.9 -1.9)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "987d6ecb-c12d-45d9-9619-904468fee663")
|
||||
)
|
||||
(fp_line
|
||||
(start -0.93 1.9)
|
||||
(end 1.9 1.9)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "d355660a-f52e-4ee5-9fbb-9b86dbbe4282")
|
||||
)
|
||||
(fp_line
|
||||
(start 1.9 -1.9)
|
||||
(end 1.9 -1.05)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "c9c0c7e3-9efc-4a85-8de7-3a6319ff7a44")
|
||||
)
|
||||
(fp_line
|
||||
(start 1.9 -1.05)
|
||||
(end 2.85 -1.05)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "a5329e51-5a4e-4a5c-aafd-07e647458f36")
|
||||
)
|
||||
(fp_line
|
||||
(start 1.9 1.05)
|
||||
(end 1.9 1.9)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "4e327b6e-6096-4ec1-abdc-e5aadd5ce3b4")
|
||||
)
|
||||
(fp_line
|
||||
(start 2.85 -1.05)
|
||||
(end 2.85 1.05)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "2ba849f1-a85e-4b96-baeb-988620c13ad1")
|
||||
)
|
||||
(fp_line
|
||||
(start 2.85 1.05)
|
||||
(end 1.9 1.05)
|
||||
(stroke
|
||||
(width 0.05)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "cc4ec5b8-906b-4358-9df6-626341ba4078")
|
||||
)
|
||||
(fp_line
|
||||
(start -1.65 -0.825)
|
||||
(end -1.65 0.825)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.Fab")
|
||||
(uuid "d09f4d86-628e-4106-8571-3557d500000e")
|
||||
)
|
||||
(fp_line
|
||||
(start -1.65 -0.825)
|
||||
(end -0.825 -1.65)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.Fab")
|
||||
(uuid "5a3a7862-c16f-49e9-a4a5-7301da652b4b")
|
||||
)
|
||||
(fp_line
|
||||
(start -1.65 0.825)
|
||||
(end -0.825 1.65)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.Fab")
|
||||
(uuid "6f9a2956-13af-4d8c-a116-e81678cf86b7")
|
||||
)
|
||||
(fp_line
|
||||
(start -1.110469 -0.8)
|
||||
(end -0.810469 -0.8)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.Fab")
|
||||
(uuid "6480eda9-f610-45f9-9815-aa694aa23157")
|
||||
)
|
||||
(fp_line
|
||||
(start -0.960469 -0.95)
|
||||
(end -0.960469 -0.65)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.Fab")
|
||||
(uuid "2f8eccda-c7ba-4ac5-8b4a-1bdee552ce2e")
|
||||
)
|
||||
(fp_line
|
||||
(start -0.825 -1.65)
|
||||
(end 1.65 -1.65)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.Fab")
|
||||
(uuid "26170f0f-c996-4313-a1ba-3f7eb878fcb0")
|
||||
)
|
||||
(fp_line
|
||||
(start -0.825 1.65)
|
||||
(end 1.65 1.65)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.Fab")
|
||||
(uuid "dfac16eb-141f-4383-93a1-185012c558dc")
|
||||
)
|
||||
(fp_line
|
||||
(start 1.65 -1.65)
|
||||
(end 1.65 1.65)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(layer "F.Fab")
|
||||
(uuid "7ead1618-1e1b-485f-a82b-60b82c9a147a")
|
||||
)
|
||||
(fp_circle
|
||||
(center 0 0)
|
||||
(end 1.5 0)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(fill no)
|
||||
(layer "F.Fab")
|
||||
(uuid "5ee3d052-a5df-4679-917d-290393b4ac4a")
|
||||
)
|
||||
(fp_text user "${REFERENCE}"
|
||||
(at 0 0 0)
|
||||
(layer "F.Fab")
|
||||
(uuid "587b737e-6398-4d6d-9ee7-7db10fa2e562")
|
||||
(effects
|
||||
(font
|
||||
(size 0.6 0.6)
|
||||
(thickness 0.09)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pad "1" smd roundrect
|
||||
(at -1.5 0)
|
||||
(size 2.2 1.6)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15625)
|
||||
(net 2 "Net-(R1-Pad6)")
|
||||
(pintype "passive")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "595baab1-9b66-41dd-838b-67f9f7f008e9")
|
||||
)
|
||||
(pad "2" smd roundrect
|
||||
(at 1.5 0)
|
||||
(size 2.2 1.6)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15625)
|
||||
(net 3 "VCC")
|
||||
(pintype "passive")
|
||||
(tenting
|
||||
(front none)
|
||||
(back none)
|
||||
)
|
||||
(uuid "d75d5164-5460-42c3-9d1d-54ff2da81393")
|
||||
)
|
||||
(embedded_fonts no)
|
||||
(model "${KICAD8_3DMODEL_DIR}/Capacitor_SMD.3dshapes/CP_Elec_3x5.4.wrl"
|
||||
(offset
|
||||
(xyz 0 0 0)
|
||||
)
|
||||
(scale
|
||||
(xyz 1 1 1)
|
||||
)
|
||||
(rotate
|
||||
(xyz 0 0 0)
|
||||
)
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
632
qa/data/pcbnew/stacked/stacked.kicad_pro
Normal file
632
qa/data/pcbnew/stacked/stacked.kicad_pro
Normal file
@ -0,0 +1,632 @@
|
||||
{
|
||||
"board": {
|
||||
"3dviewports": [],
|
||||
"design_settings": {
|
||||
"defaults": {
|
||||
"apply_defaults_to_fp_fields": false,
|
||||
"apply_defaults_to_fp_shapes": false,
|
||||
"apply_defaults_to_fp_text": false,
|
||||
"board_outline_line_width": 0.05,
|
||||
"copper_line_width": 0.2,
|
||||
"copper_text_italic": false,
|
||||
"copper_text_size_h": 1.5,
|
||||
"copper_text_size_v": 1.5,
|
||||
"copper_text_thickness": 0.3,
|
||||
"copper_text_upright": false,
|
||||
"courtyard_line_width": 0.05,
|
||||
"dimension_precision": 4,
|
||||
"dimension_units": 3,
|
||||
"dimensions": {
|
||||
"arrow_length": 1270000,
|
||||
"extension_offset": 500000,
|
||||
"keep_text_aligned": true,
|
||||
"suppress_zeroes": true,
|
||||
"text_position": 0,
|
||||
"units_format": 0
|
||||
},
|
||||
"fab_line_width": 0.1,
|
||||
"fab_text_italic": false,
|
||||
"fab_text_size_h": 1.0,
|
||||
"fab_text_size_v": 1.0,
|
||||
"fab_text_thickness": 0.15,
|
||||
"fab_text_upright": false,
|
||||
"other_line_width": 0.1,
|
||||
"other_text_italic": false,
|
||||
"other_text_size_h": 1.0,
|
||||
"other_text_size_v": 1.0,
|
||||
"other_text_thickness": 0.15,
|
||||
"other_text_upright": false,
|
||||
"pads": {
|
||||
"drill": 0.8,
|
||||
"height": 1.27,
|
||||
"width": 2.54
|
||||
},
|
||||
"silk_line_width": 0.1,
|
||||
"silk_text_italic": false,
|
||||
"silk_text_size_h": 1.0,
|
||||
"silk_text_size_v": 1.0,
|
||||
"silk_text_thickness": 0.1,
|
||||
"silk_text_upright": false,
|
||||
"zones": {
|
||||
"min_clearance": 0.5
|
||||
}
|
||||
},
|
||||
"diff_pair_dimensions": [],
|
||||
"drc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 2
|
||||
},
|
||||
"rule_severities": {
|
||||
"annular_width": "error",
|
||||
"clearance": "error",
|
||||
"connection_width": "warning",
|
||||
"copper_edge_clearance": "error",
|
||||
"copper_sliver": "warning",
|
||||
"courtyards_overlap": "error",
|
||||
"creepage": "error",
|
||||
"diff_pair_gap_out_of_range": "error",
|
||||
"diff_pair_uncoupled_length_too_long": "error",
|
||||
"drill_out_of_range": "error",
|
||||
"duplicate_footprints": "warning",
|
||||
"extra_footprint": "warning",
|
||||
"footprint": "error",
|
||||
"footprint_filters_mismatch": "ignore",
|
||||
"footprint_symbol_mismatch": "warning",
|
||||
"footprint_type_mismatch": "ignore",
|
||||
"hole_clearance": "error",
|
||||
"hole_to_hole": "warning",
|
||||
"holes_co_located": "warning",
|
||||
"invalid_outline": "error",
|
||||
"isolated_copper": "warning",
|
||||
"item_on_disabled_layer": "error",
|
||||
"items_not_allowed": "error",
|
||||
"length_out_of_range": "error",
|
||||
"lib_footprint_issues": "warning",
|
||||
"lib_footprint_mismatch": "warning",
|
||||
"malformed_courtyard": "error",
|
||||
"microvia_drill_out_of_range": "error",
|
||||
"mirrored_text_on_front_layer": "warning",
|
||||
"missing_courtyard": "ignore",
|
||||
"missing_footprint": "warning",
|
||||
"net_conflict": "warning",
|
||||
"nonmirrored_text_on_back_layer": "warning",
|
||||
"npth_inside_courtyard": "ignore",
|
||||
"padstack": "warning",
|
||||
"pth_inside_courtyard": "ignore",
|
||||
"shorting_items": "error",
|
||||
"silk_edge_clearance": "warning",
|
||||
"silk_over_copper": "warning",
|
||||
"silk_overlap": "warning",
|
||||
"skew_out_of_range": "error",
|
||||
"solder_mask_bridge": "error",
|
||||
"starved_thermal": "error",
|
||||
"text_height": "warning",
|
||||
"text_on_edge_cuts": "error",
|
||||
"text_thickness": "warning",
|
||||
"through_hole_pad_without_hole": "error",
|
||||
"too_many_vias": "error",
|
||||
"track_angle": "error",
|
||||
"track_dangling": "warning",
|
||||
"track_segment_length": "error",
|
||||
"track_width": "error",
|
||||
"tracks_crossing": "error",
|
||||
"unconnected_items": "error",
|
||||
"unresolved_variable": "error",
|
||||
"via_dangling": "warning",
|
||||
"zones_intersect": "error"
|
||||
},
|
||||
"rules": {
|
||||
"max_error": 0.005,
|
||||
"min_clearance": 0.0,
|
||||
"min_connection": 0.0,
|
||||
"min_copper_edge_clearance": 0.5,
|
||||
"min_groove_width": 0.0,
|
||||
"min_hole_clearance": 0.25,
|
||||
"min_hole_to_hole": 0.25,
|
||||
"min_microvia_diameter": 0.2,
|
||||
"min_microvia_drill": 0.1,
|
||||
"min_resolved_spokes": 2,
|
||||
"min_silk_clearance": 0.0,
|
||||
"min_text_height": 0.8,
|
||||
"min_text_thickness": 0.08,
|
||||
"min_through_hole_diameter": 0.3,
|
||||
"min_track_width": 0.0,
|
||||
"min_via_annular_width": 0.1,
|
||||
"min_via_diameter": 0.5,
|
||||
"solder_mask_to_copper_clearance": 0.0,
|
||||
"use_height_for_length_calcs": true
|
||||
},
|
||||
"teardrop_options": [
|
||||
{
|
||||
"td_onpthpad": true,
|
||||
"td_onroundshapesonly": false,
|
||||
"td_onsmdpad": true,
|
||||
"td_ontrackend": false,
|
||||
"td_onvia": true
|
||||
}
|
||||
],
|
||||
"teardrop_parameters": [
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_round_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_rect_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_track_end",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
}
|
||||
],
|
||||
"track_widths": [],
|
||||
"tuning_pattern_settings": {
|
||||
"diff_pair_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 1.0
|
||||
},
|
||||
"diff_pair_skew_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 0.6
|
||||
},
|
||||
"single_track_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 0.6
|
||||
}
|
||||
},
|
||||
"via_dimensions": [],
|
||||
"zones_allow_external_fillets": false
|
||||
},
|
||||
"ipc2581": {
|
||||
"dist": "",
|
||||
"distpn": "",
|
||||
"internal_id": "",
|
||||
"mfg": "",
|
||||
"mpn": ""
|
||||
},
|
||||
"layer_pairs": [],
|
||||
"layer_presets": [],
|
||||
"viewports": []
|
||||
},
|
||||
"boards": [],
|
||||
"component_class_settings": {
|
||||
"assignments": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"sheet_component_classes": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"cvpcb": {
|
||||
"equivalence_files": []
|
||||
},
|
||||
"erc": {
|
||||
"erc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"pin_map": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
]
|
||||
],
|
||||
"rule_severities": {
|
||||
"bus_definition_conflict": "error",
|
||||
"bus_entry_needed": "error",
|
||||
"bus_to_bus_conflict": "error",
|
||||
"bus_to_net_conflict": "error",
|
||||
"different_unit_footprint": "error",
|
||||
"different_unit_net": "error",
|
||||
"duplicate_reference": "error",
|
||||
"duplicate_sheet_names": "error",
|
||||
"endpoint_off_grid": "warning",
|
||||
"extra_units": "error",
|
||||
"footprint_filter": "ignore",
|
||||
"footprint_link_issues": "warning",
|
||||
"four_way_junction": "ignore",
|
||||
"ground_pin_not_ground": "warning",
|
||||
"hier_label_mismatch": "error",
|
||||
"isolated_pin_label": "warning",
|
||||
"label_dangling": "error",
|
||||
"label_multiple_wires": "warning",
|
||||
"lib_symbol_issues": "warning",
|
||||
"lib_symbol_mismatch": "warning",
|
||||
"missing_bidi_pin": "warning",
|
||||
"missing_input_pin": "warning",
|
||||
"missing_power_pin": "error",
|
||||
"missing_unit": "warning",
|
||||
"multiple_net_names": "warning",
|
||||
"net_not_bus_member": "warning",
|
||||
"no_connect_connected": "warning",
|
||||
"no_connect_dangling": "warning",
|
||||
"pin_not_connected": "error",
|
||||
"pin_not_driven": "error",
|
||||
"pin_to_pin": "warning",
|
||||
"power_pin_not_driven": "error",
|
||||
"same_local_global_label": "warning",
|
||||
"similar_label_and_power": "warning",
|
||||
"similar_labels": "warning",
|
||||
"similar_power": "warning",
|
||||
"simulation_model_issue": "ignore",
|
||||
"single_global_label": "ignore",
|
||||
"stacked_pin_name": "warning",
|
||||
"unannotated": "error",
|
||||
"unconnected_wire_endpoint": "warning",
|
||||
"undefined_netclass": "error",
|
||||
"unit_value_mismatch": "error",
|
||||
"unresolved_variable": "error",
|
||||
"wire_dangling": "error"
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"pinned_footprint_libs": [],
|
||||
"pinned_symbol_libs": []
|
||||
},
|
||||
"meta": {
|
||||
"filename": "stacked.kicad_pro",
|
||||
"version": 3
|
||||
},
|
||||
"net_settings": {
|
||||
"classes": [
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.2,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Default",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 2147483647,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.2,
|
||||
"tuning_profile": "",
|
||||
"via_diameter": 0.6,
|
||||
"via_drill": 0.3,
|
||||
"wire_width": 6
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"version": 5
|
||||
},
|
||||
"net_colors": null,
|
||||
"netclass_assignments": null,
|
||||
"netclass_patterns": []
|
||||
},
|
||||
"pcbnew": {
|
||||
"last_paths": {
|
||||
"idf": "",
|
||||
"netlist": "",
|
||||
"plot": "",
|
||||
"specctra_dsn": "",
|
||||
"vrml": ""
|
||||
},
|
||||
"page_layout_descr_file": ""
|
||||
},
|
||||
"schematic": {
|
||||
"annotate_start_num": 0,
|
||||
"annotation": {
|
||||
"method": 0,
|
||||
"sort_order": 0
|
||||
},
|
||||
"bom_export_filename": "${PROJECTNAME}.csv",
|
||||
"bom_fmt_presets": [],
|
||||
"bom_fmt_settings": {
|
||||
"field_delimiter": ",",
|
||||
"keep_line_breaks": false,
|
||||
"keep_tabs": false,
|
||||
"name": "CSV",
|
||||
"ref_delimiter": ",",
|
||||
"ref_range_delimiter": "",
|
||||
"string_delimiter": "\""
|
||||
},
|
||||
"bom_presets": [],
|
||||
"bom_settings": {
|
||||
"exclude_dnp": false,
|
||||
"fields_ordered": [
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Reference",
|
||||
"name": "Reference",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Qty",
|
||||
"name": "${QUANTITY}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Value",
|
||||
"name": "Value",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "DNP",
|
||||
"name": "${DNP}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from BOM",
|
||||
"name": "${EXCLUDE_FROM_BOM}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from Board",
|
||||
"name": "${EXCLUDE_FROM_BOARD}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Footprint",
|
||||
"name": "Footprint",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Datasheet",
|
||||
"name": "Datasheet",
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"filter_string": "",
|
||||
"group_symbols": true,
|
||||
"include_excluded_from_bom": true,
|
||||
"name": "Default Editing",
|
||||
"sort_asc": true,
|
||||
"sort_field": "Reference"
|
||||
},
|
||||
"connection_grid_size": 50.0,
|
||||
"drawing": {
|
||||
"dashed_lines_dash_length_ratio": 12.0,
|
||||
"dashed_lines_gap_length_ratio": 3.0,
|
||||
"default_line_thickness": 6.0,
|
||||
"default_text_size": 50.0,
|
||||
"field_names": [],
|
||||
"hop_over_size_choice": 0,
|
||||
"intersheets_ref_own_page": false,
|
||||
"intersheets_ref_prefix": "",
|
||||
"intersheets_ref_short": false,
|
||||
"intersheets_ref_show": false,
|
||||
"intersheets_ref_suffix": "",
|
||||
"junction_size_choice": 3,
|
||||
"label_size_ratio": 0.375,
|
||||
"operating_point_overlay_i_precision": 3,
|
||||
"operating_point_overlay_i_range": "~A",
|
||||
"operating_point_overlay_v_precision": 3,
|
||||
"operating_point_overlay_v_range": "~V",
|
||||
"overbar_offset_ratio": 1.23,
|
||||
"pin_symbol_size": 25.0,
|
||||
"text_offset_ratio": 0.15
|
||||
},
|
||||
"legacy_lib_dir": "",
|
||||
"legacy_lib_list": [],
|
||||
"meta": {
|
||||
"version": 1
|
||||
},
|
||||
"page_layout_descr_file": "",
|
||||
"plot_directory": "",
|
||||
"reuse_designators": true,
|
||||
"subpart_first_id": 65,
|
||||
"subpart_id_separator": 0,
|
||||
"used_designators": "C1-2"
|
||||
},
|
||||
"sheets": [
|
||||
[
|
||||
"f3609c98-5e8f-46ae-a21a-09d0b96782a9",
|
||||
"Root"
|
||||
]
|
||||
],
|
||||
"text_variables": {},
|
||||
"time_domain_parameters": {
|
||||
"delay_profiles_user_defined": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
}
|
||||
}
|
||||
}
|
827
qa/data/pcbnew/stacked/stacked.kicad_sch
Normal file
827
qa/data/pcbnew/stacked/stacked.kicad_sch
Normal file
@ -0,0 +1,827 @@
|
||||
(kicad_sch
|
||||
(version 20250829)
|
||||
(generator "eeschema")
|
||||
(generator_version "9.99")
|
||||
(uuid "f3609c98-5e8f-46ae-a21a-09d0b96782a9")
|
||||
(paper "A4")
|
||||
(lib_symbols
|
||||
(symbol "Device:C"
|
||||
(pin_numbers
|
||||
(hide yes)
|
||||
)
|
||||
(pin_names
|
||||
(offset 0.254)
|
||||
)
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(duplicate_pin_numbers_are_jumpers no)
|
||||
(property "Reference" "C"
|
||||
(at 0.635 2.54 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify left)
|
||||
)
|
||||
)
|
||||
(property "Value" "C"
|
||||
(at 0.635 -2.54 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify left)
|
||||
)
|
||||
)
|
||||
(property "Footprint" ""
|
||||
(at 0.9652 -3.81 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" "Unpolarized capacitor"
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "ki_keywords" "cap capacitor"
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "ki_fp_filters" "C_*"
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "C_0_1"
|
||||
(polyline
|
||||
(pts
|
||||
(xy -2.032 0.762) (xy 2.032 0.762)
|
||||
)
|
||||
(stroke
|
||||
(width 0.508)
|
||||
(type default)
|
||||
)
|
||||
(fill
|
||||
(type none)
|
||||
)
|
||||
)
|
||||
(polyline
|
||||
(pts
|
||||
(xy -2.032 -0.762) (xy 2.032 -0.762)
|
||||
)
|
||||
(stroke
|
||||
(width 0.508)
|
||||
(type default)
|
||||
)
|
||||
(fill
|
||||
(type none)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "C_1_1"
|
||||
(pin passive line
|
||||
(at 0 3.81 270)
|
||||
(length 2.794)
|
||||
(name ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "1"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin passive line
|
||||
(at 0 -3.81 90)
|
||||
(length 2.794)
|
||||
(name ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "2"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
(symbol "Device:R"
|
||||
(pin_names
|
||||
(offset 0)
|
||||
)
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(duplicate_pin_numbers_are_jumpers no)
|
||||
(property "Reference" "R1"
|
||||
(at 6.35 0 90)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "R"
|
||||
(at 3.81 0 90)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Footprint" ""
|
||||
(at -1.778 0 90)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" "Resistor"
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "ki_keywords" "R res resistor"
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "ki_fp_filters" "R_*"
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "R_0_1"
|
||||
(rectangle
|
||||
(start -1.016 -2.54)
|
||||
(end 1.016 2.54)
|
||||
(stroke
|
||||
(width 0.254)
|
||||
(type default)
|
||||
)
|
||||
(fill
|
||||
(type none)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "R_1_1"
|
||||
(pin passive line
|
||||
(at 0 6.35 270)
|
||||
(length 1.27)
|
||||
(name ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "[1-5]"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin passive line
|
||||
(at 0 -8.636 90)
|
||||
(length 1.27)
|
||||
(name ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "[6,7,9-11]"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
(symbol "power:GND"
|
||||
(power global)
|
||||
(pin_numbers
|
||||
(hide yes)
|
||||
)
|
||||
(pin_names
|
||||
(offset 0)
|
||||
(hide yes)
|
||||
)
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(duplicate_pin_numbers_are_jumpers no)
|
||||
(property "Reference" "#PWR"
|
||||
(at 0 -6.35 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "GND"
|
||||
(at 0 -3.81 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Footprint" ""
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" "Power symbol creates a global label with name \"GND\" , ground"
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "ki_keywords" "global power"
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "GND_0_1"
|
||||
(polyline
|
||||
(pts
|
||||
(xy 0 0) (xy 0 -1.27) (xy 1.27 -1.27) (xy 0 -2.54) (xy -1.27 -1.27) (xy 0 -1.27)
|
||||
)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(fill
|
||||
(type none)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "GND_1_1"
|
||||
(pin power_in line
|
||||
(at 0 0 270)
|
||||
(length 0)
|
||||
(name ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "1"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
(symbol "power:VCC"
|
||||
(power global)
|
||||
(pin_numbers
|
||||
(hide yes)
|
||||
)
|
||||
(pin_names
|
||||
(offset 0)
|
||||
(hide yes)
|
||||
)
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(duplicate_pin_numbers_are_jumpers no)
|
||||
(property "Reference" "#PWR"
|
||||
(at 0 -3.81 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "VCC"
|
||||
(at 0 3.556 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Footprint" ""
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" "Power symbol creates a global label with name \"VCC\""
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "ki_keywords" "global power"
|
||||
(at 0 0 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "VCC_0_1"
|
||||
(polyline
|
||||
(pts
|
||||
(xy -0.762 1.27) (xy 0 2.54)
|
||||
)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(fill
|
||||
(type none)
|
||||
)
|
||||
)
|
||||
(polyline
|
||||
(pts
|
||||
(xy 0 2.54) (xy 0.762 1.27)
|
||||
)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(fill
|
||||
(type none)
|
||||
)
|
||||
)
|
||||
(polyline
|
||||
(pts
|
||||
(xy 0 0) (xy 0 2.54)
|
||||
)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(fill
|
||||
(type none)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "VCC_1_1"
|
||||
(pin power_in line
|
||||
(at 0 0 90)
|
||||
(length 0)
|
||||
(name ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "1"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
)
|
||||
(wire
|
||||
(pts
|
||||
(xy 124.206 83.82) (xy 134.62 83.82)
|
||||
)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(uuid "977d311c-737b-4d33-a7c0-027068800d27")
|
||||
)
|
||||
(wire
|
||||
(pts
|
||||
(xy 100.33 87.63) (xy 100.33 83.82)
|
||||
)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(uuid "a74e3420-cc0c-4d70-a108-b408a357a728")
|
||||
)
|
||||
(wire
|
||||
(pts
|
||||
(xy 100.33 83.82) (xy 109.22 83.82)
|
||||
)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(uuid "b640eecc-a8af-45c1-97a3-9a874bceb1f7")
|
||||
)
|
||||
(wire
|
||||
(pts
|
||||
(xy 152.4 83.82) (xy 152.4 76.2)
|
||||
)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(uuid "b86edfbb-be66-4823-bf66-7580fa3069d2")
|
||||
)
|
||||
(wire
|
||||
(pts
|
||||
(xy 142.24 83.82) (xy 152.4 83.82)
|
||||
)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(uuid "fdc72005-7acb-48aa-85c0-6d26e865dc00")
|
||||
)
|
||||
(symbol
|
||||
(lib_id "Device:R")
|
||||
(at 115.57 83.82 90)
|
||||
(unit 1)
|
||||
(body_style 1)
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(dnp no)
|
||||
(fields_autoplaced yes)
|
||||
(uuid "2166cf4e-5b03-495b-af0a-be2718b61b04")
|
||||
(property "Reference" "R1"
|
||||
(at 116.713 77.47 90)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "R"
|
||||
(at 116.713 80.01 90)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Footprint" "Connector:Tag-Connect_TC2050-IDC-FP_2x05_P1.27mm_Vertical"
|
||||
(at 115.57 85.598 90)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 115.57 83.82 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" "Resistor"
|
||||
(at 115.57 83.82 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin "[6,7,9-11]"
|
||||
(uuid "f62cbf3f-56f0-413b-a710-93a48027efbb")
|
||||
)
|
||||
(pin "[1-5]"
|
||||
(uuid "e856ce98-c7c6-4066-97a8-60e95541ae81")
|
||||
)
|
||||
(instances
|
||||
(project ""
|
||||
(path "/f3609c98-5e8f-46ae-a21a-09d0b96782a9"
|
||||
(reference "R1")
|
||||
(unit 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol
|
||||
(lib_id "power:VCC")
|
||||
(at 152.4 76.2 0)
|
||||
(unit 1)
|
||||
(body_style 1)
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(dnp no)
|
||||
(fields_autoplaced yes)
|
||||
(uuid "2b72c13a-dfcb-4980-a25b-daac640cdb9b")
|
||||
(property "Reference" "#PWR01"
|
||||
(at 152.4 80.01 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "VCC"
|
||||
(at 152.4 71.12 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Footprint" ""
|
||||
(at 152.4 76.2 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 152.4 76.2 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" "Power symbol creates a global label with name \"VCC\""
|
||||
(at 152.4 76.2 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin "1"
|
||||
(uuid "ff7185fb-347f-48f5-bcf0-de5808edd725")
|
||||
)
|
||||
(instances
|
||||
(project ""
|
||||
(path "/f3609c98-5e8f-46ae-a21a-09d0b96782a9"
|
||||
(reference "#PWR01")
|
||||
(unit 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol
|
||||
(lib_id "power:GND")
|
||||
(at 100.33 87.63 0)
|
||||
(unit 1)
|
||||
(body_style 1)
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(dnp no)
|
||||
(fields_autoplaced yes)
|
||||
(uuid "4e64c428-4aed-4183-826f-2fa5009f4177")
|
||||
(property "Reference" "#PWR02"
|
||||
(at 100.33 93.98 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "GND"
|
||||
(at 100.33 92.71 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Footprint" ""
|
||||
(at 100.33 87.63 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 100.33 87.63 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" "Power symbol creates a global label with name \"GND\" , ground"
|
||||
(at 100.33 87.63 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin "1"
|
||||
(uuid "b8c00030-efa1-4421-9af4-303142b1c17d")
|
||||
)
|
||||
(instances
|
||||
(project ""
|
||||
(path "/f3609c98-5e8f-46ae-a21a-09d0b96782a9"
|
||||
(reference "#PWR02")
|
||||
(unit 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol
|
||||
(lib_id "Device:C")
|
||||
(at 138.43 83.82 90)
|
||||
(unit 1)
|
||||
(body_style 1)
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(dnp no)
|
||||
(fields_autoplaced yes)
|
||||
(uuid "e65020b4-b867-4d63-a46c-163f7210b6bb")
|
||||
(property "Reference" "X1"
|
||||
(at 138.43 76.2 90)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "C"
|
||||
(at 138.43 78.74 90)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Footprint" "Capacitor_SMD:CP_Elec_3x5.4"
|
||||
(at 142.24 82.8548 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 138.43 83.82 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" "Unpolarized capacitor"
|
||||
(at 138.43 83.82 0)
|
||||
(hide yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin "2"
|
||||
(uuid "30431dfb-acd4-46c0-af86-7738834b40fd")
|
||||
)
|
||||
(pin "1"
|
||||
(uuid "047a8bd9-b214-498e-b161-2c9a3c6501f3")
|
||||
)
|
||||
(instances
|
||||
(project ""
|
||||
(path "/f3609c98-5e8f-46ae-a21a-09d0b96782a9"
|
||||
(reference "X1")
|
||||
(unit 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(sheet_instances
|
||||
(path "/"
|
||||
(page "1")
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
@ -94,6 +94,10 @@ set( QA_EESCHEMA_SRCS
|
||||
test_sch_symbol.cpp
|
||||
test_schematic.cpp
|
||||
test_symbol_library_manager.cpp
|
||||
test_stacked_pin_nomenclature.cpp
|
||||
test_stacked_pin_conversion.cpp
|
||||
test_pin_stacked_layout.cpp
|
||||
test_netlist_exporter_xml_stacked.cpp
|
||||
test_resolve_drivers.cpp
|
||||
test_saveas_copy_subsheets.cpp
|
||||
test_update_items_connectivity.cpp
|
||||
|
150
qa/tests/eeschema/test_netlist_exporter_xml_stacked.cpp
Normal file
150
qa/tests/eeschema/test_netlist_exporter_xml_stacked.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers
|
||||
*
|
||||
* 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 3
|
||||
* 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 at
|
||||
* http://www.gnu.org/licenses/
|
||||
*/
|
||||
|
||||
#include <qa_utils/wx_utils/unit_test_utils.h>
|
||||
#include <schematic_utils/schematic_file_util.h>
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <schematic.h>
|
||||
#include <settings/settings_manager.h>
|
||||
#include <netlist_exporter_xml.h>
|
||||
|
||||
#include <wx/filename.h>
|
||||
#include <wx/xml/xml.h>
|
||||
|
||||
struct XML_STACKED_PIN_FIXTURE
|
||||
{
|
||||
XML_STACKED_PIN_FIXTURE() : m_settingsManager( true /* headless */ ) {}
|
||||
|
||||
SETTINGS_MANAGER m_settingsManager;
|
||||
std::unique_ptr<SCHEMATIC> m_schematic;
|
||||
};
|
||||
|
||||
static std::set<wxString> as_set( const std::initializer_list<const char*>& init )
|
||||
{
|
||||
std::set<wxString> out;
|
||||
for( const char* s : init )
|
||||
out.emplace( wxString::FromUTF8( s ) );
|
||||
return out;
|
||||
}
|
||||
|
||||
static wxXmlNode* find_child( wxXmlNode* parent, const wxString& name )
|
||||
{
|
||||
for( wxXmlNode* child = parent->GetChildren(); child; child = child->GetNext() )
|
||||
{
|
||||
if( child->GetName() == name )
|
||||
return child;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::vector<wxXmlNode*> find_children( wxXmlNode* parent, const wxString& name )
|
||||
{
|
||||
std::vector<wxXmlNode*> out;
|
||||
|
||||
for( wxXmlNode* child = parent->GetChildren(); child; child = child->GetNext() )
|
||||
{
|
||||
if( child->GetName() == name )
|
||||
out.push_back( child );
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE( NetlistExporterXML_StackedPinNomenclature, XML_STACKED_PIN_FIXTURE )
|
||||
{
|
||||
// Load schematic with stacked pin numbers
|
||||
KI_TEST::LoadSchematic( m_settingsManager, wxT( "stacked_pin_nomenclature" ), m_schematic );
|
||||
|
||||
// Write XML netlist to a test file next to the project
|
||||
wxFileName netFile = m_schematic->Project().GetProjectFullName();
|
||||
netFile.SetName( netFile.GetName() + wxT( "_xml_test" ) );
|
||||
netFile.SetExt( wxT( "xml" ) );
|
||||
|
||||
if( wxFileExists( netFile.GetFullPath() ) )
|
||||
wxRemoveFile( netFile.GetFullPath() );
|
||||
|
||||
WX_STRING_REPORTER reporter;
|
||||
std::unique_ptr<NETLIST_EXPORTER_XML> exporter =
|
||||
std::make_unique<NETLIST_EXPORTER_XML>( m_schematic.get() );
|
||||
|
||||
bool success = exporter->WriteNetlist( netFile.GetFullPath(), 0, reporter );
|
||||
BOOST_REQUIRE( success && reporter.GetMessages().IsEmpty() );
|
||||
|
||||
// Parse the XML back
|
||||
wxXmlDocument xdoc;
|
||||
BOOST_REQUIRE( xdoc.Load( netFile.GetFullPath() ) );
|
||||
|
||||
wxXmlNode* root = xdoc.GetRoot();
|
||||
BOOST_REQUIRE( root );
|
||||
|
||||
wxXmlNode* nets = find_child( root, wxT( "nets" ) );
|
||||
BOOST_REQUIRE( nets );
|
||||
|
||||
// Collect pin sets for R1 on each power net
|
||||
std::set<wxString> setA;
|
||||
std::set<wxString> setB;
|
||||
int foundSets = 0;
|
||||
|
||||
for( wxXmlNode* net : find_children( nets, wxT( "net" ) ) )
|
||||
{
|
||||
wxString netName = net->GetAttribute( wxT( "name" ), wxEmptyString );
|
||||
if( netName != wxT( "VCC" ) && netName != wxT( "GND" ) )
|
||||
continue;
|
||||
|
||||
std::set<wxString>* target = ( foundSets == 0 ? &setA : &setB );
|
||||
|
||||
for( wxXmlNode* node : find_children( net, wxT( "node" ) ) )
|
||||
{
|
||||
if( node->GetAttribute( wxT( "ref" ), wxEmptyString ) != wxT( "R1" ) )
|
||||
continue;
|
||||
|
||||
wxString pin = node->GetAttribute( wxT( "pin" ), wxEmptyString );
|
||||
wxString pinfunction = node->GetAttribute( wxT( "pinfunction" ), wxEmptyString );
|
||||
wxString pintype = node->GetAttribute( wxT( "pintype" ), wxEmptyString );
|
||||
|
||||
// Expect pinfunction to equal the expanded number when base name is empty
|
||||
BOOST_CHECK_EQUAL( pinfunction, pin );
|
||||
// Expect plain passive type (no +no_connect on these nets)
|
||||
BOOST_CHECK_EQUAL( pintype, wxT( "passive" ) );
|
||||
|
||||
target->insert( pin );
|
||||
}
|
||||
|
||||
foundSets++;
|
||||
}
|
||||
|
||||
// We should have found two power nets with R1 nodes
|
||||
BOOST_REQUIRE_EQUAL( foundSets, 2 );
|
||||
|
||||
// Expect one side to be 1..5 and the other to be 6,7,9,10,11 (order independent)
|
||||
const std::set<wxString> expectedTop = as_set( { "1", "2", "3", "4", "5" } );
|
||||
const std::set<wxString> expectedBot = as_set( { "6", "7", "9", "10", "11" } );
|
||||
|
||||
bool matchA = ( setA == expectedTop && setB == expectedBot );
|
||||
bool matchB = ( setA == expectedBot && setB == expectedTop );
|
||||
BOOST_CHECK( matchA || matchB );
|
||||
|
||||
// Cleanup test artifact
|
||||
wxRemoveFile( netFile.GetFullPath() );
|
||||
}
|
543
qa/tests/eeschema/test_pin_stacked_layout.cpp
Normal file
543
qa/tests/eeschema/test_pin_stacked_layout.cpp
Normal file
@ -0,0 +1,543 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file test_pin_stacked_layout.cpp
|
||||
* Test pin number layout for stacked multi-line numbers across all rotations
|
||||
*/
|
||||
|
||||
#include <qa_utils/wx_utils/unit_test_utils.h>
|
||||
|
||||
#include <sch_pin.h>
|
||||
#include <lib_symbol.h>
|
||||
#include <pin_layout_cache.h>
|
||||
#include <transform.h>
|
||||
#include <sch_io/sch_io_mgr.h>
|
||||
|
||||
#include <wx/log.h>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
BOOST_AUTO_TEST_SUITE( PinStackedLayout )
|
||||
|
||||
/**
|
||||
* Create a test symbol with stacked pin numbers for rotation testing
|
||||
*/
|
||||
static std::unique_ptr<LIB_SYMBOL> createTestResistorSymbol()
|
||||
{
|
||||
auto symbol = std::make_unique<LIB_SYMBOL>( wxT( "TestResistor" ) );
|
||||
|
||||
// Create first pin with stacked numbers [1-5]
|
||||
auto pin1 = std::make_unique<SCH_PIN>( symbol.get() );
|
||||
pin1->SetPosition( VECTOR2I( 0, schIUScale.MilsToIU( 250 ) ) ); // top pin
|
||||
pin1->SetOrientation( PIN_ORIENTATION::PIN_DOWN );
|
||||
pin1->SetLength( schIUScale.MilsToIU( 50 ) );
|
||||
pin1->SetNumber( wxT( "[1-5]" ) );
|
||||
pin1->SetName( wxT( "A" ) ); // Short name
|
||||
pin1->SetType( ELECTRICAL_PINTYPE::PT_PASSIVE );
|
||||
pin1->SetUnit( 1 );
|
||||
|
||||
// Create second pin with stacked numbers [6,7,9-11]
|
||||
auto pin2 = std::make_unique<SCH_PIN>( symbol.get() );
|
||||
pin2->SetPosition( VECTOR2I( 0, schIUScale.MilsToIU( -340 ) ) ); // bottom pin
|
||||
pin2->SetOrientation( PIN_ORIENTATION::PIN_UP );
|
||||
pin2->SetLength( schIUScale.MilsToIU( 50 ) );
|
||||
pin2->SetNumber( wxT( "[6,7,9-11]" ) );
|
||||
pin2->SetName( wxT( "B" ) ); // Short name
|
||||
pin2->SetType( ELECTRICAL_PINTYPE::PT_PASSIVE );
|
||||
pin2->SetUnit( 1 );
|
||||
|
||||
// Add pins to symbol
|
||||
symbol->AddDrawItem( pin1.release() );
|
||||
symbol->AddDrawItem( pin2.release() );
|
||||
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pin geometry (line segment from connection point to pin end)
|
||||
*/
|
||||
static VECTOR2I getPinLineEnd( const SCH_PIN* pin, const TRANSFORM& transform )
|
||||
{
|
||||
VECTOR2I start = pin->GetPosition();
|
||||
VECTOR2I end = start;
|
||||
|
||||
int length = pin->GetLength();
|
||||
|
||||
switch( pin->PinDrawOrient( transform ) )
|
||||
{
|
||||
case PIN_ORIENTATION::PIN_UP:
|
||||
end.y += length;
|
||||
break;
|
||||
case PIN_ORIENTATION::PIN_DOWN:
|
||||
end.y -= length;
|
||||
break;
|
||||
case PIN_ORIENTATION::PIN_LEFT:
|
||||
end.x -= length;
|
||||
break;
|
||||
case PIN_ORIENTATION::PIN_RIGHT:
|
||||
end.x += length;
|
||||
break;
|
||||
case PIN_ORIENTATION::INHERIT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a box intersects with a line segment
|
||||
*/
|
||||
static bool boxIntersectsLine( const BOX2I& box, const VECTOR2I& lineStart, const VECTOR2I& lineEnd )
|
||||
{
|
||||
// Simple bbox vs line segment intersection
|
||||
// First check if line bbox intersects text bbox
|
||||
BOX2I lineBbox;
|
||||
lineBbox.SetOrigin( std::min( lineStart.x, lineEnd.x ), std::min( lineStart.y, lineEnd.y ) );
|
||||
lineBbox.SetEnd( std::max( lineStart.x, lineEnd.x ), std::max( lineStart.y, lineEnd.y ) );
|
||||
|
||||
if( !lineBbox.Intersects( box ) )
|
||||
return false;
|
||||
|
||||
// For vertical/horizontal lines, do precise check
|
||||
if( lineStart.x == lineEnd.x ) // vertical line
|
||||
{
|
||||
int lineX = lineStart.x;
|
||||
return ( lineX >= box.GetLeft() && lineX <= box.GetRight() &&
|
||||
box.GetTop() <= std::max( lineStart.y, lineEnd.y ) &&
|
||||
box.GetBottom() >= std::min( lineStart.y, lineEnd.y ) );
|
||||
}
|
||||
else if( lineStart.y == lineEnd.y ) // horizontal line
|
||||
{
|
||||
int lineY = lineStart.y;
|
||||
return ( lineY >= box.GetBottom() && lineY <= box.GetTop() &&
|
||||
box.GetLeft() <= std::max( lineStart.x, lineEnd.x ) &&
|
||||
box.GetRight() >= std::min( lineStart.x, lineEnd.x ) );
|
||||
}
|
||||
|
||||
// For diagonal lines, use the bbox intersection as approximation
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that pin numbers don't overlap with pin geometry across all rotations
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( PinNumbersNoOverlapAllRotations )
|
||||
{
|
||||
// Create test symbol
|
||||
auto symbol = createTestResistorSymbol();
|
||||
BOOST_REQUIRE( symbol );
|
||||
|
||||
// Get the pins
|
||||
std::vector<SCH_PIN*> pins;
|
||||
for( auto& item : symbol->GetDrawItems() )
|
||||
{
|
||||
if( item.Type() == SCH_PIN_T )
|
||||
pins.push_back( static_cast<SCH_PIN*>( &item ) );
|
||||
}
|
||||
|
||||
BOOST_REQUIRE_EQUAL( pins.size(), 2 );
|
||||
|
||||
// Test rotations: 0°, 90°, 180°, 270°
|
||||
std::vector<TRANSFORM> rotations = {
|
||||
TRANSFORM( 1, 0, 0, 1 ), // 0° (identity)
|
||||
TRANSFORM( 0, -1, 1, 0 ), // 90° CCW
|
||||
TRANSFORM( -1, 0, 0, -1 ), // 180°
|
||||
TRANSFORM( 0, 1, -1, 0 ) // 270° CCW (90° CW)
|
||||
};
|
||||
|
||||
std::vector<wxString> rotationNames = { wxT("0°"), wxT("90°"), wxT("180°"), wxT("270°") };
|
||||
|
||||
for( size_t r = 0; r < rotations.size(); r++ )
|
||||
{
|
||||
const TRANSFORM& transform = rotations[r];
|
||||
const wxString& rotName = rotationNames[r];
|
||||
|
||||
// Set global transform for this test
|
||||
TRANSFORM oldTransform = DefaultTransform;
|
||||
DefaultTransform = transform;
|
||||
|
||||
for( size_t p = 0; p < pins.size(); p++ )
|
||||
{
|
||||
SCH_PIN* pin = pins[p];
|
||||
|
||||
// Create layout cache for this pin
|
||||
PIN_LAYOUT_CACHE cache( *pin );
|
||||
|
||||
// Get pin number text info (shadow width 0 for testing)
|
||||
auto numberInfoOpt = cache.GetPinNumberInfo( 0 );
|
||||
|
||||
if( !numberInfoOpt.has_value() )
|
||||
continue;
|
||||
|
||||
PIN_LAYOUT_CACHE::TEXT_INFO numberInfo = numberInfoOpt.value();
|
||||
|
||||
if( numberInfo.m_Text.IsEmpty() )
|
||||
continue;
|
||||
|
||||
// Get pin line geometry
|
||||
VECTOR2I pinStart = pin->GetPosition();
|
||||
VECTOR2I pinEnd = getPinLineEnd( pin, transform );
|
||||
|
||||
// Get text bounding box - we need to estimate this since we don't have full font rendering
|
||||
// For now, use a simple estimation based on text size and string length
|
||||
int textHeight = numberInfo.m_TextSize;
|
||||
int textWidth = numberInfo.m_Text.Length() * numberInfo.m_TextSize * 0.6; // rough char width
|
||||
|
||||
// Handle multi-line text
|
||||
if( numberInfo.m_Text.Contains( '\n' ) )
|
||||
{
|
||||
wxArrayString lines;
|
||||
wxStringSplit( numberInfo.m_Text, lines, '\n' );
|
||||
|
||||
if( numberInfo.m_Angle == ANGLE_VERTICAL )
|
||||
{
|
||||
// For vertical text, lines are spaced horizontally
|
||||
int lineSpacing = textHeight * 1.3;
|
||||
textWidth = lines.size() * lineSpacing;
|
||||
// Find longest line for height
|
||||
size_t maxLen = 0;
|
||||
for( const wxString& line : lines )
|
||||
maxLen = std::max( maxLen, line.Length() );
|
||||
textHeight = maxLen * textHeight * 0.6;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For horizontal text, lines are spaced vertically
|
||||
int lineSpacing = textHeight * 1.3;
|
||||
textHeight = lines.size() * lineSpacing;
|
||||
// Find longest line for width
|
||||
size_t maxLen = 0;
|
||||
for( const wxString& line : lines )
|
||||
maxLen = std::max( maxLen, line.Length() );
|
||||
textWidth = maxLen * textHeight * 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
// Create text bounding box around text position
|
||||
BOX2I textBbox;
|
||||
textBbox.SetOrigin( numberInfo.m_TextPosition.x - textWidth/2,
|
||||
numberInfo.m_TextPosition.y - textHeight/2 );
|
||||
textBbox.SetSize( textWidth, textHeight );
|
||||
|
||||
// Check for intersection
|
||||
bool overlaps = boxIntersectsLine( textBbox, pinStart, pinEnd );
|
||||
|
||||
// Log detailed info for debugging
|
||||
wxLogMessage( wxT("Rotation %s, Pin %s: pos=(%d,%d) textPos=(%d,%d) pinLine=(%d,%d)-(%d,%d) textBox=(%d,%d,%dx%d) overlap=%s"),
|
||||
rotName, pin->GetNumber(),
|
||||
pinStart.x, pinStart.y,
|
||||
numberInfo.m_TextPosition.x, numberInfo.m_TextPosition.y,
|
||||
pinStart.x, pinStart.y, pinEnd.x, pinEnd.y,
|
||||
(int)textBbox.GetLeft(), (int)textBbox.GetTop(), (int)textBbox.GetWidth(), (int)textBbox.GetHeight(),
|
||||
overlaps ? wxT("YES") : wxT("NO") );
|
||||
|
||||
// Test assertion
|
||||
BOOST_CHECK_MESSAGE( !overlaps,
|
||||
"Pin number '" << pin->GetNumber() << "' overlaps with pin geometry at rotation " << rotName );
|
||||
}
|
||||
|
||||
// Restore original transform
|
||||
DefaultTransform = oldTransform;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that multiline and non-multiline pin numbers/names are positioned consistently
|
||||
* on the same side of the pin for each rotation
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( PinTextConsistentSidePlacement )
|
||||
{
|
||||
// Create test symbol with both types of pins
|
||||
auto symbol = createTestResistorSymbol();
|
||||
BOOST_REQUIRE( symbol );
|
||||
|
||||
// Get the pins - one will be multiline formatted, one will not
|
||||
std::vector<SCH_PIN*> pins;
|
||||
for( auto& item : symbol->GetDrawItems() )
|
||||
{
|
||||
if( item.Type() == SCH_PIN_T )
|
||||
pins.push_back( static_cast<SCH_PIN*>( &item ) );
|
||||
}
|
||||
|
||||
BOOST_REQUIRE_EQUAL( pins.size(), 2 );
|
||||
|
||||
// Test rotations
|
||||
std::vector<TRANSFORM> rotations = {
|
||||
TRANSFORM( 1, 0, 0, 1 ), // 0° (identity)
|
||||
TRANSFORM( 0, -1, 1, 0 ), // 90° CCW
|
||||
TRANSFORM( -1, 0, 0, -1 ), // 180°
|
||||
TRANSFORM( 0, 1, -1, 0 ) // 270° CCW (90° CW)
|
||||
};
|
||||
|
||||
std::vector<wxString> rotationNames = { wxT("0°"), wxT("90°"), wxT("180°"), wxT("270°") };
|
||||
|
||||
for( size_t r = 0; r < rotations.size(); r++ )
|
||||
{
|
||||
const TRANSFORM& transform = rotations[r];
|
||||
const wxString& rotName = rotationNames[r];
|
||||
|
||||
// Set global transform for this test
|
||||
TRANSFORM oldTransform = DefaultTransform;
|
||||
DefaultTransform = transform;
|
||||
|
||||
// For each rotation, collect pin number and name positions relative to pin center
|
||||
struct PinTextInfo {
|
||||
VECTOR2I pinPos;
|
||||
VECTOR2I numberPos;
|
||||
VECTOR2I namePos;
|
||||
wxString pinNumber;
|
||||
bool isMultiline;
|
||||
};
|
||||
|
||||
std::vector<PinTextInfo> pinInfos;
|
||||
|
||||
for( auto* pin : pins )
|
||||
{
|
||||
PinTextInfo info;
|
||||
info.pinPos = pin->GetPosition();
|
||||
info.pinNumber = pin->GetNumber();
|
||||
|
||||
// Create layout cache for this pin
|
||||
PIN_LAYOUT_CACHE cache( *pin );
|
||||
|
||||
// Get number position (shadow width 0 for testing)
|
||||
auto numberInfoOpt = cache.GetPinNumberInfo( 0 );
|
||||
if( numberInfoOpt.has_value() )
|
||||
{
|
||||
auto numberInfo = numberInfoOpt.value();
|
||||
info.numberPos = numberInfo.m_TextPosition;
|
||||
info.isMultiline = numberInfo.m_Text.Contains( '\n' );
|
||||
}
|
||||
|
||||
// Get name position
|
||||
auto nameInfoOpt = cache.GetPinNameInfo( 0 );
|
||||
if( nameInfoOpt.has_value() )
|
||||
{
|
||||
auto nameInfo = nameInfoOpt.value();
|
||||
info.namePos = nameInfo.m_TextPosition;
|
||||
}
|
||||
|
||||
pinInfos.push_back( info );
|
||||
|
||||
wxLogDebug( "Rotation %s, Pin %s: pos=(%d,%d) numberPos=(%d,%d) namePos=(%d,%d) multiline=%s",
|
||||
rotName, info.pinNumber,
|
||||
info.pinPos.x, info.pinPos.y,
|
||||
info.numberPos.x, info.numberPos.y,
|
||||
info.namePos.x, info.namePos.y,
|
||||
info.isMultiline ? wxT("YES") : wxT("NO") );
|
||||
}
|
||||
|
||||
BOOST_REQUIRE_EQUAL( pinInfos.size(), 2 );
|
||||
|
||||
// New semantics:
|
||||
// * Vertical pins (UP/DOWN): numbers and names must be LEFT (x < pin.x)
|
||||
// * Horizontal pins (LEFT/RIGHT): numbers/names must be ABOVE (y < pin.y)
|
||||
PIN_ORIENTATION orient = pins[0]->PinDrawOrient( DefaultTransform );
|
||||
|
||||
if( orient == PIN_ORIENTATION::PIN_UP || orient == PIN_ORIENTATION::PIN_DOWN )
|
||||
{
|
||||
for( const auto& inf : pinInfos )
|
||||
{
|
||||
BOOST_CHECK_MESSAGE( inf.numberPos.x < inf.pinPos.x,
|
||||
"At rotation " << rotName << ", number for pin " << inf.pinNumber << " not left of vertical pin." );
|
||||
BOOST_CHECK_MESSAGE( inf.namePos.x < inf.pinPos.x,
|
||||
"At rotation " << rotName << ", name for pin " << inf.pinNumber << " not left of vertical pin." );
|
||||
}
|
||||
}
|
||||
else if( orient == PIN_ORIENTATION::PIN_LEFT || orient == PIN_ORIENTATION::PIN_RIGHT )
|
||||
{
|
||||
for( const auto& inf : pinInfos )
|
||||
{
|
||||
BOOST_CHECK_MESSAGE( inf.numberPos.y < inf.pinPos.y,
|
||||
"At rotation " << rotName << ", number for pin " << inf.pinNumber << " not above horizontal pin." );
|
||||
BOOST_CHECK_MESSAGE( inf.namePos.y < inf.pinPos.y,
|
||||
"At rotation " << rotName << ", name for pin " << inf.pinNumber << " not above horizontal pin." );
|
||||
}
|
||||
}
|
||||
|
||||
// Restore original transform
|
||||
DefaultTransform = oldTransform;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that multiline and non-multiline pin numbers/names have the same bottom coordinate
|
||||
* (distance from pin along the axis connecting pin and text)
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( PinTextSameBottomCoordinate )
|
||||
{
|
||||
// Create test symbol with both types of pins
|
||||
auto symbol = createTestResistorSymbol();
|
||||
BOOST_REQUIRE( symbol );
|
||||
|
||||
// Get the pins - one will be multiline formatted, one will not
|
||||
std::vector<SCH_PIN*> pins;
|
||||
for( auto& item : symbol->GetDrawItems() )
|
||||
{
|
||||
if( item.Type() == SCH_PIN_T )
|
||||
pins.push_back( static_cast<SCH_PIN*>( &item ) );
|
||||
}
|
||||
|
||||
BOOST_REQUIRE_EQUAL( pins.size(), 2 );
|
||||
|
||||
// Test rotations
|
||||
std::vector<TRANSFORM> rotations = {
|
||||
TRANSFORM( 1, 0, 0, 1 ), // 0° (identity)
|
||||
TRANSFORM( 0, -1, 1, 0 ), // 90° CCW
|
||||
TRANSFORM( -1, 0, 0, -1 ), // 180°
|
||||
TRANSFORM( 0, 1, -1, 0 ) // 270° CCW (90° CW)
|
||||
};
|
||||
|
||||
std::vector<wxString> rotationNames = { wxT("0°"), wxT("90°"), wxT("180°"), wxT("270°") };
|
||||
|
||||
for( size_t r = 0; r < rotations.size(); r++ )
|
||||
{
|
||||
const TRANSFORM& transform = rotations[r];
|
||||
const wxString& rotName = rotationNames[r];
|
||||
|
||||
// Set global transform for this test
|
||||
TRANSFORM oldTransform = DefaultTransform;
|
||||
DefaultTransform = transform;
|
||||
|
||||
// For each rotation, collect pin and text position data
|
||||
struct PinTextData {
|
||||
VECTOR2I pinPos;
|
||||
VECTOR2I numberPos;
|
||||
VECTOR2I namePos;
|
||||
wxString pinNumber;
|
||||
bool isMultiline;
|
||||
int numberBottomDistance;
|
||||
int nameBottomDistance;
|
||||
};
|
||||
|
||||
std::vector<PinTextData> pinData;
|
||||
|
||||
for( auto* pin : pins )
|
||||
{
|
||||
PinTextData data;
|
||||
data.pinPos = pin->GetPosition();
|
||||
data.pinNumber = pin->GetNumber();
|
||||
|
||||
// Create layout cache for this pin
|
||||
PIN_LAYOUT_CACHE cache( *pin );
|
||||
|
||||
// Get number position (shadow width 0 for testing)
|
||||
auto numberInfoOpt = cache.GetPinNumberInfo( 0 );
|
||||
PIN_LAYOUT_CACHE::TEXT_INFO numberInfo; // store for later heuristics
|
||||
if( numberInfoOpt.has_value() )
|
||||
{
|
||||
numberInfo = numberInfoOpt.value();
|
||||
data.numberPos = numberInfo.m_TextPosition;
|
||||
data.isMultiline = numberInfo.m_Text.Contains( '\n' );
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOST_FAIL( "Expected pin number text info" );
|
||||
}
|
||||
|
||||
// Get name position
|
||||
auto nameInfoOpt = cache.GetPinNameInfo( 0 );
|
||||
PIN_LAYOUT_CACHE::TEXT_INFO nameInfo; // store for width/height heuristic
|
||||
if( nameInfoOpt.has_value() )
|
||||
{
|
||||
nameInfo = nameInfoOpt.value();
|
||||
data.namePos = nameInfo.m_TextPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOST_FAIL( "Expected pin name text info" );
|
||||
}
|
||||
|
||||
// Calculate bottom distance (closest distance to pin along pin-text axis)
|
||||
PIN_ORIENTATION orient = pin->PinDrawOrient( DefaultTransform );
|
||||
|
||||
if( orient == PIN_ORIENTATION::PIN_UP || orient == PIN_ORIENTATION::PIN_DOWN )
|
||||
{
|
||||
// Vertical pins: measure clearance from pin (at pin.x) to RIGHT edge of text box.
|
||||
// We approximate half width from text length heuristic.
|
||||
int textWidth = data.isMultiline ? 0 : (int)( data.pinNumber.Length() * numberInfo.m_TextSize * 0.6 );
|
||||
// (Multiline case: numberInfo.m_Text already contains \n; heuristic in earlier section)
|
||||
if( data.isMultiline )
|
||||
{
|
||||
wxArrayString lines; wxStringSplit( numberInfo.m_Text, lines, '\n' );
|
||||
int lineSpacing = numberInfo.m_TextSize * 1.3;
|
||||
textWidth = lines.size() * lineSpacing; // when vertical orientation text is rotated
|
||||
}
|
||||
int rightEdge = data.numberPos.x + textWidth / 2;
|
||||
data.numberBottomDistance = data.pinPos.x - rightEdge; // positive gap
|
||||
int nameWidth = (int)( nameInfo.m_Text.Length() * nameInfo.m_TextSize * 0.6 );
|
||||
int nameRightEdge = data.namePos.x + nameWidth / 2;
|
||||
data.nameBottomDistance = data.pinPos.x - nameRightEdge; // expect similar across pins
|
||||
}
|
||||
else
|
||||
{
|
||||
// Horizontal pins: we align centers at a fixed offset above the pin. Measure center gap.
|
||||
data.numberBottomDistance = data.pinPos.y - data.numberPos.y; // center gap constant
|
||||
data.nameBottomDistance = data.pinPos.y - data.namePos.y; // center gap for names
|
||||
}
|
||||
|
||||
pinData.push_back( data );
|
||||
|
||||
wxLogDebug( "Rotation %s, Pin %s: pos=(%d,%d) numberPos=(%d,%d) namePos=(%d,%d) multiline=%s numberBottomDist=%d nameBottomDist=%d",
|
||||
rotName, data.pinNumber,
|
||||
data.pinPos.x, data.pinPos.y,
|
||||
data.numberPos.x, data.numberPos.y,
|
||||
data.namePos.x, data.namePos.y,
|
||||
data.isMultiline ? wxT("YES") : wxT("NO"),
|
||||
data.numberBottomDistance, data.nameBottomDistance );
|
||||
}
|
||||
|
||||
BOOST_REQUIRE_EQUAL( pinData.size(), 2 );
|
||||
|
||||
// Check that both pins have their numbers at the same bottom distance from pin
|
||||
// Allow small tolerance for rounding differences
|
||||
const int tolerance = 100; // 100 internal units tolerance
|
||||
|
||||
int bottomDist1 = pinData[0].numberBottomDistance;
|
||||
int bottomDist2 = pinData[1].numberBottomDistance;
|
||||
int distanceDiff = abs( bottomDist1 - bottomDist2 );
|
||||
|
||||
BOOST_CHECK_MESSAGE( distanceDiff <= tolerance,
|
||||
"At rotation " << rotName << ", pin numbers have different bottom distances from pin. "
|
||||
<< "Pin " << pinData[0].pinNumber << " distance=" << bottomDist1
|
||||
<< ", Pin " << pinData[1].pinNumber << " distance=" << bottomDist2
|
||||
<< ", difference=" << distanceDiff << " (tolerance=" << tolerance << ")" );
|
||||
|
||||
// Check that both pins have their names at the same bottom distance from pin
|
||||
int nameBottomDist1 = pinData[0].nameBottomDistance;
|
||||
int nameBottomDist2 = pinData[1].nameBottomDistance;
|
||||
int nameDistanceDiff = abs( nameBottomDist1 - nameBottomDist2 );
|
||||
|
||||
BOOST_CHECK_MESSAGE( nameDistanceDiff <= tolerance,
|
||||
"At rotation " << rotName << ", pin names have different bottom distances from pin. "
|
||||
<< "Pin " << pinData[0].pinNumber << " name distance=" << nameBottomDist1
|
||||
<< ", Pin " << pinData[1].pinNumber << " name distance=" << nameBottomDist2
|
||||
<< ", difference=" << nameDistanceDiff << " (tolerance=" << tolerance << ")" );
|
||||
|
||||
// Restore original transform
|
||||
DefaultTransform = oldTransform;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
742
qa/tests/eeschema/test_stacked_pin_conversion.cpp
Normal file
742
qa/tests/eeschema/test_stacked_pin_conversion.cpp
Normal file
@ -0,0 +1,742 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2024 KiCad Developers
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <qa_utils/wx_utils/unit_test_utils.h>
|
||||
#include <sch_pin.h>
|
||||
#include <lib_symbol.h>
|
||||
#include <eeschema_test_utils.h>
|
||||
|
||||
struct STACKED_PIN_CONVERSION_FIXTURE
|
||||
{
|
||||
STACKED_PIN_CONVERSION_FIXTURE()
|
||||
{
|
||||
m_settingsManager = std::make_unique<SETTINGS_MANAGER>( true );
|
||||
m_symbol = std::make_unique<LIB_SYMBOL>( "TestSymbol" );
|
||||
}
|
||||
|
||||
std::unique_ptr<SETTINGS_MANAGER> m_settingsManager;
|
||||
std::unique_ptr<LIB_SYMBOL> m_symbol;
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE( StackedPinConversion, STACKED_PIN_CONVERSION_FIXTURE )
|
||||
|
||||
|
||||
/**
|
||||
* Test basic stacked pin number expansion functionality
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestStackedPinExpansion )
|
||||
{
|
||||
// Test simple list notation
|
||||
SCH_PIN* pin = new SCH_PIN( m_symbol.get() );
|
||||
pin->SetNumber( wxT("[1,2,3]") );
|
||||
|
||||
bool isValid;
|
||||
std::vector<wxString> expanded = pin->GetStackedPinNumbers( &isValid );
|
||||
|
||||
BOOST_CHECK( isValid );
|
||||
BOOST_REQUIRE_EQUAL( expanded.size(), 3 );
|
||||
BOOST_CHECK_EQUAL( expanded[0], "1" );
|
||||
BOOST_CHECK_EQUAL( expanded[1], "2" );
|
||||
BOOST_CHECK_EQUAL( expanded[2], "3" );
|
||||
|
||||
delete pin;
|
||||
|
||||
// Test range notation
|
||||
pin = new SCH_PIN( m_symbol.get() );
|
||||
pin->SetNumber( wxT("[5-7]") );
|
||||
|
||||
expanded = pin->GetStackedPinNumbers( &isValid );
|
||||
|
||||
BOOST_CHECK( isValid );
|
||||
BOOST_REQUIRE_EQUAL( expanded.size(), 3 );
|
||||
BOOST_CHECK_EQUAL( expanded[0], "5" );
|
||||
BOOST_CHECK_EQUAL( expanded[1], "6" );
|
||||
BOOST_CHECK_EQUAL( expanded[2], "7" );
|
||||
|
||||
delete pin;
|
||||
|
||||
// Test mixed notation
|
||||
pin = new SCH_PIN( m_symbol.get() );
|
||||
pin->SetNumber( wxT("[1,3,5-7]") );
|
||||
|
||||
expanded = pin->GetStackedPinNumbers( &isValid );
|
||||
|
||||
BOOST_CHECK( isValid );
|
||||
BOOST_REQUIRE_EQUAL( expanded.size(), 5 );
|
||||
BOOST_CHECK_EQUAL( expanded[0], "1" );
|
||||
BOOST_CHECK_EQUAL( expanded[1], "3" );
|
||||
BOOST_CHECK_EQUAL( expanded[2], "5" );
|
||||
BOOST_CHECK_EQUAL( expanded[3], "6" );
|
||||
BOOST_CHECK_EQUAL( expanded[4], "7" );
|
||||
|
||||
delete pin;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test stacked pin validity checking
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestStackedPinValidity )
|
||||
{
|
||||
SCH_PIN* pin = new SCH_PIN( m_symbol.get() );
|
||||
|
||||
// Test valid single pin (should not be considered stacked)
|
||||
pin->SetNumber( wxT("1") );
|
||||
bool isValid;
|
||||
std::vector<wxString> expanded = pin->GetStackedPinNumbers( &isValid );
|
||||
BOOST_CHECK( isValid );
|
||||
BOOST_CHECK_EQUAL( expanded.size(), 1 );
|
||||
BOOST_CHECK_EQUAL( expanded[0], "1" );
|
||||
|
||||
// Test invalid notation (malformed brackets)
|
||||
pin->SetNumber( wxT("[1,2") );
|
||||
expanded = pin->GetStackedPinNumbers( &isValid );
|
||||
BOOST_CHECK( !isValid );
|
||||
|
||||
// Test invalid range
|
||||
pin->SetNumber( wxT("[5-3]") ); // backwards range
|
||||
expanded = pin->GetStackedPinNumbers( &isValid );
|
||||
BOOST_CHECK( !isValid );
|
||||
|
||||
// Test empty brackets
|
||||
pin->SetNumber( wxT("[]") );
|
||||
expanded = pin->GetStackedPinNumbers( &isValid );
|
||||
BOOST_CHECK( !isValid );
|
||||
|
||||
delete pin;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test pin creation and positioning for conversion tests
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestPinCreation )
|
||||
{
|
||||
// Create multiple pins at the same location
|
||||
VECTOR2I position( 0, 0 );
|
||||
|
||||
SCH_PIN* pin1 = new SCH_PIN( m_symbol.get() );
|
||||
pin1->SetNumber( wxT("1") );
|
||||
pin1->SetPosition( position );
|
||||
pin1->SetOrientation( PIN_ORIENTATION::PIN_RIGHT );
|
||||
|
||||
SCH_PIN* pin2 = new SCH_PIN( m_symbol.get() );
|
||||
pin2->SetNumber( wxT("2") );
|
||||
pin2->SetPosition( position );
|
||||
pin2->SetOrientation( PIN_ORIENTATION::PIN_RIGHT );
|
||||
|
||||
SCH_PIN* pin3 = new SCH_PIN( m_symbol.get() );
|
||||
pin3->SetNumber( wxT("3") );
|
||||
pin3->SetPosition( position );
|
||||
pin3->SetOrientation( PIN_ORIENTATION::PIN_RIGHT );
|
||||
|
||||
// Verify pins are at same location
|
||||
BOOST_CHECK_EQUAL( pin1->GetPosition(), pin2->GetPosition() );
|
||||
BOOST_CHECK_EQUAL( pin2->GetPosition(), pin3->GetPosition() );
|
||||
|
||||
// Test IsStacked functionality
|
||||
BOOST_CHECK( pin1->IsStacked( pin2 ) );
|
||||
BOOST_CHECK( pin2->IsStacked( pin3 ) );
|
||||
|
||||
delete pin1;
|
||||
delete pin2;
|
||||
delete pin3;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test net name generation with stacked pins
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestStackedPinNetNaming )
|
||||
{
|
||||
SCH_PIN* pin = new SCH_PIN( m_symbol.get() );
|
||||
pin->SetNumber( wxT("[8,9,10]") );
|
||||
|
||||
// This test would require a full SCH_SYMBOL context to test GetDefaultNetName
|
||||
// For now just verify the pin number expansion works
|
||||
bool isValid;
|
||||
std::vector<wxString> expanded = pin->GetStackedPinNumbers( &isValid );
|
||||
|
||||
BOOST_CHECK( isValid );
|
||||
BOOST_REQUIRE_EQUAL( expanded.size(), 3 );
|
||||
// The smallest number should be first for deterministic net naming
|
||||
BOOST_CHECK_EQUAL( expanded[0], "8" );
|
||||
|
||||
delete pin;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test conversion from multiple co-located pins to stacked notation
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestConvertMultiplePinsToStacked )
|
||||
{
|
||||
// Create multiple pins at the same location
|
||||
VECTOR2I position( 0, 0 );
|
||||
|
||||
SCH_PIN* pin1 = new SCH_PIN( m_symbol.get() );
|
||||
pin1->SetNumber( wxT("1") );
|
||||
pin1->SetPosition( position );
|
||||
pin1->SetOrientation( PIN_ORIENTATION::PIN_RIGHT );
|
||||
pin1->SetVisible( true );
|
||||
|
||||
SCH_PIN* pin2 = new SCH_PIN( m_symbol.get() );
|
||||
pin2->SetNumber( wxT("2") );
|
||||
pin2->SetPosition( position );
|
||||
pin2->SetOrientation( PIN_ORIENTATION::PIN_RIGHT );
|
||||
pin2->SetVisible( true );
|
||||
|
||||
SCH_PIN* pin3 = new SCH_PIN( m_symbol.get() );
|
||||
pin3->SetNumber( wxT("3") );
|
||||
pin3->SetPosition( position );
|
||||
pin3->SetOrientation( PIN_ORIENTATION::PIN_RIGHT );
|
||||
pin3->SetVisible( true );
|
||||
|
||||
// Test basic property access before adding to symbol
|
||||
BOOST_CHECK_EQUAL( pin1->GetNumber(), "1" );
|
||||
BOOST_CHECK_EQUAL( pin2->GetNumber(), "2" );
|
||||
BOOST_CHECK_EQUAL( pin3->GetNumber(), "3" );
|
||||
|
||||
// Just test the basic conversion logic without symbol management
|
||||
// Build the stacked notation string
|
||||
wxString stackedNotation = wxT("[");
|
||||
stackedNotation += pin1->GetNumber();
|
||||
stackedNotation += wxT(",");
|
||||
stackedNotation += pin2->GetNumber();
|
||||
stackedNotation += wxT(",");
|
||||
stackedNotation += pin3->GetNumber();
|
||||
stackedNotation += wxT("]");
|
||||
|
||||
// Test stacked notation creation
|
||||
BOOST_CHECK_EQUAL( stackedNotation, "[1,2,3]" );
|
||||
|
||||
// Set stacked notation on one pin
|
||||
pin1->SetNumber( stackedNotation );
|
||||
BOOST_CHECK_EQUAL( pin1->GetNumber(), "[1,2,3]" );
|
||||
|
||||
// Verify the stacked pin expansion works
|
||||
bool isValid;
|
||||
std::vector<wxString> expanded = pin1->GetStackedPinNumbers( &isValid );
|
||||
BOOST_CHECK( isValid );
|
||||
BOOST_REQUIRE_EQUAL( expanded.size(), 3 );
|
||||
BOOST_CHECK_EQUAL( expanded[0], "1" );
|
||||
BOOST_CHECK_EQUAL( expanded[1], "2" );
|
||||
BOOST_CHECK_EQUAL( expanded[2], "3" );
|
||||
|
||||
// Clean up - delete pins manually since they're not in symbol
|
||||
delete pin1;
|
||||
delete pin2;
|
||||
delete pin3;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test range collapsing functionality in stacked pin conversion
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestRangeCollapsingConversion )
|
||||
{
|
||||
// Test range collapsing logic directly without symbol management
|
||||
|
||||
// Test consecutive pins that should collapse to a range
|
||||
std::vector<long> numbers = { 1, 2, 3, 4 };
|
||||
|
||||
// Build collapsed ranges
|
||||
wxString result;
|
||||
size_t i = 0;
|
||||
while( i < numbers.size() )
|
||||
{
|
||||
if( !result.IsEmpty() )
|
||||
result += wxT(",");
|
||||
|
||||
long start = numbers[i];
|
||||
long end = start;
|
||||
|
||||
// Find the end of consecutive sequence
|
||||
while( i + 1 < numbers.size() && numbers[i + 1] == numbers[i] + 1 )
|
||||
{
|
||||
i++;
|
||||
end = numbers[i];
|
||||
}
|
||||
|
||||
// Add range or single number
|
||||
if( end > start + 1 ) // Range of 3+ numbers
|
||||
result += wxString::Format( wxT("%ld-%ld"), start, end );
|
||||
else if( end == start + 1 ) // Two consecutive numbers
|
||||
result += wxString::Format( wxT("%ld,%ld"), start, end );
|
||||
else // Single number
|
||||
result += wxString::Format( wxT("%ld"), start );
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// Verify range collapsing: 1,2,3,4 should become "1-4"
|
||||
BOOST_CHECK_EQUAL( result, "1-4" );
|
||||
|
||||
// Test with mixed consecutive and non-consecutive: 1,2,3,4,7,8,9
|
||||
numbers = { 1, 2, 3, 4, 7, 8, 9 };
|
||||
result.Clear();
|
||||
i = 0;
|
||||
|
||||
while( i < numbers.size() )
|
||||
{
|
||||
if( !result.IsEmpty() )
|
||||
result += wxT(",");
|
||||
|
||||
long start = numbers[i];
|
||||
long end = start;
|
||||
|
||||
// Find the end of consecutive sequence
|
||||
while( i + 1 < numbers.size() && numbers[i + 1] == numbers[i] + 1 )
|
||||
{
|
||||
i++;
|
||||
end = numbers[i];
|
||||
}
|
||||
|
||||
// Add range or single number
|
||||
if( end > start + 1 ) // Range of 3+ numbers
|
||||
result += wxString::Format( wxT("%ld-%ld"), start, end );
|
||||
else if( end == start + 1 ) // Two consecutive numbers
|
||||
result += wxString::Format( wxT("%ld,%ld"), start, end );
|
||||
else // Single number
|
||||
result += wxString::Format( wxT("%ld"), start );
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// Verify mixed ranges: 1,2,3,4,7,8,9 should become "1-4,7-9"
|
||||
BOOST_CHECK_EQUAL( result, "1-4,7-9" );
|
||||
|
||||
// Test edge cases
|
||||
numbers = { 1, 3, 5 }; // Non-consecutive
|
||||
result.Clear();
|
||||
i = 0;
|
||||
|
||||
while( i < numbers.size() )
|
||||
{
|
||||
if( !result.IsEmpty() )
|
||||
result += wxT(",");
|
||||
|
||||
long start = numbers[i];
|
||||
long end = start;
|
||||
|
||||
// Find the end of consecutive sequence
|
||||
while( i + 1 < numbers.size() && numbers[i + 1] == numbers[i] + 1 )
|
||||
{
|
||||
i++;
|
||||
end = numbers[i];
|
||||
}
|
||||
|
||||
// Add range or single number
|
||||
if( end > start + 1 ) // Range of 3+ numbers
|
||||
result += wxString::Format( wxT("%ld-%ld"), start, end );
|
||||
else if( end == start + 1 ) // Two consecutive numbers
|
||||
result += wxString::Format( wxT("%ld,%ld"), start, end );
|
||||
else // Single number
|
||||
result += wxString::Format( wxT("%ld"), start );
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// Verify non-consecutive: 1,3,5 should remain "1,3,5"
|
||||
BOOST_CHECK_EQUAL( result, "1,3,5" );
|
||||
|
||||
// Test two consecutive numbers
|
||||
numbers = { 5, 6 };
|
||||
result.Clear();
|
||||
i = 0;
|
||||
|
||||
while( i < numbers.size() )
|
||||
{
|
||||
if( !result.IsEmpty() )
|
||||
result += wxT(",");
|
||||
|
||||
long start = numbers[i];
|
||||
long end = start;
|
||||
|
||||
// Find the end of consecutive sequence
|
||||
while( i + 1 < numbers.size() && numbers[i + 1] == numbers[i] + 1 )
|
||||
{
|
||||
i++;
|
||||
end = numbers[i];
|
||||
}
|
||||
|
||||
// Add range or single number
|
||||
if( end > start + 1 ) // Range of 3+ numbers
|
||||
result += wxString::Format( wxT("%ld-%ld"), start, end );
|
||||
else if( end == start + 1 ) // Two consecutive numbers
|
||||
result += wxString::Format( wxT("%ld,%ld"), start, end );
|
||||
else // Single number
|
||||
result += wxString::Format( wxT("%ld"), start );
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// Verify two consecutive: 5,6 should remain "5,6" (not convert to range)
|
||||
BOOST_CHECK_EQUAL( result, "5,6" );
|
||||
|
||||
// Test complex mixed case: 1,2,4,5,6,8,9,10,11
|
||||
numbers = { 1, 2, 4, 5, 6, 8, 9, 10, 11 };
|
||||
result.Clear();
|
||||
i = 0;
|
||||
|
||||
while( i < numbers.size() )
|
||||
{
|
||||
if( !result.IsEmpty() )
|
||||
result += wxT(",");
|
||||
|
||||
long start = numbers[i];
|
||||
long end = start;
|
||||
|
||||
// Find the end of consecutive sequence
|
||||
while( i + 1 < numbers.size() && numbers[i + 1] == numbers[i] + 1 )
|
||||
{
|
||||
i++;
|
||||
end = numbers[i];
|
||||
}
|
||||
|
||||
// Add range or single number
|
||||
if( end > start + 1 ) // Range of 3+ numbers
|
||||
result += wxString::Format( wxT("%ld-%ld"), start, end );
|
||||
else if( end == start + 1 ) // Two consecutive numbers
|
||||
result += wxString::Format( wxT("%ld,%ld"), start, end );
|
||||
else // Single number
|
||||
result += wxString::Format( wxT("%ld"), start );
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// Verify complex case: 1,2,4,5,6,8,9,10,11 should become "1,2,4-6,8-11"
|
||||
BOOST_CHECK_EQUAL( result, "1,2,4-6,8-11" );
|
||||
|
||||
// Test that our range notation can be expanded back correctly
|
||||
SCH_PIN* rangePin = new SCH_PIN( m_symbol.get() );
|
||||
rangePin->SetNumber( wxT("[1-4,7-9]") );
|
||||
|
||||
bool isValid;
|
||||
std::vector<wxString> expanded = rangePin->GetStackedPinNumbers( &isValid );
|
||||
BOOST_CHECK( isValid );
|
||||
BOOST_REQUIRE_EQUAL( expanded.size(), 7 );
|
||||
|
||||
// Should expand to: 1,2,3,4,7,8,9
|
||||
BOOST_CHECK_EQUAL( expanded[0], "1" );
|
||||
BOOST_CHECK_EQUAL( expanded[1], "2" );
|
||||
BOOST_CHECK_EQUAL( expanded[2], "3" );
|
||||
BOOST_CHECK_EQUAL( expanded[3], "4" );
|
||||
BOOST_CHECK_EQUAL( expanded[4], "7" );
|
||||
BOOST_CHECK_EQUAL( expanded[5], "8" );
|
||||
BOOST_CHECK_EQUAL( expanded[6], "9" );
|
||||
|
||||
delete rangePin;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test round-trip conversion: multiple pins -> stacked -> multiple pins
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestRoundTripConversion )
|
||||
{
|
||||
// Create multiple pins at the same location
|
||||
VECTOR2I position( 100, 200 );
|
||||
|
||||
SCH_PIN* pin5 = new SCH_PIN( m_symbol.get() );
|
||||
pin5->SetNumber( wxT("5") );
|
||||
pin5->SetPosition( position );
|
||||
pin5->SetOrientation( PIN_ORIENTATION::PIN_LEFT );
|
||||
pin5->SetType( ELECTRICAL_PINTYPE::PT_INPUT );
|
||||
pin5->SetName( wxT("TestPin") );
|
||||
pin5->SetVisible( true );
|
||||
|
||||
SCH_PIN* pin7 = new SCH_PIN( m_symbol.get() );
|
||||
pin7->SetNumber( wxT("7") );
|
||||
pin7->SetPosition( position );
|
||||
pin7->SetOrientation( PIN_ORIENTATION::PIN_LEFT );
|
||||
pin7->SetType( ELECTRICAL_PINTYPE::PT_INPUT );
|
||||
pin7->SetName( wxT("TestPin") );
|
||||
pin7->SetVisible( true );
|
||||
|
||||
SCH_PIN* pin9 = new SCH_PIN( m_symbol.get() );
|
||||
pin9->SetNumber( wxT("9") );
|
||||
pin9->SetPosition( position );
|
||||
pin9->SetOrientation( PIN_ORIENTATION::PIN_LEFT );
|
||||
pin9->SetType( ELECTRICAL_PINTYPE::PT_INPUT );
|
||||
pin9->SetName( wxT("TestPin") );
|
||||
pin9->SetVisible( true );
|
||||
|
||||
// Store original properties for comparison
|
||||
PIN_ORIENTATION originalOrientation = pin5->GetOrientation();
|
||||
ELECTRICAL_PINTYPE originalType = pin5->GetType();
|
||||
wxString originalName = pin5->GetName();
|
||||
int originalLength = pin5->GetLength();
|
||||
|
||||
// Step 1: Convert to stacked notation (simulating ConvertStackedPins)
|
||||
std::vector<SCH_PIN*> pinsToConvert = { pin5, pin7, pin9 };
|
||||
|
||||
// Sort pins numerically
|
||||
std::sort( pinsToConvert.begin(), pinsToConvert.end(),
|
||||
[]( SCH_PIN* a, SCH_PIN* b )
|
||||
{
|
||||
long numA, numB;
|
||||
if( a->GetNumber().ToLong( &numA ) && b->GetNumber().ToLong( &numB ) )
|
||||
return numA < numB;
|
||||
return a->GetNumber() < b->GetNumber();
|
||||
});
|
||||
|
||||
// Build stacked notation
|
||||
wxString stackedNotation = wxT("[5,7,9]");
|
||||
pinsToConvert[0]->SetNumber( stackedNotation );
|
||||
|
||||
// Remove other pins (don't delete them yet for testing)
|
||||
SCH_PIN* stackedPin = pinsToConvert[0];
|
||||
|
||||
// Step 2: Verify stacked notation
|
||||
bool isValid;
|
||||
std::vector<wxString> expanded = stackedPin->GetStackedPinNumbers( &isValid );
|
||||
BOOST_CHECK( isValid );
|
||||
BOOST_REQUIRE_EQUAL( expanded.size(), 3 );
|
||||
BOOST_CHECK_EQUAL( expanded[0], "5" );
|
||||
BOOST_CHECK_EQUAL( expanded[1], "7" );
|
||||
BOOST_CHECK_EQUAL( expanded[2], "9" );
|
||||
|
||||
// Step 3: Convert back to individual pins (simulating ExplodeStackedPin)
|
||||
// Sort the stacked numbers (should already be sorted in our case)
|
||||
std::sort( expanded.begin(), expanded.end(),
|
||||
[]( const wxString& a, const wxString& b )
|
||||
{
|
||||
long numA, numB;
|
||||
if( a.ToLong( &numA ) && b.ToLong( &numB ) )
|
||||
return numA < numB;
|
||||
return a < b;
|
||||
});
|
||||
|
||||
// Change the original pin to use the first (smallest) number and make it visible
|
||||
stackedPin->SetNumber( expanded[0] );
|
||||
stackedPin->SetVisible( true );
|
||||
|
||||
// Create additional pins for the remaining numbers and make them invisible
|
||||
std::vector<SCH_PIN*> explodedPins;
|
||||
explodedPins.push_back( stackedPin );
|
||||
|
||||
for( size_t i = 1; i < expanded.size(); ++i )
|
||||
{
|
||||
SCH_PIN* newPin = new SCH_PIN( m_symbol.get() );
|
||||
|
||||
// Copy all properties from the original pin
|
||||
newPin->SetPosition( stackedPin->GetPosition() );
|
||||
newPin->SetOrientation( stackedPin->GetOrientation() );
|
||||
newPin->SetShape( stackedPin->GetShape() );
|
||||
newPin->SetLength( stackedPin->GetLength() );
|
||||
newPin->SetType( stackedPin->GetType() );
|
||||
newPin->SetName( stackedPin->GetName() );
|
||||
newPin->SetNumber( expanded[i] );
|
||||
newPin->SetNameTextSize( stackedPin->GetNameTextSize() );
|
||||
newPin->SetNumberTextSize( stackedPin->GetNumberTextSize() );
|
||||
newPin->SetUnit( stackedPin->GetUnit() );
|
||||
newPin->SetBodyStyle( stackedPin->GetBodyStyle() );
|
||||
newPin->SetVisible( false ); // Make all other pins invisible
|
||||
|
||||
explodedPins.push_back( newPin );
|
||||
}
|
||||
|
||||
// Step 4: Verify the round-trip conversion
|
||||
BOOST_REQUIRE_EQUAL( explodedPins.size(), 3 );
|
||||
|
||||
// Check pin numbers
|
||||
BOOST_CHECK_EQUAL( explodedPins[0]->GetNumber(), "5" );
|
||||
BOOST_CHECK_EQUAL( explodedPins[1]->GetNumber(), "7" );
|
||||
BOOST_CHECK_EQUAL( explodedPins[2]->GetNumber(), "9" );
|
||||
|
||||
// Check visibility (only first pin should be visible)
|
||||
BOOST_CHECK( explodedPins[0]->IsVisible() );
|
||||
BOOST_CHECK( !explodedPins[1]->IsVisible() );
|
||||
BOOST_CHECK( !explodedPins[2]->IsVisible() );
|
||||
|
||||
// Check that properties were preserved
|
||||
for( SCH_PIN* pin : explodedPins )
|
||||
{
|
||||
BOOST_CHECK_EQUAL( pin->GetPosition(), position );
|
||||
BOOST_CHECK( pin->GetOrientation() == originalOrientation );
|
||||
BOOST_CHECK( pin->GetType() == originalType );
|
||||
BOOST_CHECK_EQUAL( pin->GetName(), originalName );
|
||||
BOOST_CHECK_EQUAL( pin->GetLength(), originalLength );
|
||||
}
|
||||
|
||||
// Clean up
|
||||
for( size_t i = 1; i < explodedPins.size(); ++i )
|
||||
delete explodedPins[i];
|
||||
// Note: explodedPins[0] is the original stackedPin, don't delete twice
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test visibility behavior during conversions
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestVisibilityHandling )
|
||||
{
|
||||
// Create a single pin to test visibility handling
|
||||
SCH_PIN* pin = new SCH_PIN( m_symbol.get() );
|
||||
pin->SetNumber( wxT("[8,10,12]") );
|
||||
pin->SetVisible( false ); // Start invisible
|
||||
|
||||
// Test expansion of stacked notation
|
||||
bool isValid;
|
||||
std::vector<wxString> expanded = pin->GetStackedPinNumbers( &isValid );
|
||||
BOOST_CHECK( isValid );
|
||||
BOOST_REQUIRE_EQUAL( expanded.size(), 3 );
|
||||
|
||||
// Sort expanded numbers
|
||||
std::sort( expanded.begin(), expanded.end(),
|
||||
[]( const wxString& a, const wxString& b )
|
||||
{
|
||||
long numA, numB;
|
||||
if( a.ToLong( &numA ) && b.ToLong( &numB ) )
|
||||
return numA < numB;
|
||||
return a < b;
|
||||
});
|
||||
|
||||
// Verify sorted order is correct
|
||||
BOOST_CHECK_EQUAL( expanded[0], "8" );
|
||||
BOOST_CHECK_EQUAL( expanded[1], "10" );
|
||||
BOOST_CHECK_EQUAL( expanded[2], "12" );
|
||||
|
||||
// Set the smallest pin number and make it visible
|
||||
pin->SetNumber( expanded[0] ); // "8"
|
||||
pin->SetVisible( true ); // Make visible
|
||||
|
||||
// Verify the smallest pin is visible and has correct number
|
||||
BOOST_CHECK_EQUAL( pin->GetNumber(), "8" );
|
||||
BOOST_CHECK( pin->IsVisible() );
|
||||
|
||||
// Clean up
|
||||
delete pin;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test alphanumeric range collapsing functionality
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestAlphanumericRangeCollapsing )
|
||||
{
|
||||
// Test the new alphanumeric prefix parsing logic
|
||||
|
||||
// Helper function to test prefix parsing
|
||||
auto testPrefixParsing = []( const wxString& pinNumber ) -> std::pair<wxString, long>
|
||||
{
|
||||
wxString prefix;
|
||||
long numValue = -1;
|
||||
|
||||
// Find where numeric part starts (scan from end)
|
||||
size_t numStart = pinNumber.length();
|
||||
for( int i = pinNumber.length() - 1; i >= 0; i-- )
|
||||
{
|
||||
if( !wxIsdigit( pinNumber[i] ) )
|
||||
{
|
||||
numStart = i + 1;
|
||||
break;
|
||||
}
|
||||
if( i == 0 ) // All digits
|
||||
numStart = 0;
|
||||
}
|
||||
|
||||
if( numStart < pinNumber.length() ) // Has numeric suffix
|
||||
{
|
||||
prefix = pinNumber.Left( numStart );
|
||||
wxString numericPart = pinNumber.Mid( numStart );
|
||||
numericPart.ToLong( &numValue );
|
||||
}
|
||||
|
||||
return std::make_pair( prefix, numValue );
|
||||
};
|
||||
|
||||
// Test basic prefix parsing
|
||||
auto [prefix1, num1] = testPrefixParsing( wxT("A1") );
|
||||
BOOST_CHECK_EQUAL( prefix1, "A" );
|
||||
BOOST_CHECK_EQUAL( num1, 1 );
|
||||
|
||||
auto [prefix2, num2] = testPrefixParsing( wxT("AB12") );
|
||||
BOOST_CHECK_EQUAL( prefix2, "AB" );
|
||||
BOOST_CHECK_EQUAL( num2, 12 );
|
||||
|
||||
auto [prefix3, num3] = testPrefixParsing( wxT("123") );
|
||||
BOOST_CHECK_EQUAL( prefix3, "" );
|
||||
BOOST_CHECK_EQUAL( num3, 123 );
|
||||
|
||||
auto [prefix4, num4] = testPrefixParsing( wxT("XYZ") );
|
||||
BOOST_CHECK_EQUAL( prefix4, "" ); // No numeric suffix
|
||||
BOOST_CHECK_EQUAL( num4, -1 );
|
||||
|
||||
// Test grouping logic with example: AA1,AA2,AA3,AB4,CD12,CD13,CD14
|
||||
std::map<wxString, std::vector<long>> prefixGroups;
|
||||
std::vector<wxString> testPins = { wxT("AA1"), wxT("AA2"), wxT("AA3"), wxT("AB4"), wxT("CD12"), wxT("CD13"), wxT("CD14") };
|
||||
|
||||
for( const wxString& pinNumber : testPins )
|
||||
{
|
||||
auto [prefix, numValue] = testPrefixParsing( pinNumber );
|
||||
if( numValue != -1 )
|
||||
prefixGroups[prefix].push_back( numValue );
|
||||
}
|
||||
|
||||
// Verify grouping
|
||||
BOOST_CHECK_EQUAL( prefixGroups.size(), 3 );
|
||||
BOOST_CHECK_EQUAL( prefixGroups[wxT("AA")].size(), 3 );
|
||||
BOOST_CHECK_EQUAL( prefixGroups[wxT("AB")].size(), 1 );
|
||||
BOOST_CHECK_EQUAL( prefixGroups[wxT("CD")].size(), 3 );
|
||||
|
||||
// Build expected result: AA1-AA3,AB4,CD12-CD14
|
||||
wxString expectedResult;
|
||||
for( auto& [prefix, numbers] : prefixGroups )
|
||||
{
|
||||
if( !expectedResult.IsEmpty() )
|
||||
expectedResult += wxT(",");
|
||||
|
||||
std::sort( numbers.begin(), numbers.end() );
|
||||
|
||||
size_t i = 0;
|
||||
while( i < numbers.size() )
|
||||
{
|
||||
if( i > 0 )
|
||||
expectedResult += wxT(",");
|
||||
|
||||
long start = numbers[i];
|
||||
long end = start;
|
||||
|
||||
// Find consecutive sequence
|
||||
while( i + 1 < numbers.size() && numbers[i + 1] == numbers[i] + 1 )
|
||||
{
|
||||
i++;
|
||||
end = numbers[i];
|
||||
}
|
||||
|
||||
// Format with prefix
|
||||
if( end > start + 1 ) // Range of 3+ numbers
|
||||
expectedResult += wxString::Format( wxT("%s%ld-%s%ld"), prefix, start, prefix, end );
|
||||
else if( end == start + 1 ) // Two consecutive numbers
|
||||
expectedResult += wxString::Format( wxT("%s%ld,%s%ld"), prefix, start, prefix, end );
|
||||
else // Single number
|
||||
expectedResult += wxString::Format( wxT("%s%ld"), prefix, start );
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Should result in: AA1-AA3,AB4,CD12-CD14
|
||||
BOOST_CHECK_EQUAL( expectedResult, "AA1-AA3,AB4,CD12-CD14" );
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
101
qa/tests/eeschema/test_stacked_pin_nomenclature.cpp
Normal file
101
qa/tests/eeschema/test_stacked_pin_nomenclature.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers
|
||||
*
|
||||
* 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 3
|
||||
* 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 at
|
||||
* http://www.gnu.org/licenses/
|
||||
*/
|
||||
|
||||
#include <qa_utils/wx_utils/unit_test_utils.h>
|
||||
#include <schematic_utils/schematic_file_util.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <schematic.h>
|
||||
#include <sch_screen.h>
|
||||
#include <sch_symbol.h>
|
||||
#include <sch_pin.h>
|
||||
#include <settings/settings_manager.h>
|
||||
#include <locale_io.h>
|
||||
|
||||
struct STACKED_PIN_FIXTURE
|
||||
{
|
||||
STACKED_PIN_FIXTURE() : m_settingsManager( true /* headless */ ) {}
|
||||
|
||||
SETTINGS_MANAGER m_settingsManager;
|
||||
std::unique_ptr<SCHEMATIC> m_schematic;
|
||||
};
|
||||
|
||||
static std::vector<wxString> ToVector( const std::initializer_list<const char*>& init )
|
||||
{
|
||||
std::vector<wxString> out;
|
||||
for( const char* s : init )
|
||||
out.emplace_back( wxString::FromUTF8( s ) );
|
||||
return out;
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE( StackedPinNomenclature_ExpandsCorrectly, STACKED_PIN_FIXTURE )
|
||||
{
|
||||
LOCALE_IO dummy;
|
||||
|
||||
// Load the custom schematic with bracketed pin numbers
|
||||
KI_TEST::LoadSchematic( m_settingsManager, "stacked_pin_nomenclature", m_schematic );
|
||||
|
||||
SCH_SHEET_LIST sheets = m_schematic->BuildSheetListSortedByPageNumbers();
|
||||
BOOST_REQUIRE( sheets.size() >= 1 );
|
||||
SCH_SCREEN* screen = sheets.at( 0 ).LastScreen();
|
||||
|
||||
// Find the Device:R symbol on the sheet
|
||||
SCH_SYMBOL* resistor = nullptr;
|
||||
for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
|
||||
{
|
||||
SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
|
||||
if( sym->GetSchSymbolLibraryName() == wxT( "Device:R" ) )
|
||||
{
|
||||
resistor = sym;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_REQUIRE_MESSAGE( resistor, "Resistor symbol not found in test schematic" );
|
||||
|
||||
// Collect both pins (each with bracketed numbers)
|
||||
std::vector<SCH_PIN*> rpins = resistor->GetPins( &sheets.at( 0 ) );
|
||||
BOOST_REQUIRE_EQUAL( rpins.size(), 2 );
|
||||
|
||||
// For determinism, sort by local Y position
|
||||
std::sort( rpins.begin(), rpins.end(), []( SCH_PIN* a, SCH_PIN* b ) {
|
||||
return a->GetLocalPosition().y < b->GetLocalPosition().y;
|
||||
} );
|
||||
|
||||
// Top pin is [1-5]
|
||||
bool validTop = false;
|
||||
std::vector<wxString> top = rpins[0]->GetStackedPinNumbers( &validTop );
|
||||
BOOST_CHECK( validTop );
|
||||
std::vector<wxString> expectedTop = ToVector( { "1", "2", "3", "4", "5" } );
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS( top.begin(), top.end(), expectedTop.begin(), expectedTop.end() );
|
||||
|
||||
// Bottom pin is [6,7,9-11]
|
||||
bool validBot = false;
|
||||
std::vector<wxString> bot = rpins[1]->GetStackedPinNumbers( &validBot );
|
||||
BOOST_CHECK( validBot );
|
||||
std::vector<wxString> expectedBot = ToVector( { "6", "7", "9", "10", "11" } );
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS( bot.begin(), bot.end(), expectedBot.begin(), expectedBot.end() );
|
||||
|
||||
// Total expanded count across both pins should be 10
|
||||
size_t total = top.size() + bot.size();
|
||||
BOOST_CHECK_EQUAL( total, 10 );
|
||||
}
|
@ -51,6 +51,7 @@ set( QA_PCBNEW_SRCS
|
||||
test_shape_corner_radius.cpp
|
||||
test_pcb_grid_helper.cpp
|
||||
test_save_load.cpp
|
||||
test_stacked_pin_netlist.cpp
|
||||
test_tracks_cleaner.cpp
|
||||
test_triangulation.cpp
|
||||
test_multichannel.cpp
|
||||
|
266
qa/tests/pcbnew/test_stacked_pin_netlist.cpp
Normal file
266
qa/tests/pcbnew/test_stacked_pin_netlist.cpp
Normal file
@ -0,0 +1,266 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2024 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 <boost/test/unit_test.hpp>
|
||||
|
||||
#include <netlist_reader/pcb_netlist.h>
|
||||
#include <lib_id.h>
|
||||
#include <board.h>
|
||||
#include <footprint.h>
|
||||
#include <pad.h>
|
||||
#include <pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h>
|
||||
#include <project.h>
|
||||
#include <settings/settings_manager.h>
|
||||
#include <wx/log.h>
|
||||
#include <qa_utils/utility_registry.h>
|
||||
#include <reporter.h>
|
||||
|
||||
BOOST_AUTO_TEST_SUITE( StackedPinNetlist )
|
||||
|
||||
|
||||
/**
|
||||
* Test that COMPONENT::GetNet properly handles stacked pin notation like [8,9,10]
|
||||
* and finds individual pin numbers within the stacked group.
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestStackedPinNetMatch )
|
||||
{
|
||||
// Create a component with a stacked pin notation
|
||||
LIB_ID fpid( wxT( "TestLib" ), wxT( "TestFootprint" ) );
|
||||
wxString reference = wxT( "U1" );
|
||||
wxString value = wxT( "TestIC" );
|
||||
KIID_PATH path;
|
||||
std::vector<KIID> kiids;
|
||||
|
||||
COMPONENT component( fpid, reference, value, path, kiids );
|
||||
|
||||
// Add a net with stacked pin notation [8,9,10]
|
||||
component.AddNet( wxT( "[8,9,10]" ), wxT( "DATA_BUS" ), wxT( "bidirectional" ), wxT( "bidirectional" ) );
|
||||
|
||||
// Test that individual pins within the stack are found
|
||||
const COMPONENT_NET& net8 = component.GetNet( wxT( "8" ) );
|
||||
const COMPONENT_NET& net9 = component.GetNet( wxT( "9" ) );
|
||||
const COMPONENT_NET& net10 = component.GetNet( wxT( "10" ) );
|
||||
|
||||
BOOST_CHECK( net8.IsValid() );
|
||||
BOOST_CHECK( net9.IsValid() );
|
||||
BOOST_CHECK( net10.IsValid() );
|
||||
|
||||
BOOST_CHECK_EQUAL( net8.GetNetName(), wxString( "DATA_BUS" ) );
|
||||
BOOST_CHECK_EQUAL( net9.GetNetName(), wxString( "DATA_BUS" ) );
|
||||
BOOST_CHECK_EQUAL( net10.GetNetName(), wxString( "DATA_BUS" ) );
|
||||
|
||||
// Test that pins outside the stack are not found
|
||||
const COMPONENT_NET& net7 = component.GetNet( wxT( "7" ) );
|
||||
const COMPONENT_NET& net11 = component.GetNet( wxT( "11" ) );
|
||||
|
||||
BOOST_CHECK( !net7.IsValid() );
|
||||
BOOST_CHECK( !net11.IsValid() );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test stacked pin notation with range syntax [1-4]
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestStackedPinRangeMatch )
|
||||
{
|
||||
LIB_ID fpid( wxT( "TestLib" ), wxT( "TestFootprint" ) );
|
||||
wxString reference = wxT( "U2" );
|
||||
wxString value = wxT( "TestIC" );
|
||||
KIID_PATH path;
|
||||
std::vector<KIID> kiids;
|
||||
|
||||
COMPONENT component( fpid, reference, value, path, kiids );
|
||||
|
||||
// Add a net with range notation [1-4]
|
||||
component.AddNet( wxT( "[1-4]" ), wxT( "POWER_BUS" ), wxT( "power_in" ), wxT( "power_in" ) );
|
||||
|
||||
// Test that all pins in the range are found
|
||||
for( int i = 1; i <= 4; i++ )
|
||||
{
|
||||
const COMPONENT_NET& net = component.GetNet( wxString::Format( wxT( "%d" ), i ) );
|
||||
BOOST_CHECK( net.IsValid() );
|
||||
BOOST_CHECK_EQUAL( net.GetNetName(), wxString( "POWER_BUS" ) );
|
||||
}
|
||||
|
||||
// Test pins outside the range
|
||||
const COMPONENT_NET& net0 = component.GetNet( wxT( "0" ) );
|
||||
const COMPONENT_NET& net5 = component.GetNet( wxT( "5" ) );
|
||||
|
||||
BOOST_CHECK( !net0.IsValid() );
|
||||
BOOST_CHECK( !net5.IsValid() );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test mixed notation [1,3,5-7]
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestStackedPinMixedMatch )
|
||||
{
|
||||
LIB_ID fpid( wxT( "TestLib" ), wxT( "TestFootprint" ) );
|
||||
wxString reference = wxT( "U3" );
|
||||
wxString value = wxT( "TestIC" );
|
||||
KIID_PATH path;
|
||||
std::vector<KIID> kiids;
|
||||
|
||||
COMPONENT component( fpid, reference, value, path, kiids );
|
||||
|
||||
// Add a net with mixed notation [1,3,5-7]
|
||||
component.AddNet( wxT( "[1,3,5-7]" ), wxT( "CONTROL_BUS" ), wxT( "output" ), wxT( "output" ) );
|
||||
|
||||
// Test individual pins and ranges
|
||||
std::vector<int> expectedPins = { 1, 3, 5, 6, 7 };
|
||||
for( int pin : expectedPins )
|
||||
{
|
||||
const COMPONENT_NET& net = component.GetNet( wxString::Format( wxT( "%d" ), pin ) );
|
||||
BOOST_CHECK( net.IsValid() );
|
||||
BOOST_CHECK_EQUAL( net.GetNetName(), wxString( "CONTROL_BUS" ) );
|
||||
}
|
||||
|
||||
// Test pins that should not be found
|
||||
std::vector<int> unexpectedPins = { 2, 4, 8 };
|
||||
for( int pin : unexpectedPins )
|
||||
{
|
||||
const COMPONENT_NET& net = component.GetNet( wxString::Format( wxT( "%d" ), pin ) );
|
||||
BOOST_CHECK( !net.IsValid() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that regular (non-stacked) pin names still work
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestRegularPinMatch )
|
||||
{
|
||||
LIB_ID fpid( wxT( "TestLib" ), wxT( "TestFootprint" ) );
|
||||
wxString reference = wxT( "R1" );
|
||||
wxString value = wxT( "1k" );
|
||||
KIID_PATH path;
|
||||
std::vector<KIID> kiids;
|
||||
|
||||
COMPONENT component( fpid, reference, value, path, kiids );
|
||||
|
||||
// Add regular pins
|
||||
component.AddNet( wxT( "1" ), wxT( "VCC" ), wxT( "passive" ), wxT( "passive" ) );
|
||||
component.AddNet( wxT( "2" ), wxT( "GND" ), wxT( "passive" ), wxT( "passive" ) );
|
||||
|
||||
const COMPONENT_NET& net1 = component.GetNet( wxT( "1" ) );
|
||||
const COMPONENT_NET& net2 = component.GetNet( wxT( "2" ) );
|
||||
|
||||
BOOST_CHECK( net1.IsValid() );
|
||||
BOOST_CHECK( net2.IsValid() );
|
||||
BOOST_CHECK_EQUAL( net1.GetNetName(), wxString( "VCC" ) );
|
||||
BOOST_CHECK_EQUAL( net2.GetNetName(), wxString( "GND" ) );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This test creates a mock netlist that matches the stacked project structure and
|
||||
* validates that PCB pad lookups work correctly with stacked pin notation.
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestStackedProjectNetlistUpdate )
|
||||
{
|
||||
BOOST_TEST_MESSAGE( "Testing stacked pin project netlist functionality" );
|
||||
|
||||
// Create a component that matches the R1 component from the stacked project
|
||||
LIB_ID fpid( wxT( "Connector" ), wxT( "Tag-Connect_TC2050-IDC-FP_2x05_P1.27mm_Vertical" ) );
|
||||
wxString reference = wxT( "R1" );
|
||||
wxString value = wxT( "R" );
|
||||
KIID_PATH path;
|
||||
std::vector<KIID> kiids;
|
||||
|
||||
COMPONENT component( fpid, reference, value, path, kiids );
|
||||
|
||||
// Add nets matching the stacked project
|
||||
// The schematic has two stacked pin groups: [1-5] and [6,7,9-11]
|
||||
component.AddNet( wxT( "[1-5]" ), wxT( "Net-(R1-Pad1)" ), wxT( "passive" ), wxT( "passive" ) );
|
||||
component.AddNet( wxT( "[6,7,9-11]" ), wxT( "Net-(R1-Pad6)" ), wxT( "passive" ), wxT( "passive" ) );
|
||||
|
||||
BOOST_TEST_MESSAGE( "Created R1 component with stacked pins [1-5] and [6,7,9-11]" );
|
||||
|
||||
// Log all nets for the component
|
||||
BOOST_TEST_MESSAGE( "R1 component nets:" );
|
||||
for( unsigned i = 0; i < component.GetNetCount(); i++ )
|
||||
{
|
||||
const COMPONENT_NET& net = component.GetNet( i );
|
||||
BOOST_TEST_MESSAGE( " Pin: " + net.GetPinName() + " -> Net: " + net.GetNetName() );
|
||||
}
|
||||
|
||||
// Test individual pin lookups
|
||||
// Pins 1-5 should be found (they're in the [1-5] stacked group)
|
||||
for( int pin = 1; pin <= 5; pin++ )
|
||||
{
|
||||
wxString pinStr = wxString::Format( wxT( "%d" ), pin );
|
||||
const COMPONENT_NET& net = component.GetNet( pinStr );
|
||||
|
||||
BOOST_CHECK_MESSAGE( net.IsValid(),
|
||||
"Pin " + pinStr + " should be found in stacked group [1-5]" );
|
||||
|
||||
if( net.IsValid() )
|
||||
{
|
||||
BOOST_CHECK_EQUAL( net.GetNetName(), wxString( "Net-(R1-Pad1)" ) );
|
||||
BOOST_TEST_MESSAGE( "Pin " + pinStr + " found with net: " + net.GetNetName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOST_TEST_MESSAGE( "Pin " + pinStr + " NOT found (should be in [1-5])" );
|
||||
}
|
||||
}
|
||||
|
||||
// Pins 6,7,9,10,11 should be found (they're in the [6,7,9-11] stacked group)
|
||||
std::vector<int> groupTwoPins = { 6, 7, 9, 10, 11 };
|
||||
for( int pin : groupTwoPins )
|
||||
{
|
||||
wxString pinStr = wxString::Format( wxT( "%d" ), pin );
|
||||
const COMPONENT_NET& net = component.GetNet( pinStr );
|
||||
|
||||
BOOST_CHECK_MESSAGE( net.IsValid(),
|
||||
"Pin " + pinStr + " should be found in stacked group [6,7,9-11]" );
|
||||
|
||||
if( net.IsValid() )
|
||||
{
|
||||
BOOST_CHECK_EQUAL( net.GetNetName(), wxString( "Net-(R1-Pad6)" ) );
|
||||
BOOST_TEST_MESSAGE( "Pin " + pinStr + " found with net: " + net.GetNetName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOST_TEST_MESSAGE( "Pin " + pinStr + " NOT found (should be in [6,7,9-11])" );
|
||||
}
|
||||
}
|
||||
|
||||
// Pin 8 should NOT be found (it's not in either stacked group)
|
||||
const COMPONENT_NET& net8 = component.GetNet( wxT( "8" ) );
|
||||
BOOST_CHECK_MESSAGE( !net8.IsValid(),
|
||||
"Pin 8 should NOT be found (not in any stacked group)" );
|
||||
|
||||
if( net8.IsValid() )
|
||||
{
|
||||
BOOST_TEST_MESSAGE( "Pin 8 unexpectedly found with net: " + net8.GetNetName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOST_TEST_MESSAGE( "Pin 8 correctly NOT found (expected behavior)" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
Loading…
x
Reference in New Issue
Block a user