From 5822cd85c45f8804092cf70d4ab50125e98184cf Mon Sep 17 00:00:00 2001 From: Roberto Fernandez Bautista Date: Sat, 1 May 2021 23:56:37 +0100 Subject: [PATCH] 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 --- common/kiid.cpp | 23 +- common/refdes_utils.cpp | 22 +- .../sch_plugins/kicad/sch_sexpr_parser.cpp | 6 - .../sch_plugins/kicad/sch_sexpr_plugin.cpp | 50 ++- eeschema/sch_plugins/kicad/sch_sexpr_plugin.h | 4 +- eeschema/sch_sheet_path.cpp | 55 +++- eeschema/sch_sheet_path.h | 37 +++ eeschema/schematic_undo_redo.cpp | 1 + eeschema/tools/sch_editor_control.cpp | 301 +++++++++++++----- eeschema/tools/sch_editor_control.h | 20 +- include/kiid.h | 1 + include/refdes_utils.h | 10 + 12 files changed, 427 insertions(+), 103 deletions(-) diff --git a/common/kiid.cpp b/common/kiid.cpp index b9f89b1d6f..1f5ff86fb3 100644 --- a/common/kiid.cpp +++ b/common/kiid.cpp @@ -3,7 +3,7 @@ * * Copyright (C) 2020 Ian McInerney * 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 * 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 path; diff --git a/common/refdes_utils.cpp b/common/refdes_utils.cpp index aa12c359d9..7cf46f0725 100644 --- a/common/refdes_utils.cpp +++ b/common/refdes_utils.cpp @@ -1,7 +1,7 @@ /* * 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 * 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( result ); + } + + return retval; +} + + int RefDesStringCompare( const wxString& aFirst, const wxString& aSecond ) { // Compare unescaped text diff --git a/eeschema/sch_plugins/kicad/sch_sexpr_parser.cpp b/eeschema/sch_plugins/kicad/sch_sexpr_parser.cpp index 7ea4f4f1ad..68e79b45ae 100644 --- a/eeschema/sch_plugins/kicad/sch_sexpr_parser.cpp +++ b/eeschema/sch_plugins/kicad/sch_sexpr_parser.cpp @@ -2162,16 +2162,10 @@ void SCH_SEXPR_PARSER::ParseSchematic( SCH_SHEET* aSheet, bool aIsCopyableOnly, break; case T_sheet_instances: - if( aIsCopyableOnly ) - Unexpected( T_sheet_instances ); - parseSchSheetInstances( aSheet, screen ); break; case T_symbol_instances: - if( aIsCopyableOnly ) - Unexpected( T_symbol_instances ); - parseSchSymbolInstances( screen ); break; diff --git a/eeschema/sch_plugins/kicad/sch_sexpr_plugin.cpp b/eeschema/sch_plugins/kicad/sch_sexpr_plugin.cpp index 48e7d45054..ebf5acec00 100644 --- a/eeschema/sch_plugins/kicad/sch_sexpr_plugin.cpp +++ b/eeschema/sch_plugins/kicad/sch_sexpr_plugin.cpp @@ -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 ) { - wxCHECK( aSelection && aFormatter, /* void */ ); + wxCHECK( aSelection && aSelectionPath && aFullSheetHierarchy && aFormatter, /* void */ ); 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" ); } + // 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 ) { 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() ) { case SCH_COMPONENT_T: - saveSymbol( static_cast( item ), aSheetPath, 0 ); + saveSymbol( static_cast( item ), aSelectionPath, 0 ); + + aSelectionPath->AppendSymbol( selectedSymbols, static_cast( item ), + true, true ); break; case SCH_BITMAP_T: @@ -781,6 +791,16 @@ void SCH_SEXPR_PLUGIN::Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSheetP case SCH_SHEET_T: saveSheet( static_cast< SCH_SHEET* >( item ), 0 ); + + { + SCH_SHEET_PATH subSheetPath = *aSelectionPath; + subSheetPath.push_back( static_cast( item ) ); + + aFullSheetHierarchy->GetSheetsWithinPath( selectedSheets, subSheetPath ); + aFullSheetHierarchy->GetSymbolsWithinPath( selectedSymbols, subSheetPath, true, + true ); + } + break; 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()" ); } } + + // Make all instance information relative to the selection path + KIID_PATH selectionPath = aSelectionPath->PathWithoutRootUuid(); + + selectedSheets.SortByPageNumbers(); + std::vector 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 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 ); } diff --git a/eeschema/sch_plugins/kicad/sch_sexpr_plugin.h b/eeschema/sch_plugins/kicad/sch_sexpr_plugin.h index 12b8dec440..0be43866c0 100644 --- a/eeschema/sch_plugins/kicad/sch_sexpr_plugin.h +++ b/eeschema/sch_plugins/kicad/sch_sexpr_plugin.h @@ -99,8 +99,8 @@ public: void Format( SCH_SHEET* aSheet ); - void Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSheetPath, - OUTPUTFORMATTER* aFormatter ); + void Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSelectionPath, + SCH_SHEET_LIST* aFullSheetHierarchy, OUTPUTFORMATTER* aFormatter ); void EnumerateSymbolLib( wxArrayString& aSymbolNameList, const wxString& aLibraryPath, diff --git a/eeschema/sch_sheet_path.cpp b/eeschema/sch_sheet_path.cpp index e124d2dc61..2445e563a2 100644 --- a/eeschema/sch_sheet_path.cpp +++ b/eeschema/sch_sheet_path.cpp @@ -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 { if( !empty() ) @@ -551,7 +566,7 @@ bool SCH_SHEET_LIST::PageNumberExists( const wxString& aPageNumber ) const { for( const SCH_SHEET_PATH& sheet : *this ) { - if( sheet.Last()->GetPageNumber( sheet ) == aPageNumber ) + if( sheet.GetPageNumber() == aPageNumber ) 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, 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( const std::vector& aSymbolInstances ) { diff --git a/eeschema/sch_sheet_path.h b/eeschema/sch_sheet_path.h index 76c78fbe65..5287e20c3f 100644 --- a/eeschema/sch_sheet_path.h +++ b/eeschema/sch_sheet_path.h @@ -223,6 +223,14 @@ public: */ 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. * @@ -432,6 +440,30 @@ public: void GetSymbols( SCH_REFERENCE_LIST& aReferences, bool aIncludePowerSymbols = true, 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 * 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 ); + /** + * 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. * diff --git a/eeschema/schematic_undo_redo.cpp b/eeschema/schematic_undo_redo.cpp index 6184032f26..eb0ea3ae4b 100644 --- a/eeschema/schematic_undo_redo.cpp +++ b/eeschema/schematic_undo_redo.cpp @@ -380,6 +380,7 @@ void SCH_EDIT_FRAME::RollbackSchematicFromUndo() delete undo; SetSheetNumberAndCount(); + UpdateHierarchyNavigator(); TestDanglingEnds(); } diff --git a/eeschema/tools/sch_editor_control.cpp b/eeschema/tools/sch_editor_control.cpp index 309baf4ce3..d120c4ea4f 100644 --- a/eeschema/tools/sch_editor_control.cpp +++ b/eeschema/tools/sch_editor_control.cpp @@ -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; 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() ); } @@ -1302,60 +1300,118 @@ int SCH_EDITOR_CONTROL::Copy( const TOOL_EVENT& aEvent ) } -void SCH_EDITOR_CONTROL::updatePastedInstances( const SCH_SHEET_PATH& aPastePath, - const KIID_PATH& aClipPath, SCH_SHEET* aSheet, - bool aForceKeepAnnotations ) +void SCH_EDITOR_CONTROL::updatePastedSymbol( SCH_COMPONENT* aSymbol, SCH_SCREEN* aPasteScreen, + const SCH_SHEET_PATH& aPastePath, + 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( 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() ) { if( item->Type() == SCH_COMPONENT_T ) { SCH_COMPONENT* symbol = static_cast( item ); - KIID_PATH clipItemPath = aClipPath; - clipItemPath.push_back( symbol->m_Uuid ); - - // 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 ); - } + updatePastedSymbol( symbol, aSheet->GetScreen(), sheetPath, aClipPath, + aForceKeepAnnotations ); } else if( item->Type() == SCH_SHEET_T ) { - SCH_SHEET* sheet = static_cast( item ); - SCH_SHEET_PATH pastePath = aPastePath; - pastePath.push_back( sheet ); + SCH_SHEET* subsheet = static_cast( item ); - KIID_PATH clipPath = aClipPath; - clipPath.push_back( sheet->m_Uuid ); + KIID_PATH newClipPath = aClipPath; + newClipPath.push_back( subsheet->m_Uuid ); - sheet->AddInstance( pastePath.Path() ); - updatePastedInstances( pastePath, clipPath, sheet, aForceKeepAnnotations ); + updatePastedSheet( sheetPath, newClipPath, subsheet, 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; + // Save loaded screen instances to m_clipboardSheetInstances + setClipboardInstances( paste_screen ); + if( aEvent.IsAction( &ACTIONS::pasteSpecial ) ) { DIALOG_PASTE_SPECIAL dlg( m_frame, &forceKeepAnnotations ); @@ -1416,10 +1475,35 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent ) if( destFn.IsRelative() ) 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 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 pastedSymbols; + std::map pastedSheets; + for( SCH_ITEM* item : paste_screen->Items() ) { 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 ) { SCH_SHEET* sheet = static_cast( item ); @@ -1448,7 +1532,7 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent ) for( unsigned i = 0; i < loadedItems.size(); ++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 ) { @@ -1463,29 +1547,51 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent ) wxCHECK2( currentScreen, continue ); auto it = currentScreen->GetLibSymbols().find( symbol->GetSchSymbolLibraryName() ); + auto end = currentScreen->GetLibSymbols().end(); - if( it != currentScreen->GetLibSymbols().end() ) - symbol->SetLibSymbol( new LIB_PART( *it->second ) ); - - if( !forceKeepAnnotations ) + if( it == end ) { - // clear the annotation, but preserve the selected unit - int unit = symbol->GetUnit(); - symbol->ClearAnnotation( nullptr ); - symbol->SetUnit( unit ); + // If can't find library definition in the design, use the pasted library + it = paste_screen->GetLibSymbols().find( symbol->GetSchSymbolLibraryName() ); + end = paste_screen->GetLibSymbols().end(); } + 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( item->m_Uuid ) = KIID(); + // Make sure pins get a new UUID for( SCH_PIN* pin : symbol->GetPins() ) const_cast( 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_FIELD& nameField = sheet->GetFields()[SHEETNAME]; - wxFileName fn = sheet->GetFileName(); - SCH_SCREEN* existingScreen = nullptr; wxString baseName = nameField.GetText(); wxString candidateName = baseName; wxString number; @@ -1496,20 +1602,20 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent ) 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; - // 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 ) ) candidateName = wxString::Format( wxT( "%s%d" ), baseName, uniquifier++ ); nameField.SetText( candidateName ); + wxFileName fn = sheet->GetFileName(); + SCH_SCREEN* existingScreen = nullptr; + sheet->SetParent( pasteRoot.Last() ); sheet->SetScreen( nullptr ); - sheetsPasted = true; if( !fn.IsAbsolute() ) { @@ -1517,10 +1623,14 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent ) 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 ), &existingScreen ) ) { - searchSupplementaryClipboard( sheet->GetFileName(), &existingScreen ); + if( loadedScreens.count( sheet->GetFileName() ) > 0 ) + existingScreen = loadedScreens.at( sheet->GetFileName() ); + else + searchSupplementaryClipboard( sheet->GetFileName(), &existingScreen ); } if( existingScreen ) @@ -1533,34 +1643,34 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent ) m_frame->InitSheet( sheet, sheet->GetFileName() ); } + sheetsPasted = true; + // Push it to the clipboard path while it still has its old KIID clipPath.push_back( sheet->m_Uuid ); - } - // Everything gets a new KIID - const_cast( item->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 ); + // Assign a new KIID to the pasted sheet + const_cast( sheet->m_Uuid ) = KIID(); // Make sure pins get a new UUID for( SCH_SHEET_PIN* pin : sheet->GetPins() ) const_cast( 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( item->m_Uuid ) = KIID(); } 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 ); } + pasteInstances.SortByPageNumbers(); + m_frame->GetCurrentSheet().UpdateAllScreenReferences(); + 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->UpdateHierarchyNavigator(); } diff --git a/eeschema/tools/sch_editor_control.h b/eeschema/tools/sch_editor_control.h index ba6d5af51f..0b5b1a7f25 100644 --- a/eeschema/tools/sch_editor_control.h +++ b/eeschema/tools/sch_editor_control.h @@ -161,8 +161,16 @@ private: void doCrossProbeSchToPcb( const TOOL_EVENT& aEvent, bool aForce ); - void updatePastedInstances( const SCH_SHEET_PATH& aPastePath, const KIID_PATH& aClipPath, - SCH_SHEET* aSheet, bool aForceKeepAnnotations ); + void updatePastedSymbol( SCH_COMPONENT* aSymbol, SCH_SCREEN* aPasteScreen, + 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. @@ -209,8 +217,12 @@ private: // A map of sheet filename --> screens for the clipboard contents. We use these to hook up // cut/paste operations for unsaved sheet content. std::map m_supplementaryClipboard; - SCH_REFERENCE_LIST m_supplementaryClipboardInstances; - KIID_PATH m_supplementaryClipboardPath; + + // A map of KIID_PATH --> symbol instances for the clipboard contents. + std::map m_clipboardSymbolInstances; + + // A map of KIID_PATH --> sheet instances for the clipboard contents. + std::map m_clipboardSheetInstances; }; diff --git a/include/kiid.h b/include/kiid.h index d51bf69858..eb23cc09bb 100644 --- a/include/kiid.h +++ b/include/kiid.h @@ -113,6 +113,7 @@ public: KIID_PATH( const wxString& aString ); + bool MakeRelativeTo( const KIID_PATH& aPath ); wxString AsString() const; diff --git a/include/refdes_utils.h b/include/refdes_utils.h index feb87f9678..bf244c872d 100644 --- a/include/refdes_utils.h +++ b/include/refdes_utils.h @@ -54,6 +54,16 @@ wxString GetRefDesPrefix( 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 * correctly for sorting. eg. A10 > A2