mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
[ADDED]: A panel to the schematic editor that allows quick access to all of the items connected to the currently highlighted net. This is an initial swag at implementing a full net navigator feature. For now it only shows the currently highlighted net nodes. The incremental net list advanced setting must be enabled in order to use this feature due to performance reasons. There are still some issues with saving the panel position which will be addressed in the future. Initial code for serializing wxAuiPaneInfo settings to and from JSON have be implemented.
589 lines
15 KiB
C++
589 lines
15 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2018 CERN
|
|
* Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* @author Jon Evans <jon@craftyjon.com>
|
|
*
|
|
* 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <regex>
|
|
#include <wx/tokenzr.h>
|
|
|
|
#include <connection_graph.h>
|
|
#include <sch_symbol.h>
|
|
#include <sch_pin.h>
|
|
#include <sch_screen.h>
|
|
#include <project/net_settings.h>
|
|
#include <advanced_config.h>
|
|
#include <string_utils.h>
|
|
|
|
#include <sch_connection.h>
|
|
#include <boost/algorithm/string/join.hpp>
|
|
|
|
/**
|
|
*
|
|
* Buses can be defined in multiple ways. A bus vector consists of a prefix and
|
|
* a numeric range of suffixes:
|
|
*
|
|
* BUS_NAME[M..N]
|
|
*
|
|
* For example, the bus A[3..0] will contain nets A3, A2, A1, and A0.
|
|
* The BUS_NAME is required. M and N must be integers but do not need to be in
|
|
* any particular order -- A[0..3] produces the same result.
|
|
*
|
|
* Like net names, bus names cannot contain whitespace.
|
|
*
|
|
* A bus group is just a grouping of signals, separated by spaces, some
|
|
* of which may be bus vectors. Bus groups can have names, but do not need to.
|
|
*
|
|
* MEMORY{A[15..0] D[7..0] RW CE OE}
|
|
*
|
|
* In named bus groups, the net names are expanded as <BUS_NAME>.<NET_NAME>
|
|
* In the above example, the nets would be named like MEMORY.A15, MEMORY.D0, etc.
|
|
*
|
|
* {USB_DP USB_DN}
|
|
*
|
|
* In the above example, the bus is unnamed and so the underlying net names are
|
|
* just USB_DP and USB_DN.
|
|
*
|
|
*/
|
|
|
|
SCH_CONNECTION::SCH_CONNECTION( SCH_ITEM* aParent, const SCH_SHEET_PATH& aPath ) :
|
|
m_sheet( aPath ),
|
|
m_local_sheet( aPath ),
|
|
m_parent( aParent ),
|
|
m_driver( nullptr ),
|
|
m_graph( nullptr )
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
|
|
SCH_CONNECTION::SCH_CONNECTION( CONNECTION_GRAPH* aGraph ) :
|
|
m_sheet( SCH_SHEET_PATH() ),
|
|
m_local_sheet( SCH_SHEET_PATH() ),
|
|
m_parent( nullptr ),
|
|
m_driver( nullptr ),
|
|
m_graph( aGraph )
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
|
|
bool SCH_CONNECTION::operator==( const SCH_CONNECTION& aOther ) const
|
|
{
|
|
// NOTE: Not comparing m_dirty or net/bus/subgraph codes
|
|
if( ( aOther.m_driver == m_driver ) &&
|
|
( aOther.m_type == m_type ) &&
|
|
( aOther.m_name == m_name ) &&
|
|
( aOther.m_sheet == m_sheet ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void SCH_CONNECTION::SetDriver( SCH_ITEM* aItem )
|
|
{
|
|
m_driver = aItem;
|
|
|
|
recacheName();
|
|
|
|
for( const std::shared_ptr<SCH_CONNECTION>& member : m_members )
|
|
member->SetDriver( aItem );
|
|
}
|
|
|
|
|
|
void SCH_CONNECTION::SetSheet( SCH_SHEET_PATH aSheet )
|
|
{
|
|
m_sheet = aSheet;
|
|
m_local_sheet = aSheet;
|
|
|
|
recacheName();
|
|
|
|
for( const std::shared_ptr<SCH_CONNECTION>& member : m_members )
|
|
member->SetSheet( aSheet );
|
|
}
|
|
|
|
|
|
bool SCH_CONNECTION::operator!=( const SCH_CONNECTION& aOther ) const
|
|
{
|
|
return !( aOther == *this );
|
|
}
|
|
|
|
|
|
void SCH_CONNECTION::ConfigureFromLabel( const wxString& aLabel )
|
|
{
|
|
m_members.clear();
|
|
|
|
m_name = aLabel;
|
|
m_local_name = aLabel;
|
|
m_local_prefix = m_prefix;
|
|
|
|
wxString prefix;
|
|
std::vector<wxString> members;
|
|
|
|
wxString unescaped = UnescapeString( aLabel );
|
|
|
|
if( NET_SETTINGS::ParseBusVector( unescaped, &prefix, &members ) )
|
|
{
|
|
m_type = CONNECTION_TYPE::BUS;
|
|
m_vector_prefix = prefix;
|
|
|
|
long i = 0;
|
|
|
|
for( const wxString& vector_member : members )
|
|
{
|
|
auto member = std::make_shared<SCH_CONNECTION>( m_parent, m_sheet );
|
|
member->m_type = CONNECTION_TYPE::NET;
|
|
member->m_prefix = m_prefix;
|
|
member->m_local_name = vector_member;
|
|
member->m_local_prefix = m_prefix;
|
|
member->m_vector_index = i++;
|
|
member->SetName( vector_member );
|
|
member->SetGraph( m_graph );
|
|
m_members.push_back( member );
|
|
}
|
|
}
|
|
else if( NET_SETTINGS::ParseBusGroup( unescaped, &prefix, &members ) )
|
|
{
|
|
m_type = CONNECTION_TYPE::BUS_GROUP;
|
|
m_bus_prefix = prefix;
|
|
|
|
// Named bus groups generate a net prefix, unnamed ones don't
|
|
if( !prefix.IsEmpty() )
|
|
prefix += wxT( "." );
|
|
|
|
for( const wxString& group_member : members )
|
|
{
|
|
// Handle alias inside bus group member list
|
|
if( auto alias = m_graph->GetBusAlias( group_member ) )
|
|
{
|
|
for( const wxString& alias_member : alias->Members() )
|
|
{
|
|
auto member = std::make_shared< SCH_CONNECTION >( m_parent, m_sheet );
|
|
member->SetPrefix( prefix );
|
|
member->SetGraph( m_graph );
|
|
member->ConfigureFromLabel( alias_member );
|
|
m_members.push_back( member );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto member = std::make_shared< SCH_CONNECTION >( m_parent, m_sheet );
|
|
member->SetPrefix( prefix );
|
|
member->SetGraph( m_graph );
|
|
member->ConfigureFromLabel( group_member );
|
|
m_members.push_back( member );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_type = CONNECTION_TYPE::NET;
|
|
}
|
|
|
|
recacheName();
|
|
}
|
|
|
|
|
|
void SCH_CONNECTION::Reset()
|
|
{
|
|
m_type = CONNECTION_TYPE::NONE;
|
|
m_name.Empty();
|
|
m_local_name.Empty();
|
|
m_local_prefix.Empty();
|
|
m_cached_name.Empty();
|
|
m_cached_name_with_path.Empty();
|
|
m_prefix.Empty();
|
|
m_bus_prefix.Empty();
|
|
m_suffix .Empty();
|
|
m_lastDriver = m_driver;
|
|
m_driver = nullptr;
|
|
m_members.clear();
|
|
m_dirty = true;
|
|
m_net_code = 0;
|
|
m_bus_code = 0;
|
|
m_subgraph_code = 0;
|
|
m_vector_start = 0;
|
|
m_vector_end = 0;
|
|
m_vector_index = 0;
|
|
m_vector_prefix.Empty();
|
|
}
|
|
|
|
|
|
void SCH_CONNECTION::Clone( const SCH_CONNECTION& aOther )
|
|
{
|
|
m_graph = aOther.m_graph;
|
|
// Note: m_lastDriver is not cloned as it needs to be the last driver of *this* connection
|
|
m_driver = aOther.Driver();
|
|
m_sheet = aOther.Sheet();
|
|
// Note: m_local_sheet is not cloned
|
|
m_name = aOther.m_name;
|
|
// Note: m_local_name is not cloned if not set yet
|
|
if( m_local_name.IsEmpty() )
|
|
{
|
|
m_local_name = aOther.LocalName();
|
|
m_local_prefix = aOther.Prefix();
|
|
}
|
|
|
|
m_prefix = aOther.Prefix();
|
|
// m_bus_prefix is not cloned; only used for local names
|
|
m_suffix = aOther.Suffix();
|
|
m_net_code = aOther.NetCode();
|
|
m_bus_code = aOther.BusCode();
|
|
m_vector_start = aOther.VectorStart();
|
|
m_vector_end = aOther.VectorEnd();
|
|
// Note: m_vector_index is not cloned
|
|
m_vector_prefix = aOther.VectorPrefix();
|
|
|
|
// Note: subgraph code isn't cloned, it should remain with the original object
|
|
|
|
// Handle vector bus members: make sure local names are preserved where possible
|
|
const std::vector<std::shared_ptr<SCH_CONNECTION>>& otherMembers = aOther.Members();
|
|
|
|
if( m_type == CONNECTION_TYPE::BUS && aOther.Type() == CONNECTION_TYPE::BUS )
|
|
{
|
|
if( m_members.empty() )
|
|
{
|
|
m_members = otherMembers;
|
|
}
|
|
else
|
|
{
|
|
size_t cloneLimit = std::min( m_members.size(), otherMembers.size() );
|
|
|
|
for( size_t i = 0; i < cloneLimit; ++i )
|
|
m_members[i]->Clone( *otherMembers[i] );
|
|
}
|
|
}
|
|
else if( m_type == CONNECTION_TYPE::BUS_GROUP && aOther.Type() == CONNECTION_TYPE::BUS_GROUP )
|
|
{
|
|
if( m_members.empty() )
|
|
{
|
|
m_members = otherMembers;
|
|
}
|
|
else
|
|
{
|
|
// TODO: refactor this once we support deep nesting
|
|
for( std::shared_ptr<SCH_CONNECTION>& member : m_members )
|
|
{
|
|
auto it = std::find_if( otherMembers.begin(), otherMembers.end(),
|
|
[&]( const std::shared_ptr<SCH_CONNECTION>& aTest )
|
|
{
|
|
return aTest->LocalName() == member->LocalName();
|
|
} );
|
|
|
|
if( it != otherMembers.end() )
|
|
member->Clone( **it );
|
|
}
|
|
}
|
|
}
|
|
|
|
m_type = aOther.Type();
|
|
|
|
recacheName();
|
|
}
|
|
|
|
|
|
bool SCH_CONNECTION::IsDriver() const
|
|
{
|
|
wxASSERT( Parent() );
|
|
|
|
switch( Parent()->Type() )
|
|
{
|
|
case SCH_LABEL_T:
|
|
case SCH_GLOBAL_LABEL_T:
|
|
case SCH_HIER_LABEL_T:
|
|
case SCH_SHEET_PIN_T:
|
|
case SCH_SHEET_T:
|
|
case LIB_PIN_T:
|
|
return true;
|
|
|
|
case SCH_PIN_T:
|
|
{
|
|
SCH_PIN* pin = static_cast<SCH_PIN*>( Parent() );
|
|
|
|
// Only annotated symbols should drive nets.
|
|
return pin->IsGlobalPower() || pin->GetParentSymbol()->IsAnnotated( &m_sheet );
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool SCH_CONNECTION::HasDriverChanged() const
|
|
{
|
|
return m_driver != m_lastDriver;
|
|
}
|
|
|
|
|
|
void SCH_CONNECTION::ClearDriverChanged()
|
|
{
|
|
m_lastDriver = m_driver;
|
|
}
|
|
|
|
|
|
|
|
wxString SCH_CONNECTION::Name( bool aIgnoreSheet ) const
|
|
{
|
|
wxASSERT( !m_cached_name.IsEmpty() );
|
|
return aIgnoreSheet ? m_cached_name : m_cached_name_with_path;
|
|
}
|
|
|
|
|
|
wxString SCH_CONNECTION::GetNetName() const
|
|
{
|
|
wxString retv;
|
|
|
|
if( m_graph )
|
|
{
|
|
CONNECTION_SUBGRAPH* subgraph = m_graph->GetSubgraphForItem( m_parent );
|
|
|
|
if( subgraph )
|
|
retv = subgraph->GetNetName();
|
|
}
|
|
|
|
return retv;
|
|
}
|
|
|
|
|
|
void SCH_CONNECTION::recacheName()
|
|
{
|
|
m_cached_name = m_name.IsEmpty() ? wxString( wxT( "<NO NET>" ) )
|
|
: wxString( m_prefix ) << m_name << m_suffix;
|
|
|
|
bool prepend_path = true;
|
|
|
|
if( !Parent() || m_type == CONNECTION_TYPE::NONE )
|
|
prepend_path = false;
|
|
|
|
if( m_driver )
|
|
{
|
|
switch( m_driver->Type() )
|
|
{
|
|
case SCH_GLOBAL_LABEL_T:
|
|
case SCH_PIN_T:
|
|
// Pins are either power connections or belong to a uniquely-annotated
|
|
// symbol, so they don't need a path if they are driving the subgraph.
|
|
prepend_path = false;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_cached_name_with_path = prepend_path ? m_sheet.PathHumanReadable() << m_cached_name
|
|
: m_cached_name;
|
|
}
|
|
|
|
|
|
void SCH_CONNECTION::SetPrefix( const wxString& aPrefix )
|
|
{
|
|
m_prefix = aPrefix;
|
|
|
|
recacheName();
|
|
|
|
for( const std::shared_ptr<SCH_CONNECTION>& m : Members() )
|
|
m->SetPrefix( aPrefix );
|
|
}
|
|
|
|
|
|
void SCH_CONNECTION::SetSuffix( const wxString& aSuffix )
|
|
{
|
|
m_suffix = aSuffix;
|
|
|
|
recacheName();
|
|
|
|
for( const std::shared_ptr<SCH_CONNECTION>& m : Members() )
|
|
m->SetSuffix( aSuffix );
|
|
}
|
|
|
|
|
|
void SCH_CONNECTION::AppendInfoToMsgPanel( std::vector<MSG_PANEL_ITEM>& aList ) const
|
|
{
|
|
wxString msg, group_name, members;
|
|
std::vector<wxString> group_members;
|
|
|
|
aList.emplace_back( _( "Connection Name" ), UnescapeString( Name() ) );
|
|
|
|
if( std::shared_ptr<BUS_ALIAS> alias = m_graph->GetBusAlias( m_name ) )
|
|
{
|
|
msg.Printf( _( "Bus Alias %s Members" ), m_name );
|
|
aList.emplace_back( msg, boost::algorithm::join( alias->Members(), " " ) );
|
|
}
|
|
else if( NET_SETTINGS::ParseBusGroup( m_name, &group_name, &group_members ) )
|
|
{
|
|
for( const wxString& group_member : group_members )
|
|
{
|
|
if( std::shared_ptr<BUS_ALIAS> group_alias = m_graph->GetBusAlias( group_member ) )
|
|
{
|
|
msg.Printf( _( "Bus Alias %s Members" ), group_alias->GetName() );
|
|
aList.emplace_back( msg, boost::algorithm::join( group_alias->Members(), " " ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
// These messages are not flagged as translatable, because they are only debug messages
|
|
|
|
if( IsBus() )
|
|
aList.emplace_back( wxT( "Bus Code" ), wxString::Format( "%d", m_bus_code ) );
|
|
|
|
aList.emplace_back( wxT( "Subgraph Code" ), wxString::Format( "%d", m_subgraph_code ) );
|
|
|
|
if( SCH_ITEM* driver = Driver() )
|
|
{
|
|
UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MILLIMETRES );
|
|
|
|
msg.Printf( wxS( "%s at %p" ), driver->GetItemDescription( &unitsProvider ), driver );
|
|
aList.emplace_back( wxT( "Connection Source" ), msg );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
bool SCH_CONNECTION::IsBusLabel( const wxString& aLabel )
|
|
{
|
|
const wxString& unescaped = UnescapeString( aLabel );
|
|
|
|
return NET_SETTINGS::ParseBusVector( unescaped, nullptr, nullptr )
|
|
|| NET_SETTINGS::ParseBusGroup( unescaped, nullptr, nullptr );
|
|
}
|
|
|
|
|
|
bool SCH_CONNECTION::MightBeBusLabel( const wxString& aLabel )
|
|
{
|
|
// Weak heuristic for performance reasons. Stronger test will be used for connectivity
|
|
wxString label = UnescapeString( aLabel );
|
|
|
|
return label.Contains( wxT( "[" ) ) || label.Contains( wxT( "{" ) );
|
|
}
|
|
|
|
|
|
const std::vector< std::shared_ptr< SCH_CONNECTION > > SCH_CONNECTION::AllMembers() const
|
|
{
|
|
std::vector< std::shared_ptr< SCH_CONNECTION > > ret( m_members );
|
|
|
|
for( const std::shared_ptr<SCH_CONNECTION>& member : m_members )
|
|
{
|
|
if( member->IsBus() )
|
|
ret.insert( ret.end(), member->Members().begin(), member->Members().end() );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static bool isSuperSubOverbar( wxChar c )
|
|
{
|
|
return c == '_' || c == '^' || c == '~';
|
|
};
|
|
|
|
|
|
wxString SCH_CONNECTION::PrintBusForUI( const wxString& aGroup )
|
|
{
|
|
size_t groupLen = aGroup.length();
|
|
size_t i = 0;
|
|
wxString ret;
|
|
int braceNesting = 0;
|
|
|
|
// Parse prefix
|
|
//
|
|
for( ; i < groupLen; ++i )
|
|
{
|
|
if( isSuperSubOverbar( aGroup[i] ) && i + 1 < groupLen && aGroup[i+1] == '{' )
|
|
{
|
|
braceNesting++;
|
|
i++;
|
|
continue;
|
|
}
|
|
else if( aGroup[i] == '}' )
|
|
{
|
|
braceNesting--;
|
|
continue;
|
|
}
|
|
|
|
ret += aGroup[i];
|
|
|
|
if( aGroup[i] == '{' )
|
|
break;
|
|
}
|
|
|
|
// Parse members
|
|
//
|
|
i++; // '{' character
|
|
|
|
for( ; i < groupLen; ++i )
|
|
{
|
|
if( isSuperSubOverbar( aGroup[i] ) && i + 1 < groupLen && aGroup[i+1] == '{' )
|
|
{
|
|
braceNesting++;
|
|
i++;
|
|
continue;
|
|
}
|
|
else if( aGroup[i] == '}' )
|
|
{
|
|
braceNesting--;
|
|
continue;
|
|
}
|
|
|
|
ret += aGroup[i];
|
|
|
|
if( aGroup[i] == '}' )
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
bool SCH_CONNECTION::IsSubsetOf( SCH_CONNECTION* aOther ) const
|
|
{
|
|
if( !aOther->IsBus() )
|
|
return false;
|
|
|
|
for( const std::shared_ptr<SCH_CONNECTION>& member : aOther->Members() )
|
|
{
|
|
if( member->FullLocalName() == FullLocalName() )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool SCH_CONNECTION::IsMemberOfBus( SCH_CONNECTION* aOther ) const
|
|
{
|
|
if( !aOther->IsBus() )
|
|
return false;
|
|
|
|
wxString me = Name( true );
|
|
|
|
for( const std::shared_ptr<SCH_CONNECTION>& m : aOther->Members() )
|
|
{
|
|
if( m->Name( true ) == me )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|