/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck * Copyright (C) 2008 Wayne Stambaugh * Copyright (C) 1992-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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // For V6 to V7 simulation model migration. #include #include // TODO(JE) Debugging only #include #include "sch_bus_entry.h" /* * Flag to enable profiling of the TestDanglingEnds() function. * @ingroup trace_env_vars */ static const wxChar DanglingProfileMask[] = wxT( "DANGLING_PROFILE" ); SCH_SCREEN::SCH_SCREEN( EDA_ITEM* aParent ) : BASE_SCREEN( aParent, SCH_SCREEN_T ), m_fileFormatVersionAtLoad( 0 ), m_paper( wxT( "A4" ) ), m_isReadOnly( false ), m_fileExists( false ) { m_modification_sync = 0; m_refCount = 0; m_zoomInitialized = false; m_LastZoomLevel = 1.0; // Suitable for schematic only. For symbol_editor and viewlib, must be set to true m_Center = false; InitDataPoints( m_paper.GetSizeIU( schIUScale.IU_PER_MILS ) ); } SCH_SCREEN::~SCH_SCREEN() { clearLibSymbols(); FreeDrawList(); } SCHEMATIC* SCH_SCREEN::Schematic() const { wxCHECK_MSG( GetParent() && GetParent()->Type() == SCHEMATIC_T, nullptr, wxT( "SCH_SCREEN must have a SCHEMATIC parent!" ) ); return static_cast( GetParent() ); } void SCH_SCREEN::clearLibSymbols() { for( const std::pair& libSymbol : m_libSymbols ) delete libSymbol.second; m_libSymbols.clear(); } void SCH_SCREEN::SetFileName( const wxString& aFileName ) { wxASSERT( aFileName.IsEmpty() || wxIsAbsolutePath( aFileName ) ); m_fileName = aFileName; } void SCH_SCREEN::IncRefCount() { m_refCount++; } void SCH_SCREEN::DecRefCount() { wxCHECK_RET( m_refCount != 0, wxT( "Screen reference count already zero. Bad programmer!" ) ); m_refCount--; } bool SCH_SCREEN::HasItems( KICAD_T aItemType ) const { EE_RTREE::EE_TYPE sheets = m_rtree.OfType( aItemType ); return sheets.begin() != sheets.end(); } bool SCH_SCREEN::ClassOf( const EDA_ITEM* aItem ) { return aItem && SCH_SCREEN_T == aItem->Type(); } void SCH_SCREEN::Append( SCH_ITEM* aItem, bool aUpdateLibSymbol ) { if( aItem->Type() != SCH_SHEET_PIN_T && aItem->Type() != SCH_FIELD_T ) { // Ensure the item can reach the SCHEMATIC through this screen aItem->SetParent( this ); if( aItem->Type() == SCH_SYMBOL_T && aUpdateLibSymbol ) { SCH_SYMBOL* symbol = static_cast( aItem ); if( symbol->GetLibSymbolRef() ) { symbol->GetLibSymbolRef()->GetDrawItems().sort(); auto it = m_libSymbols.find( symbol->GetSchSymbolLibraryName() ); if( it == m_libSymbols.end() || !it->second ) { m_libSymbols[symbol->GetSchSymbolLibraryName()] = new LIB_SYMBOL( *symbol->GetLibSymbolRef() ); } else { // The original library symbol may have changed since the last time // it was added to the schematic. If it has changed, then a new name // must be created for the library symbol list to prevent all of the // other schematic symbols referencing that library symbol from changing. LIB_SYMBOL* foundSymbol = it->second; foundSymbol->GetDrawItems().sort(); if( *foundSymbol != *symbol->GetLibSymbolRef() ) { wxString newName; std::vector matches; getLibSymbolNameMatches( *symbol, matches ); foundSymbol = nullptr; for( const wxString& libSymbolName : matches ) { it = m_libSymbols.find( libSymbolName ); if( it == m_libSymbols.end() ) continue; foundSymbol = it->second; wxCHECK2( foundSymbol, continue ); wxString tmp = symbol->GetLibSymbolRef()->GetName(); // Temporarily update the new symbol library symbol name so it // doesn't fail on the name comparison below. symbol->GetLibSymbolRef()->SetName( foundSymbol->GetName() ); if( *foundSymbol == *symbol->GetLibSymbolRef() ) { newName = libSymbolName; symbol->GetLibSymbolRef()->SetName( tmp ); break; } symbol->GetLibSymbolRef()->SetName( tmp ); foundSymbol = nullptr; } if( !foundSymbol ) { int cnt = 1; newName.Printf( wxT( "%s_%d" ), symbol->GetLibId().GetUniStringLibItemName(), cnt ); while( m_libSymbols.find( newName ) != m_libSymbols.end() ) { cnt += 1; newName.Printf( wxT( "%s_%d" ), symbol->GetLibId().GetUniStringLibItemName(), cnt ); } } // Update the schematic symbol library link as this symbol only exists // in the schematic. symbol->SetSchSymbolLibraryName( newName ); if( !foundSymbol ) { // Update the schematic symbol library link as this symbol does not // exist in any symbol library. LIB_ID newLibId( wxEmptyString, newName ); LIB_SYMBOL* newLibSymbol = new LIB_SYMBOL( *symbol->GetLibSymbolRef() ); newLibSymbol->SetLibId( newLibId ); newLibSymbol->SetName( newName ); symbol->SetLibSymbol( newLibSymbol->Flatten().release() ); m_libSymbols[newName] = newLibSymbol; } } } } } m_rtree.insert( aItem ); --m_modification_sync; } } void SCH_SCREEN::Append( SCH_SCREEN* aScreen ) { wxCHECK_RET( aScreen, "Invalid screen object." ); // No need to descend the hierarchy. Once the top level screen is copied, all of its // children are copied as well. for( SCH_ITEM* aItem : aScreen->m_rtree ) Append( aItem ); aScreen->Clear( false ); } void SCH_SCREEN::Clear( bool aFree ) { if( aFree ) { FreeDrawList(); clearLibSymbols(); } else { m_rtree.clear(); } // Clear the project settings m_virtualPageNumber = m_pageCount = 1; m_titles.Clear(); } void SCH_SCREEN::FreeDrawList() { // We don't know which order we will encounter dependent items (e.g. pins or fields), so // we store the items to be deleted until we've fully cleared the tree before deleting std::vector delete_list; std::copy_if( m_rtree.begin(), m_rtree.end(), std::back_inserter( delete_list ), []( SCH_ITEM* aItem ) { return ( aItem->Type() != SCH_SHEET_PIN_T && aItem->Type() != SCH_FIELD_T ); } ); m_rtree.clear(); for( SCH_ITEM* item : delete_list ) delete item; } void SCH_SCREEN::Update( SCH_ITEM* aItem, bool aUpdateLibSymbol ) { if( Remove( aItem, aUpdateLibSymbol ) ) Append( aItem, aUpdateLibSymbol ); } bool SCH_SCREEN::Remove( SCH_ITEM* aItem, bool aUpdateLibSymbol ) { bool retv = m_rtree.remove( aItem ); // Check if the library symbol for the removed schematic symbol is still required. if( retv && aItem->Type() == SCH_SYMBOL_T && aUpdateLibSymbol ) { SCH_SYMBOL* removedSymbol = static_cast( aItem ); bool removeUnusedLibSymbol = true; for( SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); if( removedSymbol->GetSchSymbolLibraryName() == symbol->GetSchSymbolLibraryName() ) { removeUnusedLibSymbol = false; break; } } if( removeUnusedLibSymbol ) { auto it = m_libSymbols.find( removedSymbol->GetSchSymbolLibraryName() ); if( it != m_libSymbols.end() ) { delete it->second; m_libSymbols.erase( it ); } } } return retv; } void SCH_SCREEN::DeleteItem( SCH_ITEM* aItem ) { wxCHECK_RET( aItem, wxT( "Cannot delete invalid item from screen." ) ); // Markers are not saved in the file, no need to flag as modified. // TODO: Maybe we should have a listing somewhere of items that aren't saved? if( aItem->Type() != SCH_MARKER_T ) SetContentModified(); Remove( aItem ); if( aItem->Type() == SCH_SHEET_PIN_T ) { // This structure is attached to a sheet, get the parent sheet object. SCH_SHEET_PIN* sheetPin = (SCH_SHEET_PIN*) aItem; SCH_SHEET* sheet = sheetPin->GetParent(); wxCHECK_RET( sheet, wxT( "Sheet pin parent not properly set, bad programmer!" ) ); sheet->RemovePin( sheetPin ); return; } delete aItem; } bool SCH_SCREEN::CheckIfOnDrawList( const SCH_ITEM* aItem ) const { return m_rtree.contains( aItem, true ); } SCH_ITEM* SCH_SCREEN::GetItem( const VECTOR2I& aPosition, int aAccuracy, KICAD_T aType ) const { BOX2I bbox; bbox.SetOrigin( aPosition ); bbox.Inflate( aAccuracy ); for( SCH_ITEM* item : Items().Overlapping( aType, bbox ) ) { if( item->HitTest( aPosition, aAccuracy ) ) return item; } return nullptr; } std::set SCH_SCREEN::MarkConnections( SCH_LINE* aSegment, bool aSecondPass ) { #define PROCESSED CANDIDATE // Don't use SKIP_STRUCT; IsConnected() returns false if it's set. std::set retval; std::stack to_search; wxCHECK_MSG( aSegment && aSegment->Type() == SCH_LINE_T, retval, wxT( "Invalid pointer." ) ); to_search.push( aSegment ); while( !to_search.empty() ) { SCH_ITEM* item = to_search.top(); to_search.pop(); if( item->HasFlag( PROCESSED ) ) continue; item->SetFlags( PROCESSED ); for( SCH_ITEM* candidate : Items().Overlapping( SCH_LINE_T, item->GetBoundingBox() ) ) { SCH_LINE* line = static_cast( candidate ); if( line->HasFlag( PROCESSED ) ) continue; // Skip connecting lines on different layers (e.g. buses) if( item->GetLayer() != line->GetLayer() ) continue; for( VECTOR2I pt : { line->GetStartPoint(), line->GetEndPoint() } ) { if( item->IsConnected( pt ) ) { SCH_ITEM* junction = GetItem( pt, 0, SCH_JUNCTION_T ); SCH_ITEM* pin = GetItem( pt, 0, SCH_PIN_T ); if( item->IsSelected() && aSecondPass ) { if( junction ) retval.insert( junction ); retval.insert( line ); to_search.push( line ); } else if( !junction && !pin ) { retval.insert( line ); to_search.push( line ); } break; } else if( line->GetLayer() == LAYER_NOTES && item->GetLayer() == LAYER_NOTES ) { retval.insert( line ); to_search.push( line ); } } } } for( SCH_ITEM* item : Items() ) item->ClearTempFlags(); return retval; } bool SCH_SCREEN::IsJunction( const VECTOR2I& aPosition ) const { bool hasExplicitJunction; bool hasBusEntry; bool isJunction = doIsJunction( aPosition, false, &hasExplicitJunction, &hasBusEntry ); return isJunction; } bool SCH_SCREEN::IsExplicitJunction( const VECTOR2I& aPosition ) const { bool hasExplicitJunction; bool hasBusEntry; bool isJunction = doIsJunction( aPosition, false, &hasExplicitJunction, &hasBusEntry ); return isJunction && !hasBusEntry; } bool SCH_SCREEN::IsExplicitJunctionNeeded( const VECTOR2I& aPosition ) const { bool hasExplicitJunction; bool hasBusEntry; bool isJunction = doIsJunction( aPosition, false, &hasExplicitJunction, &hasBusEntry ); return isJunction && !hasBusEntry && !hasExplicitJunction; } SPIN_STYLE SCH_SCREEN::GetLabelOrientationForPoint( const VECTOR2I& aPosition, SPIN_STYLE aDefaultOrientation, const SCH_SHEET_PATH* aSheet ) const { auto ret = aDefaultOrientation; for( SCH_ITEM* item : Items().Overlapping( aPosition ) ) { if( item->GetEditFlags() & STRUCT_DELETED ) continue; switch( item->Type() ) { case SCH_BUS_WIRE_ENTRY_T: { auto busEntry = static_cast( item ); if( busEntry->m_connected_bus_item ) { // bus connected, take the bus direction into consideration ony if it is // vertical or horizontal auto bus = static_cast( busEntry->m_connected_bus_item ); if( bus->Angle().AsDegrees() == 90.0 ) { // bus is vertical -> label shall be horizontal and // shall be placed to the side where the bus entry is if( aPosition.x < bus->GetPosition().x ) ret = SPIN_STYLE::LEFT; else if( aPosition.x > bus->GetPosition().x ) ret = SPIN_STYLE::RIGHT; } else if( bus->Angle().AsDegrees() == 0.0 ) { // bus is horizontal -> label shall be vertical and // shall be placed to the side where the bus entry is if( aPosition.y < bus->GetPosition().y ) ret = SPIN_STYLE::UP; else if( aPosition.y > bus->GetPosition().y ) ret = SPIN_STYLE::BOTTOM; } } } break; case SCH_LINE_T: { auto line = static_cast( item ); // line angles goes between -90 and 90 degrees, but normalize auto angle = line->Angle().Normalize90().AsDegrees(); if( -45 < angle && angle <= 45 ) { if( line->GetStartPoint().x <= line->GetEndPoint().x ) ret = line->GetEndPoint() == aPosition ? SPIN_STYLE::RIGHT : SPIN_STYLE::LEFT; else ret = line->GetEndPoint() == aPosition ? SPIN_STYLE::LEFT : SPIN_STYLE::RIGHT; } else { if( line->GetStartPoint().y <= line->GetEndPoint().y ) ret = line->GetEndPoint() == aPosition ? SPIN_STYLE::BOTTOM : SPIN_STYLE::UP; else ret = line->GetEndPoint() == aPosition ? SPIN_STYLE::UP : SPIN_STYLE::BOTTOM; } } break; case SCH_SYMBOL_T: { SCH_SYMBOL* symbol = static_cast( item ); for( SCH_PIN* pin : symbol->GetPins( aSheet ) ) { if( pin->GetPosition() == aPosition ) { if( pin->GetOrientation() == PIN_ORIENTATION::PIN_RIGHT ) ret = SPIN_STYLE::LEFT; else if( pin->GetOrientation() == PIN_ORIENTATION::PIN_LEFT ) ret = SPIN_STYLE::RIGHT; else if( pin->GetOrientation() == PIN_ORIENTATION::PIN_UP ) ret = SPIN_STYLE::BOTTOM; else if( pin->GetOrientation() == PIN_ORIENTATION::PIN_DOWN ) ret = SPIN_STYLE::UP; switch( static_cast( symbol->GetOrientation() & ( ~( SYM_MIRROR_X | SYM_MIRROR_Y ) ) ) ) { case SYM_ROTATE_CLOCKWISE: case SYM_ORIENT_90: if( ret == SPIN_STYLE::UP ) ret = SPIN_STYLE::LEFT; else if( ret == SPIN_STYLE::BOTTOM ) ret = SPIN_STYLE::RIGHT; else if( ret == SPIN_STYLE::LEFT ) ret = SPIN_STYLE::BOTTOM; else if( ret == SPIN_STYLE::RIGHT ) ret = SPIN_STYLE::UP; if( symbol->GetOrientation() & SYM_MIRROR_X ) { if( ret == SPIN_STYLE::UP ) ret = SPIN_STYLE::BOTTOM; else if( ret == SPIN_STYLE::BOTTOM ) ret = SPIN_STYLE::UP; } if( symbol->GetOrientation() & SYM_MIRROR_Y ) { if( ret == SPIN_STYLE::LEFT ) ret = SPIN_STYLE::RIGHT; else if( ret == SPIN_STYLE::RIGHT ) ret = SPIN_STYLE::LEFT; } break; case SYM_ROTATE_COUNTERCLOCKWISE: case SYM_ORIENT_270: if( ret == SPIN_STYLE::UP ) ret = SPIN_STYLE::RIGHT; else if( ret == SPIN_STYLE::BOTTOM ) ret = SPIN_STYLE::LEFT; else if( ret == SPIN_STYLE::LEFT ) ret = SPIN_STYLE::UP; else if( ret == SPIN_STYLE::RIGHT ) ret = SPIN_STYLE::BOTTOM; if( symbol->GetOrientation() & SYM_MIRROR_X ) { if( ret == SPIN_STYLE::UP ) ret = SPIN_STYLE::BOTTOM; else if( ret == SPIN_STYLE::BOTTOM ) ret = SPIN_STYLE::UP; } if( symbol->GetOrientation() & SYM_MIRROR_Y ) { if( ret == SPIN_STYLE::LEFT ) ret = SPIN_STYLE::RIGHT; else if( ret == SPIN_STYLE::RIGHT ) ret = SPIN_STYLE::LEFT; } break; case SYM_ORIENT_180: if( ret == SPIN_STYLE::UP ) ret = SPIN_STYLE::BOTTOM; else if( ret == SPIN_STYLE::BOTTOM ) ret = SPIN_STYLE::UP; else if( ret == SPIN_STYLE::LEFT ) ret = SPIN_STYLE::RIGHT; else if( ret == SPIN_STYLE::RIGHT ) ret = SPIN_STYLE::LEFT; if( symbol->GetOrientation() & SYM_MIRROR_X ) { if( ret == SPIN_STYLE::UP ) ret = SPIN_STYLE::BOTTOM; else if( ret == SPIN_STYLE::BOTTOM ) ret = SPIN_STYLE::UP; } if( symbol->GetOrientation() & SYM_MIRROR_Y ) { if( ret == SPIN_STYLE::LEFT ) ret = SPIN_STYLE::RIGHT; else if( ret == SPIN_STYLE::RIGHT ) ret = SPIN_STYLE::LEFT; } break; case SYM_ORIENT_0: case SYM_NORMAL: default: if( symbol->GetOrientation() & SYM_MIRROR_X ) { if( ret == SPIN_STYLE::UP ) ret = SPIN_STYLE::BOTTOM; else if( ret == SPIN_STYLE::BOTTOM ) ret = SPIN_STYLE::UP; } if( symbol->GetOrientation() & SYM_MIRROR_Y ) { if( ret == SPIN_STYLE::LEFT ) ret = SPIN_STYLE::RIGHT; else if( ret == SPIN_STYLE::RIGHT ) ret = SPIN_STYLE::LEFT; } break; } break; } } } break; default: break; } } return ret; } bool SCH_SCREEN::IsExplicitJunctionAllowed( const VECTOR2I& aPosition ) const { bool hasExplicitJunction; bool hasBusEntry; bool isJunction = doIsJunction( aPosition, true, &hasExplicitJunction, &hasBusEntry ); return isJunction && !hasBusEntry; } bool SCH_SCREEN::doIsJunction( const VECTOR2I& aPosition, bool aBreakCrossings, bool* aHasExplicitJunctionDot, bool* aHasBusEntry ) const { enum layers { WIRES = 0, BUSES }; *aHasExplicitJunctionDot = false; *aHasBusEntry = false; bool breakLines[ 2 ] = { false }; std::unordered_set exitAngles[ 2 ]; std::vector midPointLines[ 2 ]; // A pin at 90° still shouldn't match a line at 90° so just give pins unique numbers int uniqueAngle = 10000; for( const SCH_ITEM* item : Items().Overlapping( aPosition ) ) { if( item->GetEditFlags() & STRUCT_DELETED ) continue; switch( item->Type() ) { case SCH_JUNCTION_T: if( item->HitTest( aPosition, -1 ) ) *aHasExplicitJunctionDot = true; break; case SCH_LINE_T: { const SCH_LINE* line = static_cast( item ); int layer; if( line->GetStartPoint() == line->GetEndPoint() ) break; else if( line->GetLayer() == LAYER_WIRE ) layer = WIRES; else if( line->GetLayer() == LAYER_BUS ) layer = BUSES; else break; if( line->IsConnected( aPosition ) ) { breakLines[ layer ] = true; exitAngles[ layer ].insert( line->GetAngleFrom( aPosition ) ); } else if( line->HitTest( aPosition, -1 ) ) { if( aBreakCrossings ) breakLines[ layer ] = true; // Defer any line midpoints until we know whether or not we're breaking them midPointLines[ layer ].push_back( line ); } } break; case SCH_BUS_WIRE_ENTRY_T: if( item->IsConnected( aPosition ) ) { breakLines[ BUSES ] = true; exitAngles[ BUSES ].insert( uniqueAngle++ ); breakLines[ WIRES ] = true; exitAngles[ WIRES ].insert( uniqueAngle++ ); *aHasBusEntry = true; } break; case SCH_SYMBOL_T: case SCH_SHEET_T: if( item->IsConnected( aPosition ) ) { breakLines[ WIRES ] = true; exitAngles[ WIRES ].insert( uniqueAngle++ ); } break; default: break; } } for( int layer : { WIRES, BUSES } ) { if( breakLines[ layer ] ) { for( const SCH_LINE* line : midPointLines[ layer ] ) { exitAngles[ layer ].insert( line->GetAngleFrom( aPosition ) ); exitAngles[ layer ].insert( line->GetReverseAngleFrom( aPosition ) ); } } } return exitAngles[ WIRES ].size() >= 3 || exitAngles[ BUSES ].size() >= 3; } bool SCH_SCREEN::IsTerminalPoint( const VECTOR2I& aPosition, int aLayer ) const { wxCHECK_MSG( aLayer == LAYER_NOTES || aLayer == LAYER_BUS || aLayer == LAYER_WIRE, false, wxT( "Invalid layer type passed to SCH_SCREEN::IsTerminalPoint()." ) ); SCH_SHEET_PIN* sheetPin; SCH_LABEL_BASE* label; switch( aLayer ) { case LAYER_BUS: if( GetBus( aPosition ) ) return true; sheetPin = GetSheetPin( aPosition ); if( sheetPin && sheetPin->IsConnected( aPosition ) ) return true; label = GetLabel( aPosition ); if( label && !label->IsNew() && label->IsConnected( aPosition ) ) return true; break; case LAYER_NOTES: if( GetLine( aPosition ) ) return true; break; case LAYER_WIRE: if( GetItem( aPosition, 1, SCH_BUS_WIRE_ENTRY_T ) ) return true; if( GetItem( aPosition, 1, SCH_JUNCTION_T ) ) return true; if( GetPin( aPosition, nullptr, true ) ) return true; if( GetWire( aPosition ) ) return true; label = GetLabel( aPosition, 1 ); if( label && !label->IsNew() && label->IsConnected( aPosition ) ) return true; sheetPin = GetSheetPin( aPosition ); if( sheetPin && sheetPin->IsConnected( aPosition ) ) return true; break; default: break; } return false; } void SCH_SCREEN::UpdateSymbolLinks( REPORTER* aReporter ) { wxCHECK_RET( Schematic(), "Cannot call SCH_SCREEN::UpdateSymbolLinks with no SCHEMATIC" ); wxString msg; std::unique_ptr< LIB_SYMBOL > libSymbol; std::vector symbols; SYMBOL_LIB_TABLE* libs = PROJECT_SCH::SchSymbolLibTable( &Schematic()->Prj() ); // This will be a nullptr if an s-expression schematic is loaded. SYMBOL_LIBS* legacyLibs = PROJECT_SCH::SchLibs( &Schematic()->Prj() ); for( SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) ) symbols.push_back( static_cast( item ) ); // Remove them from the R tree. There bounding box size may change. for( SCH_SYMBOL* symbol : symbols ) Remove( symbol ); // Clear all existing symbol links. clearLibSymbols(); for( SCH_SYMBOL* symbol : symbols ) { LIB_SYMBOL* tmp = nullptr; libSymbol.reset(); // If the symbol is already in the internal library, map the symbol to it. auto it = m_libSymbols.find( symbol->GetSchSymbolLibraryName() ); if( ( it != m_libSymbols.end() ) ) { if( aReporter ) { msg.Printf( _( "Setting schematic symbol '%s %s' library identifier to '%s'." ), symbol->GetField( REFERENCE_FIELD )->GetText(), symbol->GetField( VALUE_FIELD )->GetText(), UnescapeString( symbol->GetLibId().Format() ) ); aReporter->ReportTail( msg, RPT_SEVERITY_INFO ); } // Internal library symbols are already flattened so just make a copy. symbol->SetLibSymbol( new LIB_SYMBOL( *it->second ) ); continue; } if( !symbol->GetLibId().IsValid() ) { if( aReporter ) { msg.Printf( _( "Schematic symbol reference '%s' library identifier is not valid. " "Unable to link library symbol." ), UnescapeString( symbol->GetLibId().Format() ) ); aReporter->ReportTail( msg, RPT_SEVERITY_WARNING ); } continue; } // LIB_TABLE_BASE::LoadSymbol() throws an IO_ERROR if the library nickname // is not found in the table so check if the library still exists in the table // before attempting to load the symbol. if( !libs->HasLibrary( symbol->GetLibId().GetLibNickname() ) && !legacyLibs ) { if( aReporter ) { msg.Printf( _( "Symbol library '%s' not found and no fallback cache library " "available. Unable to link library symbol." ), symbol->GetLibId().GetLibNickname().wx_str() ); aReporter->ReportTail( msg, RPT_SEVERITY_WARNING ); } continue; } if( libs->HasLibrary( symbol->GetLibId().GetLibNickname() ) ) { try { tmp = libs->LoadSymbol( symbol->GetLibId() ); } catch( const IO_ERROR& ioe ) { if( aReporter ) { msg.Printf( _( "I/O error %s resolving library symbol %s" ), ioe.What(), UnescapeString( symbol->GetLibId().Format() ) ); aReporter->ReportTail( msg, RPT_SEVERITY_ERROR ); } } } if( !tmp && legacyLibs && legacyLibs->GetLibraryCount() ) { SYMBOL_LIB& legacyCacheLib = legacyLibs->back(); // It better be the cache library. wxCHECK2( legacyCacheLib.IsCache(), continue ); wxString id = symbol->GetLibId().Format(); id.Replace( ':', '_' ); if( aReporter ) { msg.Printf( _( "Falling back to cache to set symbol '%s:%s' link '%s'." ), symbol->GetField( REFERENCE_FIELD )->GetText(), symbol->GetField( VALUE_FIELD )->GetText(), UnescapeString( id ) ); aReporter->ReportTail( msg, RPT_SEVERITY_WARNING ); } tmp = legacyCacheLib.FindSymbol( id ); } if( tmp ) { // We want a full symbol not just the top level child symbol. libSymbol = tmp->Flatten(); libSymbol->SetParent(); m_libSymbols.insert( { symbol->GetSchSymbolLibraryName(), new LIB_SYMBOL( *libSymbol.get() ) } ); if( aReporter ) { msg.Printf( _( "Setting schematic symbol '%s %s' library identifier to '%s'." ), symbol->GetField( REFERENCE_FIELD )->GetText(), symbol->GetField( VALUE_FIELD )->GetText(), UnescapeString( symbol->GetLibId().Format() ) ); aReporter->ReportTail( msg, RPT_SEVERITY_INFO ); } } else { if( aReporter ) { msg.Printf( _( "No library symbol found for schematic symbol '%s %s'." ), symbol->GetField( REFERENCE_FIELD )->GetText(), symbol->GetField( VALUE_FIELD )->GetText() ); aReporter->ReportTail( msg, RPT_SEVERITY_ERROR ); } } if( libSymbol.get() ) // Only change the old link if the new link exists symbol->SetLibSymbol( libSymbol.release() ); } // Changing the symbol may adjust the bbox of the symbol. This re-inserts the // item with the new bbox for( SCH_SYMBOL* symbol : symbols ) Append( symbol ); } void SCH_SCREEN::UpdateLocalLibSymbolLinks() { std::vector symbols; for( SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) ) symbols.push_back( static_cast( item ) ); for( SCH_SYMBOL* symbol : symbols ) { // Changing the symbol may adjust the bbox of the symbol; remove and reinsert it afterwards. m_rtree.remove( symbol ); auto it = m_libSymbols.find( symbol->GetSchSymbolLibraryName() ); LIB_SYMBOL* libSymbol = nullptr; if( it != m_libSymbols.end() ) libSymbol = new LIB_SYMBOL( *it->second ); symbol->SetLibSymbol( libSymbol ); m_rtree.insert( symbol ); } } void SCH_SCREEN::SetConnectivityDirty() { for( SCH_ITEM* item : Items() ) item->SetConnectivityDirty( true ); } void SCH_SCREEN::Print( const SCH_RENDER_SETTINGS* aSettings ) { // Ensure links are up to date, even if a library was reloaded for some reason: std::vector junctions; std::vector bitmaps; std::vector other; for( SCH_ITEM* item : Items() ) { if( item->IsMoving() ) continue; if( item->Type() == SCH_JUNCTION_T ) junctions.push_back( item ); else if( item->Type() == SCH_BITMAP_T ) bitmaps.push_back( item ); else other.push_back( item ); } /// Sort to ensure plot-order consistency with screen drawing std::stable_sort( other.begin(), other.end(), []( const SCH_ITEM* a, const SCH_ITEM* b ) { if( a->Type() == b->Type() ) return a->GetLayer() > b->GetLayer(); return a->Type() < b->Type(); } ); for( SCH_ITEM* item : bitmaps ) item->Print( aSettings, 0, 0, VECTOR2I( 0, 0 ), false, false ); for( SCH_ITEM* item : other ) item->PrintBackground( aSettings, 0, 0, VECTOR2I( 0, 0 ), false ); for( SCH_ITEM* item : other ) item->Print( aSettings, 0, 0, VECTOR2I( 0, 0 ), false, false ); for( SCH_ITEM* item : junctions ) item->Print( aSettings, 0, 0, VECTOR2I( 0, 0 ), false, false ); } void SCH_SCREEN::Plot( PLOTTER* aPlotter, const SCH_PLOT_OPTS& aPlotOpts ) const { // Ensure links are up to date, even if a library was reloaded for some reason: std::vector junctions; std::vector bitmaps; std::vector symbols; std::vector other; for( SCH_ITEM* item : Items() ) { if( item->IsMoving() ) continue; if( item->Type() == SCH_JUNCTION_T ) junctions.push_back( item ); else if( item->Type() == SCH_BITMAP_T ) bitmaps.push_back( item ); else other.push_back( item ); // Where the symbols overlap each other, we need to plot the text items a second // time to get them on top of the overlapping element. This collection is in addition // to the symbols already collected in `other` if( item->Type() == SCH_SYMBOL_T ) { for( SCH_ITEM* sym : m_rtree.Overlapping( SCH_SYMBOL_T, item->GetBoundingBox() ) ) { if( sym != item ) { symbols.push_back( static_cast( item ) ); break; } } } } /// Sort to ensure plot-order consistency with screen drawing std::sort( other.begin(), other.end(), []( const SCH_ITEM* a, const SCH_ITEM* b ) { if( a->Type() == b->Type() ) return a->GetLayer() > b->GetLayer(); return a->Type() > b->Type(); } ); auto* renderSettings = static_cast( aPlotter->RenderSettings() ); constexpr bool background = true; // Bitmaps are drawn first to ensure they are in the background // This is particularly important for the wxPostscriptDC (used in *nix printers) as // the bitmap PS command clears the screen for( SCH_ITEM* item : bitmaps ) { aPlotter->SetCurrentLineWidth( item->GetEffectivePenWidth( renderSettings ) ); item->Plot( aPlotter, background, aPlotOpts, 0, 0, { 0, 0 }, false ); } // Plot the background items for( SCH_ITEM* item : other ) { aPlotter->SetCurrentLineWidth( item->GetEffectivePenWidth( renderSettings ) ); item->Plot( aPlotter, background, aPlotOpts, 0, 0, { 0, 0 }, false ); } // Plot the foreground items for( SCH_ITEM* item : other ) { aPlotter->SetCurrentLineWidth( item->GetEffectivePenWidth( renderSettings ) ); item->Plot( aPlotter, !background, aPlotOpts, 0, 0, { 0, 0 }, false ); } // After plotting the symbols as a group above (in `other`), we need to overplot the pins // and symbols to ensure that they are always visible TRANSFORM savedTransform = renderSettings->m_Transform; for( const SCH_SYMBOL* sym :symbols ) { renderSettings->m_Transform = sym->GetTransform(); aPlotter->SetCurrentLineWidth( sym->GetEffectivePenWidth( renderSettings ) ); for( SCH_FIELD field : sym->GetFields() ) { field.ClearRenderCache(); field.Plot( aPlotter, false, aPlotOpts, sym->GetUnit(), sym->GetBodyStyle(), { 0, 0 }, sym->GetDNP() ); } sym->PlotPins( aPlotter ); if( sym->GetDNP() ) sym->PlotDNP( aPlotter ); } renderSettings->m_Transform = savedTransform; for( SCH_ITEM* item : junctions ) { aPlotter->SetCurrentLineWidth( item->GetEffectivePenWidth( renderSettings ) ); item->Plot( aPlotter, !background, aPlotOpts, 0, 0, { 0, 0 }, false ); } } void SCH_SCREEN::ClearDrawingState() { for( SCH_ITEM* item : Items() ) item->ClearTempFlags(); } SCH_PIN* SCH_SCREEN::GetPin( const VECTOR2I& aPosition, SCH_SYMBOL** aSymbol, bool aEndPointOnly ) const { SCH_SYMBOL* candidate = nullptr; SCH_PIN* pin = nullptr; for( SCH_ITEM* item : Items().Overlapping( SCH_SYMBOL_T, aPosition ) ) { candidate = static_cast( item ); if( aEndPointOnly ) { pin = nullptr; if( !candidate->GetLibSymbolRef() ) continue; for( SCH_PIN* test_pin : candidate->GetLibPins() ) { if( candidate->GetPinPhysicalPosition( test_pin ) == aPosition ) { pin = test_pin; break; } } if( pin ) break; } else { pin = static_cast( candidate->GetDrawItem( aPosition, SCH_PIN_T ) ); if( pin ) break; } } if( pin && aSymbol ) *aSymbol = candidate; return pin; } SCH_SHEET_PIN* SCH_SCREEN::GetSheetPin( const VECTOR2I& aPosition ) const { SCH_SHEET_PIN* sheetPin = nullptr; for( SCH_ITEM* item : Items().Overlapping( SCH_SHEET_T, aPosition ) ) { SCH_SHEET* sheet = static_cast( item ); sheetPin = sheet->GetPin( aPosition ); if( sheetPin ) break; } return sheetPin; } size_t SCH_SCREEN::CountConnectedItems( const VECTOR2I& aPos, bool aTestJunctions ) const { size_t count = 0; for( const SCH_ITEM* item : Items().Overlapping( aPos ) ) { if( ( item->Type() != SCH_JUNCTION_T || aTestJunctions ) && item->IsConnected( aPos ) ) count++; } return count; } void SCH_SCREEN::ClearAnnotation( SCH_SHEET_PATH* aSheetPath, bool aResetPrefix ) { for( SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); symbol->ClearAnnotation( aSheetPath, aResetPrefix ); } } void SCH_SCREEN::EnsureAlternateReferencesExist() { if( GetClientSheetPaths().size() <= 1 ) // No need for alternate reference return; for( SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); // Add (when not existing) all sheet path entries for( const SCH_SHEET_PATH& sheet : GetClientSheetPaths() ) symbol->AddSheetPathReferenceEntryIfMissing( sheet.Path() ); } } void SCH_SCREEN::GetHierarchicalItems( std::vector* aItems ) const { for( SCH_ITEM* item : Items() ) { if( item->IsType( { SCH_SYMBOL_T, SCH_SHEET_T, SCH_LABEL_LOCATE_ANY_T } ) ) aItems->push_back( item ); } } void SCH_SCREEN::GetSheets( std::vector* aItems ) const { for( SCH_ITEM* item : Items().OfType( SCH_SHEET_T ) ) aItems->push_back( item ); std::sort( aItems->begin(), aItems->end(), []( EDA_ITEM* a, EDA_ITEM* b ) -> bool { if( a->GetPosition().x == b->GetPosition().x ) { // Ensure deterministic sort if( a->GetPosition().y == b->GetPosition().y ) return a->m_Uuid < b->m_Uuid; return a->GetPosition().y < b->GetPosition().y; } else { return a->GetPosition().x < b->GetPosition().x; } } ); } void SCH_SCREEN::TestDanglingEnds( const SCH_SHEET_PATH* aPath, std::function* aChangedHandler ) const { PROF_TIMER timer( __FUNCTION__ ); std::vector endPointsByPos; std::vector endPointsByType; auto get_ends = [&]( SCH_ITEM* item ) { if( item->IsConnectable() ) item->GetEndPoints( endPointsByType ); }; auto update_state = [&]( SCH_ITEM* item ) { if( item->UpdateDanglingState( endPointsByType, endPointsByPos, aPath ) ) { if( aChangedHandler ) ( *aChangedHandler )( item ); } }; for( SCH_ITEM* item : Items() ) { get_ends( item ); item->RunOnChildren( get_ends ); } PROF_TIMER sortTimer( "SCH_SCREEN::TestDanglingEnds pre-sort" ); endPointsByPos = endPointsByType; DANGLING_END_ITEM_HELPER::sort_dangling_end_items( endPointsByType, endPointsByPos ); sortTimer.Stop(); if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) ) sortTimer.Show(); for( SCH_ITEM* item : Items() ) { update_state( item ); item->RunOnChildren( update_state ); } if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) ) timer.Show(); } SCH_LINE* SCH_SCREEN::GetLine( const VECTOR2I& aPosition, int aAccuracy, int aLayer, SCH_LINE_TEST_T aSearchType ) const { // an accuracy of 0 had problems with rounding errors; use at least 1 aAccuracy = std::max( aAccuracy, 1 ); for( SCH_ITEM* item : Items().Overlapping( aPosition, aAccuracy ) ) { if( item->Type() != SCH_LINE_T ) continue; if( item->GetLayer() != aLayer ) continue; if( !item->HitTest( aPosition, aAccuracy ) ) continue; switch( aSearchType ) { case ENTIRE_LENGTH_T: return (SCH_LINE*) item; case EXCLUDE_END_POINTS_T: if( !( (SCH_LINE*) item )->IsEndPoint( aPosition ) ) return (SCH_LINE*) item; break; case END_POINTS_ONLY_T: if( ( (SCH_LINE*) item )->IsEndPoint( aPosition ) ) return (SCH_LINE*) item; } } return nullptr; } std::vector SCH_SCREEN::GetBusesAndWires( const VECTOR2I& aPosition, bool aIgnoreEndpoints ) const { std::vector retVal; for( SCH_ITEM* item : Items().Overlapping( SCH_LINE_T, aPosition ) ) { if( item->IsType( { SCH_ITEM_LOCATE_WIRE_T, SCH_ITEM_LOCATE_BUS_T } ) ) { SCH_LINE* wire = static_cast( item ); if( aIgnoreEndpoints && wire->IsEndPoint( aPosition ) ) continue; if( IsPointOnSegment( wire->GetStartPoint(), wire->GetEndPoint(), aPosition ) ) retVal.push_back( wire ); } } return retVal; } std::vector SCH_SCREEN::GetConnections() const { std::vector retval; for( SCH_ITEM* item : Items() ) { // Avoid items that are changing if( !( item->GetEditFlags() & ( IS_MOVING | IS_DELETED ) ) ) { std::vector pts = item->GetConnectionPoints(); retval.insert( retval.end(), pts.begin(), pts.end() ); } } // We always have some overlapping connection points. Drop duplicates here std::sort( retval.begin(), retval.end(), []( const VECTOR2I& a, const VECTOR2I& b ) -> bool { return a.x < b.x || ( a.x == b.x && a.y < b.y ); } ); retval.erase( std::unique( retval.begin(), retval.end() ), retval.end() ); return retval; } std::vector SCH_SCREEN::GetNeededJunctions( const std::deque& aItems ) const { std::vector pts; std::vector connections = GetConnections(); for( const EDA_ITEM* edaItem : aItems ) { const SCH_ITEM* item = dynamic_cast( edaItem ); if( !item || !item->IsConnectable() ) continue; std::vector new_pts = item->GetConnectionPoints(); pts.insert( pts.end(), new_pts.begin(), new_pts.end() ); // If the item is a line, we also add any connection points from the rest of the schematic // that terminate on the line after it is moved. if( item->Type() == SCH_LINE_T ) { SCH_LINE* line = (SCH_LINE*) item; for( const VECTOR2I& pt : connections ) { if( IsPointOnSegment( line->GetStartPoint(), line->GetEndPoint(), pt ) ) pts.push_back( pt ); } } } // We always have some overlapping connection points. Drop duplicates here std::sort( pts.begin(), pts.end(), []( const VECTOR2I& a, const VECTOR2I& b ) -> bool { return a.x < b.x || ( a.x == b.x && a.y < b.y ); } ); pts.erase( unique( pts.begin(), pts.end() ), pts.end() ); // We only want the needed junction points, remove all the others pts.erase( std::remove_if( pts.begin(), pts.end(), [this]( const VECTOR2I& a ) -> bool { return !IsExplicitJunctionNeeded( a ); } ), pts.end() ); return pts; } SCH_LABEL_BASE* SCH_SCREEN::GetLabel( const VECTOR2I& aPosition, int aAccuracy ) const { for( SCH_ITEM* item : Items().Overlapping( aPosition, aAccuracy ) ) { switch( item->Type() ) { case SCH_LABEL_T: case SCH_GLOBAL_LABEL_T: case SCH_HIER_LABEL_T: case SCH_DIRECTIVE_LABEL_T: if( item->HitTest( aPosition, aAccuracy ) ) return static_cast( item ); break; default: ; } } return nullptr; } void SCH_SCREEN::AddLibSymbol( LIB_SYMBOL* aLibSymbol ) { wxCHECK( aLibSymbol, /* void */ ); wxString libSymbolName = aLibSymbol->GetLibId().Format().wx_str(); auto it = m_libSymbols.find( libSymbolName ); if( it != m_libSymbols.end() ) { delete it->second; m_libSymbols.erase( it ); } m_libSymbols[libSymbolName] = aLibSymbol; } void SCH_SCREEN::AddBusAlias( std::shared_ptr aAlias ) { m_aliases.insert( aAlias ); } void SCH_SCREEN::SetLegacySymbolInstanceData() { for( SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); // Add missing value and footprint instance data for legacy schematics. for( const SCH_SYMBOL_INSTANCE& instance : symbol->GetInstances() ) { symbol->AddHierarchicalReference( instance.m_Path, instance.m_Reference, instance.m_Unit ); } } } void SCH_SCREEN::FixLegacyPowerSymbolMismatches() { for( SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); // Fix pre-8.0 legacy power symbols with invisible pins // that have mismatched pin names and value fields if( symbol->GetLibSymbolRef() && symbol->GetLibSymbolRef()->IsPower() && symbol->GetAllLibPins().size() > 0 && symbol->GetAllLibPins()[0]->IsGlobalPower() && !symbol->GetAllLibPins()[0]->IsVisible() ) { symbol->SetValueFieldText( symbol->GetAllLibPins()[0]->GetName() ); } } } size_t SCH_SCREEN::getLibSymbolNameMatches( const SCH_SYMBOL& aSymbol, std::vector& aMatches ) { wxString searchName = aSymbol.GetLibId().GetUniStringLibId(); if( m_libSymbols.find( searchName ) != m_libSymbols.end() ) aMatches.emplace_back( searchName ); searchName = aSymbol.GetLibId().GetUniStringLibItemName() + wxS( "_" ); long tmp; wxString suffix; for( auto& pair : m_libSymbols ) { if( pair.first.StartsWith( searchName, &suffix ) && suffix.ToLong( &tmp ) ) aMatches.emplace_back( pair.first ); } return aMatches.size(); } void SCH_SCREEN::PruneOrphanedSymbolInstances( const wxString& aProjectName, const SCH_SHEET_LIST& aValidSheetPaths ) { // The project name cannot be empty. Projects older than 7.0 did not save project names // when saving instance data. Running this algorithm with an empty project name would // clobber all instance data for projects other than the current one when a schematic // file is shared across multiple projects. Because running the schematic editor in // stand alone mode can result in an empty project name, do not assert here. if( aProjectName.IsEmpty() ) return; for( SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); wxCHECK2( symbol, continue ); std::set pathsToPrune; const std::vector instances = symbol->GetInstances(); for( const SCH_SYMBOL_INSTANCE& instance : instances ) { // Ignore instance paths from other projects. if( aProjectName != instance.m_ProjectName ) continue; std::optional pathFound = aValidSheetPaths.GetSheetPathByKIIDPath( instance.m_Path ); // Check for paths that do not exist in the current project and paths that do // not contain the current symbol. if( !pathFound ) pathsToPrune.emplace( instance.m_Path ); else if( pathFound.value().LastScreen() != this ) pathsToPrune.emplace( pathFound.value().Path() ); } for( const KIID_PATH& sheetPath : pathsToPrune ) { wxLogTrace( traceSchSheetPaths, wxS( "Pruning project '%s' symbol instance %s." ), aProjectName, sheetPath.AsString() ); symbol->RemoveInstance( sheetPath ); } } } void SCH_SCREEN::PruneOrphanedSheetInstances( const wxString& aProjectName, const SCH_SHEET_LIST& aValidSheetPaths ) { // The project name cannot be empty. Projects older than 7.0 did not save project names // when saving instance data. Running this algorithm with an empty project name would // clobber all instance data for projects other than the current one when a schematic // file is shared across multiple projects. Because running the schematic editor in // stand alone mode can result in an empty project name, do not assert here. if( aProjectName.IsEmpty() ) return; for( SCH_ITEM* item : Items().OfType( SCH_SHEET_T ) ) { SCH_SHEET* sheet = static_cast( item ); wxCHECK2( sheet, continue ); std::set pathsToPrune; const std::vector instances = sheet->GetInstances(); for( const SCH_SHEET_INSTANCE& instance : instances ) { // Ignore instance paths from other projects. if( aProjectName != instance.m_ProjectName ) continue; std::optional pathFound = aValidSheetPaths.GetSheetPathByKIIDPath( instance.m_Path ); // Check for paths that do not exist in the current project and paths that do // not contain the current symbol. if( !pathFound ) pathsToPrune.emplace( instance.m_Path ); else if( pathFound.value().LastScreen() != this ) pathsToPrune.emplace( pathFound.value().Path() ); } for( const KIID_PATH& sheetPath : pathsToPrune ) { wxLogTrace( traceSchSheetPaths, wxS( "Pruning project '%s' sheet instance %s." ), aProjectName, sheetPath.AsString() ); sheet->RemoveInstance( sheetPath ); } } } #if defined(DEBUG) void SCH_SCREEN::Show( int nestLevel, std::ostream& os ) const { // for now, make it look like XML, expand on this later. NestedSpace( nestLevel, os ) << '<' << GetClass().Lower().mb_str() << ">\n"; for( const SCH_ITEM* item : Items() ) item->Show( nestLevel + 1, os ); NestedSpace( nestLevel, os ) << "\n"; } #endif SCH_SCREENS::SCH_SCREENS( SCH_SHEET* aSheet ) { m_index = 0; buildScreenList( aSheet ); } SCH_SCREENS::~SCH_SCREENS() { } SCH_SCREEN* SCH_SCREENS::GetFirst() { m_index = 0; if( m_screens.size() > 0 ) return m_screens[0]; return nullptr; } SCH_SCREEN* SCH_SCREENS::GetNext() { if( m_index < m_screens.size() ) m_index++; return GetScreen( m_index ); } SCH_SCREEN* SCH_SCREENS::GetScreen( unsigned int aIndex ) const { if( aIndex < m_screens.size() ) return m_screens[ aIndex ]; return nullptr; } SCH_SHEET* SCH_SCREENS::GetSheet( unsigned int aIndex ) const { if( aIndex < m_sheets.size() ) return m_sheets[ aIndex ]; return nullptr; } void SCH_SCREENS::addScreenToList( SCH_SCREEN* aScreen, SCH_SHEET* aSheet ) { if( aScreen == nullptr ) return; for( const SCH_SCREEN* screen : m_screens ) { if( screen == aScreen ) return; } m_screens.push_back( aScreen ); m_sheets.push_back( aSheet ); } void SCH_SCREENS::buildScreenList( SCH_SHEET* aSheet ) { if( aSheet && aSheet->Type() == SCH_SHEET_T ) { SCH_SCREEN* screen = aSheet->GetScreen(); wxCHECK_RET( screen, "No screen for aSheet" ); addScreenToList( screen, aSheet ); for( SCH_ITEM* item : screen->Items().OfType( SCH_SHEET_T ) ) buildScreenList( static_cast( item ) ); } } void SCH_SCREENS::ClearAnnotationOfNewSheetPaths( SCH_SHEET_LIST& aInitialSheetPathList ) { SCH_SCREEN* first = GetFirst(); if( !first ) return; SCHEMATIC* sch = first->Schematic(); wxCHECK_RET( sch, "Null schematic in SCH_SCREENS::ClearAnnotationOfNewSheetPaths" ); // Clear the annotation for symbols inside new sheetpaths not already in aInitialSheetList SCH_SCREENS screensList( sch->Root() ); // The list of screens, shared by sheet paths screensList.BuildClientSheetPathList(); // build the shared by sheet paths, by screen // Search for new sheet paths, not existing in aInitialSheetPathList // and existing in sheetpathList for( SCH_SHEET_PATH& sheetpath : sch->GetSheets() ) { bool path_exists = false; for( const SCH_SHEET_PATH& existing_sheetpath: aInitialSheetPathList ) { if( existing_sheetpath.Path() == sheetpath.Path() ) { path_exists = true; break; } } if( !path_exists ) { // A new sheet path is found: clear the annotation corresponding to this new path: SCH_SCREEN* curr_screen = sheetpath.LastScreen(); // Clear annotation and create the AR for this path, if not exists, // when the screen is shared by sheet paths. // Otherwise ClearAnnotation do nothing, because the F1 field is used as // reference default value and takes the latest displayed value curr_screen->EnsureAlternateReferencesExist(); curr_screen->ClearAnnotation( &sheetpath, false ); } } } int SCH_SCREENS::ReplaceDuplicateTimeStamps() { std::vector items; int count = 0; auto timestamp_cmp = []( const EDA_ITEM* a, const EDA_ITEM* b ) -> bool { return a->m_Uuid < b->m_Uuid; }; std::set unique_stamps( timestamp_cmp ); for( SCH_SCREEN* screen : m_screens ) screen->GetHierarchicalItems( &items ); if( items.size() < 2 ) return 0; for( EDA_ITEM* item : items ) { if( !unique_stamps.insert( item ).second ) { // Reset to fully random UUID. This may lose reference, but better to be // deterministic about it rather than to have duplicate UUIDs with random // side-effects. const_cast( item->m_Uuid ) = KIID(); count++; // @todo If the item is a sheet, we need to decend the heirarchy from the sheet // and repace all instances of the changed UUID in sheet paths. Otherwise, // all instance paths with the sheet's UUID will get clobbered. } } return count; } void SCH_SCREENS::ClearEditFlags() { for( SCH_SCREEN* screen = GetFirst(); screen; screen = GetNext() ) { for( SCH_ITEM* item : screen->Items() ) item->ClearEditFlags(); } } void SCH_SCREENS::DeleteMarker( SCH_MARKER* aMarker ) { for( SCH_SCREEN* screen = GetFirst(); screen; screen = GetNext() ) { for( SCH_ITEM* item : screen->Items().OfType( SCH_MARKER_T ) ) { if( item == aMarker ) { screen->DeleteItem( item ); return; } } } } void SCH_SCREENS::DeleteMarkers( enum MARKER_BASE::MARKER_T aMarkerType, int aErrorCode, bool aIncludeExclusions ) { for( SCH_SCREEN* screen = GetFirst(); screen; screen = GetNext() ) { std::vector markers; for( SCH_ITEM* item : screen->Items().OfType( SCH_MARKER_T ) ) { SCH_MARKER* marker = static_cast( item ); std::shared_ptrrcItem = marker->GetRCItem(); if( marker->GetMarkerType() == aMarkerType && ( aErrorCode == ERCE_UNSPECIFIED || rcItem->GetErrorCode() == aErrorCode ) && ( !marker->IsExcluded() || aIncludeExclusions ) ) { markers.push_back( item ); } } for( SCH_ITEM* marker : markers ) screen->DeleteItem( marker ); } } void SCH_SCREENS::DeleteAllMarkers( enum MARKER_BASE::MARKER_T aMarkerType, bool aIncludeExclusions ) { DeleteMarkers( aMarkerType, ERCE_UNSPECIFIED, aIncludeExclusions ); } void SCH_SCREENS::UpdateSymbolLinks( REPORTER* aReporter ) { for( SCH_SCREEN* screen = GetFirst(); screen; screen = GetNext() ) screen->UpdateSymbolLinks( aReporter ); SCH_SCREEN* first = GetFirst(); if( !first ) return; SCHEMATIC* sch = first->Schematic(); wxCHECK_RET( sch, "Null schematic in SCH_SCREENS::UpdateSymbolLinks" ); SCH_SHEET_LIST sheets = sch->GetSheets(); // All of the library symbols have been replaced with copies so the connection graph // pointers are stale. if( sch->ConnectionGraph() ) sch->ConnectionGraph()->Recalculate( sheets, true ); } bool SCH_SCREENS::HasNoFullyDefinedLibIds() { bool has_symbols = false; for( SCH_SCREEN* screen = GetFirst(); screen; screen = GetNext() ) { for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); has_symbols = true; if( !symbol->GetLibId().GetLibNickname().empty() ) return false; } } // return true (i.e. has no fully defined symbol) only if at least one symbol is found return has_symbols ? true : false; } size_t SCH_SCREENS::GetLibNicknames( wxArrayString& aLibNicknames ) { for( SCH_SCREEN* screen = GetFirst(); screen; screen = GetNext() ) { for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); const UTF8& nickname = symbol->GetLibId().GetLibNickname(); if( !nickname.empty() && ( aLibNicknames.Index( nickname ) == wxNOT_FOUND ) ) aLibNicknames.Add( nickname ); } } return aLibNicknames.GetCount(); } int SCH_SCREENS::ChangeSymbolLibNickname( const wxString& aFrom, const wxString& aTo ) { SCH_SCREEN* screen; int cnt = 0; for( screen = GetFirst(); screen; screen = GetNext() ) { for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); if( symbol->GetLibId().GetLibNickname().wx_str() != aFrom ) continue; LIB_ID id = symbol->GetLibId(); id.SetLibNickname( aTo ); symbol->SetLibId( id ); cnt++; } } return cnt; } bool SCH_SCREENS::HasSchematic( const wxString& aSchematicFileName ) { for( const SCH_SCREEN* screen = GetFirst(); screen; screen = GetNext() ) { if( screen->GetFileName() == aSchematicFileName ) return true; } return false; } void SCH_SCREENS::BuildClientSheetPathList() { SCH_SCREEN* first = GetFirst(); if( !first ) return; SCHEMATIC* sch = first->Schematic(); wxCHECK_RET( sch, "Null schematic in SCH_SCREENS::BuildClientSheetPathList" ); for( SCH_SCREEN* curr_screen = GetFirst(); curr_screen; curr_screen = GetNext() ) curr_screen->GetClientSheetPaths().clear(); for( SCH_SHEET_PATH& sheetpath : sch->GetSheets() ) { SCH_SCREEN* used_screen = sheetpath.LastScreen(); // Search for the used_screen in list and add this unique sheet path: for( SCH_SCREEN* curr_screen = GetFirst(); curr_screen; curr_screen = GetNext() ) { if( used_screen == curr_screen ) { curr_screen->GetClientSheetPaths().push_back( sheetpath ); break; } } } } void SCH_SCREENS::SetLegacySymbolInstanceData() { for( SCH_SCREEN* screen = GetFirst(); screen; screen = GetNext() ) screen->SetLegacySymbolInstanceData(); } void SCH_SCREENS::FixLegacyPowerSymbolMismatches() { for( SCH_SCREEN* screen = GetFirst(); screen; screen = GetNext() ) screen->FixLegacyPowerSymbolMismatches(); } void SCH_SCREEN::MigrateSimModels() { LOCALE_IO toggle; // V6 schematics may specify model names in Value fields, which we don't do in V7. // Migrate by adding an equivalent model for these symbols. for( SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); SIM_MODEL::MigrateSimModel( *symbol, &Schematic()->Prj() ); } } void SCH_SCREENS::PruneOrphanedSymbolInstances( const wxString& aProjectName, const SCH_SHEET_LIST& aValidSheetPaths ) { if( aProjectName.IsEmpty() ) return; for( SCH_SCREEN* screen = GetFirst(); screen; screen = GetNext() ) screen->PruneOrphanedSymbolInstances( aProjectName, aValidSheetPaths ); } void SCH_SCREENS::PruneOrphanedSheetInstances( const wxString& aProjectName, const SCH_SHEET_LIST& aValidSheetPaths ) { if( aProjectName.IsEmpty() ) return; for( SCH_SCREEN* screen = GetFirst(); screen; screen = GetNext() ) screen->PruneOrphanedSheetInstances( aProjectName, aValidSheetPaths ); }