KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_design_block_utils.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <pgm_base.h>
25#include <kiway.h>
26#include <design_block.h>
29#include <sch_edit_frame.h>
30#include <sch_group.h>
31#include <wx/choicdlg.h>
32#include <wx/msgdlg.h>
33#include <wx/textdlg.h>
35#include <paths.h>
36#include <env_paths.h>
37#include <common.h>
38#include <kidialog.h>
39#include <confirm.h>
40#include <tool/tool_manager.h>
41#include <sch_selection_tool.h>
43#include <json_common.h>
44
45bool checkOverwriteDb( wxWindow* aFrame, wxString& libname, wxString& newName )
46{
47 wxString msg = wxString::Format( _( "Design block '%s' already exists in library '%s'." ),
48 newName.GetData(),
49 libname.GetData() );
50
51 if( OKOrCancelDialog( aFrame, _( "Confirmation" ), msg, _( "Overwrite existing design block?" ), _( "Overwrite" ) )
52 != wxID_OK )
53 {
54 return false;
55 }
56
57 return true;
58}
59
60
61bool checkOverwriteDbSchematic( wxWindow* aFrame, const LIB_ID& aLibId )
62{
63 wxString msg = wxString::Format( _( "Design block '%s' already has a schematic." ),
64 aLibId.GetUniStringLibItemName() );
65
66 if( OKOrCancelDialog( aFrame, _( "Confirmation" ), msg, _( "Overwrite existing schematic?" ), _( "Overwrite" ) )
67 != wxID_OK )
68 {
69 return false;
70 }
71
72 return true;
73}
74
75
76bool SCH_EDIT_FRAME::SaveSheetAsDesignBlock( const wxString& aLibraryName, SCH_SHEET_PATH& aSheetPath )
77{
78 // Make sure the user has selected a library to save into
80 {
81 DisplayErrorMessage( this, _( "Please select a library to save the design block to." ) );
82 return false;
83 }
84
85 // Just block all attempts to create design blocks with nested sheets at this point
86 std::vector<SCH_ITEM*> sheets;
87 aSheetPath.LastScreen()->GetSheets( &sheets );
88
89 if( !sheets.empty() )
90 {
91 DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
92 return false;
93 }
94
95 DESIGN_BLOCK blk;
96 wxFileName fn = wxFileNameFromPath( aSheetPath.Last()->GetName() );
97
98 blk.SetLibId( LIB_ID( aLibraryName, fn.GetName() ) );
99
100 // Copy all fields from the sheet to the design block
101 for( SCH_FIELD& field : aSheetPath.Last()->GetFields() )
102 {
103 if( field.GetId() == FIELD_T::SHEET_NAME || field.GetId() == FIELD_T::SHEET_FILENAME )
104 continue;
105
106 blk.GetFields()[field.GetCanonicalName()] = field.GetText();
107 }
108
109 DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, &blk );
110
111 if( dlg.ShowModal() != wxID_OK )
112 return false;
113
114 wxString libName = blk.GetLibId().GetLibNickname();
115 wxString newName = blk.GetLibId().GetLibItemName();
116
117 if( Prj().DesignBlockLibs()->DesignBlockExists( libName, newName ) && !checkOverwriteDb( this, libName, newName ) )
118 return false;
119
120 // Save a temporary copy of the schematic file, as the plugin is just going to move it
121 wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
122 if( !saveSchematicFile( aSheetPath.Last(), tempFile ) )
123 {
124 DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
125 wxRemoveFile( tempFile );
126 return false;
127 }
128
129 blk.SetSchematicFile( tempFile );
130
131 bool success = false;
132
133 try
134 {
135 success = Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk ) == DESIGN_BLOCK_LIB_TABLE::SAVE_OK;
136 }
137 catch( const IO_ERROR& ioe )
138 {
139 DisplayError( this, ioe.What() );
140 }
141
142 // Clean up the temporary file
143 wxRemoveFile( tempFile );
144
147
148 return success;
149}
150
151
153{
154 // Make sure the user has selected a library to save into
155 if( !Prj().DesignBlockLibs()->DesignBlockExists( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) )
156 {
157 DisplayErrorMessage( this, _( "Please select a design block to save the schematic to." ) );
158 return false;
159 }
160
161 // Just block all attempts to create design blocks with nested sheets at this point
162 std::vector<SCH_ITEM*> sheets;
163 aSheetPath.LastScreen()->GetSheets( &sheets );
164
165 if( !sheets.empty() )
166 {
167 DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
168 return false;
169 }
170
171 std::unique_ptr<DESIGN_BLOCK> blk;
172
173 try
174 {
175 blk.reset( Prj().DesignBlockLibs()->DesignBlockLoad( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) );
176 }
177 catch( const IO_ERROR& ioe )
178 {
179 DisplayError( this, ioe.What() );
180 return false;
181 }
182
183 if( !blk->GetSchematicFile().IsEmpty() && !checkOverwriteDbSchematic( this, aLibId ) )
184 return false;
185
186 // Copy all fields from the sheet to the design block.
187 // Note: this will overwrite any existing fields in the design block, but
188 // will leave extra fields not in this source sheet alone.
189 for( SCH_FIELD& field : aSheetPath.Last()->GetFields() )
190 {
191 if( field.GetId() == FIELD_T::SHEET_NAME || field.GetId() == FIELD_T::SHEET_FILENAME )
192 continue;
193
194 blk->GetFields()[field.GetCanonicalName()] = field.GetText();
195 }
196
197 DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, blk.get(), true );
198
199 if( dlg.ShowModal() != wxID_OK )
200 return false;
201
202 // Save a temporary copy of the schematic file, as the plugin is just going to move it
203 wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
204 if( !saveSchematicFile( aSheetPath.Last(), tempFile ) )
205 {
206 DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
207 wxRemoveFile( tempFile );
208 return false;
209 }
210
211 blk->SetSchematicFile( tempFile );
212
213 bool success = false;
214
215 try
216 {
217 success = Prj().DesignBlockLibs()->DesignBlockSave( aLibId.GetLibNickname(), blk.get() )
219 }
220 catch( const IO_ERROR& ioe )
221 {
222 DisplayError( this, ioe.What() );
223 }
224
225 // Clean up the temporary file
226 wxRemoveFile( tempFile );
227
229 m_designBlocksPane->SelectLibId( blk->GetLibId() );
230
231 return success;
232}
233
234
235bool SCH_EDIT_FRAME::SaveSelectionAsDesignBlock( const wxString& aLibraryName )
236{
237 // Get all selected items
238 SCH_SELECTION selection = m_toolManager->GetTool<SCH_SELECTION_TOOL>()->GetSelection();
239
240 if( selection.Empty() )
241 {
242 DisplayErrorMessage( this, _( "Please select some items to save as a design block." ) );
243 return false;
244 }
245
246 // Make sure the user has selected a library to save into
248 {
249 DisplayErrorMessage( this, _( "Please select a library to save the design block to." ) );
250 return false;
251 }
252
253 // Just block all attempts to create design blocks with nested sheets at this point
254 if( selection.HasType( SCH_SHEET_T ) )
255 {
256 if( selection.Size() == 1 )
257 {
258 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( selection.Front() );
260
261 curPath.push_back( sheet );
262 SaveSheetAsDesignBlock( aLibraryName, curPath );
263 }
264 else
265 {
266 DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
267 }
268
269 return false;
270 }
271
272 DESIGN_BLOCK blk;
273 wxFileName fn = wxFileNameFromPath( GetScreen()->GetFileName() );
274
275 blk.SetLibId( LIB_ID( aLibraryName, fn.GetName() ) );
276
277 DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, &blk );
278
279 if( dlg.ShowModal() != wxID_OK )
280 return false;
281
282 wxString libName = blk.GetLibId().GetLibNickname();
283 wxString newName = blk.GetLibId().GetLibItemName();
284
285 if( Prj().DesignBlockLibs()->DesignBlockExists( libName, newName ) && !checkOverwriteDb( this, libName, newName ) )
286 return false;
287
288 // Create a temporary screen
289 SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic );
290
291 // Copy the selected items to the temporary screen
292 for( EDA_ITEM* item : selection )
293 {
294 EDA_ITEM* copy = item->Clone();
295 tempScreen->Append( static_cast<SCH_ITEM*>( copy ) );
296 }
297
298 // Create a sheet for the temporary screen
299 SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic );
300 tempSheet->SetScreen( tempScreen );
301
302 // Save a temporary copy of the schematic file, as the plugin is just going to move it
303 wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
304 if( !saveSchematicFile( tempSheet, tempFile ) )
305 {
306 DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
307 wxRemoveFile( tempFile );
308 return false;
309 }
310
311 blk.SetSchematicFile( tempFile );
312
313 bool success = false;
314
315 try
316 {
317 success = Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk ) == DESIGN_BLOCK_LIB_TABLE::SAVE_OK;
318 }
319 catch( const IO_ERROR& ioe )
320 {
321 DisplayError( this, ioe.What() );
322 }
323
324 // Clean up the temporaries
325 wxRemoveFile( tempFile );
326 // This will also delete the screen
327 delete tempSheet;
328
331
332 return success;
333}
334
335
337{
338 // Get all selected items
339 SCH_SELECTION selection = m_toolManager->GetTool<SCH_SELECTION_TOOL>()->GetSelection();
340
341 if( selection.Empty() )
342 {
343 DisplayErrorMessage( this, _( "Please select some items to save as a design block." ) );
344 return false;
345 }
346
347 // Make sure the user has selected a library to save into
348 if( !Prj().DesignBlockLibs()->DesignBlockExists( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) )
349 {
350 DisplayErrorMessage( this, _( "Please select a design block to save the schematic to." ) );
351 return false;
352 }
353
354 // Just block all attempts to create design blocks with nested sheets at this point
355 if( selection.HasType( SCH_SHEET_T ) )
356 {
357 if( selection.Size() == 1 )
358 {
359 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( selection.Front() );
361
362 curPath.push_back( sheet );
363 SaveSheetToDesignBlock( aLibId, curPath );
364 }
365 else
366 {
367 DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
368 }
369
370 return false;
371 }
372
373 // If we have a single group, we want to strip the group and select the children
374 SCH_GROUP* group = nullptr;
375
376 if( selection.Size() == 1 )
377 {
378 EDA_ITEM* item = selection.Front();
379
380 if( item->Type() == SCH_GROUP_T )
381 {
382 group = static_cast<SCH_GROUP*>( item );
383
384 selection.Remove( group );
385
386 // Don't recurse; if we have a group of groups the user probably intends the inner groups to be saved
387 group->RunOnChildren( [&]( EDA_ITEM* aItem )
388 {
389 selection.Add( aItem );
390 },
391 RECURSE_MODE::NO_RECURSE );
392 }
393 }
394
395 std::unique_ptr<DESIGN_BLOCK> blk;
396
397 try
398 {
399 blk.reset( Prj().DesignBlockLibs()->DesignBlockLoad( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) );
400 }
401 catch( const IO_ERROR& ioe )
402 {
403 DisplayError( this, ioe.What() );
404 return false;
405 }
406
407 if( !blk->GetSchematicFile().IsEmpty() && !checkOverwriteDbSchematic( this, aLibId ) )
408 return false;
409
410 // Create a temporary screen
411 SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic );
412
413 auto cloneAndAdd =
414 [&] ( EDA_ITEM* aItem ) -> SCH_ITEM*
415 {
416 if( !aItem->IsSCH_ITEM() )
417 return nullptr;
418
419 SCH_ITEM* copy = static_cast<SCH_ITEM*>( aItem->Clone() );
420 tempScreen->Append( static_cast<SCH_ITEM*>( copy ) );
421 return copy;
422 };
423
424 // Copy the selected items to the temporary board
425 for( EDA_ITEM* item : selection )
426 {
427 // Remove parent group membership since we strip the first group layer
428 if( SCH_ITEM* copy = cloneAndAdd( item ) )
429 copy->SetParentGroup( nullptr );
430
431 if( item->Type() == SCH_GROUP_T )
432 {
433 SCH_GROUP* innerGroup = static_cast<SCH_GROUP*>( item );
434
435 // Groups also need their children copied
436 innerGroup->RunOnChildren( cloneAndAdd, RECURSE_MODE::RECURSE );
437 }
438 }
439
440 // Create a sheet for the temporary screen
441 SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic );
442 tempSheet->SetScreen( tempScreen );
443
444 // Save a temporary copy of the schematic file, as the plugin is just going to move it
445 wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
446
447 if( !saveSchematicFile( tempSheet, tempFile ) )
448 {
449 DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
450 wxRemoveFile( tempFile );
451 return false;
452 }
453
454 blk->SetSchematicFile( tempFile );
455
456 bool success = false;
457
458 try
459 {
460 success = Prj().DesignBlockLibs()->DesignBlockSave( aLibId.GetLibNickname(), blk.get() )
462
463 // If we had a group, we need to reselect it
464 if( group )
465 {
466 selection.Clear();
467 selection.Add( group );
468
469 // If we didn't have a design block link before, add one for convenience
470 if( !group->HasDesignBlockLink() )
471 {
472 SCH_COMMIT commit( m_toolManager );
473
474 commit.Modify( group );
475 group->SetDesignBlockLibId( aLibId );
476
477 commit.Push( "Set Group Design Block Link" );
478 }
479 }
480 }
481 catch( const IO_ERROR& ioe )
482 {
483 DisplayError( this, ioe.What() );
484 }
485
486 // Clean up the temporaries
487 wxRemoveFile( tempFile );
488 // This will also delete the screen
489 delete tempSheet;
490
492 m_designBlocksPane->SelectLibId( blk->GetLibId() );
493
494 return success;
495}
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition: commit.h:107
DESIGN_BLOCK * DesignBlockLoad(const wxString &aNickname, const wxString &aDesignBlockName, bool aKeepUUID=false)
Load a design block having aDesignBlockName from the library given by aNickname.
SAVE_T DesignBlockSave(const wxString &aNickname, const DESIGN_BLOCK *aDesignBlock, bool aOverwrite=true)
Write aDesignBlock to an existing library given by aNickname.
void SelectLibId(const LIB_ID &aLibId)
LIB_ID GetSelectedLibId(int *aUnit=nullptr) const
void SetSchematicFile(const wxString &aFile)
Definition: design_block.h:46
void SetLibId(const LIB_ID &aName)
Definition: design_block.h:36
const LIB_ID & GetLibId() const
Definition: design_block.h:37
const nlohmann::ordered_map< wxString, wxString > & GetFields() const
Definition: design_block.h:51
int ShowModal() override
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:98
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:110
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
Definition: ki_exception.h:77
virtual const wxString What() const
A composite of Problem() and Where()
Definition: exceptions.cpp:30
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:49
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
Definition: lib_id.h:112
const UTF8 & GetLibItemName() const
Definition: lib_id.h:102
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition: lib_id.h:87
virtual DESIGN_BLOCK_LIB_TABLE * DesignBlockLibs()
Return the table of design block libraries.
Definition: project.cpp:432
virtual void Push(const wxString &aMessage=wxT("A commit"), int aCommitFlags=0) override
Execute the changes.
Definition: sch_commit.cpp:489
bool SaveSheetToDesignBlock(const LIB_ID &aLibId, SCH_SHEET_PATH &aSheetPath)
SCH_SCREEN * GetScreen() const override
Return a pointer to a BASE_SCREEN or one of its derivatives.
bool SaveSelectionToDesignBlock(const LIB_ID &aLibId)
SCHEMATIC * m_schematic
The currently loaded schematic.
SCH_SHEET_PATH & GetCurrentSheet() const
bool saveSchematicFile(SCH_SHEET *aSheet, const wxString &aSavePath)
Save aSheet to a schematic file.
SCH_DESIGN_BLOCK_PANE * m_designBlocksPane
bool SaveSheetAsDesignBlock(const wxString &aLibraryName, SCH_SHEET_PATH &aSheetPath)
bool SaveSelectionAsDesignBlock(const wxString &aLibraryName)
A set of SCH_ITEMs (i.e., without duplicates).
Definition: sch_group.h:52
void RunOnChildren(const std::function< void(SCH_ITEM *)> &aFunction, RECURSE_MODE aMode) override
Definition: sch_group.cpp:346
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition: sch_item.h:168
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
Definition: sch_screen.cpp:160
void GetSheets(std::vector< SCH_ITEM * > *aItems) const
Similar to Items().OfType( SCH_SHEET_T ), but return the sheets in a deterministic order (L-R,...
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
SCH_SCREEN * LastScreen()
SCH_SHEET * Last() const
Return a pointer to the last SCH_SHEET of the list.
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition: sch_sheet.h:47
std::vector< SCH_FIELD > & GetFields()
Return a reference to the vector holding the sheet's fields.
Definition: sch_sheet.h:87
wxString GetName() const
Definition: sch_sheet.h:113
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Definition: sch_sheet.cpp:137
virtual void Add(EDA_ITEM *aItem)
Definition: selection.cpp:42
virtual void Remove(EDA_ITEM *aItem)
Definition: selection.cpp:60
EDA_ITEM * Front() const
Definition: selection.h:177
virtual void Clear() override
Remove all the stored items from the group.
Definition: selection.h:98
bool HasType(KICAD_T aType) const
Checks if there is at least one item of requested kind.
Definition: selection.cpp:143
int Size() const
Returns the number of selected parts.
Definition: selection.h:121
bool Empty() const
Checks if there is anything selected.
Definition: selection.h:115
TOOL_MANAGER * m_toolManager
Definition: tools_holder.h:171
bool empty() const
Definition: utf8.h:110
The common library.
int OKOrCancelDialog(wxWindow *aParent, const wxString &aWarning, const wxString &aMessage, const wxString &aDetailedMessage, const wxString &aOKLabel, const wxString &aCancelLabel, bool *aApplyToAll)
Display a warning dialog with aMessage and returns the user response.
Definition: confirm.cpp:142
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:194
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:169
This file is part of the common library.
#define _(s)
Helper functions to substitute paths with environmental variables.
PROJECT & Prj()
Definition: kicad.cpp:608
This file is part of the common library.
see class PGM_BASE
bool checkOverwriteDb(wxWindow *aFrame, wxString &libname, wxString &newName)
bool checkOverwriteDbSchematic(wxWindow *aFrame, const LIB_ID &aLibId)
Class to handle a set of SCH_ITEMs.
@ SCH_GROUP_T
Definition: typeinfo.h:174
@ SCH_SHEET_T
Definition: typeinfo.h:176
Definition of file extensions used in Kicad.