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
79 if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() )
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
145 m_designBlocksPane->RefreshLibs();
146 m_designBlocksPane->SelectLibId( blk.GetLibId() );
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
228 m_designBlocksPane->RefreshLibs();
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
247 if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() )
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 SCH_GROUP* group = nullptr;
274
275 if( selection.Size() == 1 && selection.HasType( SCH_GROUP_T ) )
276 group = static_cast<SCH_GROUP*>( selection.Front() );
277
278 if( group && !group->GetName().IsEmpty() )
279 // If the user has selected a single group, they probably want the design block named after the group
280 blk.SetLibId( LIB_ID( aLibraryName, group->GetName() ) );
281 else
282 {
283 // Otherwise, use the current screen name
284 wxFileName fn = wxFileNameFromPath( GetScreen()->GetFileName() );
285 blk.SetLibId( LIB_ID( aLibraryName, fn.GetName() ) );
286 }
287
288 DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, &blk );
289
290 if( dlg.ShowModal() != wxID_OK )
291 return false;
292
293 wxString libName = blk.GetLibId().GetLibNickname();
294 wxString newName = blk.GetLibId().GetLibItemName();
295
296 if( Prj().DesignBlockLibs()->DesignBlockExists( libName, newName ) && !checkOverwriteDb( this, libName, newName ) )
297 return false;
298
299 // Create a temporary screen
300 SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic );
301
302 // If we have a single group, we want to strip the group and select the children
303 if( group )
304 {
305 selection.Remove( group );
306
307 // Don't recurse; if we have a group of groups the user probably intends the inner groups to be saved
308 group->RunOnChildren(
309 [&]( EDA_ITEM* aItem )
310 {
311 selection.Add( aItem );
312 },
314 }
315
316 // Copy the selected items to the temporary screen
317 for( EDA_ITEM* item : selection )
318 {
319 // We need to deep copy since selections of groups will not have the children
320 if( item->Type() == SCH_GROUP_T )
321 {
322 SCH_GROUP* clonedGroup = static_cast<SCH_GROUP*>( item )->DeepClone();
323
324 tempScreen->Append( clonedGroup );
325
326 clonedGroup->RunOnChildren(
327 [&]( EDA_ITEM* aItem )
328 {
329 tempScreen->Append( static_cast<SCH_ITEM*>( aItem ) );
330 },
332 }
333 else
334 {
335 EDA_ITEM* copy = item->Clone();
336 tempScreen->Append( static_cast<SCH_ITEM*>( copy ) );
337 }
338 }
339
340 // Create a sheet for the temporary screen
341 SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic );
342 tempSheet->SetScreen( tempScreen );
343
344 // Save a temporary copy of the schematic file, as the plugin is just going to move it
345 wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
346 if( !saveSchematicFile( tempSheet, tempFile ) )
347 {
348 DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
349 wxRemoveFile( tempFile );
350 return false;
351 }
352
353 blk.SetSchematicFile( tempFile );
354
355 bool success = false;
356
357 try
358 {
359 success = Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk ) == DESIGN_BLOCK_LIB_TABLE::SAVE_OK;
360 }
361 catch( const IO_ERROR& ioe )
362 {
363 DisplayError( this, ioe.What() );
364 }
365
366 // Clean up the temporaries
367 wxRemoveFile( tempFile );
368 // This will also delete the screen
369 delete tempSheet;
370
371 m_designBlocksPane->RefreshLibs();
372 m_designBlocksPane->SelectLibId( blk.GetLibId() );
373
374 return success;
375}
376
377
379{
380 // Get all selected items
381 SCH_SELECTION selection = m_toolManager->GetTool<SCH_SELECTION_TOOL>()->GetSelection();
382
383 if( selection.Empty() )
384 {
385 DisplayErrorMessage( this, _( "Please select some items to save as a design block." ) );
386 return false;
387 }
388
389 // Make sure the user has selected a library to save into
390 if( !Prj().DesignBlockLibs()->DesignBlockExists( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) )
391 {
392 DisplayErrorMessage( this, _( "Please select a design block to save the schematic to." ) );
393 return false;
394 }
395
396 // Just block all attempts to create design blocks with nested sheets at this point
397 if( selection.HasType( SCH_SHEET_T ) )
398 {
399 if( selection.Size() == 1 )
400 {
401 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( selection.Front() );
403
404 curPath.push_back( sheet );
405 SaveSheetToDesignBlock( aLibId, curPath );
406 }
407 else
408 {
409 DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
410 }
411
412 return false;
413 }
414
415 // If we have a single group, we want to strip the group and select the children
416 SCH_GROUP* group = nullptr;
417
418 if( selection.Size() == 1 )
419 {
420 EDA_ITEM* item = selection.Front();
421
422 if( item->Type() == SCH_GROUP_T )
423 {
424 group = static_cast<SCH_GROUP*>( item );
425
426 selection.Remove( group );
427
428 // Don't recurse; if we have a group of groups the user probably intends the inner groups to be saved
429 group->RunOnChildren( [&]( EDA_ITEM* aItem )
430 {
431 selection.Add( aItem );
432 },
434 }
435 }
436
437 std::unique_ptr<DESIGN_BLOCK> blk;
438
439 try
440 {
441 blk.reset( Prj().DesignBlockLibs()->DesignBlockLoad( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) );
442 }
443 catch( const IO_ERROR& ioe )
444 {
445 DisplayError( this, ioe.What() );
446 return false;
447 }
448
449 if( !blk->GetSchematicFile().IsEmpty() && !checkOverwriteDbSchematic( this, aLibId ) )
450 return false;
451
452 // Create a temporary screen
453 SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic );
454
455 auto cloneAndAdd =
456 [&] ( EDA_ITEM* aItem ) -> SCH_ITEM*
457 {
458 if( !aItem->IsSCH_ITEM() )
459 return nullptr;
460
461 SCH_ITEM* copy = static_cast<SCH_ITEM*>( aItem->Clone() );
462 tempScreen->Append( static_cast<SCH_ITEM*>( copy ) );
463 return copy;
464 };
465
466 // Copy the selected items to the temporary board
467 for( EDA_ITEM* item : selection )
468 {
469 // Remove parent group membership since we strip the first group layer
470 if( SCH_ITEM* copy = cloneAndAdd( item ) )
471 copy->SetParentGroup( nullptr );
472
473 if( item->Type() == SCH_GROUP_T )
474 {
475 SCH_GROUP* innerGroup = static_cast<SCH_GROUP*>( item );
476
477 // Groups also need their children copied
478 innerGroup->RunOnChildren( cloneAndAdd, RECURSE_MODE::RECURSE );
479 }
480 }
481
482 // Create a sheet for the temporary screen
483 SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic );
484 tempSheet->SetScreen( tempScreen );
485
486 // Save a temporary copy of the schematic file, as the plugin is just going to move it
487 wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
488
489 if( !saveSchematicFile( tempSheet, tempFile ) )
490 {
491 DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
492 wxRemoveFile( tempFile );
493 return false;
494 }
495
496 blk->SetSchematicFile( tempFile );
497
498 bool success = false;
499
500 try
501 {
502 success = Prj().DesignBlockLibs()->DesignBlockSave( aLibId.GetLibNickname(), blk.get() )
504
505 // If we had a group, we need to reselect it
506 if( group )
507 {
508 selection.Clear();
509 selection.Add( group );
510
511 // If we didn't have a design block link before, add one for convenience
512 if( !group->HasDesignBlockLink() )
513 {
514 SCH_COMMIT commit( m_toolManager );
515
516 commit.Modify( group, GetScreen() );
517 group->SetDesignBlockLibId( aLibId );
518
519 commit.Push( "Set Group Design Block Link" );
520 }
521 }
522 }
523 catch( const IO_ERROR& ioe )
524 {
525 DisplayError( this, ioe.What() );
526 }
527
528 // Clean up the temporaries
529 wxRemoveFile( tempFile );
530 // This will also delete the screen
531 delete tempSheet;
532
533 m_designBlocksPane->RefreshLibs();
534 m_designBlocksPane->SelectLibId( blk->GetLibId() );
535
536 return success;
537}
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:106
SAVE_T DesignBlockSave(const wxString &aNickname, const DESIGN_BLOCK *aDesignBlock, bool aOverwrite=true)
Write aDesignBlock to an existing library given by aNickname.
void SetSchematicFile(const wxString &aFile)
void SetLibId(const LIB_ID &aName)
const LIB_ID & GetLibId() const
const nlohmann::ordered_map< wxString, wxString > & GetFields() const
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.
virtual const wxString What() const
A composite of Problem() and Where()
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:445
virtual void Push(const wxString &aMessage=wxT("A commit"), int aCommitFlags=0) override
Execute the changes.
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
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:167
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
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.
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.
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
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)
@ RECURSE
Definition eda_item.h:51
@ NO_RECURSE
Definition eda_item.h:52
Helper functions to substitute paths with environmental variables.
PROJECT & Prj()
Definition kicad.cpp:612
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:175
@ SCH_SHEET_T
Definition typeinfo.h:177
Definition of file extensions used in Kicad.