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 std::vector<SCH_FIELD>& shFields = aSheetPath.Last()->GetFields();
102 nlohmann::ordered_map<wxString, wxString> dbFields;
103
104 for( SCH_FIELD& f : shFields )
105 {
106 if( f.GetId() == FIELD_T::SHEET_NAME || f.GetId() == FIELD_T::SHEET_FILENAME )
107 continue;
108
109 dbFields[f.GetCanonicalName()] = f.GetText();
110 }
111
112 blk.SetFields( dbFields );
113
114 DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, &blk );
115
116 if( dlg.ShowModal() != wxID_OK )
117 return false;
118
119 wxString libName = blk.GetLibId().GetLibNickname();
120 wxString newName = blk.GetLibId().GetLibItemName();
121
122 if( Prj().DesignBlockLibs()->DesignBlockExists( libName, newName ) && !checkOverwriteDb( this, libName, newName ) )
123 return false;
124
125 // Save a temporary copy of the schematic file, as the plugin is just going to move it
126 wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
127 if( !saveSchematicFile( aSheetPath.Last(), tempFile ) )
128 {
129 DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
130 wxRemoveFile( tempFile );
131 return false;
132 }
133
134 blk.SetSchematicFile( tempFile );
135
136 bool success = false;
137
138 try
139 {
140 success = Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk ) == DESIGN_BLOCK_LIB_TABLE::SAVE_OK;
141 }
142 catch( const IO_ERROR& ioe )
143 {
144 DisplayError( this, ioe.What() );
145 }
146
147 // Clean up the temporary file
148 wxRemoveFile( tempFile );
149
152
153 return success;
154}
155
156
158{
159 // Make sure the user has selected a library to save into
160 if( !Prj().DesignBlockLibs()->DesignBlockExists( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) )
161 {
162 DisplayErrorMessage( this, _( "Please select a design block to save the schematic to." ) );
163 return false;
164 }
165
166 // Just block all attempts to create design blocks with nested sheets at this point
167 std::vector<SCH_ITEM*> sheets;
168 aSheetPath.LastScreen()->GetSheets( &sheets );
169
170 if( !sheets.empty() )
171 {
172 DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
173 return false;
174 }
175
176 std::unique_ptr<DESIGN_BLOCK> blk;
177
178 try
179 {
180 blk.reset( Prj().DesignBlockLibs()->DesignBlockLoad( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) );
181 }
182 catch( const IO_ERROR& ioe )
183 {
184 DisplayError( this, ioe.What() );
185 return false;
186 }
187
188 if( !blk->GetSchematicFile().IsEmpty() && !checkOverwriteDbSchematic( this, aLibId ) )
189 return false;
190
191 // Copy all fields from the sheet to the design block.
192 // Note: this will overwrite any existing fields in the design block, but
193 // will leave extra fields not in this source sheet alone.
194 std::vector<SCH_FIELD>& shFields = aSheetPath.Last()->GetFields();
195 nlohmann::ordered_map<wxString, wxString> dbFields = blk->GetFields();
196
197 for( SCH_FIELD& f : shFields )
198 {
199 if( f.GetId() == FIELD_T::SHEET_NAME || f.GetId() == FIELD_T::SHEET_FILENAME )
200 continue;
201
202 dbFields[f.GetCanonicalName()] = f.GetText();
203 }
204
205 blk->SetFields( dbFields );
206
207 DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, blk.get(), true );
208
209 if( dlg.ShowModal() != wxID_OK )
210 return false;
211
212 // Save a temporary copy of the schematic file, as the plugin is just going to move it
213 wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
214 if( !saveSchematicFile( aSheetPath.Last(), tempFile ) )
215 {
216 DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
217 wxRemoveFile( tempFile );
218 return false;
219 }
220
221 blk->SetSchematicFile( tempFile );
222
223 bool success = false;
224
225 try
226 {
227 success = Prj().DesignBlockLibs()->DesignBlockSave( aLibId.GetLibNickname(), blk.get() )
229 }
230 catch( const IO_ERROR& ioe )
231 {
232 DisplayError( this, ioe.What() );
233 }
234
235 // Clean up the temporary file
236 wxRemoveFile( tempFile );
237
239 m_designBlocksPane->SelectLibId( blk->GetLibId() );
240
241 return success;
242}
243
244
245bool SCH_EDIT_FRAME::SaveSelectionAsDesignBlock( const wxString& aLibraryName )
246{
247 // Get all selected items
248 SCH_SELECTION selection = m_toolManager->GetTool<SCH_SELECTION_TOOL>()->GetSelection();
249
250 if( selection.Empty() )
251 {
252 DisplayErrorMessage( this, _( "Please select some items to save as a design block." ) );
253 return false;
254 }
255
256 // Make sure the user has selected a library to save into
258 {
259 DisplayErrorMessage( this, _( "Please select a library to save the design block to." ) );
260 return false;
261 }
262
263 // Just block all attempts to create design blocks with nested sheets at this point
264 if( selection.HasType( SCH_SHEET_T ) )
265 {
266 if( selection.Size() == 1 )
267 {
268 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( selection.Front() );
270
271 curPath.push_back( sheet );
272 SaveSheetAsDesignBlock( aLibraryName, curPath );
273 }
274 else
275 {
276 DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
277 }
278
279 return false;
280 }
281
282 DESIGN_BLOCK blk;
283 wxFileName fn = wxFileNameFromPath( GetScreen()->GetFileName() );
284
285 blk.SetLibId( LIB_ID( aLibraryName, fn.GetName() ) );
286
287 DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, &blk );
288
289 if( dlg.ShowModal() != wxID_OK )
290 return false;
291
292 wxString libName = blk.GetLibId().GetLibNickname();
293 wxString newName = blk.GetLibId().GetLibItemName();
294
295 if( Prj().DesignBlockLibs()->DesignBlockExists( libName, newName ) && !checkOverwriteDb( this, libName, newName ) )
296 return false;
297
298 // Create a temporary screen
299 SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic );
300
301 // Copy the selected items to the temporary screen
302 for( EDA_ITEM* item : selection )
303 {
304 EDA_ITEM* copy = item->Clone();
305 tempScreen->Append( static_cast<SCH_ITEM*>( copy ) );
306 }
307
308 // Create a sheet for the temporary screen
309 SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic );
310 tempSheet->SetScreen( tempScreen );
311
312 // Save a temporary copy of the schematic file, as the plugin is just going to move it
313 wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
314 if( !saveSchematicFile( tempSheet, tempFile ) )
315 {
316 DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
317 wxRemoveFile( tempFile );
318 return false;
319 }
320
321 blk.SetSchematicFile( tempFile );
322
323 bool success = false;
324
325 try
326 {
327 success = Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk ) == DESIGN_BLOCK_LIB_TABLE::SAVE_OK;
328 }
329 catch( const IO_ERROR& ioe )
330 {
331 DisplayError( this, ioe.What() );
332 }
333
334 // Clean up the temporaries
335 wxRemoveFile( tempFile );
336 // This will also delete the screen
337 delete tempSheet;
338
341
342 return success;
343}
344
345
347{
348 // Get all selected items
349 SCH_SELECTION selection = m_toolManager->GetTool<SCH_SELECTION_TOOL>()->GetSelection();
350
351 if( selection.Empty() )
352 {
353 DisplayErrorMessage( this, _( "Please select some items to save as a design block." ) );
354 return false;
355 }
356
357 // Make sure the user has selected a library to save into
358 if( !Prj().DesignBlockLibs()->DesignBlockExists( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) )
359 {
360 DisplayErrorMessage( this, _( "Please select a design block to save the schematic to." ) );
361 return false;
362 }
363
364 // Just block all attempts to create design blocks with nested sheets at this point
365 if( selection.HasType( SCH_SHEET_T ) )
366 {
367 if( selection.Size() == 1 )
368 {
369 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( selection.Front() );
371
372 curPath.push_back( sheet );
373 SaveSheetToDesignBlock( aLibId, curPath );
374 }
375 else
376 {
377 DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
378 }
379
380 return false;
381 }
382
383 // If we have a single group, we want to strip the group and select the children
384 SCH_GROUP* group = nullptr;
385
386 if( selection.Size() == 1 )
387 {
388 EDA_ITEM* item = selection.Front();
389
390 if( item->Type() == SCH_GROUP_T )
391 {
392 group = static_cast<SCH_GROUP*>( item );
393
394 selection.Remove( group );
395
396 // Don't recurse; if we have a group of groups the user probably intends the inner groups to be saved
397 group->RunOnChildren( [&]( EDA_ITEM* aItem )
398 {
399 selection.Add( aItem );
400 },
401 RECURSE_MODE::NO_RECURSE );
402 }
403 }
404
405 std::unique_ptr<DESIGN_BLOCK> blk;
406
407 try
408 {
409 blk.reset( Prj().DesignBlockLibs()->DesignBlockLoad( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) );
410 }
411 catch( const IO_ERROR& ioe )
412 {
413 DisplayError( this, ioe.What() );
414 return false;
415 }
416
417 if( !blk->GetSchematicFile().IsEmpty() && !checkOverwriteDbSchematic( this, aLibId ) )
418 return false;
419
420 // Create a temporary screen
421 SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic );
422
423 auto cloneAndAdd =
424 [&] ( EDA_ITEM* aItem ) -> SCH_ITEM*
425 {
426 if( !aItem->IsSCH_ITEM() )
427 return nullptr;
428
429 SCH_ITEM* copy = static_cast<SCH_ITEM*>( aItem->Clone() );
430 tempScreen->Append( static_cast<SCH_ITEM*>( copy ) );
431 return copy;
432 };
433
434 // Copy the selected items to the temporary board
435 for( EDA_ITEM* item : selection )
436 {
437 // Remove parent group membership since we strip the first group layer
438 if( SCH_ITEM* copy = cloneAndAdd( item ) )
439 copy->SetParentGroup( nullptr );
440
441 if( item->Type() == SCH_GROUP_T )
442 {
443 SCH_GROUP* innerGroup = static_cast<SCH_GROUP*>( item );
444
445 // Groups also need their children copied
446 innerGroup->RunOnChildren( cloneAndAdd, RECURSE_MODE::RECURSE );
447 }
448 }
449
450 // Create a sheet for the temporary screen
451 SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic );
452 tempSheet->SetScreen( tempScreen );
453
454 // Save a temporary copy of the schematic file, as the plugin is just going to move it
455 wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
456
457 if( !saveSchematicFile( tempSheet, tempFile ) )
458 {
459 DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
460 wxRemoveFile( tempFile );
461 return false;
462 }
463
464 blk->SetSchematicFile( tempFile );
465
466 bool success = false;
467
468 try
469 {
470 success = Prj().DesignBlockLibs()->DesignBlockSave( aLibId.GetLibNickname(), blk.get() )
472
473 // If we had a group, we need to reselect it
474 if( group )
475 {
476 selection.Clear();
477 selection.Add( group );
478
479 // If we didn't have a design block link before, add one for convenience
480 if( !group->HasDesignBlockLink() )
481 {
482 SCH_COMMIT commit( m_toolManager );
483
484 commit.Modify( group );
485 group->SetDesignBlockLibId( aLibId );
486
487 commit.Push( "Set Group Design Block Link" );
488 }
489 }
490 }
491 catch( const IO_ERROR& ioe )
492 {
493 DisplayError( this, ioe.What() );
494 }
495
496 // Clean up the temporaries
497 wxRemoveFile( tempFile );
498 // This will also delete the screen
499 delete tempSheet;
500
502 m_designBlocksPane->SelectLibId( blk->GetLibId() );
503
504 return success;
505}
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
void SetFields(nlohmann::ordered_map< wxString, wxString > &aFields)
Definition: design_block.h:51
const LIB_ID & GetLibId() const
Definition: design_block.h:37
int ShowModal() override
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:97
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:109
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:431
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:104
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:601
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.