Fix eeschema copy/paste: save and load sheet and symbol instances

Save sheet and symbol instance information to the clipboard on copy

Load sheet and symbol instance information from the clipboard on paste
and renumber page numbers after loading.

Correctly handle pasting in a multiple hierarchy by ensuring symbol and
sheet instances are updated for all instances of the destination sheet.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/8207
This commit is contained in:
Roberto Fernandez Bautista 2021-05-01 23:56:37 +01:00
parent 91fd28c4be
commit 5822cd85c4
12 changed files with 427 additions and 103 deletions

View File

@ -3,7 +3,7 @@
* *
* Copyright (C) 2020 Ian McInerney <ian.s.mcinerney@ieee.org> * Copyright (C) 2020 Ian McInerney <ian.s.mcinerney@ieee.org>
* Copyright (C) 2007-2014 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2007-2014 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 1992-2020 KiCad Developers, see CHANGELOG.TXT for contributors. * Copyright (C) 1992-2021 KiCad Developers, see CHANGELOG.TXT for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -253,6 +253,27 @@ KIID_PATH::KIID_PATH( const wxString& aString )
} }
bool KIID_PATH::MakeRelativeTo( const KIID_PATH& aPath )
{
KIID_PATH copy = *this;
clear();
if( aPath.size() > copy.size() )
return false; // this path is not contained within aPath
for( size_t i = 0; i < aPath.size(); ++i )
{
if( copy.at( i ).AsString() != aPath.at( i ).AsString() )
return false; // this path is not contained within aPath
}
for( size_t i = aPath.size(); i < copy.size(); ++i )
push_back( copy.at( i ) );
return true;
}
wxString KIID_PATH::AsString() const wxString KIID_PATH::AsString() const
{ {
wxString path; wxString path;

View File

@ -1,7 +1,7 @@
/* /*
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Copyright (C) 2019 KiCad Developers, see CHANGELOG.TXT for contributors. * Copyright (C) 2019-2021 KiCad Developers, see CHANGELOG.TXT for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -51,6 +51,26 @@ wxString GetRefDesUnannotated( const wxString& aSource )
} }
int GetRefDesNumber( const wxString& aRefDes )
{
int retval = -1; // negative to indicate not found
size_t firstnum = aRefDes.find_first_of( "0123456789" );
if( firstnum != wxString::npos )
{
wxString candidateValue = aRefDes.Mid( firstnum );
long result;
if( !candidateValue.ToLong( &result ) )
retval = -1;
else
retval = static_cast<int>( result );
}
return retval;
}
int RefDesStringCompare( const wxString& aFirst, const wxString& aSecond ) int RefDesStringCompare( const wxString& aFirst, const wxString& aSecond )
{ {
// Compare unescaped text // Compare unescaped text

View File

@ -2162,16 +2162,10 @@ void SCH_SEXPR_PARSER::ParseSchematic( SCH_SHEET* aSheet, bool aIsCopyableOnly,
break; break;
case T_sheet_instances: case T_sheet_instances:
if( aIsCopyableOnly )
Unexpected( T_sheet_instances );
parseSchSheetInstances( aSheet, screen ); parseSchSheetInstances( aSheet, screen );
break; break;
case T_symbol_instances: case T_symbol_instances:
if( aIsCopyableOnly )
Unexpected( T_symbol_instances );
parseSchSymbolInstances( screen ); parseSchSymbolInstances( screen );
break; break;

View File

@ -717,10 +717,11 @@ void SCH_SEXPR_PLUGIN::Format( SCH_SHEET* aSheet )
} }
void SCH_SEXPR_PLUGIN::Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSheetPath, void SCH_SEXPR_PLUGIN::Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSelectionPath,
SCH_SHEET_LIST* aFullSheetHierarchy,
OUTPUTFORMATTER* aFormatter ) OUTPUTFORMATTER* aFormatter )
{ {
wxCHECK( aSelection && aFormatter, /* void */ ); wxCHECK( aSelection && aSelectionPath && aFullSheetHierarchy && aFormatter, /* void */ );
LOCALE_IO toggle; LOCALE_IO toggle;
@ -765,6 +766,12 @@ void SCH_SEXPR_PLUGIN::Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSheetP
m_out->Print( 0, ")\n\n" ); m_out->Print( 0, ")\n\n" );
} }
// Store the selected sheets instance information
SCH_SHEET_LIST selectedSheets;
selectedSheets.push_back( *aSelectionPath ); // Include the "root" of the selection
SCH_REFERENCE_LIST selectedSymbols;
for( i = 0; i < aSelection->GetSize(); ++i ) for( i = 0; i < aSelection->GetSize(); ++i )
{ {
item = (SCH_ITEM*) aSelection->GetItem( i ); item = (SCH_ITEM*) aSelection->GetItem( i );
@ -772,7 +779,10 @@ void SCH_SEXPR_PLUGIN::Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSheetP
switch( item->Type() ) switch( item->Type() )
{ {
case SCH_COMPONENT_T: case SCH_COMPONENT_T:
saveSymbol( static_cast<SCH_COMPONENT*>( item ), aSheetPath, 0 ); saveSymbol( static_cast<SCH_COMPONENT*>( item ), aSelectionPath, 0 );
aSelectionPath->AppendSymbol( selectedSymbols, static_cast<SCH_COMPONENT*>( item ),
true, true );
break; break;
case SCH_BITMAP_T: case SCH_BITMAP_T:
@ -781,6 +791,16 @@ void SCH_SEXPR_PLUGIN::Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSheetP
case SCH_SHEET_T: case SCH_SHEET_T:
saveSheet( static_cast< SCH_SHEET* >( item ), 0 ); saveSheet( static_cast< SCH_SHEET* >( item ), 0 );
{
SCH_SHEET_PATH subSheetPath = *aSelectionPath;
subSheetPath.push_back( static_cast<SCH_SHEET*>( item ) );
aFullSheetHierarchy->GetSheetsWithinPath( selectedSheets, subSheetPath );
aFullSheetHierarchy->GetSymbolsWithinPath( selectedSymbols, subSheetPath, true,
true );
}
break; break;
case SCH_JUNCTION_T: case SCH_JUNCTION_T:
@ -811,6 +831,30 @@ void SCH_SEXPR_PLUGIN::Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSheetP
wxASSERT( "Unexpected schematic object type in SCH_SEXPR_PLUGIN::Format()" ); wxASSERT( "Unexpected schematic object type in SCH_SEXPR_PLUGIN::Format()" );
} }
} }
// Make all instance information relative to the selection path
KIID_PATH selectionPath = aSelectionPath->PathWithoutRootUuid();
selectedSheets.SortByPageNumbers();
std::vector<SCH_SHEET_INSTANCE> sheetinstances = selectedSheets.GetSheetInstances();
for( SCH_SHEET_INSTANCE& sheetInstance : sheetinstances )
{
wxASSERT_MSG( sheetInstance.m_Path.MakeRelativeTo( selectionPath ),
"Sheet is not inside the selection path?" );
}
selectedSymbols.SortByReferenceOnly();
std::vector<SYMBOL_INSTANCE_REFERENCE> symbolInstances = selectedSymbols.GetSymbolInstances();
for( SYMBOL_INSTANCE_REFERENCE& symbolInstance : symbolInstances )
{
wxASSERT_MSG( symbolInstance.m_Path.MakeRelativeTo( selectionPath ),
"Symbol is not inside the selection path?" );
}
saveInstances( sheetinstances, symbolInstances, 0 );
} }

