From 169f63a6c02dc12bd08923dc887ffc9f08d90e4d Mon Sep 17 00:00:00 2001 From: Wayne Stambaugh Date: Sun, 26 Apr 2020 16:53:29 -0400 Subject: [PATCH] Eeschema: make schematic sharing truly safe across all designs. There has been a long standing (since the beginning of the project?) issue with sharing schematics between projects. It has been somewhat supported for complex hierarchies (a sheet shared multiple times in a single design) but it has not been well supported for simple hierarchies (the symbol references cannot be changed in the shared schematic). This issue has been resolved by moving all of the symbol instance sheet paths from the symbol definitions in the all of the project files and save all symbol path instances in the root sheet. This ensures that orphaned symbol instance paths do not accumulate in shared schematic files and that designs that reuse schematic in simple hierarchies can how have different references. It also allows the root schematic from one project to be uses as a sub-sheet in another project. When legacy schematics are loaded, all sheet and symbol UUIDs are converted from time stamps to true UUIDs. This is done to ensure there are no sheet path instance clashes between projects. That being said, there are no checks for this. It is assumed that the probability of UUID clashes is so low that it doesn't make sense to test for them. --- common/common.cpp | 22 ++- common/trace_helpers.cpp | 5 +- eeschema/dialogs/dialog_sch_sheet_props.cpp | 51 ++++-- eeschema/ee_collectors.cpp | 1 + eeschema/fields_grid_table.cpp | 2 +- eeschema/files-io.cpp | 125 +++++++++++--- .../netlist_exporters/netlist_exporter.cpp | 1 + eeschema/netlist_object_list.cpp | 1 + eeschema/sch_component.cpp | 42 +++++ eeschema/sch_component.h | 25 +-- eeschema/sch_edit_frame.h | 12 +- eeschema/sch_io_mgr.h | 8 +- eeschema/sch_legacy_plugin.cpp | 24 +-- eeschema/sch_legacy_plugin.h | 4 +- eeschema/sch_plugin.cpp | 2 +- eeschema/sch_reference_list.h | 10 +- eeschema/sch_screen.cpp | 22 ++- eeschema/sch_screen.h | 36 +++- eeschema/sch_sexpr_parser.cpp | 100 ++++++----- eeschema/sch_sexpr_parser.h | 8 +- eeschema/sch_sexpr_plugin.cpp | 160 +++++++++--------- eeschema/sch_sexpr_plugin.h | 6 +- eeschema/sch_sheet.h | 9 +- eeschema/sch_sheet_path.cpp | 100 ++++++++--- eeschema/sch_sheet_path.h | 43 ++++- eeschema/schematic.keywords | 2 +- eeschema/sheet.cpp | 4 + include/common.h | 7 + include/trace_helpers.h | 9 +- 29 files changed, 589 insertions(+), 252 deletions(-) diff --git a/common/common.cpp b/common/common.cpp index 2f24e692d8..7adcd5fc9d 100644 --- a/common/common.cpp +++ b/common/common.cpp @@ -57,18 +57,6 @@ KIID::KIID() : m_uuid( randomGenerator() ), m_cached_timestamp( 0 ) { -#if defined(EESCHEMA) - // JEY TODO: use legacy timestamps until new EEschema file format is in - static timestamp_t oldTimeStamp; - timestamp_t newTimeStamp = time( NULL ); - - if( newTimeStamp <= oldTimeStamp ) - newTimeStamp = oldTimeStamp + 1; - - oldTimeStamp = newTimeStamp; - - *this = KIID( wxString::Format( "%8.8X", newTimeStamp ) ); -#endif } @@ -178,6 +166,16 @@ wxString KIID::AsLegacyTimestampString() const } +void KIID::ConvertTimestampToUuid() +{ + if( !IsLegacyTimestamp() ) + return; + + m_cached_timestamp = 0; + m_uuid = randomGenerator(); +} + + /** * Global variables definitions. * diff --git a/common/trace_helpers.cpp b/common/trace_helpers.cpp index 1508267f48..8b8748e5c2 100644 --- a/common/trace_helpers.cpp +++ b/common/trace_helpers.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2018 Wayne Stambaugh - * Copyright (C) 2018-2019 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2018-2020 KiCad Developers, see change_log.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 @@ -46,7 +46,8 @@ const wxChar* const traceLocale = wxT( "KICAD_LOCALE" ); const wxChar* const traceScreen = wxT( "KICAD_SCREEN" ); const wxChar* const traceZoomScroll = wxT( "KICAD_ZOOM_SCROLL" ); const wxChar* const traceSymbolResolver = wxT( "KICAD_SYM_RESOLVE" ); -const wxChar* const traceDisplayLocation = wxT( "KICAD_DISPLAY_LOCATION"); +const wxChar* const traceDisplayLocation = wxT( "KICAD_DISPLAY_LOCATION" ); +const wxChar* const traceSchSheetPaths = wxT( "KICAD_SCH_SHEET_PATHS" ); wxString dump( const wxArrayString& aArray ) diff --git a/eeschema/dialogs/dialog_sch_sheet_props.cpp b/eeschema/dialogs/dialog_sch_sheet_props.cpp index fcd19f9f2e..901fa7141a 100644 --- a/eeschema/dialogs/dialog_sch_sheet_props.cpp +++ b/eeschema/dialogs/dialog_sch_sheet_props.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include "panel_eeschema_color_settings.h" DIALOG_SCH_SHEET_PROPS::DIALOG_SCH_SHEET_PROPS( SCH_EDIT_FRAME* aParent, SCH_SHEET* aSheet, @@ -244,9 +245,11 @@ bool DIALOG_SCH_SHEET_PROPS::TransferDataFromWindow() // Ensure filepath is not empty. (In normal use will be caught by grid validators, // but unedited data from existing files can be bad.) + + // @todo What happens when there are invalid file name characters? if( newRelativeNativeFilename.IsEmpty() ) { - wxMessageBox( _( "A sheet must have a file specified." ) ); + wxMessageBox( _( "A sheet must have a valid file name." ) ); return false; } @@ -254,9 +257,9 @@ bool DIALOG_SCH_SHEET_PROPS::TransferDataFromWindow() // validators, but unedited data from existing files can be bad.) wxFileName fn( newRelativeNativeFilename ); - if( fn.GetExt() != "sch" ) + if( fn.GetExt().CmpNoCase( KiCadSchematicFileExtension ) != 0 ) { - wxMessageBox( _( "Sheet file must have a '.sch' extension." ) ); + wxMessageBox( _( "Sheet file must have a '.kicad_sch' extension." ) ); return false; } @@ -331,10 +334,26 @@ bool DIALOG_SCH_SHEET_PROPS::TransferDataFromWindow() bool DIALOG_SCH_SHEET_PROPS::onSheetFilenameChanged( const wxString& aNewFilename ) { + wxString msg; + // Relative file names are relative to the path of the current sheet. This allows for // nesting of schematic files in subfolders. wxFileName fileName( aNewFilename ); + if( fileName.GetExt().IsEmpty() ) + { + fileName.SetExt( KiCadSchematicFileExtension ); + } + else if( fileName.GetExt().CmpNoCase( KiCadSchematicFileExtension ) != 0 ) + { + msg.Printf( _( "The file \"%s\" does not appear to be a valid schematic file." ), + fileName.GetFullName() ); + wxMessageDialog badSchFileDialog( this, msg, _( "Invalid Schematic File" ), + wxOK | wxCENTRE | wxICON_EXCLAMATION ); + badSchFileDialog.ShowModal(); + return false; + } + if( !fileName.IsAbsolute() ) { const SCH_SCREEN* currentScreen = g_CurrentSheet->LastScreen(); @@ -354,7 +373,6 @@ bool DIALOG_SCH_SHEET_PROPS::onSheetFilenameChanged( const wxString& aNewFilenam // Inside Eeschema, filenames are stored using unix notation newAbsoluteFilename.Replace( wxT( "\\" ), wxT( "/" ) ); - wxString msg; bool renameFile = false; bool loadFromFile = false; bool clearAnnotation = false; @@ -367,7 +385,8 @@ bool DIALOG_SCH_SHEET_PROPS::onSheetFilenameChanged( const wxString& aNewFilenam if( !g_RootSheet->SearchHierarchy( newAbsoluteFilename, &useScreen ) ) { loadFromFile = wxFileExists( newAbsoluteFilename ); - wxLogDebug( "Sheet requested file \"%s\", %s", + + wxLogTrace( tracePathsAndFiles, "Sheet requested file \"%s\", %s", newAbsoluteFilename, ( loadFromFile ) ? "found" : "not found" ); } @@ -465,7 +484,7 @@ bool DIALOG_SCH_SHEET_PROPS::onSheetFilenameChanged( const wxString& aNewFilenam if( renameFile ) { - SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) ); + SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) ); // If the the associated screen is shared by more than one sheet, do not // change the filename of the corresponding screen here. @@ -476,11 +495,12 @@ bool DIALOG_SCH_SHEET_PROPS::onSheetFilenameChanged( const wxString& aNewFilenam try { - pi->Save( newAbsoluteFilename, m_sheet->GetScreen(), &Kiway() ); + pi->Save( newAbsoluteFilename, m_sheet, &Kiway() ); } catch( const IO_ERROR& ioe ) { - msg.Printf( _( "Error occurred saving schematic file \"%s\"." ), newAbsoluteFilename ); + msg.Printf( _( "Error occurred saving schematic file \"%s\"." ), + newAbsoluteFilename ); DisplayErrorMessage( this, msg, ioe.What() ); msg.Printf( _( "Failed to save schematic \"%s\"" ), newAbsoluteFilename ); @@ -569,17 +589,22 @@ void DIALOG_SCH_SHEET_PROPS::OnGridCellChanging( wxGridEvent& event ) success = false; } } - else if( event.GetRow() == SHEETFILENAME && event.GetCol() == FDC_VALUE ) + else if( event.GetRow() == SHEETFILENAME && event.GetCol() == FDC_VALUE && textControl ) { - if( textControl && textControl->IsEmpty() ) + if( textControl->IsEmpty() ) { wxMessageBox( _( "A sheet must have a file specified." ) ); success = false; } - else if( textControl && !textControl->GetValue().EndsWith( wxT( ".sch" ) ) ) + else { - wxMessageBox( _( "Sheet filename must have a '.sch' extension." ) ); - success = false; + wxFileName fn = textControl->GetValue(); + + if( fn.GetExt().CmpNoCase( KiCadSchematicFileExtension ) != 0 ) + { + wxMessageBox( _( "Sheet filename must have a '.sch' extension." ) ); + success = false; + } } } diff --git a/eeschema/ee_collectors.cpp b/eeschema/ee_collectors.cpp index 672cb68c4a..64bad8eae4 100644 --- a/eeschema/ee_collectors.cpp +++ b/eeschema/ee_collectors.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include "sch_reference_list.h" diff --git a/eeschema/fields_grid_table.cpp b/eeschema/fields_grid_table.cpp index 11794a2c5d..35a7d77636 100644 --- a/eeschema/fields_grid_table.cpp +++ b/eeschema/fields_grid_table.cpp @@ -131,7 +131,7 @@ void FIELDS_GRID_TABLE::initGrid( DIALOG_SHIM* aDialog ) // Create a wild card using wxFileDialog syntax. wxString wildCard( _( "Schematic Files" ) ); std::vector exts; - exts.push_back( LegacySchematicFileExtension ); + exts.push_back( KiCadSchematicFileExtension ); wildCard += AddFileExtListToFilter( exts ); GRID_CELL_PATH_EDITOR* filepathEditor = diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp index 0a8658bfe6..bef97ad806 100644 --- a/eeschema/files-io.cpp +++ b/eeschema/files-io.cpp @@ -55,22 +55,26 @@ #include -bool SCH_EDIT_FRAME::SaveEEFile( SCH_SCREEN* aScreen, bool aSaveUnderNewName, +bool SCH_EDIT_FRAME::SaveEEFile( SCH_SHEET* aSheet, bool aSaveUnderNewName, bool aCreateBackupFile ) { wxString msg; wxFileName schematicFileName; bool success; - if( aScreen == NULL ) - aScreen = GetScreen(); + if( aSheet == NULL ) + aSheet = GetCurrentSheet().Last(); + + SCH_SCREEN* screen = aSheet->GetScreen(); + + wxCHECK( screen, false ); // If no name exists in the window yet - save as new. - if( aScreen->GetFileName().IsEmpty() ) + if( screen->GetFileName().IsEmpty() ) aSaveUnderNewName = true; // Construct the name of the file to be saved - schematicFileName = Prj().AbsolutePath( aScreen->GetFileName() ); + schematicFileName = Prj().AbsolutePath( screen->GetFileName() ); if( aSaveUnderNewName ) { @@ -127,7 +131,7 @@ bool SCH_EDIT_FRAME::SaveEEFile( SCH_SCREEN* aScreen, bool aSaveUnderNewName, try { - pi->Save( schematicFileName.GetFullPath(), aScreen, &Kiway() ); + pi->Save( schematicFileName.GetFullPath(), aSheet, &Kiway() ); success = true; } catch( const IO_ERROR& ioe ) @@ -160,14 +164,14 @@ bool SCH_EDIT_FRAME::SaveEEFile( SCH_SCREEN* aScreen, bool aSaveUnderNewName, // Update the screen and frame info and reset the lock file. if( aSaveUnderNewName ) { - aScreen->SetFileName( schematicFileName.GetFullPath() ); + screen->SetFileName( schematicFileName.GetFullPath() ); LockFile( schematicFileName.GetFullPath() ); } - aScreen->ClrSave(); - aScreen->ClrModify(); + screen->ClrSave(); + screen->ClrModify(); - msg.Printf( _( "File %s saved" ), aScreen->GetFileName() ); + msg.Printf( _( "File %s saved" ), screen->GetFileName() ); SetStatusText( msg, 0 ); } else @@ -428,6 +432,31 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector& aFileSet, in // Update all symbol library links for all sheets. schematic.UpdateSymbolLinks(); + // Replace sheet and symbol time stamps with real UUIDs and update symbol instance + // sheet paths using the new UUID based sheet paths. + + // Save the time stamp sheet paths. + SCH_SHEET_LIST timeStampSheetPaths( g_RootSheet ); + + std::vector oldSheetPaths = timeStampSheetPaths.GetPaths(); + + // The root sheet now gets a permanent UUID. + const_cast( g_RootSheet->m_Uuid ).ConvertTimestampToUuid(); + + // Change the sheet and symbol time stamps to UUIDs. + for( SCH_SCREEN* screen = schematic.GetFirst(); screen; screen = schematic.GetNext() ) + { + for( auto sheet : screen->Items().OfType( SCH_SHEET_T ) ) + const_cast( sheet->m_Uuid ).ConvertTimestampToUuid(); + + for( auto symbol : screen->Items().OfType( SCH_COMPONENT_T ) ) + const_cast( symbol->m_Uuid ).ConvertTimestampToUuid(); + } + + SCH_SHEET_LIST uuidSheetPaths( g_RootSheet ); + + timeStampSheetPaths.ReplaceLegacySheetPaths( oldSheetPaths ); + if( !cfg || cfg->m_Appearance.show_sexpr_file_convert_warning ) { wxRichMessageDialog newFileFormatDlg( @@ -452,6 +481,9 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector& aFileSet, in { for( SCH_SCREEN* screen = schematic.GetFirst(); screen; screen = schematic.GetNext() ) screen->UpdateLocalLibSymbolLinks(); + + // Restore all of the loaded symbol instances from the root sheet screen. + sheetList.UpdateSymbolInstances( g_RootSheet->GetScreen()->m_symbolInstances ); } g_ConnectionGraph->Reset(); @@ -588,8 +620,9 @@ void SCH_EDIT_FRAME::OnImportProject( wxCommandEvent& aEvent ) bool SCH_EDIT_FRAME::SaveProject() { + wxString msg; SCH_SCREEN* screen; - SCH_SCREENS screenList; + SCH_SCREENS screens; bool success = true; bool updateFileType = false; @@ -599,13 +632,63 @@ bool SCH_EDIT_FRAME::SaveProject() if( !fn.IsDirWritable() ) { - wxString msg = wxString::Format( _( "Directory \"%s\" is not writable." ), fn.GetPath() ); + msg = wxString::Format( _( "Directory \"%s\" is not writable." ), fn.GetPath() ); DisplayError( this, msg ); return false; } - for( screen = screenList.GetFirst(); screen; screen = screenList.GetNext() ) + // Warn user on potential file overwrite. This can happen on shared sheets. + wxArrayString overwrittenFiles; + + for( size_t i = 0; i < screens.GetCount(); i++ ) { + screen = screens.GetScreen( i ); + + wxCHECK2( screen, continue ); + + // Convert legacy schematics file name extensions for the new format. + wxFileName tmpFn = screen->GetFileName(); + + if( tmpFn.GetExt() == KiCadSchematicFileExtension ) + continue; + + tmpFn.SetExt( KiCadSchematicFileExtension ); + + if( tmpFn.FileExists() ) + overwrittenFiles.Add( tmpFn.GetFullPath() ); + } + + if( !overwrittenFiles.IsEmpty() ) + { + for( auto overwrittenFile : overwrittenFiles ) + { + if( msg.IsEmpty() ) + msg = overwrittenFile; + else + msg += "\n" + overwrittenFile; + } + + msg = _( "The following files will be overwritten:\n\n" ) + msg; + + wxRichMessageDialog dlg( + this, + _( "Saving the project to the new file format will overwrite existing files." ), + _( "Project Save Warning" ), + wxOK | wxCANCEL | wxCANCEL_DEFAULT | wxCENTER | wxICON_EXCLAMATION ); + dlg.ShowDetailedText( msg ); + dlg.SetOKCancelLabels( wxMessageDialog::ButtonLabel( _( "Overwrite Files" ) ), + wxMessageDialog::ButtonLabel( _( "Abort Project Save" ) ) ); + + if( dlg.ShowModal() == wxID_CANCEL ) + return false; + } + + for( size_t i = 0; i < screens.GetCount(); i++ ) + { + screen = screens.GetScreen( i ); + + wxCHECK2( screen, continue ); + // Convert legacy schematics file name extensions for the new format. wxFileName tmpFn = screen->GetFileName(); @@ -629,7 +712,7 @@ bool SCH_EDIT_FRAME::SaveProject() screen->SetFileName( tmpFn.GetFullPath() ); } - success &= SaveEEFile( screen ); + success &= SaveEEFile( screens.GetSheet( i ) ); } if( updateFileType ) @@ -678,25 +761,25 @@ bool SCH_EDIT_FRAME::doAutoSave() if( !IsWritable( tmp ) ) return false; - for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() ) + for( size_t i = 0; i < screens.GetCount(); i++ ) { // Only create auto save files for the schematics that have been modified. - if( !screen->IsSave() ) + if( !screens.GetScreen( i )->IsSave() ) continue; - tmpFileName = fn = screen->GetFileName(); + tmpFileName = fn = screens.GetScreen( i )->GetFileName(); // Auto save file name is the normal file name prefixed with GetAutoSavePrefix(). fn.SetName( GetAutoSaveFilePrefix() + fn.GetName() ); - screen->SetFileName( fn.GetFullPath() ); + screens.GetScreen( i )->SetFileName( fn.GetFullPath() ); - if( SaveEEFile( screen, false, NO_BACKUP_FILE ) ) - screen->SetModify(); + if( SaveEEFile( screens.GetSheet( i ), false, NO_BACKUP_FILE ) ) + screens.GetScreen( i )->SetModify(); else autoSaveOk = false; - screen->SetFileName( tmpFileName.GetFullPath() ); + screens.GetScreen( i )->SetFileName( tmpFileName.GetFullPath() ); } if( autoSaveOk ) diff --git a/eeschema/netlist_exporters/netlist_exporter.cpp b/eeschema/netlist_exporters/netlist_exporter.cpp index 62c7250ac3..50cfdfb06c 100644 --- a/eeschema/netlist_exporters/netlist_exporter.cpp +++ b/eeschema/netlist_exporters/netlist_exporter.cpp @@ -34,6 +34,7 @@ #include #include #include +#include wxString NETLIST_EXPORTER::MakeCommandLine( const wxString& aFormatString, diff --git a/eeschema/netlist_object_list.cpp b/eeschema/netlist_object_list.cpp index e375c944b3..33fa31c142 100644 --- a/eeschema/netlist_object_list.cpp +++ b/eeschema/netlist_object_list.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include diff --git a/eeschema/sch_component.cpp b/eeschema/sch_component.cpp index 7e23640437..30f6b99e54 100644 --- a/eeschema/sch_component.cpp +++ b/eeschema/sch_component.cpp @@ -383,6 +383,11 @@ void SCH_COMPONENT::AddHierarchicalReference( const KIID_PATH& aPath, const wxSt { if( m_instanceReferences[ii].m_Path == aPath ) { + wxLogTrace( traceSchSheetPaths, + "Removing symbol instance:\n sheet path %s\n reference %s, unit %d\n from symbol %s.", + aPath.AsString(), m_instanceReferences[ii].m_Reference, + m_instanceReferences[ii].m_Unit, m_Uuid.AsString() ); + m_instanceReferences.erase( m_instanceReferences.begin() + ii ); ii--; } @@ -392,6 +397,11 @@ void SCH_COMPONENT::AddHierarchicalReference( const KIID_PATH& aPath, const wxSt instance.m_Path = aPath; instance.m_Reference = aRef; instance.m_Unit = aUnit; + + wxLogTrace( traceSchSheetPaths, + "Adding symbol instance:\n sheet path %s\n reference %s, unit %d\n to symbol %s.", + aPath.AsString(), aRef, aUnit, m_Uuid.AsString() ); + m_instanceReferences.push_back( instance ); } @@ -405,6 +415,10 @@ const wxString SCH_COMPONENT::GetRef( const SCH_SHEET_PATH* sheet, bool aInclude { if( instance.m_Path == path ) { + wxLogTrace( traceSchSheetPaths, + "Setting symbol instance:\n sheet path %s\n reference %s, unit %d\n found in symbol %s.", + path.AsString(), instance.m_Reference, instance.m_Unit, m_Uuid.AsString() ); + ref = instance.m_Reference; break; } @@ -865,6 +879,34 @@ bool SCH_COMPONENT::AddSheetPathReferenceEntryIfMissing( const KIID_PATH& aSheet } +bool SCH_COMPONENT::ReplaceInstanceSheetPath( const KIID_PATH& aOldSheetPath, + const KIID_PATH& aNewSheetPath ) +{ + auto it = std::find_if( m_instanceReferences.begin(), m_instanceReferences.end(), + [ aOldSheetPath ]( COMPONENT_INSTANCE_REFERENCE& r )->bool + { + return aOldSheetPath == r.m_Path; + } + ); + + if( it != m_instanceReferences.end() ) + { + wxLogTrace( traceSchSheetPaths, + "Replacing sheet path %s\n with sheet path %s\n for symbol %s.", + aOldSheetPath.AsString(), aNewSheetPath.AsString(), m_Uuid.AsString() ); + + it->m_Path = aNewSheetPath; + return true; + } + + wxLogTrace( traceSchSheetPaths, + "Could not find sheet path %s\n to replace with sheet path %s\n for symbol %s.", + aOldSheetPath.AsString(), aNewSheetPath.AsString(), m_Uuid.AsString() ); + + return false; +} + + void SCH_COMPONENT::SetOrientation( int aOrientation ) { TRANSFORM temp = TRANSFORM(); diff --git a/eeschema/sch_component.h b/eeschema/sch_component.h index cb818eab49..5862a6d9ca 100644 --- a/eeschema/sch_component.h +++ b/eeschema/sch_component.h @@ -45,17 +45,15 @@ #include #include -#include -#include #include #include #include -#include +#include // COMPONENT_INSTANCE_REFERENCE #include #include +class COMPONENT_SELECTION; class SCH_SCREEN; -class SCH_SHEET_PATH; class LIB_ITEM; class LIB_PIN; class LIB_PART; @@ -84,14 +82,6 @@ typedef std::weak_ptr PART_REF; extern std::string toUTFTildaText( const wxString& txt ); -struct COMPONENT_INSTANCE_REFERENCE -{ - KIID_PATH m_Path; - wxString m_Reference; - int m_Unit; -}; - - /** * Schematic symbol object. */ @@ -363,6 +353,17 @@ public: */ bool AddSheetPathReferenceEntryIfMissing( const KIID_PATH& aSheetPath ); + /** + * Replace \a aOldSheetPath with \a aNewSheetPath in the instance list. + * + * @param aOldSheetPath is a #KIID_PATH object of an existing path in the instance list. + * @param aNewSheetPath is a #KIID_PATH object of the path to replace the existing path. + * + * @return true if \a aOldSheetPath was found and replaced or false if \a aOldSheetPath was + * not found in the instance list. + */ + bool ReplaceInstanceSheetPath( const KIID_PATH& aOldSheetPath, const KIID_PATH& aNewSheetPath ); + /** * Clear the HIGHLIGHTED flag of all items of the component (fields, pins ...) */ diff --git a/eeschema/sch_edit_frame.h b/eeschema/sch_edit_frame.h index 0eb0e6bc45..2f22300714 100644 --- a/eeschema/sch_edit_frame.h +++ b/eeschema/sch_edit_frame.h @@ -592,10 +592,10 @@ public: bool AppendSchematic(); /** - * Save \a aScreen to a schematic file. + * Save \a aSheet to a schematic file. * - * @param aScreen A pointer to the SCH_SCREEN object to save. A NULL pointer saves - * the current screen. + * @param aSheet A pointer to the #SCH_SHEET object to save. A NULL pointer saves + * the current screen only. * @param aSaveUnderNewName Controls how the file is to be saved;: using previous name * or under a new name . * @param aCreateBackupFile Creates a back of the file associated with \a aScreen @@ -604,9 +604,9 @@ public: * #NO_BACKUP_FILE are defined for improved code readability. * @return True if the file has been saved. */ - bool SaveEEFile( SCH_SCREEN* aScreen, - bool aSaveUnderNewName = false, - bool aCreateBackupFile = CREATE_BACKUP_FILE ); + bool SaveEEFile( SCH_SHEET* aSheet, + bool aSaveUnderNewName = false, + bool aCreateBackupFile = CREATE_BACKUP_FILE ); /** diff --git a/eeschema/sch_io_mgr.h b/eeschema/sch_io_mgr.h index 9657f5cfb8..fe2edd9ff4 100644 --- a/eeschema/sch_io_mgr.h +++ b/eeschema/sch_io_mgr.h @@ -218,7 +218,7 @@ public: * * @param aFileName is the name of a file to save to on disk. * - * @param aSchematic is the class #SCH_SCREEN in memory document tree from which to extract + * @param aSchematic is the class #SCH_SHEET in memory document tree from which to extract * information when writing to \a aFileName. The caller continues to * own the SCHEMATIC, and the plugin should refrain from modifying the * SCHEMATIC if possible. @@ -230,11 +230,13 @@ public: * save the file, because it can take any number of additional named * tuning arguments that the plugin is known to support. The caller * continues to own this object (plugin may not delete it), and plugins - * should expect it to be optionally NULL. + * should expect it to be optionally NULL. Set the + * #PropSaveCurrentSheetOnly property to only save the current sheet. + * Otherwise, all hierarchial sheets are saved. * * @throw IO_ERROR if there is a problem saving or exporting. */ - virtual void Save( const wxString& aFileName, SCH_SCREEN* aSchematic, KIWAY* aKiway, + virtual void Save( const wxString& aFileName, SCH_SHEET* aSchematic, KIWAY* aKiway, const PROPERTIES* aProperties = NULL ); /** diff --git a/eeschema/sch_legacy_plugin.cpp b/eeschema/sch_legacy_plugin.cpp index b4c18661bb..deb059b41b 100644 --- a/eeschema/sch_legacy_plugin.cpp +++ b/eeschema/sch_legacy_plugin.cpp @@ -1817,10 +1817,10 @@ std::shared_ptr SCH_LEGACY_PLUGIN::loadBusAlias( LINE_READER& aReader } -void SCH_LEGACY_PLUGIN::Save( const wxString& aFileName, SCH_SCREEN* aScreen, KIWAY* aKiway, +void SCH_LEGACY_PLUGIN::Save( const wxString& aFileName, SCH_SHEET* aSheet, KIWAY* aKiway, const PROPERTIES* aProperties ) { - wxCHECK_RET( aScreen != NULL, "NULL SCH_SCREEN object." ); + wxCHECK_RET( aSheet != NULL, "NULL SCH_SHEET object." ); wxCHECK_RET( !aFileName.IsEmpty(), "No schematic file name defined." ); LOCALE_IO toggle; // toggles on, then off, the C locale, to write floating point values. @@ -1837,15 +1837,19 @@ void SCH_LEGACY_PLUGIN::Save( const wxString& aFileName, SCH_SCREEN* aScreen, KI m_out = &formatter; // no ownership - Format( aScreen ); + Format( aSheet ); } -void SCH_LEGACY_PLUGIN::Format( SCH_SCREEN* aScreen ) +void SCH_LEGACY_PLUGIN::Format( SCH_SHEET* aSheet ) { - wxCHECK_RET( aScreen != NULL, "NULL SCH_SCREEN* object." ); + wxCHECK_RET( aSheet != NULL, "NULL SCH_SHEET* object." ); wxCHECK_RET( m_kiway != NULL, "NULL KIWAY* object." ); + SCH_SCREEN* screen = aSheet->GetScreen(); + + wxCHECK( screen, /* void */ ); + // Write the header m_out->Print( 0, "%s %s %d\n", "EESchema", SCHEMATIC_HEAD_STRING, EESCHEMA_VERSION ); @@ -1858,15 +1862,15 @@ void SCH_LEGACY_PLUGIN::Format( SCH_SCREEN* aScreen ) * simple hierarchy and flat hierarchy. Used also to search the root * sheet ( ScreenNumber = 1 ) within the files */ - const TITLE_BLOCK& tb = aScreen->GetTitleBlock(); - const PAGE_INFO& page = aScreen->GetPageSettings(); + const TITLE_BLOCK& tb = screen->GetTitleBlock(); + const PAGE_INFO& page = screen->GetPageSettings(); m_out->Print( 0, "$Descr %s %d %d%s\n", TO_UTF8( page.GetType() ), page.GetWidthMils(), page.GetHeightMils(), !page.IsCustom() && page.IsPortrait() ? " portrait" : "" ); m_out->Print( 0, "encoding utf-8\n" ); - m_out->Print( 0, "Sheet %d %d\n", aScreen->m_ScreenNumber, aScreen->m_NumberOfScreens ); + m_out->Print( 0, "Sheet %d %d\n", screen->m_ScreenNumber, screen->m_NumberOfScreens ); m_out->Print( 0, "Title %s\n", EscapedUTF8( tb.GetTitle() ).c_str() ); m_out->Print( 0, "Date %s\n", EscapedUTF8( tb.GetDate() ).c_str() ); m_out->Print( 0, "Rev %s\n", EscapedUTF8( tb.GetRevision() ).c_str() ); @@ -1882,7 +1886,7 @@ void SCH_LEGACY_PLUGIN::Format( SCH_SCREEN* aScreen ) m_out->Print( 0, "Comment9 %s\n", EscapedUTF8( tb.GetComment( 8 ) ).c_str() ); m_out->Print( 0, "$EndDescr\n" ); - for( const auto& alias : aScreen->GetBusAliases() ) + for( const auto& alias : screen->GetBusAliases() ) { saveBusAlias( alias ); } @@ -1891,7 +1895,7 @@ void SCH_LEGACY_PLUGIN::Format( SCH_SCREEN* aScreen ) auto cmp = []( const SCH_ITEM* a, const SCH_ITEM* b ) { return *a < *b; }; std::multiset save_map( cmp ); - for( auto item : aScreen->Items() ) + for( auto item : screen->Items() ) save_map.insert( item ); diff --git a/eeschema/sch_legacy_plugin.h b/eeschema/sch_legacy_plugin.h index f3e65fefff..ded4bf81a5 100644 --- a/eeschema/sch_legacy_plugin.h +++ b/eeschema/sch_legacy_plugin.h @@ -104,10 +104,10 @@ public: void LoadContent( LINE_READER& aReader, SCH_SCREEN* aScreen, int version = EESCHEMA_VERSION ); - void Save( const wxString& aFileName, SCH_SCREEN* aScreen, KIWAY* aKiway, + void Save( const wxString& aFileName, SCH_SHEET* aScreen, KIWAY* aKiway, const PROPERTIES* aProperties = nullptr ) override; - void Format( SCH_SCREEN* aScreen ); + void Format( SCH_SHEET* aSheet ); void Format( SELECTION* aSelection, OUTPUTFORMATTER* aFormatter ); diff --git a/eeschema/sch_plugin.cpp b/eeschema/sch_plugin.cpp index 67643bedfd..8b9f96039c 100644 --- a/eeschema/sch_plugin.cpp +++ b/eeschema/sch_plugin.cpp @@ -55,7 +55,7 @@ SCH_SHEET* SCH_PLUGIN::Load( const wxString& aFileName, KIWAY* aKiway, SCH_SHEET } -void SCH_PLUGIN::Save( const wxString& aFileName, SCH_SCREEN* aSchematic, KIWAY* aKiway, +void SCH_PLUGIN::Save( const wxString& aFileName, SCH_SHEET* aSheet, KIWAY* aKiway, const PROPERTIES* aProperties ) { // not pure virtual so that plugins only have to implement subset of the SCH_PLUGIN interface. diff --git a/eeschema/sch_reference_list.h b/eeschema/sch_reference_list.h index 2bf48b59c8..4611e2a469 100644 --- a/eeschema/sch_reference_list.h +++ b/eeschema/sch_reference_list.h @@ -36,9 +36,6 @@ #include -class SCH_REFERENCE; -class SCH_REFERENCE_LIST; - /** * SCH_REFERENCE * is used as a helper to define a component's reference designator in a schematic. This @@ -68,7 +65,6 @@ class SCH_REFERENCE friend class SCH_REFERENCE_LIST; - public: SCH_REFERENCE() : @@ -97,6 +93,8 @@ public: int GetUnit() const { return m_Unit; } + void SetUnit( int aUnit ) { m_Unit = aUnit; } + void SetSheetNumber( int aSheetNumber ) { m_SheetNum = aSheetNumber; } const wxString GetPath() const @@ -132,10 +130,12 @@ public: { return m_Ref; } + void SetRefStr( const std::string& aReference ) { m_Ref = aReference; } + const char* GetRefStr() const { return m_Ref.c_str(); @@ -229,7 +229,7 @@ public: * Function GetCount * @return the number of items in the list */ - unsigned GetCount() + unsigned GetCount() const { return flatList.size(); } diff --git a/eeschema/sch_screen.cpp b/eeschema/sch_screen.cpp index 71277197c6..a631fad3d3 100644 --- a/eeschema/sch_screen.cpp +++ b/eeschema/sch_screen.cpp @@ -177,6 +177,14 @@ void SCH_SCREEN::DecRefCount() } +bool SCH_SCREEN::HasItems( KICAD_T aItemType ) const +{ + EE_RTREE::EE_TYPE sheets = const_cast( m_rtree ).OfType( aItemType ); + + return sheets.begin() != sheets.end(); +} + + void SCH_SCREEN::Append( SCH_ITEM* aItem ) { if( aItem->Type() != SCH_SHEET_PIN_T && aItem->Type() != SCH_FIELD_T ) @@ -1267,7 +1275,16 @@ SCH_SCREEN* SCH_SCREENS::GetScreen( unsigned int aIndex ) const } -void SCH_SCREENS::addScreenToList( SCH_SCREEN* aScreen ) +SCH_SHEET* SCH_SCREENS::GetSheet( unsigned int aIndex ) const +{ + if( aIndex < m_sheets.size() ) + return m_sheets[ aIndex ]; + + return NULL; +} + + +void SCH_SCREENS::addScreenToList( SCH_SCREEN* aScreen, SCH_SHEET* aSheet ) { if( aScreen == NULL ) return; @@ -1279,6 +1296,7 @@ void SCH_SCREENS::addScreenToList( SCH_SCREEN* aScreen ) } m_screens.push_back( aScreen ); + m_sheets.push_back( aSheet ); } @@ -1288,7 +1306,7 @@ void SCH_SCREENS::buildScreenList( SCH_SHEET* aSheet ) { SCH_SCREEN* screen = aSheet->GetScreen(); - addScreenToList( screen ); + addScreenToList( screen, aSheet ); for( SCH_ITEM* item : screen->Items().OfType( SCH_SHEET_T ) ) buildScreenList( static_cast( item ) ); diff --git a/eeschema/sch_screen.h b/eeschema/sch_screen.h index cac52e3e05..b58f38344f 100644 --- a/eeschema/sch_screen.h +++ b/eeschema/sch_screen.h @@ -45,6 +45,8 @@ #include #include +#include // COMPONENT_INSTANCE_REFERENCE +#include #include #include #include @@ -58,7 +60,11 @@ class SCH_LINE; class SCH_TEXT; class PLOTTER; class REPORTER; +class SCH_EDIT_FRAME; +class SCH_SHEET; class SCH_SHEET_LIST; +class SCH_SEXPR_PARSER; +class SCH_SEXPR_PLUGIN; enum SCH_LINE_TEST_T { @@ -120,6 +126,26 @@ private: /// Library symbols required for this schematic. std::map m_libSymbols; + /** + * The list of symbol instances loaded from the schematic file. + * + * This list is only used to as temporary storage when the schematic file is loaded. + * If the screen is the root sheet, then this information is used to update the + * #SCH_COMPONENT instance reference and unit information after the entire schematic + * is loaded and is never used again. If this screen is not the root sheet, then the + * schematic file is the root sheet of another project and this information is saved + * unchanged back to the schematic file. + * + * @warning Under no circumstances is this information to be modified or used after the + * schematic file is loaded. It is read only and it is only written to non-root + * schematic files. + */ + std::vector m_symbolInstances; + + friend SCH_EDIT_FRAME; // Only to populate m_symbolInstances. + friend SCH_SEXPR_PARSER; // Only to load instance information from schematic file. + friend SCH_SEXPR_PLUGIN; // Only to save the loaded instance information to schematic file. + void clearLibSymbols(); public: @@ -146,6 +172,10 @@ public: return m_rtree.empty(); } + bool HasItems( KICAD_T aItemType ) const; + + bool HasSheets() const { return HasItems( SCH_SHEET_T ); } + static inline bool ClassOf( const EDA_ITEM* aItem ) { return aItem && SCH_SCREEN_T == aItem->Type(); @@ -530,15 +560,17 @@ class SCH_SCREENS { private: std::vector< SCH_SCREEN* > m_screens; + std::vector< SCH_SHEET* > m_sheets; unsigned int m_index; public: SCH_SCREENS( SCH_SHEET* aSheet = NULL ); ~SCH_SCREENS(); - int GetCount() const { return m_screens.size(); } + size_t GetCount() const { return m_screens.size(); } SCH_SCREEN* GetFirst(); SCH_SCREEN* GetNext(); SCH_SCREEN* GetScreen( unsigned int aIndex ) const; + SCH_SHEET* GetSheet( unsigned int aIndex ) const; /** * Clear the annotation for all components in the hierarchy. @@ -656,7 +688,7 @@ public: bool CanCauseCaseSensitivityIssue( const wxString& aSchematicFileName ) const; private: - void addScreenToList( SCH_SCREEN* aScreen ); + void addScreenToList( SCH_SCREEN* aScreen, SCH_SHEET* aSheet ); void buildScreenList( SCH_SHEET* aSheet); }; diff --git a/eeschema/sch_sexpr_parser.cpp b/eeschema/sch_sexpr_parser.cpp index 5b8b330f4f..448dfdcacb 100644 --- a/eeschema/sch_sexpr_parser.cpp +++ b/eeschema/sch_sexpr_parser.cpp @@ -443,13 +443,18 @@ void SCH_SEXPR_PARSER::parseStroke( STROKE_PARAMS& aStroke ) } case T_color: - aStroke.m_Color = - COLOR4D( parseInt( "red" ) / 255.0, - parseInt( "green" ) / 255.0, - parseInt( "blue" ) / 255.0, - parseDouble( "alpha" ) ); + { + COLOR4D color; + + color.r = parseInt( "red" ) / 255.0; + color.g = parseInt( "green" ) / 255.0; + color.b = parseInt( "blue" ) / 255.0; + color.a = Clamp( parseDouble( "alpha" ), 0.0, 1.0 ); + + aStroke.m_Color = color; NeedRIGHT(); break; + } default: Expecting( "width, type, or color" ); @@ -496,14 +501,17 @@ void SCH_SEXPR_PARSER::parseFill( FILL_PARAMS& aFill ) } case T_color: - aFill.m_Color = - COLOR4D( parseInt( "red" ) / 255.0, - parseInt( "green" ) / 255.0, - parseInt( "blue" ) / 255.0, - parseDouble( "alpha" ) ); + { + COLOR4D color; + color.r = parseInt( "red" ) / 255.0; + color.g = parseInt( "green" ) / 255.0; + color.b = parseInt( "blue" ) / 255.0; + color.a = Clamp( parseDouble( "alpha" ), 0.0, 1.0 ); + aFill.m_Color = color; NeedRIGHT(); break; + } default: Expecting( "type or color" ); @@ -1791,11 +1799,12 @@ SCH_SHEET_PIN* SCH_SEXPR_PARSER::parseSchSheetPin( SCH_SHEET* aSheet ) } -void SCH_SEXPR_PARSER::parseSchSymbolInstances( std::unique_ptr& aSymbol ) +void SCH_SEXPR_PARSER::parseSchSymbolInstances( SCH_SCREEN* aScreen ) { - wxCHECK_RET( CurTok() == T_instances, + wxCHECK_RET( CurTok() == T_symbol_instances, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a instances token." ) ); + wxCHECK( aScreen, /* void */ ); T token; @@ -1812,9 +1821,9 @@ void SCH_SEXPR_PARSER::parseSchSymbolInstances( std::unique_ptr& { NeedSYMBOL(); - int unit = 1; - wxString reference; - KIID_PATH path( FromUTF8() ); + COMPONENT_INSTANCE_REFERENCE instance; + + instance.m_Path = KIID_PATH( FromUTF8() ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { @@ -1827,12 +1836,12 @@ void SCH_SEXPR_PARSER::parseSchSymbolInstances( std::unique_ptr& { case T_reference: NeedSYMBOL(); - reference = FromUTF8(); + instance.m_Reference = FromUTF8(); NeedRIGHT(); break; case T_unit: - unit = parseInt( "symbol unit" ); + instance.m_Unit = parseInt( "symbol unit" ); NeedRIGHT(); break; @@ -1841,8 +1850,7 @@ void SCH_SEXPR_PARSER::parseSchSymbolInstances( std::unique_ptr& } } - aSymbol->AddHierarchicalReference( path, reference, unit ); - aSymbol->GetField( REFERENCE )->SetText( reference ); + aScreen->m_symbolInstances.emplace_back( instance ); break; } @@ -1853,9 +1861,13 @@ void SCH_SEXPR_PARSER::parseSchSymbolInstances( std::unique_ptr& } -void SCH_SEXPR_PARSER::ParseSchematic( SCH_SCREEN* aScreen ) +void SCH_SEXPR_PARSER::ParseSchematic( SCH_SHEET* aSheet ) { - wxCHECK_RET( aScreen != nullptr, "" ); + wxCHECK( aSheet != nullptr, /* void */ ); + + SCH_SCREEN* screen = aSheet->GetScreen(); + + wxCHECK( screen != nullptr, /* void */ ); T token; @@ -1880,7 +1892,7 @@ void SCH_SEXPR_PARSER::ParseSchematic( SCH_SCREEN* aScreen ) { PAGE_INFO pageInfo; parsePAGE_INFO( pageInfo ); - aScreen->SetPageSettings( pageInfo ); + screen->SetPageSettings( pageInfo ); break; } @@ -1888,7 +1900,7 @@ void SCH_SEXPR_PARSER::ParseSchematic( SCH_SCREEN* aScreen ) { TITLE_BLOCK tb; parseTITLE_BLOCK( tb ); - aScreen->SetTitleBlock( tb ); + screen->SetTitleBlock( tb ); break; } @@ -1907,7 +1919,7 @@ void SCH_SEXPR_PARSER::ParseSchematic( SCH_SCREEN* aScreen ) switch( token ) { case T_symbol: - aScreen->AddLibSymbol( ParseSymbol( symbolLibMap, true ) ); + screen->AddLibSymbol( ParseSymbol( symbolLibMap, true ) ); break; default: @@ -1919,49 +1931,63 @@ void SCH_SEXPR_PARSER::ParseSchematic( SCH_SCREEN* aScreen ) } case T_symbol: - aScreen->Append( static_cast( parseSchematicSymbol() ) ); + screen->Append( static_cast( parseSchematicSymbol() ) ); break; case T_image: - aScreen->Append( static_cast( parseImage() ) ); + screen->Append( static_cast( parseImage() ) ); break; case T_sheet: - aScreen->Append( static_cast( parseSheet() ) ); + { + SCH_SHEET* sheet = parseSheet(); + + // Set the parent to aSheet. This effectively creates a method to find + // the root sheet from any sheet so a pointer to the root sheet does not + // need to be stored globally. Note: this is not the same as a hierarchy. + // Complex hierarchies can have multiple copies of a sheet. This only + // provides a simple tree to find the root sheet. + sheet->SetParent( aSheet ); + screen->Append( static_cast( sheet ) ); break; + } case T_junction: - aScreen->Append( static_cast( parseJunction() ) ); + screen->Append( static_cast( parseJunction() ) ); break; case T_no_connect: - aScreen->Append( static_cast( parseNoConnect() ) ); + screen->Append( static_cast( parseNoConnect() ) ); break; case T_bus_entry: - aScreen->Append( static_cast( parseBusEntry() ) ); + screen->Append( static_cast( parseBusEntry() ) ); break; case T_polyline: case T_bus: case T_wire: - aScreen->Append( static_cast( parseLine() ) ); + screen->Append( static_cast( parseLine() ) ); break; case T_text: case T_label: case T_global_label: case T_hierarchical_label: - aScreen->Append( static_cast( parseSchText() ) ); + screen->Append( static_cast( parseSchText() ) ); + break; + + case T_symbol_instances: + parseSchSymbolInstances( screen ); break; default: - Expecting( "symbol, bitmap, sheet, junction, no_connect, bus_entry, line" - "bus, text, label, global_label, or hierarchical_label" ); + Expecting( "symbol, bitmap, sheet, junction, no_connect, bus_entry, line, bus" + "text, label, global_label, hierarchical_label, or symbol_instances" ); } } - aScreen->UpdateLocalLibSymbolLinks(); + screen->UpdateLocalLibSymbolLinks(); } @@ -2094,10 +2120,6 @@ SCH_COMPONENT* SCH_SEXPR_PARSER::parseSchematicSymbol() break; } - case T_instances: - parseSchSymbolInstances( symbol ); - break; - default: Expecting( "lib_id, lib_name, at, mirror, uuid, property, or instances" ); } diff --git a/eeschema/sch_sexpr_parser.h b/eeschema/sch_sexpr_parser.h index 0d65e2866b..85bef18ab4 100644 --- a/eeschema/sch_sexpr_parser.h +++ b/eeschema/sch_sexpr_parser.h @@ -203,7 +203,7 @@ class SCH_SEXPR_PARSER : public SCHEMATIC_LEXER void parsePAGE_INFO( PAGE_INFO& aPageInfo ); void parseTITLE_BLOCK( TITLE_BLOCK& aTitleBlock ); - void parseSchSymbolInstances( std::unique_ptr& aSymbol ); + void parseSchSymbolInstances( SCH_SCREEN* aScreen ); SCH_SHEET_PIN* parseSchSheetPin( SCH_SHEET* aSheet ); SCH_FIELD* parseSchField( SCH_COMPONENT* aParentSymbol ); @@ -226,13 +226,13 @@ public: LIB_ITEM* ParseDrawItem(); /** - * Parse a single schematic file into \a aScreen. + * Parse a single schematic file into \a aSheet. * * @note This does not load any sub-sheets or decent complex sheet hierarchies. * - * @param aScreen The #SCH_SCREEN object to store the parsed schematic file. + * @param aSheet The #SCH_SHEET object to store the parsed schematic file. */ - void ParseSchematic( SCH_SCREEN* aScreen ); + void ParseSchematic( SCH_SHEET* aSheet ); /** * Return whether a version number, if any was parsed, was too recent diff --git a/eeschema/sch_sexpr_plugin.cpp b/eeschema/sch_sexpr_plugin.cpp index 745d6727c2..0c39846beb 100644 --- a/eeschema/sch_sexpr_plugin.cpp +++ b/eeschema/sch_sexpr_plugin.cpp @@ -73,6 +73,7 @@ #include // for MAX_UNIT_COUNT_PER_PACKAGE definition #include #include +#include #include #include // for PropPowerSymsOnly definintion. #include @@ -299,11 +300,11 @@ static void formatStroke( OUTPUTFORMATTER* aFormatter, int aNestLevel, int aWidt aFormatter->Print( 0, " (type %s)", TO_UTF8( getLineStyleToken( aStyle ) ) ); if( !( aColor == COLOR4D::UNSPECIFIED ) ) - aFormatter->Print( 0, " (color %d %d %d %0.4f)", + aFormatter->Print( 0, " (color %d %d %d %s)", KiROUND( aColor.r * 255.0 ), KiROUND( aColor.g * 255.0 ), KiROUND( aColor.b * 255.0 ), - aColor.a ); + Double2Str( aColor.a ).c_str() ); aFormatter->Print( 0, ")" ); } @@ -523,23 +524,7 @@ void SCH_SEXPR_PLUGIN::loadHierarchy( SCH_SHEET* aSheet ) try { - loadFile( fileName.GetFullPath(), aSheet->GetScreen() ); - - for( auto aItem : aSheet->GetScreen()->Items().OfType( SCH_SHEET_T ) ) - { - assert( aItem->Type() == SCH_SHEET_T ); - auto sheet = static_cast( aItem ); - - // Set the parent to aSheet. This effectively creates a method to find - // the root sheet from any sheet so a pointer to the root sheet does not - // need to be stored globally. Note: this is not the same as a hierarchy. - // Complex hierarchies can have multiple copies of a sheet. This only - // provides a simple tree to find the root sheet. - sheet->SetParent( aSheet ); - - // Recursion starts here. - loadHierarchy( sheet ); - } + loadFile( fileName.GetFullPath(), aSheet ); } catch( const IO_ERROR& ioe ) { @@ -553,6 +538,17 @@ void SCH_SEXPR_PLUGIN::loadHierarchy( SCH_SHEET* aSheet ) m_error += ioe.What(); } + + // This was moved out of the try{} block so that any sheets definitionsthat + // the plugin fully parsed before the exception was raised will be loaded. + for( auto aItem : aSheet->GetScreen()->Items().OfType( SCH_SHEET_T ) ) + { + wxCHECK2( aItem->Type() == SCH_SHEET_T, /* do nothing */ ); + auto sheet = static_cast( aItem ); + + // Recursion starts here. + loadHierarchy( sheet ); + } } m_currentPath.pop(); @@ -561,20 +557,20 @@ void SCH_SEXPR_PLUGIN::loadHierarchy( SCH_SHEET* aSheet ) } -void SCH_SEXPR_PLUGIN::loadFile( const wxString& aFileName, SCH_SCREEN* aScreen ) +void SCH_SEXPR_PLUGIN::loadFile( const wxString& aFileName, SCH_SHEET* aSheet ) { FILE_LINE_READER reader( aFileName ); SCH_SEXPR_PARSER parser( &reader ); - parser.ParseSchematic( aScreen ); + parser.ParseSchematic( aSheet ); } -void SCH_SEXPR_PLUGIN::Save( const wxString& aFileName, SCH_SCREEN* aScreen, KIWAY* aKiway, +void SCH_SEXPR_PLUGIN::Save( const wxString& aFileName, SCH_SHEET* aSheet, KIWAY* aKiway, const PROPERTIES* aProperties ) { - wxCHECK_RET( aScreen != NULL, "NULL SCH_SCREEN object." ); + wxCHECK_RET( aSheet != NULL, "NULL SCH_SHEET object." ); wxCHECK_RET( !aFileName.IsEmpty(), "No schematic file name defined." ); LOCALE_IO toggle; // toggles on, then off, the C locale, to write floating point values. @@ -591,34 +587,44 @@ void SCH_SEXPR_PLUGIN::Save( const wxString& aFileName, SCH_SCREEN* aScreen, KIW m_out = &formatter; // no ownership - Format( aScreen ); + Format( aSheet ); } -void SCH_SEXPR_PLUGIN::Format( SCH_SCREEN* aScreen ) +void SCH_SEXPR_PLUGIN::Format( SCH_SHEET* aSheet ) { - wxCHECK_RET( aScreen != NULL, "NULL SCH_SCREEN* object." ); + wxCHECK_RET( aSheet != NULL, "NULL SCH_SHEET* object." ); wxCHECK_RET( m_kiway != NULL, "NULL KIWAY* object." ); + SCH_SCREEN* screen = aSheet->GetScreen(); + + wxCHECK( screen, /* void */ ); + m_out->Print( 0, "(kicad_sch (version %d) (host eeschema %s)\n\n", SEXPR_SCHEMATIC_FILE_VERSION, m_out->Quotew( GetBuildVersion() ).c_str() ); - aScreen->GetPageSettings().Format( m_out, 1, 0 ); + // Root sheet must have a permanent UUID. + // if( aSheet->IsRootSheet() && aSheet->m_Uuid.IsLegacyTimestamp() ) + // const_cast( aSheet->m_Uuid ).ConvertTimestampToUuid(); + + // m_out->Print( 1, "(uuid %s)\n\n", m_out->Quotew( aSheet->m_Uuid.AsString() ).c_str() ); + + screen->GetPageSettings().Format( m_out, 1, 0 ); m_out->Print( 0, "\n" ); - aScreen->GetTitleBlock().Format( m_out, 1, 0 ); + screen->GetTitleBlock().Format( m_out, 1, 0 ); // Save cache library. m_out->Print( 1, "(lib_symbols\n" ); - for( auto libSymbol : aScreen->GetLibSymbols() ) + for( auto libSymbol : screen->GetLibSymbols() ) SCH_SEXPR_PLUGIN_CACHE::SaveSymbol( libSymbol.second, *m_out, 2, libSymbol.first ); m_out->Print( 1, ")\n\n" ); // @todo save schematic instance information (page #). - for( const auto& alias : aScreen->GetBusAliases() ) + for( const auto& alias : screen->GetBusAliases() ) { saveBusAlias( alias, 1 ); } @@ -627,7 +633,7 @@ void SCH_SEXPR_PLUGIN::Format( SCH_SCREEN* aScreen ) auto cmp = []( const SCH_ITEM* a, const SCH_ITEM* b ) { return *a < *b; }; std::multiset save_map( cmp ); - for( auto item : aScreen->Items() ) + for( auto item : screen->Items() ) save_map.insert( item ); KICAD_T itemType = TYPE_NOT_INIT; @@ -703,6 +709,48 @@ void SCH_SEXPR_PLUGIN::Format( SCH_SCREEN* aScreen ) } } + // If this is the root sheet, save all of the sheet paths. + if( aSheet->IsRootSheet() ) + { + m_out->Print( 0, "\n" ); + m_out->Print( 1, "(symbol_instances\n" ); + + SCH_SHEET_LIST sheetPaths( aSheet ); + + for( auto sheetPath : sheetPaths ) + { + SCH_REFERENCE_LIST instances; + + sheetPath.GetComponents( instances, true, true ); + instances.SortByReferenceOnly(); + + for( size_t i = 0; i < instances.GetCount(); i++ ) + { + m_out->Print( 2, "(path %s (reference %s) (unit %d))\n", + m_out->Quotew( instances[i].GetPath() ).c_str(), + m_out->Quotew( instances[i].GetRef() ).c_str(), + instances[i].GetUnit() ); + } + } + + m_out->Print( 1, ")\n" ); // Close instances token. + } + else if( screen->m_symbolInstances.size() ) + { + m_out->Print( 0, "\n" ); + m_out->Print( 1, "(symbol_instances\n" ); + + for( auto instance : screen->m_symbolInstances ) + { + m_out->Print( 2, "(path %s (reference %s) (unit %d))\n", + m_out->Quotew( instance.m_Path.AsString() ).c_str(), + m_out->Quotew( instance.m_Reference ).c_str(), + instance.m_Unit ); + } + + m_out->Print( 1, ")\n" ); // Close instances token. + } + m_out->Print( 0, ")\n" ); } @@ -834,46 +882,6 @@ void SCH_SEXPR_PLUGIN::saveSymbol( SCH_COMPONENT* aSymbol, int aNestLevel ) saveField( &field, aNestLevel + 1 ); } - // @todo Save sheet UUID at top level of schematic file. This will require saving from - // the SCH_SHEET object instead of the SCH_SCREEN object. - - // KIID projectId; - // wxString projectName = "unknown"; - // SCH_SHEET* sheet = dynamic_cast( aSymbol->GetParent() ); - - // if( sheet ) - // { - // SCH_SHEET* rootSheet = sheet->GetRootSheet(); - - // wxASSERT( rootSheet ); - - // projectName = rootSheet->GetName(); - // projectId = rootSheet->m_Uuid; - // } - - // For simple hierarchies, the reference is defined by the reference property (field). - if( aSymbol->GetInstanceReferences().size() > 1 ) - { - m_out->Print( aNestLevel + 1, "(instances\n" ); - - // @todo Group project level instances. - for( const auto instance : aSymbol->GetInstanceReferences() ) - { - wxString path = "/"; - - // Skip root sheet - for( int i = 1; i < (int) instance.m_Path.size(); ++i ) - path += instance.m_Path[i].AsString() + "/"; - - m_out->Print( aNestLevel + 2, "(path %s (reference %s) (unit %d))\n", - m_out->Quotew( path ).c_str(), - m_out->Quotew( instance.m_Reference ).c_str(), - instance.m_Unit ); - } - - m_out->Print( aNestLevel + 1, ")\n" ); - } - m_out->Print( aNestLevel, ")\n" ); } @@ -962,7 +970,7 @@ void SCH_SEXPR_PLUGIN::saveSheet( SCH_SHEET* aSheet, int aNestLevel ) { wxCHECK_RET( aSheet != nullptr && m_out != nullptr, "" ); - m_out->Print( aNestLevel, "(sheet (at %s %s) (size %s %s)", + m_out->Print( aNestLevel, "(sheet (at %s %s) (size %s %s)\n", FormatInternalUnits( aSheet->GetPosition().x ).c_str(), FormatInternalUnits( aSheet->GetPosition().y ).c_str(), FormatInternalUnits( aSheet->GetSize().GetWidth() ).c_str(), @@ -970,21 +978,21 @@ void SCH_SEXPR_PLUGIN::saveSheet( SCH_SHEET* aSheet, int aNestLevel ) if( !aSheet->UsesDefaultStroke() ) { - m_out->Print( 0, " " ); - formatStroke( m_out, 0, aSheet->GetBorderWidth(), PLOT_DASH_TYPE::SOLID, + formatStroke( m_out, aNestLevel + 1, aSheet->GetBorderWidth(), PLOT_DASH_TYPE::SOLID, aSheet->GetBorderColor() ); + m_out->Print( 0, "\n" ); } if( !( aSheet->GetBackgroundColor() == COLOR4D::UNSPECIFIED ) ) { - m_out->Print( 0, " (fill (color %d %d %d %0.4f))", + m_out->Print( aNestLevel + 1, "(fill (color %d %d %d %0.4f))", KiROUND( aSheet->GetBackgroundColor().r * 255.0 ), KiROUND( aSheet->GetBackgroundColor().g * 255.0 ), KiROUND( aSheet->GetBackgroundColor().b * 255.0 ), aSheet->GetBackgroundColor().a ); + m_out->Print( 0, "\n" ); } - m_out->Print( 0, "\n" ); m_out->Print( aNestLevel + 1, "(uuid %s)", TO_UTF8( aSheet->m_Uuid.AsString() ) ); m_out->Print( 0, "\n" ); diff --git a/eeschema/sch_sexpr_plugin.h b/eeschema/sch_sexpr_plugin.h index 09ec4e98f8..fff2681f90 100644 --- a/eeschema/sch_sexpr_plugin.h +++ b/eeschema/sch_sexpr_plugin.h @@ -90,10 +90,10 @@ public: void LoadContent( LINE_READER& aReader, SCH_SCREEN* aScreen, int version = EESCHEMA_VERSION ); - void Save( const wxString& aFileName, SCH_SCREEN* aScreen, KIWAY* aKiway, + void Save( const wxString& aFileName, SCH_SHEET* aSheet, KIWAY* aKiway, const PROPERTIES* aProperties = nullptr ) override; - void Format( SCH_SCREEN* aScreen ); + void Format( SCH_SHEET* aSheet ); void Format( SELECTION* aSelection, OUTPUTFORMATTER* aFormatter ); @@ -126,7 +126,7 @@ public: private: void loadHierarchy( SCH_SHEET* aSheet ); - void loadFile( const wxString& aFileName, SCH_SCREEN* aScreen ); + void loadFile( const wxString& aFileName, SCH_SHEET* aSheet ); void saveSymbol( SCH_COMPONENT* aComponent, int aNestLevel ); void saveField( SCH_FIELD* aField, int aNestLevel ); diff --git a/eeschema/sch_sheet.h b/eeschema/sch_sheet.h index 5091ff97f6..de92753dc4 100644 --- a/eeschema/sch_sheet.h +++ b/eeschema/sch_sheet.h @@ -261,7 +261,7 @@ public: * and false for items moved with no reference to anchor. * * Usually return true for small items (labels, junctions) and false for - * items which can be large (hierarchical sheets, compoments) + * items which can be large (hierarchical sheets, components) * * @return false for a hierarchical sheet */ @@ -318,6 +318,11 @@ public: */ SCH_SHEET* GetRootSheet(); + /** + * @return true if this sheet is the root sheet. + */ + bool IsRootSheet() { return GetRootSheet() == this; } + /** * Set the #SCH_SCREEN associated with this sheet to \a aScreen. * @@ -485,7 +490,7 @@ public: bool LocatePathOfScreen( SCH_SCREEN* aScreen, SCH_SHEET_PATH* aList ); /** - * Count the number of sheets found in "this" sheet includeing all of the subsheets. + * Count the number of sheets found in "this" sheet including all of the subsheets. * * @return the full count of sheets+subsheets contained by "this" */ diff --git a/eeschema/sch_sheet_path.cpp b/eeschema/sch_sheet_path.cpp index b584f44497..c6c4a4c537 100644 --- a/eeschema/sch_sheet_path.cpp +++ b/eeschema/sch_sheet_path.cpp @@ -34,6 +34,8 @@ #include #include #include +#include + #include #include #include "erc_item.h" @@ -331,9 +333,6 @@ bool SCH_SHEET_PATH::TestForRecursion( const wxString& aSrcFileName, const wxStr } -/********************************************************************/ -/* Class SCH_SHEET_LIST to handle the list of Sheets in a hierarchy */ -/********************************************************************/ SCH_SHEET_LIST::SCH_SHEET_LIST( SCH_SHEET* aSheet ) { m_isRootSheet = false; @@ -636,28 +635,6 @@ bool SCH_SHEET_LIST::SetComponentFootprint( const wxString& aReference, } -bool SCH_SHEET_LIST::IsComplexHierarchy() const -{ - wxString fileName; - - for( unsigned i = 0; i < size(); i++ ) - { - fileName = at( i ).Last()->GetFileName(); - - for( unsigned j = 0; j < size(); j++ ) - { - if( i == j ) - continue; - - if( fileName == at( j ).Last()->GetFileName() ) - return true; - } - } - - return false; -} - - bool SCH_SHEET_LIST::TestForRecursion( const SCH_SHEET_LIST& aSrcSheetHierarchy, const wxString& aDestFileName ) { @@ -701,6 +678,79 @@ SCH_SHEET_PATH* SCH_SHEET_LIST::FindSheetForScreen( SCH_SCREEN* aScreen ) } +void SCH_SHEET_LIST::UpdateSymbolInstances( + std::vector& aSymbolInstances ) +{ + wxCHECK( m_isRootSheet, /* void */ ); // Only performed for the entire schematic. + + SCH_REFERENCE_LIST symbolInstances; + + GetComponents( symbolInstances, true, true ); + + for( size_t i = 0; i < symbolInstances.GetCount(); i++ ) + { + // The instance paths are stored in the file sans root path so the comparison + // should not include the root path. + wxString path = symbolInstances[i].GetPath(); + + auto it = std::find_if( aSymbolInstances.begin(), aSymbolInstances.end(), + [ path ]( COMPONENT_INSTANCE_REFERENCE& r )->bool + { + return path == r.m_Path.AsString(); + } + ); + + if( it == aSymbolInstances.end() ) + { + wxLogTrace( traceSchSheetPaths, "No symbol instance found for path \"%s\"", path ); + continue; + } + + SCH_COMPONENT* symbol = symbolInstances[i].GetComp(); + + wxCHECK2( symbol, continue ); + + // Symbol instance paths are stored and looked up in memory with the root path so use + // the full path here. + symbol->AddHierarchicalReference( symbolInstances[i].GetSheetPath().Path(), + it->m_Reference, it->m_Unit ); + symbol->GetField( REFERENCE )->SetText( it->m_Reference ); + } +} + + +std::vector SCH_SHEET_LIST::GetPaths() const +{ + std::vector paths; + + for( auto sheetPath : *this ) + paths.emplace_back( sheetPath.Path() ); + + return paths; +} + + +void SCH_SHEET_LIST::ReplaceLegacySheetPaths( const std::vector& aOldSheetPaths ) +{ + wxCHECK( size() == aOldSheetPaths.size(), /* void */ ); + + for( size_t i = 0; i < size(); i++ ) + { + const KIID_PATH oldSheetPath = aOldSheetPaths.at( i ); + const KIID_PATH newSheetPath = at( i ).Path(); + SCH_SCREEN* screen = at(i).LastScreen(); + + wxCHECK( screen, /* void */ ); + + for( auto symbol : screen->Items().OfType( SCH_COMPONENT_T ) ) + { + static_cast( symbol )->ReplaceInstanceSheetPath( oldSheetPath, + newSheetPath ); + } + } +} + + void SHEETLIST_ERC_ITEMS_PROVIDER::SetSeverities( int aSeverities ) { m_severities = aSeverities; diff --git a/eeschema/sch_sheet_path.h b/eeschema/sch_sheet_path.h index fd39c54171..5fa83eefc6 100644 --- a/eeschema/sch_sheet_path.h +++ b/eeschema/sch_sheet_path.h @@ -37,6 +37,17 @@ #include +/** + * A simple container for schematic symbol instance infromation. + */ +struct COMPONENT_INSTANCE_REFERENCE +{ + KIID_PATH m_Path; + wxString m_Reference; + int m_Unit; +}; + + /** Info about complex hierarchies handling: * A hierarchical schematic uses sheets (hierarchical sheets) included in a * given sheet. Each sheet corresponds to a schematic drawing handled by a @@ -405,15 +416,6 @@ public: bool SetComponentFootprint( const wxString& aReference, const wxString& aFootPrint, bool aSetVisible ); - /** - * Function IsComplexHierarchy - * searches all of the sheets for duplicate files names which indicates a complex - * hierarchy. - * - * @return true if the #SCH_SHEET_LIST is a complex hierarchy. - */ - bool IsComplexHierarchy() const; - /** * Function TestForRecursion * @@ -446,6 +448,29 @@ public: void BuildSheetList( SCH_SHEET* aSheet ); bool NameExists( const wxString& aSheetName ); + + /** + * Update all of the symbol instance information using \a aSymbolInstances. + * + * @param aSymbolInstances is the symbol path information loaded from the root schematic. + */ + void UpdateSymbolInstances( std::vector& aSymbolInstances ); + + std::vector GetPaths() const; + + /** + * Update all of the symbol sheet paths to the sheet paths defined in \a aOldSheetPaths. + * + * @note The list of old sheet paths must be the exact same size and order as the existing + * sheet paths. This should not be an issue if no new sheets where added between the + * creation of this sheet list and \a aOldSheetPaths. This should only be called + * when updating legacy schematics to the new schematic file format. Once this + * happens, the schematic cannot be save to the legacy file format because the + * time stamp part of UUIDs are no longer guaranteed to be unique. + * + * @param aOldSheetPaths is the #SHEET_PATH_LIST to update from. + */ + void ReplaceLegacySheetPaths( const std::vector& aOldSheetPaths ); }; diff --git a/eeschema/schematic.keywords b/eeschema/schematic.keywords index 9a7b2bdff8..ad66605538 100644 --- a/eeschema/schematic.keywords +++ b/eeschema/schematic.keywords @@ -39,7 +39,6 @@ id image input input_low -instances inverted inverted_clock italic @@ -99,6 +98,7 @@ solid start stroke symbol +symbol_instances text thickness title diff --git a/eeschema/sheet.cpp b/eeschema/sheet.cpp index e245527da7..ce1cfced8e 100644 --- a/eeschema/sheet.cpp +++ b/eeschema/sheet.cpp @@ -115,6 +115,10 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHier SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( schFileType ) ); std::unique_ptr< SCH_SHEET> newSheet( new SCH_SHEET ); + // This will cause the sheet UUID to be set to the loaded schematic UUID. This is required + // to ensure all of the sheet paths in any subsheets are correctly generated. + const_cast( newSheet->m_Uuid ) = KIID( 0 ); + wxFileName fileName( aFileName ); if( !fileName.IsAbsolute() && !fileName.MakeAbsolute() ) diff --git a/include/common.h b/include/common.h index 9fe99c68c5..7e4529ac56 100644 --- a/include/common.h +++ b/include/common.h @@ -83,6 +83,13 @@ public: wxString AsString() const; wxString AsLegacyTimestampString() const; + /** + * Change an existing time stamp based UUID into a true UUID. + * + * If this is not a time stamp based UUID, then no change is made. + */ + void ConvertTimestampToUuid(); + bool operator==( KIID const& rhs) const { return m_uuid == rhs.m_uuid; diff --git a/include/trace_helpers.h b/include/trace_helpers.h index 1f56040090..84dabbe84a 100644 --- a/include/trace_helpers.h +++ b/include/trace_helpers.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2018 Wayne Stambaugh - * Copyright (C) 2018-2019 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2018-2020 KiCad Developers, see change_log.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 @@ -167,6 +167,13 @@ extern const wxChar* const traceZoomScroll; */ extern const wxChar* const traceSymbolResolver; +/** + * Flag to enable debug output of schematic symbol sheet path manipulation code. + * + * Use "KICAD_SCH_SHEET_PATHS" to enable. + */ +extern const wxChar* const traceSchSheetPaths; + ///@} /**