/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright The KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool checkOverwriteDb( wxWindow* aFrame, wxString& libname, wxString& newName ) { wxString msg = wxString::Format( _( "Design block '%s' already exists in library '%s'." ), newName.GetData(), libname.GetData() ); if( OKOrCancelDialog( aFrame, _( "Confirmation" ), msg, _( "Overwrite existing design block?" ), _( "Overwrite" ) ) != wxID_OK ) { return false; } return true; } bool checkOverwriteDbSchematic( wxWindow* aFrame, const LIB_ID& aLibId ) { wxString msg = wxString::Format( _( "Design block '%s' already has a schematic." ), aLibId.GetUniStringLibItemName() ); if( OKOrCancelDialog( aFrame, _( "Confirmation" ), msg, _( "Overwrite existing schematic?" ), _( "Overwrite" ) ) != wxID_OK ) { return false; } return true; } bool SCH_EDIT_FRAME::SaveSheetAsDesignBlock( const wxString& aLibraryName, SCH_SHEET_PATH& aSheetPath ) { // Make sure the user has selected a library to save into if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() ) { DisplayErrorMessage( this, _( "Please select a library to save the design block to." ) ); return false; } // Just block all attempts to create design blocks with nested sheets at this point std::vector sheets; aSheetPath.LastScreen()->GetSheets( &sheets ); if( !sheets.empty() ) { DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) ); return false; } DESIGN_BLOCK blk; wxFileName fn = wxFileNameFromPath( aSheetPath.Last()->GetName() ); blk.SetLibId( LIB_ID( aLibraryName, fn.GetName() ) ); // Copy all fields from the sheet to the design block std::vector& shFields = aSheetPath.Last()->GetFields(); nlohmann::ordered_map dbFields; for( SCH_FIELD& f : shFields ) { if( f.GetId() == FIELD_T::SHEET_NAME || f.GetId() == FIELD_T::SHEET_FILENAME ) continue; dbFields[f.GetCanonicalName()] = f.GetText(); } blk.SetFields( dbFields ); DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, &blk ); if( dlg.ShowModal() != wxID_OK ) return false; wxString libName = blk.GetLibId().GetLibNickname(); wxString newName = blk.GetLibId().GetLibItemName(); if( Prj().DesignBlockLibs()->DesignBlockExists( libName, newName ) && !checkOverwriteDb( this, libName, newName ) ) { return false; } // Save a temporary copy of the schematic file, as the plugin is just going to move it wxString tempFile = wxFileName::CreateTempFileName( "design_block" ); if( !saveSchematicFile( aSheetPath.Last(), tempFile ) ) { DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) ); wxRemoveFile( tempFile ); return false; } blk.SetSchematicFile( tempFile ); bool success = false; try { success = Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk ) == DESIGN_BLOCK_LIB_TABLE::SAVE_OK; } catch( const IO_ERROR& ioe ) { DisplayError( this, ioe.What() ); } // Clean up the temporary file wxRemoveFile( tempFile ); m_designBlocksPane->RefreshLibs(); m_designBlocksPane->SelectLibId( blk.GetLibId() ); return success; } bool SCH_EDIT_FRAME::SaveSheetToDesignBlock( const LIB_ID& aLibId, SCH_SHEET_PATH& aSheetPath ) { // Make sure the user has selected a library to save into if( !Prj().DesignBlockLibs()->DesignBlockExists( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) ) { DisplayErrorMessage( this, _( "Please select a design block to save the schematic to." ) ); return false; } // Just block all attempts to create design blocks with nested sheets at this point std::vector sheets; aSheetPath.LastScreen()->GetSheets( &sheets ); if( !sheets.empty() ) { DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) ); return false; } DESIGN_BLOCK* blk = nullptr; try { blk = Prj().DesignBlockLibs()->DesignBlockLoad( aLibId.GetLibNickname(), aLibId.GetLibItemName() ); } catch( const IO_ERROR& ioe ) { DisplayError( this, ioe.What() ); return false; } if( !blk->GetSchematicFile().IsEmpty() && !checkOverwriteDbSchematic( this, aLibId ) ) { return false; } // Copy all fields from the sheet to the design block. // Note: this will overwrite any existing fields in the design block, but // will leave extra fields not in this source sheet alone. std::vector& shFields = aSheetPath.Last()->GetFields(); nlohmann::ordered_map dbFields = blk->GetFields(); for( SCH_FIELD& f : shFields ) { if( f.GetId() == FIELD_T::SHEET_NAME || f.GetId() == FIELD_T::SHEET_FILENAME ) continue; dbFields[f.GetCanonicalName()] = f.GetText(); } blk->SetFields( dbFields ); DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, blk, true ); if( dlg.ShowModal() != wxID_OK ) return false; // Save a temporary copy of the schematic file, as the plugin is just going to move it wxString tempFile = wxFileName::CreateTempFileName( "design_block" ); if( !saveSchematicFile( aSheetPath.Last(), tempFile ) ) { DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) ); wxRemoveFile( tempFile ); return false; } blk->SetSchematicFile( tempFile ); bool success = false; try { success = Prj().DesignBlockLibs()->DesignBlockSave( aLibId.GetLibNickname(), blk ) == DESIGN_BLOCK_LIB_TABLE::SAVE_OK; } catch( const IO_ERROR& ioe ) { DisplayError( this, ioe.What() ); } // Clean up the temporary file wxRemoveFile( tempFile ); m_designBlocksPane->RefreshLibs(); m_designBlocksPane->SelectLibId( blk->GetLibId() ); return success; } bool SCH_EDIT_FRAME::SaveSelectionAsDesignBlock( const wxString& aLibraryName ) { // Get all selected items SCH_SELECTION selection = m_toolManager->GetTool()->GetSelection(); if( selection.Empty() ) { DisplayErrorMessage( this, _( "Please select some items to save as a design block." ) ); return false; } // Make sure the user has selected a library to save into if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() ) { DisplayErrorMessage( this, _( "Please select a library to save the design block to." ) ); return false; } // Just block all attempts to create design blocks with nested sheets at this point if( selection.HasType( SCH_SHEET_T ) ) { if( selection.Size() == 1 ) { SCH_SHEET* sheet = static_cast( selection.Front() ); SCH_SHEET_PATH curPath = GetCurrentSheet(); curPath.push_back( sheet ); SaveSheetAsDesignBlock( aLibraryName, curPath ); } else DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) ); return false; } DESIGN_BLOCK blk; wxFileName fn = wxFileNameFromPath( GetScreen()->GetFileName() ); blk.SetLibId( LIB_ID( aLibraryName, fn.GetName() ) ); DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, &blk ); if( dlg.ShowModal() != wxID_OK ) return false; wxString libName = blk.GetLibId().GetLibNickname(); wxString newName = blk.GetLibId().GetLibItemName(); if( Prj().DesignBlockLibs()->DesignBlockExists( libName, newName ) && !checkOverwriteDb( this, libName, newName ) ) { return false; } // Create a temporary screen SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic ); // Copy the selected items to the temporary screen for( EDA_ITEM* item : selection ) { EDA_ITEM* copy = item->Clone(); tempScreen->Append( static_cast( copy ) ); } // Create a sheet for the temporary screen SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic ); tempSheet->SetScreen( tempScreen ); // Save a temporary copy of the schematic file, as the plugin is just going to move it wxString tempFile = wxFileName::CreateTempFileName( "design_block" ); if( !saveSchematicFile( tempSheet, tempFile ) ) { DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) ); wxRemoveFile( tempFile ); return false; } blk.SetSchematicFile( tempFile ); bool success = false; try { success = Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk ) == DESIGN_BLOCK_LIB_TABLE::SAVE_OK; } catch( const IO_ERROR& ioe ) { DisplayError( this, ioe.What() ); } // Clean up the temporaries wxRemoveFile( tempFile ); // This will also delete the screen delete tempSheet; m_designBlocksPane->RefreshLibs(); m_designBlocksPane->SelectLibId( blk.GetLibId() ); return success; } bool SCH_EDIT_FRAME::SaveSelectionToDesignBlock( const LIB_ID& aLibId ) { // Get all selected items SCH_SELECTION selection = m_toolManager->GetTool()->GetSelection(); if( selection.Empty() ) { DisplayErrorMessage( this, _( "Please select some items to save as a design block." ) ); return false; } // Make sure the user has selected a library to save into if( !Prj().DesignBlockLibs()->DesignBlockExists( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) ) { DisplayErrorMessage( this, _( "Please select a design block to save the schematic to." ) ); return false; } // Just block all attempts to create design blocks with nested sheets at this point if( selection.HasType( SCH_SHEET_T ) ) { if( selection.Size() == 1 ) { SCH_SHEET* sheet = static_cast( selection.Front() ); SCH_SHEET_PATH curPath = GetCurrentSheet(); curPath.push_back( sheet ); SaveSheetToDesignBlock( aLibId, curPath ); } else DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) ); return false; } // If we have a single group, we want to strip the group and select the children SCH_GROUP* group = nullptr; if( selection.Size() == 1 ) { EDA_ITEM* item = selection.Front(); if( item->Type() == SCH_GROUP_T ) { group = static_cast( item ); selection.Remove( group ); // Don't recurse; if we have a group of groups the user probably intends the inner groups to be saved group->RunOnChildren( [&]( EDA_ITEM* aItem ) { selection.Add( aItem ); }, RECURSE_MODE::NO_RECURSE ); } } std::unique_ptr blk; try { blk.reset( Prj().DesignBlockLibs()->DesignBlockLoad( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) ); } catch( const IO_ERROR& ioe ) { DisplayError( this, ioe.What() ); return false; } if( !blk->GetSchematicFile().IsEmpty() && !checkOverwriteDbSchematic( this, aLibId ) ) return false; // Create a temporary screen SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic ); auto cloneAndAdd = [&] ( EDA_ITEM* aItem ) -> SCH_ITEM* { if( !aItem->IsSCH_ITEM() ) return nullptr; SCH_ITEM* copy = static_cast( aItem->Clone() ); tempScreen->Append( static_cast( copy ) ); return copy; }; // Copy the selected items to the temporary board for( EDA_ITEM* item : selection ) { // Remove parent group membership since we strip the first group layer if( SCH_ITEM* copy = cloneAndAdd( item ) ) copy->SetParentGroup( nullptr ); if( item->Type() == SCH_GROUP_T ) { SCH_GROUP* innerGroup = static_cast( item ); // Groups also need their children copied innerGroup->RunOnChildren( cloneAndAdd, RECURSE_MODE::RECURSE ); } } // Create a sheet for the temporary screen SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic ); tempSheet->SetScreen( tempScreen ); // Save a temporary copy of the schematic file, as the plugin is just going to move it wxString tempFile = wxFileName::CreateTempFileName( "design_block" ); if( !saveSchematicFile( tempSheet, tempFile ) ) { DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) ); wxRemoveFile( tempFile ); return false; } blk->SetSchematicFile( tempFile ); bool success = false; try { success = Prj().DesignBlockLibs()->DesignBlockSave( aLibId.GetLibNickname(), blk.get() ) == DESIGN_BLOCK_LIB_TABLE::SAVE_OK; // If we had a group, we need to reselect it if( group ) { selection.Clear(); selection.Add( group ); // If we didn't have a design block link before, add one for convenience if( !group->HasDesignBlockLink() ) { SCH_COMMIT commit( m_toolManager ); commit.Modify( group ); group->SetDesignBlockLibId( aLibId ); commit.Push( "Set Group Design Block Link" ); } } } catch( const IO_ERROR& ioe ) { DisplayError( this, ioe.What() ); } // Clean up the temporaries wxRemoveFile( tempFile ); // This will also delete the screen delete tempSheet; m_designBlocksPane->RefreshLibs(); m_designBlocksPane->SelectLibId( blk->GetLibId() ); return success; }