View File

@ -99,8 +99,8 @@ public:
void Format( SCH_SHEET* aSheet ); void Format( SCH_SHEET* aSheet );
void Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSheetPath, void Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSelectionPath,
OUTPUTFORMATTER* aFormatter ); SCH_SHEET_LIST* aFullSheetHierarchy, OUTPUTFORMATTER* aFormatter );
void EnumerateSymbolLib( wxArrayString& aSymbolNameList, void EnumerateSymbolLib( wxArrayString& aSymbolNameList,
const wxString& aLibraryPath, const wxString& aLibraryPath,

View File

@ -177,6 +177,21 @@ int SCH_SHEET_PATH::ComparePageNumAndName( const SCH_SHEET_PATH& aSheetPathToTes
} }
bool SCH_SHEET_PATH::IsContainedWithin( const SCH_SHEET_PATH& aSheetPathToTest ) const
{
if( aSheetPathToTest.size() > size() )
return false;
for( size_t i = 0; i < aSheetPathToTest.size(); ++i )
{
if( at( i )->m_Uuid != aSheetPathToTest.at( i )->m_Uuid )
return false;
}
return true;
}
SCH_SHEET* SCH_SHEET_PATH::Last() const SCH_SHEET* SCH_SHEET_PATH::Last() const
{ {
if( !empty() ) if( !empty() )
@ -551,7 +566,7 @@ bool SCH_SHEET_LIST::PageNumberExists( const wxString& aPageNumber ) const
{ {
for( const SCH_SHEET_PATH& sheet : *this ) for( const SCH_SHEET_PATH& sheet : *this )
{ {
if( sheet.Last()->GetPageNumber( sheet ) == aPageNumber ) if( sheet.GetPageNumber() == aPageNumber )
return true; return true;
} }
@ -720,6 +735,30 @@ void SCH_SHEET_LIST::GetSymbols( SCH_REFERENCE_LIST& aReferences, bool aIncludeP
} }
void SCH_SHEET_LIST::GetSymbolsWithinPath( SCH_REFERENCE_LIST& aReferences,
const SCH_SHEET_PATH& aSheetPath,
bool aIncludePowerSymbols,
bool aForceIncludeOrphanSymbols ) const
{
for( const SCH_SHEET_PATH& sheet : *this )
{
if( sheet.IsContainedWithin( aSheetPath ) )
sheet.GetSymbols( aReferences, aIncludePowerSymbols, aForceIncludeOrphanSymbols );
}
}
void SCH_SHEET_LIST::GetSheetsWithinPath( SCH_SHEET_PATHS& aSheets,
const SCH_SHEET_PATH& aSheetPath ) const
{
for( const SCH_SHEET_PATH& sheet : *this )
{
if( sheet.IsContainedWithin( aSheetPath ) )
aSheets.push_back( sheet );
}
}
void SCH_SHEET_LIST::GetMultiUnitSymbols( SCH_MULTI_UNIT_REFERENCE_MAP &aRefList, void SCH_SHEET_LIST::GetMultiUnitSymbols( SCH_MULTI_UNIT_REFERENCE_MAP &aRefList,
bool aIncludePowerSymbols ) const bool aIncludePowerSymbols ) const
{ {
@ -792,6 +831,20 @@ SCH_SHEET_PATH* SCH_SHEET_LIST::FindSheetForScreen( const SCH_SCREEN* aScreen )
} }
SCH_SHEET_LIST SCH_SHEET_LIST::FindAllSheetsForScreen( const SCH_SCREEN* aScreen ) const
{
SCH_SHEET_LIST retval;
for( const SCH_SHEET_PATH& sheetpath : *this )
{
if( sheetpath.LastScreen() == aScreen )
retval.push_back( sheetpath );
}
return retval;
}
void SCH_SHEET_LIST::UpdateSymbolInstances( void SCH_SHEET_LIST::UpdateSymbolInstances(
const std::vector<SYMBOL_INSTANCE_REFERENCE>& aSymbolInstances ) const std::vector<SYMBOL_INSTANCE_REFERENCE>& aSymbolInstances )
{ {

View File

@ -223,6 +223,14 @@ public:
*/ */
int ComparePageNumAndName( const SCH_SHEET_PATH& aSheetPathToTest ) const; int ComparePageNumAndName( const SCH_SHEET_PATH& aSheetPathToTest ) const;
/**
* Check if this path is contained inside aSheetPathToTest.
*
* @param aSheetPathToTest is the sheet path to compare against.
* @return true if this path is contained inside or equal to aSheetPathToTest.
*/
bool IsContainedWithin( const SCH_SHEET_PATH& aSheetPathToTest ) const;
/** /**
* Return a pointer to the last #SCH_SHEET of the list. * Return a pointer to the last #SCH_SHEET of the list.
* *
@ -432,6 +440,30 @@ public:
void GetSymbols( SCH_REFERENCE_LIST& aReferences, bool aIncludePowerSymbols = true, void GetSymbols( SCH_REFERENCE_LIST& aReferences, bool aIncludePowerSymbols = true,
bool aForceIncludeOrphanSymbols = false ) const; bool aForceIncludeOrphanSymbols = false ) const;
/**
* Add a #SCH_REFERENCE object to \a aReferences for each symbol in the list of sheets that are
* contained within \a aSheetPath as well as recursively downwards inside aSheetPath.
*
* @param aReferences List of references to populate.
* @param aSheetPath Path to return symbols from
* @param aIncludePowerSymbols Set to false to only get normal symbols.
* @param aForceIncludeOrphanSymbols Set to true to include symbols having no symbol found
* in lib. The normal option is false, and set to true
* only to build the full list of symbols.
*/
void GetSymbolsWithinPath( SCH_REFERENCE_LIST& aReferences, const SCH_SHEET_PATH& aSheetPath,
bool aIncludePowerSymbols = true,
bool aForceIncludeOrphanSymbols = false ) const;
/**
* Add a #SCH_SHEET_PATH object to \a aSheets for each sheet in the list that are
* contained within \a aSheetPath as well as recursively downwards inside aSheetPath.
*
* @param aReferences List of sheets to populate.
* @param aSheetPath Path to return sheets from
*/
void GetSheetsWithinPath( SCH_SHEET_PATHS& aSheets, const SCH_SHEET_PATH& aSheetPath ) const;
/** /**
* Add a #SCH_REFERENCE_LIST object to \a aRefList for each same-reference set of * Add a #SCH_REFERENCE_LIST object to \a aRefList for each same-reference set of
* multi-unit parts in the list of sheets. The map key for each element will be the * multi-unit parts in the list of sheets. The map key for each element will be the
@ -460,6 +492,11 @@ public:
*/ */
SCH_SHEET_PATH* FindSheetForScreen( const SCH_SCREEN* aScreen ); SCH_SHEET_PATH* FindSheetForScreen( const SCH_SCREEN* aScreen );
/**
* Return a #SCH_SHEET_LIST with a copy of all the #SCH_SHEET_PATH using a particular screen.
*/
SCH_SHEET_LIST FindAllSheetsForScreen( const SCH_SCREEN* aScreen ) const;
/** /**
* Build the list of sheets and their sheet path from \a aSheet. * Build the list of sheets and their sheet path from \a aSheet.
* *

View File

@ -380,6 +380,7 @@ void SCH_EDIT_FRAME::RollbackSchematicFromUndo()
delete undo; delete undo;
SetSheetNumberAndCount(); SetSheetNumberAndCount();
UpdateHierarchyNavigator();
TestDanglingEnds(); TestDanglingEnds();
} }

View File

@ -1243,14 +1243,12 @@ bool SCH_EDITOR_CONTROL::doCopy()
} }
} }
m_supplementaryClipboardInstances.Clear();
schematic.GetSheets().GetSymbols( m_supplementaryClipboardInstances, true, true );
m_supplementaryClipboardPath = m_frame->GetCurrentSheet().Path();
STRING_FORMATTER formatter; STRING_FORMATTER formatter;
SCH_SEXPR_PLUGIN plugin; SCH_SEXPR_PLUGIN plugin;
SCH_SHEET_LIST hiearchy = schematic.GetSheets();
SCH_SHEET_PATH selPath = m_frame->GetCurrentSheet();
plugin.Format( &selection, &m_frame->GetCurrentSheet(), &formatter ); plugin.Format( &selection, &selPath, &hiearchy, &formatter );
return m_toolMgr->SaveClipboard( formatter.GetString() ); return m_toolMgr->SaveClipboard( formatter.GetString() );
} }
@ -1302,60 +1300,118 @@ int SCH_EDITOR_CONTROL::Copy( const TOOL_EVENT& aEvent )
} }
void SCH_EDITOR_CONTROL::updatePastedInstances( const SCH_SHEET_PATH& aPastePath, void SCH_EDITOR_CONTROL::updatePastedSymbol( SCH_COMPONENT* aSymbol, SCH_SCREEN* aPasteScreen,
const KIID_PATH& aClipPath, SCH_SHEET* aSheet, const SCH_SHEET_PATH& aPastePath,
bool aForceKeepAnnotations ) const KIID_PATH& aClipPath,
bool aForceKeepAnnotations )
{ {
KIID_PATH clipItemPath = aClipPath;
clipItemPath.push_back( aSymbol->m_Uuid );
wxString reference, value, footprint;
int unit;
if( m_clipboardSymbolInstances.count( clipItemPath ) > 0 )
{
SYMBOL_INSTANCE_REFERENCE instance = m_clipboardSymbolInstances.at( clipItemPath );
unit = instance.m_Unit;
reference = instance.m_Reference;
value = instance.m_Value;
footprint = instance.m_Footprint;
}
else
{
// Pasted from notepad or an older instance of eeschema.
// Use the values in the fields instead
reference = aSymbol->GetField( REFERENCE_FIELD )->GetText();
value = aSymbol->GetField( VALUE_FIELD )->GetText();
footprint = aSymbol->GetField( FOOTPRINT_FIELD )->GetText();
unit = aSymbol->GetUnit();
}
if( aForceKeepAnnotations && !reference.IsEmpty() )
{
aSymbol->SetRef( &aPastePath, reference );
aSymbol->SetValue( &aPastePath, value );
aSymbol->SetFootprint( &aPastePath, footprint );
}
else
{
aSymbol->ClearAnnotation( &aPastePath );
}
// We might clear annotations but always leave the original unit number from the paste
aSymbol->SetUnitSelection( &aPastePath, unit );
aSymbol->SetUnit( unit );
}
SCH_SHEET_PATH SCH_EDITOR_CONTROL::updatePastedSheet( const SCH_SHEET_PATH& aPastePath,
const KIID_PATH& aClipPath, SCH_SHEET* aSheet,
bool aForceKeepAnnotations,
SCH_SHEET_LIST* aPastedSheetsSoFar,
SCH_REFERENCE_LIST* aPastedSymbolsSoFar )
{
SCH_SHEET_PATH sheetPath = aPastePath;
sheetPath.push_back( aSheet );
aSheet->AddInstance( sheetPath.Path() );
wxString pageNum;
if( m_clipboardSheetInstances.count( aClipPath ) > 0 )
pageNum = m_clipboardSheetInstances.at( aClipPath ).m_PageNumber;
else
pageNum = wxString::Format( "%d", static_cast<int>( aPastedSheetsSoFar->size() ) );
aSheet->SetPageNumber( sheetPath, pageNum );
aPastedSheetsSoFar->push_back( sheetPath );
if( aSheet->GetScreen() == nullptr )
return sheetPath; // We can only really set the page number but not load any items
for( SCH_ITEM* item : aSheet->GetScreen()->Items() ) for( SCH_ITEM* item : aSheet->GetScreen()->Items() )
{ {
if( item->Type() == SCH_COMPONENT_T ) if( item->Type() == SCH_COMPONENT_T )
{ {
SCH_COMPONENT* symbol = static_cast<SCH_COMPONENT*>( item ); SCH_COMPONENT* symbol = static_cast<SCH_COMPONENT*>( item );
KIID_PATH clipItemPath = aClipPath; updatePastedSymbol( symbol, aSheet->GetScreen(), sheetPath, aClipPath,
clipItemPath.push_back( symbol->m_Uuid ); aForceKeepAnnotations );
// SCH_REFERENCE_LIST doesn't include the root sheet in the path
clipItemPath.erase( clipItemPath.begin() );
int ii = m_supplementaryClipboardInstances.FindRefByPath( clipItemPath.AsString() );
if( ii >= 0 )
{
SCH_REFERENCE instance = m_supplementaryClipboardInstances[ ii ];
symbol->SetUnitSelection( &aPastePath, instance.GetUnit() );
symbol->SetUnit( instance.GetUnit() );
if( aForceKeepAnnotations )
{
symbol->SetRef( &aPastePath, instance.GetRef() );
symbol->SetValue( &aPastePath, instance.GetValue() );
symbol->SetFootprint( &aPastePath, instance.GetFootprint() );
}
else
{
symbol->ClearAnnotation( &aPastePath );
}
}
else
{
symbol->ClearAnnotation( &aPastePath );
}
} }
else if( item->Type() == SCH_SHEET_T ) else if( item->Type() == SCH_SHEET_T )
{ {
SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item ); SCH_SHEET* subsheet = static_cast<SCH_SHEET*>( item );
SCH_SHEET_PATH pastePath = aPastePath;
pastePath.push_back( sheet );
KIID_PATH clipPath = aClipPath; KIID_PATH newClipPath = aClipPath;
clipPath.push_back( sheet->m_Uuid ); newClipPath.push_back( subsheet->m_Uuid );
sheet->AddInstance( pastePath.Path() ); updatePastedSheet( sheetPath, newClipPath, subsheet, aForceKeepAnnotations,
updatePastedInstances( pastePath, clipPath, sheet, aForceKeepAnnotations ); aPastedSheetsSoFar, aPastedSymbolsSoFar );
SCH_SHEET_PATH subSheetPath = sheetPath;
subSheetPath.push_back( subsheet );
subSheetPath.GetSymbols( *aPastedSymbolsSoFar );
} }
} }
return sheetPath;
}
void SCH_EDITOR_CONTROL::setClipboardInstances( const SCH_SCREEN* aPastedScreen )
{
m_clipboardSheetInstances.clear();
for( const SCH_SHEET_INSTANCE sheet : aPastedScreen->GetSheetInstances() )
m_clipboardSheetInstances[sheet.m_Path] = sheet;
m_clipboardSymbolInstances.clear();
for( const SYMBOL_INSTANCE_REFERENCE symbol : aPastedScreen->GetSymbolInstances() )
m_clipboardSymbolInstances[symbol.m_Path] = symbol;
} }
@ -1396,6 +1452,9 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
bool forceKeepAnnotations = false; bool forceKeepAnnotations = false;
// Save loaded screen instances to m_clipboardSheetInstances
setClipboardInstances( paste_screen );
if( aEvent.IsAction( &ACTIONS::pasteSpecial ) ) if( aEvent.IsAction( &ACTIONS::pasteSpecial ) )
{ {
DIALOG_PASTE_SPECIAL dlg( m_frame, &forceKeepAnnotations ); DIALOG_PASTE_SPECIAL dlg( m_frame, &forceKeepAnnotations );
@ -1416,10 +1475,35 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
if( destFn.IsRelative() ) if( destFn.IsRelative() )
destFn.MakeAbsolute( m_frame->Prj().GetProjectPath() ); destFn.MakeAbsolute( m_frame->Prj().GetProjectPath() );
// List of paths in the hierarchy that refer to the destination sheet of the paste
SCH_SHEET_LIST pasteInstances = hierarchy.FindAllSheetsForScreen( pasteRoot.LastScreen() );
pasteInstances.SortByPageNumbers();
// Build a list of screens from the current design (to avoid loading sheets that already exist)
std::map<wxString, SCH_SCREEN*> loadedScreens;
for( const SCH_SHEET_PATH& item : hierarchy )
{
if( item.LastScreen() )
loadedScreens[item.Last()->GetFileName()] = item.LastScreen();
}
// Build symbol list for reannotation of duplicates
SCH_SHEET_LIST sheets = m_frame->Schematic().GetSheets();
SCH_REFERENCE_LIST existingRefs;
sheets.GetSymbols( existingRefs );
existingRefs.SortByReferenceOnly();
// Keep track of pasted sheets and symbols for the different
// paths to the hiearchy
std::map<SCH_SHEET_PATH, SCH_REFERENCE_LIST> pastedSymbols;
std::map<SCH_SHEET_PATH, SCH_SHEET_LIST> pastedSheets;
for( SCH_ITEM* item : paste_screen->Items() ) for( SCH_ITEM* item : paste_screen->Items() )
{ {
loadedItems.push_back( item ); loadedItems.push_back( item );
//@todo: we might want to sort the sheets by page number before adding to loadedItems
if( item->Type() == SCH_SHEET_T ) if( item->Type() == SCH_SHEET_T )
{ {
SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item ); SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
@ -1448,7 +1532,7 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
for( unsigned i = 0; i < loadedItems.size(); ++i ) for( unsigned i = 0; i < loadedItems.size(); ++i )
{ {
EDA_ITEM* item = loadedItems[i]; EDA_ITEM* item = loadedItems[i];
KIID_PATH clipPath = m_supplementaryClipboardPath; KIID_PATH clipPath( wxT("/") ); // clipboard is at root
if( item->Type() == SCH_COMPONENT_T ) if( item->Type() == SCH_COMPONENT_T )
{ {
@ -1463,29 +1547,51 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
wxCHECK2( currentScreen, continue ); wxCHECK2( currentScreen, continue );
auto it = currentScreen->GetLibSymbols().find( symbol->GetSchSymbolLibraryName() ); auto it = currentScreen->GetLibSymbols().find( symbol->GetSchSymbolLibraryName() );
auto end = currentScreen->GetLibSymbols().end();
if( it != currentScreen->GetLibSymbols().end() ) if( it == end )
symbol->SetLibSymbol( new LIB_PART( *it->second ) );
if( !forceKeepAnnotations )
{ {
// clear the annotation, but preserve the selected unit // If can't find library definition in the design, use the pasted library
int unit = symbol->GetUnit(); it = paste_screen->GetLibSymbols().find( symbol->GetSchSymbolLibraryName() );
symbol->ClearAnnotation( nullptr ); end = paste_screen->GetLibSymbols().end();
symbol->SetUnit( unit );
} }
LIB_PART* libPart = nullptr;
if( it != end )
{
libPart = new LIB_PART( *it->second );
symbol->SetLibSymbol( libPart );
}
for( SCH_SHEET_PATH& instance : pasteInstances )
{
updatePastedSymbol( symbol, paste_screen, instance, clipPath,
forceKeepAnnotations );
}
// Assign a new KIID
const_cast<KIID&>( item->m_Uuid ) = KIID();
// Make sure pins get a new UUID // Make sure pins get a new UUID
for( SCH_PIN* pin : symbol->GetPins() ) for( SCH_PIN* pin : symbol->GetPins() )
const_cast<KIID&>( pin->m_Uuid ) = KIID(); const_cast<KIID&>( pin->m_Uuid ) = KIID();
}
if( item->Type() == SCH_SHEET_T ) for( SCH_SHEET_PATH& instance : pasteInstances )
{
// Ignore pseudo-symbols (e.g. power symbols) and symbols from a non-existant library
if( libPart && symbol->GetRef( &instance )[0] != wxT( '#' ) )
{
SCH_REFERENCE schReference( symbol, libPart, instance );
schReference.SetSheetNumber( instance.GetVirtualPageNumber() );
pastedSymbols[instance].AddItem( schReference );
}
}
}
else if( item->Type() == SCH_SHEET_T )
{ {
SCH_SHEET* sheet = (SCH_SHEET*) item; SCH_SHEET* sheet = (SCH_SHEET*) item;
SCH_FIELD& nameField = sheet->GetFields()[SHEETNAME]; SCH_FIELD& nameField = sheet->GetFields()[SHEETNAME];
wxFileName fn = sheet->GetFileName();
SCH_SCREEN* existingScreen = nullptr;
wxString baseName = nameField.GetText(); wxString baseName = nameField.GetText();
wxString candidateName = baseName; wxString candidateName = baseName;
wxString number; wxString number;
@ -1496,20 +1602,20 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
baseName.RemoveLast(); baseName.RemoveLast();
} }
//@todo: it might be better to just iterate through the sheet names
// in this screen instead of the whole hiearchy.
int uniquifier = std::max( 0, wxAtoi( number ) ) + 1; int uniquifier = std::max( 0, wxAtoi( number ) ) + 1;
// Ensure we have latest hierarchy, as we may have added a sheet in the previous
// iteration
hierarchy = m_frame->Schematic().GetSheets();
while( hierarchy.NameExists( candidateName ) ) while( hierarchy.NameExists( candidateName ) )
candidateName = wxString::Format( wxT( "%s%d" ), baseName, uniquifier++ ); candidateName = wxString::Format( wxT( "%s%d" ), baseName, uniquifier++ );
nameField.SetText( candidateName ); nameField.SetText( candidateName );
wxFileName fn = sheet->GetFileName();
SCH_SCREEN* existingScreen = nullptr;
sheet->SetParent( pasteRoot.Last() ); sheet->SetParent( pasteRoot.Last() );
sheet->SetScreen( nullptr ); sheet->SetScreen( nullptr );
sheetsPasted = true;
if( !fn.IsAbsolute() ) if( !fn.IsAbsolute() )
{ {
@ -1517,10 +1623,14 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
fn.Normalize( wxPATH_NORM_ALL, currentSheetFileName.GetPath() ); fn.Normalize( wxPATH_NORM_ALL, currentSheetFileName.GetPath() );
} }
// Try to find the screen for the pasted sheet by several means
if( !m_frame->Schematic().Root().SearchHierarchy( fn.GetFullPath( wxPATH_UNIX ), if( !m_frame->Schematic().Root().SearchHierarchy( fn.GetFullPath( wxPATH_UNIX ),
&existingScreen ) ) &existingScreen ) )
{ {
searchSupplementaryClipboard( sheet->GetFileName(), &existingScreen ); if( loadedScreens.count( sheet->GetFileName() ) > 0 )
existingScreen = loadedScreens.at( sheet->GetFileName() );
else
searchSupplementaryClipboard( sheet->GetFileName(), &existingScreen );
} }
if( existingScreen ) if( existingScreen )
@ -1533,34 +1643,34 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
m_frame->InitSheet( sheet, sheet->GetFileName() ); m_frame->InitSheet( sheet, sheet->GetFileName() );
} }
sheetsPasted = true;
// Push it to the clipboard path while it still has its old KIID // Push it to the clipboard path while it still has its old KIID
clipPath.push_back( sheet->m_Uuid ); clipPath.push_back( sheet->m_Uuid );
}
// Everything gets a new KIID // Assign a new KIID to the pasted sheet
const_cast<KIID&>( item->m_Uuid ) = KIID(); const_cast<KIID&>( sheet->m_Uuid ) = KIID();
// Once we have our new KIID we can update all pasted instances. This will either
// reset the annotations or copy "kept" annotations from the supplementary clipboard.
if( item->Type() == SCH_SHEET_T )
{
SCH_SHEET* sheet = (SCH_SHEET*) item;
SCH_SHEET_PATH pastePath = pasteRoot;
pastePath.push_back( sheet );
int page = 1;
wxString pageNum = wxString::Format( "%d", page );
while( hierarchy.PageNumberExists( pageNum ) )
pageNum = wxString::Format( "%d", ++page );
sheet->AddInstance( pastePath.Path() );
sheet->SetPageNumber( pastePath, pageNum );
updatePastedInstances( pastePath, clipPath, sheet, forceKeepAnnotations );
// Make sure pins get a new UUID // Make sure pins get a new UUID
for( SCH_SHEET_PIN* pin : sheet->GetPins() ) for( SCH_SHEET_PIN* pin : sheet->GetPins() )
const_cast<KIID&>( pin->m_Uuid ) = KIID(); const_cast<KIID&>( pin->m_Uuid ) = KIID();
// Once we have our new KIID we can update all pasted instances. This will either
// reset the annotations or copy "kept" annotations from the supplementary clipboard.
for( SCH_SHEET_PATH& instance : pasteInstances )
{
SCH_SHEET_PATH sheetPath = updatePastedSheet( instance, clipPath, sheet,
forceKeepAnnotations,
&pastedSheets[instance],
&pastedSymbols[instance] );
sheetPath.GetSymbols( pastedSymbols[instance] );
}
}
else
{
// Everything gets a new KIID
const_cast<KIID&>( item->m_Uuid ) = KIID();
} }
item->SetFlags( IS_NEW | IS_PASTED | IS_MOVED ); item->SetFlags( IS_NEW | IS_PASTED | IS_MOVED );
@ -1573,8 +1683,29 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
getView()->Hide( item, true ); getView()->Hide( item, true );
} }
pasteInstances.SortByPageNumbers();
m_frame->GetCurrentSheet().UpdateAllScreenReferences();
if( sheetsPasted ) if( sheetsPasted )
{ {
// Update page numbers: Find next free numeric page number
for( SCH_SHEET_PATH& instance : pasteInstances )
{
pastedSheets[instance].SortByPageNumbers();
for( SCH_SHEET_PATH& pastedSheet : pastedSheets[instance] )
{
int page = 1;
wxString pageNum = wxString::Format( "%d", page );
while( hierarchy.PageNumberExists( pageNum ) )
pageNum = wxString::Format( "%d", ++page );
pastedSheet.SetPageNumber( pageNum );
hierarchy.push_back( pastedSheet );
}
}
m_frame->SetSheetNumberAndCount(); m_frame->SetSheetNumberAndCount();
m_frame->UpdateHierarchyNavigator(); m_frame->UpdateHierarchyNavigator();
} }

View File

@ -161,8 +161,16 @@ private:
void doCrossProbeSchToPcb( const TOOL_EVENT& aEvent, bool aForce ); void doCrossProbeSchToPcb( const TOOL_EVENT& aEvent, bool aForce );
void updatePastedInstances( const SCH_SHEET_PATH& aPastePath, const KIID_PATH& aClipPath, void updatePastedSymbol( SCH_COMPONENT* aSymbol, SCH_SCREEN* aPasteScreen,
SCH_SHEET* aSheet, bool aForceKeepAnnotations ); const SCH_SHEET_PATH& aPastePath, const KIID_PATH& aClipPath,
bool aForceKeepAnnotations );
SCH_SHEET_PATH updatePastedSheet( const SCH_SHEET_PATH& aPastePath, const KIID_PATH& aClipPath,
SCH_SHEET* aSheet, bool aForceKeepAnnotations,
SCH_SHEET_LIST* aPastedSheetsSoFar,
SCH_REFERENCE_LIST* aPastedSymbolsSoFar );
void setClipboardInstances( const SCH_SCREEN* aPastedScreen );
/** /**
* Read the footprint info from each line in the stuff file by reference designator. * Read the footprint info from each line in the stuff file by reference designator.
@ -209,8 +217,12 @@ private:
// A map of sheet filename --> screens for the clipboard contents. We use these to hook up // A map of sheet filename --> screens for the clipboard contents. We use these to hook up
// cut/paste operations for unsaved sheet content. // cut/paste operations for unsaved sheet content.
std::map<wxString, SCH_SCREEN*> m_supplementaryClipboard; std::map<wxString, SCH_SCREEN*> m_supplementaryClipboard;
SCH_REFERENCE_LIST m_supplementaryClipboardInstances;
KIID_PATH m_supplementaryClipboardPath; // A map of KIID_PATH --> symbol instances for the clipboard contents.
std::map<KIID_PATH, SYMBOL_INSTANCE_REFERENCE> m_clipboardSymbolInstances;
// A map of KIID_PATH --> sheet instances for the clipboard contents.
std::map<KIID_PATH, SCH_SHEET_INSTANCE> m_clipboardSheetInstances;
}; };

View File

@ -113,6 +113,7 @@ public:
KIID_PATH( const wxString& aString ); KIID_PATH( const wxString& aString );
bool MakeRelativeTo( const KIID_PATH& aPath );
wxString AsString() const; wxString AsString() const;

View File

@ -54,6 +54,16 @@ wxString GetRefDesPrefix( const wxString& aRefDes );
*/ */
wxString GetRefDesUnannotated( const wxString& aRefDes ); wxString GetRefDesUnannotated( const wxString& aRefDes );
/**
* Get the numeric suffix from a refdes - e.g.
* R1 -> 1
* IC34 -> 34
* R? -> -1
* @param aRefDes full refdes
* @return the suffix, or -1 if nothing found
*/
int GetRefDesNumber( const wxString& aRefDes );
/** /**
* Acts just like the strcmp function but treats numbers within the string text * Acts just like the strcmp function but treats numbers within the string text
* correctly for sorting. eg. A10 > A2 * correctly for sorting. eg. A10 > A2