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