KiCad PCB EDA Suite
Loading...
Searching...
No Matches
eeschema_helpers.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 3
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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "eeschema_helpers.h"
21
22#include <algorithm>
23
24#include <connection_graph.h>
25#include <locale_io.h>
29#include <schematic.h>
30#include <reporter.h>
31#include <sch_commit.h>
32#include <sch_edit_frame.h>
33#include <sch_file_versions.h>
34#include <sch_label.h>
35#include <sch_io/sch_io.h>
36#include <sch_io/sch_io_mgr.h>
39#include <tool/tool_manager.h>
40#include <kiface_base.h>
41
42#include <wx/app.h>
43#include <reporter.h>
44
45
47
48
50{
51 s_SchEditFrame = aSchEditFrame;
52}
53
54
55SCHEMATIC* EESCHEMA_HELPERS::LoadSchematic( const wxString& aFileName, bool aSetActive,
56 bool aForceDefaultProject, PROJECT* aProject,
57 bool aCalculateConnectivity, REPORTER* aRootReporter )
58{
59 if( aFileName.EndsWith( FILEEXT::KiCadSchematicFileExtension ) )
60 return LoadSchematic( aFileName, SCH_IO_MGR::SCH_KICAD, aSetActive, aForceDefaultProject,
61 aProject, aCalculateConnectivity, aRootReporter );
62 else if( aFileName.EndsWith( FILEEXT::LegacySchematicFileExtension ) )
63 return LoadSchematic( aFileName, SCH_IO_MGR::SCH_LEGACY, aSetActive, aForceDefaultProject,
64 aProject, aCalculateConnectivity, aRootReporter );
65
66 // as fall back for any other kind use the legacy format
67 return LoadSchematic( aFileName, SCH_IO_MGR::SCH_LEGACY, aSetActive, aForceDefaultProject, aProject,
68 aCalculateConnectivity, aRootReporter );
69}
70
71
73 const std::vector<TOP_LEVEL_SHEET_INFO>& aProjectSheets, const wxString& aProjectPath,
74 const wxFileName& aFile )
75{
76 for( const TOP_LEVEL_SHEET_INFO& info : aProjectSheets )
77 {
78 wxFileName candidate( aProjectPath, info.filename );
79 candidate.MakeAbsolute();
80
81 if( candidate.SameAs( aFile ) )
82 return &info;
83 }
84
85 return nullptr;
86}
87
88
89SCHEMATIC* EESCHEMA_HELPERS::LoadSchematic( const wxString& aFileName,
90 SCH_IO_MGR::SCH_FILE_T aFormat,
91 bool aSetActive,
92 bool aForceDefaultProject,
93 PROJECT* aProject,
94 bool aCalculateConnectivity, REPORTER* aRootReporter )
95{
96 wxFileName pro = aFileName;
98 pro.MakeAbsolute();
99 wxString projectPath = pro.GetFullPath();
100
101 // Ensure the "C" locale is temporary set, before reading any file
102 // It also avoid wxWidget alerts about locale issues, later, when using Python 3
104
105 PROJECT* project = aProject;
107
108 if( !project )
109 {
110 project = mgr.GetProject( projectPath );
111 }
112
113 if( !aForceDefaultProject )
114 {
115 if( !project )
116 {
117 if( wxFileExists( projectPath ) )
118 {
119 mgr.LoadProject( projectPath, aSetActive );
120 project = mgr.GetProject( projectPath );
121 }
122 }
123 else if( s_SchEditFrame && project == &mgr.Prj() )
124 {
125 // Project is already loaded? Then so is the board
126 return &s_SchEditFrame->Schematic();
127 }
128 }
129
130 // Board cannot be loaded without a project, so create the default project
131 if( !project || aForceDefaultProject )
132 project = &mgr.Prj();
133
134 IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( aFormat ) );
135
136 std::unique_ptr<SCHEMATIC> schematic = std::make_unique<SCHEMATIC>( project );
137 schematic->CreateDefaultScreens();
138
139 wxFileName schFile = aFileName;
140 schFile.MakeAbsolute();
141
142 try
143 {
144 const wxString projectDir = project->GetProjectPath();
145 const std::vector<TOP_LEVEL_SHEET_INFO>& projectSheets =
146 project->GetProjectFile().GetTopLevelSheets();
147 const TOP_LEVEL_SHEET_INFO* namedSheet = findDeclaredTopLevelSheet( projectSheets,
148 projectDir, schFile );
149 std::vector<SCH_SHEET*> loadedSheets;
150 const auto loadSheet = [&]( const wxString& aPath, bool aDeclaredRoot )
151 {
152 SCH_SHEET* sheet = pi->LoadSchematicFile( aPath, schematic.get() );
153
154 if( !sheet || !aRootReporter || aFormat != SCH_IO_MGR::SCH_KICAD
155 || sheet->GetScreen()->GetFileFormatVersionAtLoad() < 20221110 )
156 return sheet;
157
158 const SCH_SCREEN& screen = *sheet->GetScreen();
159 wxString error;
160
161 if( !screen.GetSheetInstances().empty() )
162 {
163 error = wxString::Format( _( "Schematic '%s' has a malformed root sheet instance path." ), aPath );
164 }
165 else if( !sheet->HasRootInstance() && !aDeclaredRoot )
166 {
167 bool parentPlacement = false;
168 bool rootPlacement = false;
169 const auto inspectPlacements = [&]( const auto& instances )
170 {
171 for( const auto& instance : instances )
172 {
173 parentPlacement |= instance.m_Path.size() > 1;
174 rootPlacement |= instance.m_Path.size() == 1
175 && instance.m_Path.front() == screen.GetUuid();
176 }
177 };
178
179 for( SCH_ITEM* item : screen.Items() )
180 {
181 if( item->Type() == SCH_SYMBOL_T )
182 inspectPlacements( static_cast<SCH_SYMBOL*>( item )->GetInstances() );
183 else if( item->Type() == SCH_SHEET_T )
184 inspectPlacements( static_cast<SCH_SHEET*>( item )->GetInstances() );
185 }
186
187 if( parentPlacement && !rootPlacement )
188 error = wxString::Format(
189 _( "Schematic '%s' is a hierarchical subsheet; load its root schematic." ), aPath );
190 }
191
192 if( !error.IsEmpty() )
193 {
194 delete sheet;
195
196 for( SCH_SHEET* loaded : loadedSheets )
197 delete loaded;
198
199 aRootReporter->Report( error, RPT_SEVERITY_ERROR );
200 THROW_IO_ERROR( error );
201 }
202
203 return sheet;
204 };
205
206 // Load every declared top-level sheet so headless callers plot what the GUI does
207 // A missing named file still fails to load rather than falling back to its siblings
208 if( projectSheets.size() > 1 && namedSheet && schFile.FileExists() )
209 {
210 for( const TOP_LEVEL_SHEET_INFO& info : projectSheets )
211 {
212 wxFileName sheetFn( projectDir, info.filename );
213 sheetFn.MakeAbsolute();
214
215 if( !sheetFn.FileExists() )
216 continue;
217
218 SCH_SHEET* sheet = loadSheet( sheetFn.GetFullPath(), true );
219
220 if( !sheet )
221 continue;
222
223 // Sub-sheet instance paths key off this UUID, so only a nil entry keeps the loaded one
224 if( info.uuid != niluuid )
225 const_cast<KIID&>( sheet->m_Uuid ) = info.uuid;
226
227 if( !info.name.IsEmpty() )
228 sheet->SetName( info.name );
229
230 loadedSheets.push_back( sheet );
231 }
232 }
233
234 if( !loadedSheets.empty() )
235 {
236 schematic->SetTopLevelSheets( loadedSheets );
237 }
238 else
239 {
240 SCH_SHEET* rootSheet = loadSheet( schFile.GetFullPath(), namedSheet != nullptr );
241
242 if( !rootSheet )
243 {
244 schematic->SetProject( nullptr );
245 return nullptr;
246 }
247
248 std::vector<SCH_SHEET*> topLevelSheets = schematic->GetTopLevelSheets();
249 bool rootIsTopLevel = std::find( topLevelSheets.begin(), topLevelSheets.end(),
250 rootSheet ) != topLevelSheets.end();
251 bool rootIsVirtualRoot = rootSheet == &schematic->Root()
252 || rootSheet->IsVirtualRootSheet();
253
254 if( !rootIsTopLevel && !rootIsVirtualRoot )
255 schematic->SetTopLevelSheets( { rootSheet } );
256
257 // Make ${SHEETNAME} work on the root sheet until we properly support naming it
258 if( rootSheet->GetName().IsEmpty() )
259 {
260 if( namedSheet && !namedSheet->name.IsEmpty() )
261 rootSheet->SetName( namedSheet->name );
262 else
263 rootSheet->SetName( _( "Root" ) );
264 }
265 }
266 }
267 catch( ... )
268 {
269 schematic->SetProject( nullptr );
270 return nullptr;
271 }
272
273 SCH_SHEET_LIST sheetList = schematic->BuildSheetListSortedByPageNumbers();
274 SCH_SCREENS screens( schematic->Root() );
275
276 if( aFormat == SCH_IO_MGR::SCH_LEGACY )
277 {
278 LIBRARY_MANAGER libraries( project );
280 std::make_unique<SYMBOL_LIBRARY_ADAPTER>( libraries ) );
283 auto* adapter = static_cast<SYMBOL_LIBRARY_ADAPTER*>(
284 libraries.Adapter( LIBRARY_TABLE_TYPE::SYMBOL ).value() );
285 LEGACY_SYMBOL_LIBS legacyLibs;
286 const wxString cache = LEGACY_SYMBOL_LIBS::CacheName( schFile.GetFullPath() );
287
288 if( !cache.IsEmpty() )
289 {
290 LEGACY_SYMBOL_LIB* library = legacyLibs.AddLibrary( cache );
291
292 if( !library )
293 {
294 schematic->SetProject( nullptr );
295 return nullptr;
296 }
297
298 library->SetCache();
299 }
300
301 for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
302 {
303 screen->UpdateSymbolLinks( nullptr, &legacyLibs, adapter );
304
305 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
306 {
307 if( !static_cast<SCH_SYMBOL*>( item )->GetLibSymbolRef() )
308 {
309 schematic->SetProject( nullptr );
310 return nullptr;
311 }
312 }
313 }
314 }
315 else
316 {
317 for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
318 screen->UpdateLocalLibSymbolLinks();
319 }
320
321 if( schematic->RootScreen()->GetFileFormatVersionAtLoad() < 20221002 )
322 sheetList.UpdateSymbolInstanceData( schematic->RootScreen()->GetSymbolInstances());
323
324 sheetList.UpdateSheetInstanceData( schematic->RootScreen()->GetSheetInstances());
325
326 if( schematic->RootScreen()->GetFileFormatVersionAtLoad() < 20230221 )
328
329 for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
330 screen->MigrateSimModels();
331
332 schematic->LoadVariants();
333
334 wxString projectName = project->GetProjectName();
335
336 if( projectName.IsEmpty() )
337 projectName = schFile.GetName();
338
339 // Check must run before pruning so variant data on a stale instance path is migrated
340 // onto the new instance before the orphan is removed.
341 sheetList.CheckForMissingSymbolInstances( projectName );
342 screens.PruneOrphanedSymbolInstances( projectName, sheetList );
343 screens.PruneOrphanedSheetInstances( projectName, sheetList );
344
345 sheetList.AnnotatePowerSymbols();
346
347 if( sheetList.AllSheetPageNumbersEmpty() )
348 sheetList.SetInitialPageNumbers();
349 else
350 sheetList.RepairPageNumbers();
351
352 // A schematic written by an older version can lose the junctions that Eeschema implies from
353 // merged colinear wires, which shows up as connectivity errors. The GUI editor repairs this on
354 // load; the headless/CLI loader must do the same before connectivity is calculated. It is
355 // limited to pre-current files (the current version writes those junctions on save, so running
356 // it on a current file could silently connect an intentional crossing) and to callers that
357 // actually want connectivity.
358 if( aCalculateConnectivity
359 && schematic->RootScreen()->GetFileFormatVersionAtLoad() < SEXPR_SCHEMATIC_FILE_VERSION )
360 schematic->FixupJunctionsAfterImport();
361
362 schematic->ConnectionGraph()->Reset();
363
364 TOOL_MANAGER* toolManager = new TOOL_MANAGER;
365 toolManager->SetEnvironment( schematic.get(), nullptr, nullptr, Kiface().KifaceSettings(), nullptr );
366
367 if( aCalculateConnectivity )
368 {
369 SCH_COMMIT dummyCommit( toolManager );
370 schematic->CleanUpConnections( &dummyCommit, GLOBAL_CLEANUP );
371 dummyCommit.Push( _( "Schematic Cleanup" ), SKIP_UNDO | SKIP_CONNECTIVITY | DELETE_REMOVED_ITEMS );
372 }
373
374 schematic->SetSheetNumberAndCount();
375 schematic->RecomputeIntersheetRefs();
376
377 for( SCH_SHEET_PATH& sheet : sheetList )
378 {
379 sheet.UpdateAllScreenReferences();
380 sheet.LastScreen()->TestDanglingEnds( nullptr, nullptr );
381 }
382
383 if( aCalculateConnectivity )
384 schematic->RebuildConnectivity();
385
386 schematic->ResolveERCExclusionsPostUpdate();
387
388 return schematic.release();
389}
KIFACE_BASE & Kiface()
Global KIFACE_BASE "get" accessor.
const KIID m_Uuid
Definition eda_item.h:597
static void SetSchEditFrame(SCH_EDIT_FRAME *aSchEditFrame)
static SCH_EDIT_FRAME * s_SchEditFrame
static SCHEMATIC * LoadSchematic(const wxString &aFileName, bool aSetActive, bool aForceDefaultProject, PROJECT *aProject=nullptr, bool aCalculateConnectivity=true, REPORTER *aRootReporter=nullptr)
Definition kiid.h:46
A collection of #SYMBOL_LIB objects.
LEGACY_SYMBOL_LIB * AddLibrary(const wxString &aFileName)
Allocate and adds a symbol library to the library list.
static const wxString CacheName(const wxString &aFullProjectFilename)
Return the name of the cache library after potentially fixing it from an older naming scheme.
Object used to load, save, search, and otherwise manipulate symbol library files.
std::optional< LIBRARY_MANAGER_ADAPTER * > Adapter(LIBRARY_TABLE_TYPE aType) const
void RegisterAdapter(LIBRARY_TABLE_TYPE aType, std::unique_ptr< LIBRARY_MANAGER_ADAPTER > &&aAdapter)
void LoadProjectTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the project library tables in the given list, or all tables if no list is given
void LoadGlobalTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the global library tables in the given list, or all tables if no list is given
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
Container for project specific data.
Definition project.h:63
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
Holds all the data relating to one schematic.
Definition schematic.h:148
virtual void Push(const wxString &aMessage=wxT("A commit"), int aCommitFlags=0) override
Execute the changes.
Schematic editor (Eeschema) main window.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
Container class that holds multiple SCH_SCREEN objects in a hierarchy.
Definition sch_screen.h:758
SCH_SCREEN * GetNext()
void FixLegacyPowerSymbolMismatches()
Fix legacy power symbols that have mismatched value text fields and invisible power pin names.
SCH_SCREEN * GetFirst()
void PruneOrphanedSheetInstances(const wxString &aProjectName, const SCH_SHEET_LIST &aValidSheetPaths)
void PruneOrphanedSymbolInstances(const wxString &aProjectName, const SCH_SHEET_LIST &aValidSheetPaths)
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:118
const KIID & GetUuid() const
Definition sch_screen.h:540
int GetFileFormatVersionAtLoad() const
Definition sch_screen.h:138
const std::vector< SCH_SHEET_INSTANCE > & GetSheetInstances() const
Definition sch_screen.h:535
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
void UpdateSheetInstanceData(const std::vector< SCH_SHEET_INSTANCE > &aSheetInstances)
Update all of the sheet instance information using aSheetInstances.
void SetInitialPageNumbers()
Set initial sheet page numbers.
bool AllSheetPageNumbersEmpty() const
Check all of the sheet instance for empty page numbers.
void AnnotatePowerSymbols()
Silently annotate the not yet annotated power symbols of the entire hierarchy of the sheet path list.
void UpdateSymbolInstanceData(const std::vector< SCH_SYMBOL_INSTANCE > &aSymbolInstances)
Update all of the symbol instance information using aSymbolInstances.
bool RepairPageNumbers()
Assign valid page numbers to sheet paths whose stored page number is missing or collides with an earl...
void CheckForMissingSymbolInstances(const wxString &aProjectName)
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
bool HasRootInstance() const
Check to see if this sheet has a root sheet instance.
wxString GetName() const
Definition sch_sheet.h:142
void SetName(const wxString &aName)
Definition sch_sheet.h:143
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
bool IsVirtualRootSheet() const
Schematic symbol object.
Definition sch_symbol.h:75
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT * GetProject(const wxString &aFullPath) const
Retrieve a loaded project by name.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
Master controller class:
void SetEnvironment(EDA_ITEM *aModel, KIGFX::VIEW *aView, KIGFX::VIEW_CONTROLS *aViewControls, APP_SETTINGS_BASE *aSettings, TOOLS_HOLDER *aFrame)
Set the work environment (model, view, view controls and the parent window).
#define _(s)
static const TOP_LEVEL_SHEET_INFO * findDeclaredTopLevelSheet(const std::vector< TOP_LEVEL_SHEET_INFO > &aProjectSheets, const wxString &aProjectPath, const wxFileName &aFile)
static const std::string LegacySchematicFileExtension
static const std::string ProjectFileExtension
static const std::string KiCadSchematicFileExtension
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
KIID niluuid(0)
PGM_BASE & Pgm()
The global program "get" accessor.
@ RPT_SEVERITY_ERROR
#define SKIP_CONNECTIVITY
Definition sch_commit.h:41
#define DELETE_REMOVED_ITEMS
Definition sch_commit.h:43
#define SKIP_UNDO
Definition sch_commit.h:38
#define SEXPR_SCHEMATIC_FILE_VERSION
Schematic file version.
@ GLOBAL_CLEANUP
Definition schematic.h:94
std::vector< FAB_LAYER_COLOR > dummy
Information about a top-level schematic sheet.
wxString name
Display name for the sheet.
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_SHEET_T
Definition typeinfo.h:171
Definition of file extensions used in Kicad.