1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2020-2021 KiCad Developers, see AUTHORS.txt for contributors.
5  *
6  * This program is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <bus_alias.h>
21 #include <connection_graph.h>
22 #include <erc_settings.h>
23 #include <sch_marker.h>
24 #include <project.h>
25 #include <project/project_file.h>
26 #include <project/net_settings.h>
27 #include <schematic.h>
28 #include <sch_screen.h>
29 #include <sim/spice_settings.h>
30 
31 
SCHEMATIC(PROJECT * aPrj)32 SCHEMATIC::SCHEMATIC( PROJECT* aPrj ) :
33           EDA_ITEM( nullptr, SCHEMATIC_T ),
34           m_project( nullptr ),
35           m_rootSheet( nullptr )
36 {
37     m_currentSheet    = new SCH_SHEET_PATH();
38     m_connectionGraph = new CONNECTION_GRAPH( this );
39 
40     SetProject( aPrj );
41 }
42 
43 
~SCHEMATIC()44 SCHEMATIC::~SCHEMATIC()
45 {
46     delete m_currentSheet;
47     delete m_connectionGraph;
48 }
49 
50 
Reset()51 void SCHEMATIC::Reset()
52 {
53     // Assume project already saved
54     if( m_project )
55     {
56         PROJECT_FILE& project = m_project->GetProjectFile();
57 
58         delete project.m_ErcSettings;
59         delete project.m_SchematicSettings;
60 
61         project.m_ErcSettings       = nullptr;
62         project.m_SchematicSettings = nullptr;
63 
64         m_project = nullptr; // clear the project, so we don't do this again when setting a new one
65     }
66 
67     delete m_rootSheet;
68 
69     m_rootSheet = nullptr;
70 
71     m_connectionGraph->Reset();
72     m_currentSheet->clear();
73 }
74 
75 
SetProject(PROJECT * aPrj)76 void SCHEMATIC::SetProject( PROJECT* aPrj )
77 {
78     if( m_project )
79     {
80         PROJECT_FILE& project = m_project->GetProjectFile();
81 
82         delete project.m_ErcSettings;
83         delete project.m_SchematicSettings;
84 
85         project.m_ErcSettings       = nullptr;
86         project.m_SchematicSettings = nullptr;
87     }
88 
89     m_project = aPrj;
90 
91     if( m_project )
92     {
93         PROJECT_FILE& project       = m_project->GetProjectFile();
94         project.m_ErcSettings       = new ERC_SETTINGS( &project, "erc" );
95         project.m_SchematicSettings = new SCHEMATIC_SETTINGS( &project, "schematic" );
96 
97         project.m_SchematicSettings->LoadFromFile();
98         project.m_SchematicSettings->m_NgspiceSimulatorSettings->LoadFromFile();
99         project.m_ErcSettings->LoadFromFile();
100     }
101 }
102 
103 
SetRoot(SCH_SHEET * aRootSheet)104 void SCHEMATIC::SetRoot( SCH_SHEET* aRootSheet )
105 {
106     wxCHECK_RET( aRootSheet, "Call to SetRoot with null SCH_SHEET!" );
107 
108     m_rootSheet = aRootSheet;
109 
110     m_currentSheet->clear();
111     m_currentSheet->push_back( m_rootSheet );
112 
113     m_connectionGraph->Reset();
114 }
115 
116 
RootScreen() const117 SCH_SCREEN* SCHEMATIC::RootScreen() const
118 {
119     return IsValid() ? m_rootSheet->GetScreen() : nullptr;
120 }
121 
122 
ResolveTextVar(wxString * token,int aDepth) const123 bool SCHEMATIC::ResolveTextVar( wxString* token, int aDepth ) const
124 {
125     if( !CurrentSheet().empty() )
126     {
127         if( token->IsSameAs( wxT( "#" ) ) )
128         {
129             *token = CurrentSheet().GetPageNumber();
130             return true;
131         }
132         else if( token->IsSameAs( wxT( "##" ) ) )
133         {
134             *token = wxString::Format( "%i", Root().CountSheets() );
135             return true;
136         }
137         else if( token->IsSameAs( wxT( "SHEETNAME" ) ) )
138         {
139             *token = CurrentSheet().PathHumanReadable();
140             return true;
141         }
142         else if( token->IsSameAs( wxT( "FILENAME" ) ) )
143         {
144             wxFileName fn( GetFileName() );
145             *token = fn.GetFullName();
146             return true;
147         }
148         else if( token->IsSameAs( wxT( "PROJECTNAME" ) ) )
149         {
150             *token = Prj().GetProjectName();
151             return true;
152         }
153 
154         return CurrentSheet().LastScreen()->GetTitleBlock().TextVarResolver( token, m_project );
155     }
156 
157     return false;
158 }
159 
160 
GetFileName() const161 wxString SCHEMATIC::GetFileName() const
162 {
163     return IsValid() ? m_rootSheet->GetScreen()->GetFileName() : wxString( wxEmptyString );
164 }
165 
166 
Settings() const167 SCHEMATIC_SETTINGS& SCHEMATIC::Settings() const
168 {
169     wxASSERT( m_project );
170     return *m_project->GetProjectFile().m_SchematicSettings;
171 }
172 
173 
ErcSettings() const174 ERC_SETTINGS& SCHEMATIC::ErcSettings() const
175 {
176     wxASSERT( m_project );
177     return *m_project->GetProjectFile().m_ErcSettings;
178 }
179 
180 
ResolveERCExclusions()181 std::vector<SCH_MARKER*> SCHEMATIC::ResolveERCExclusions()
182 {
183     SCH_SHEET_LIST sheetList = GetSheets();
184     ERC_SETTINGS&  settings  = ErcSettings();
185 
186     for( const SCH_SHEET_PATH& sheet : sheetList )
187     {
188         for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_MARKER_T ) )
189         {
190             SCH_MARKER* marker = static_cast<SCH_MARKER*>( item );
191             auto        it = settings.m_ErcExclusions.find( marker->Serialize() );
192 
193             if( it != settings.m_ErcExclusions.end() )
194             {
195                 marker->SetExcluded( true );
196                 settings.m_ErcExclusions.erase( it );
197             }
198         }
199     }
200 
201     std::vector<SCH_MARKER*> newMarkers;
202 
203     for( const wxString& exclusionData : settings.m_ErcExclusions )
204     {
205         SCH_MARKER* marker = SCH_MARKER::Deserialize( exclusionData );
206 
207         if( marker )
208         {
209             marker->SetExcluded( true );
210             newMarkers.push_back( marker );
211         }
212     }
213 
214     settings.m_ErcExclusions.clear();
215 
216     return newMarkers;
217 }
218 
219 
GetBusAlias(const wxString & aLabel) const220 std::shared_ptr<BUS_ALIAS> SCHEMATIC::GetBusAlias( const wxString& aLabel ) const
221 {
222     for( const auto& sheet : GetSheets() )
223     {
224         for( const auto& alias : sheet.LastScreen()->GetBusAliases() )
225         {
226             if( alias->GetName() == aLabel )
227                 return alias;
228         }
229     }
230 
231     return nullptr;
232 }
233 
234 
GetNetClassAssignmentCandidates()235 std::vector<wxString> SCHEMATIC::GetNetClassAssignmentCandidates()
236 {
237     std::vector<wxString> names;
238 
239     // Key is a NET_NAME_CODE aka std::pair<name, code>
240     for( const NET_MAP::value_type& pair: m_connectionGraph->GetNetMap() )
241     {
242         CONNECTION_SUBGRAPH* subgraph = pair.second[0];
243 
244         if( !subgraph->m_driver_connection->IsBus()
245                 && subgraph->GetDriverPriority() >= CONNECTION_SUBGRAPH::PRIORITY::PIN )
246         {
247             names.emplace_back( pair.first.first );
248         }
249     }
250 
251     return names;
252 }
253 
254 
ResolveCrossReference(wxString * token,int aDepth) const255 bool SCHEMATIC::ResolveCrossReference( wxString* token, int aDepth ) const
256 {
257     SCH_SHEET_LIST sheetList = GetSheets();
258     wxString       remainder;
259     wxString       ref = token->BeforeFirst( ':', &remainder );
260     SCH_SHEET_PATH sheetPath;
261     SCH_ITEM*      refItem = sheetList.GetItem( KIID( ref ), &sheetPath );
262 
263     if( refItem && refItem->Type() == SCH_SYMBOL_T )
264     {
265         SCH_SYMBOL* refSymbol = static_cast<SCH_SYMBOL*>( refItem );
266 
267         if( refSymbol->ResolveTextVar( &remainder, aDepth + 1 ) )
268             *token = remainder;
269         else
270             *token = refSymbol->GetRef( &sheetPath, true ) + ":" + remainder;
271 
272         return true;    // Cross-reference is resolved whether or not the actual textvar was
273     }
274     else if( refItem && refItem->Type() == SCH_SHEET_T )
275     {
276         SCH_SHEET* refSheet = static_cast<SCH_SHEET*>( refItem );
277 
278         if( refSheet->ResolveTextVar( &remainder, aDepth + 1 ) )
279             *token = remainder;
280 
281         return true;    // Cross-reference is resolved whether or not the actual textvar was
282     }
283 
284     return false;
285 }
286 
287 
ConvertRefsToKIIDs(const wxString & aSource) const288 wxString SCHEMATIC::ConvertRefsToKIIDs( const wxString& aSource ) const
289 {
290     wxString newbuf;
291     size_t   sourceLen = aSource.length();
292 
293     for( size_t i = 0; i < sourceLen; ++i )
294     {
295         if( aSource[i] == '$' && i + 1 < sourceLen && aSource[i+1] == '{' )
296         {
297             wxString token;
298             bool     isCrossRef = false;
299 
300             for( i = i + 2; i < sourceLen; ++i )
301             {
302                 if( aSource[i] == '}' )
303                     break;
304 
305                 if( aSource[i] == ':' )
306                     isCrossRef = true;
307 
308                 token.append( aSource[i] );
309             }
310 
311             if( isCrossRef )
312             {
313                 SCH_SHEET_LIST     sheetList = GetSheets();
314                 wxString           remainder;
315                 wxString           ref = token.BeforeFirst( ':', &remainder );
316                 SCH_REFERENCE_LIST references;
317 
318                 sheetList.GetSymbols( references );
319 
320                 for( size_t jj = 0; jj < references.GetCount(); jj++ )
321                 {
322                     SCH_SYMBOL* refSymbol = references[ jj ].GetSymbol();
323 
324                     if( ref == refSymbol->GetRef( &references[ jj ].GetSheetPath(), true ) )
325                     {
326                         token = refSymbol->m_Uuid.AsString() + ":" + remainder;
327                         break;
328                     }
329                 }
330             }
331 
332             newbuf.append( "${" + token + "}" );
333         }
334         else
335         {
336             newbuf.append( aSource[i] );
337         }
338     }
339 
340     return newbuf;
341 }
342 
343 
ConvertKIIDsToRefs(const wxString & aSource) const344 wxString SCHEMATIC::ConvertKIIDsToRefs( const wxString& aSource ) const
345 {
346     wxString newbuf;
347     size_t   sourceLen = aSource.length();
348 
349     for( size_t i = 0; i < sourceLen; ++i )
350     {
351         if( aSource[i] == '$' && i + 1 < sourceLen && aSource[i+1] == '{' )
352         {
353             wxString token;
354             bool     isCrossRef = false;
355 
356             for( i = i + 2; i < sourceLen; ++i )
357             {
358                 if( aSource[i] == '}' )
359                     break;
360 
361                 if( aSource[i] == ':' )
362                     isCrossRef = true;
363 
364                 token.append( aSource[i] );
365             }
366 
367             if( isCrossRef )
368             {
369                 SCH_SHEET_LIST sheetList = GetSheets();
370                 wxString       remainder;
371                 wxString       ref = token.BeforeFirst( ':', &remainder );
372 
373                 SCH_SHEET_PATH refSheetPath;
374                 SCH_ITEM*      refItem = sheetList.GetItem( KIID( ref ), &refSheetPath );
375 
376                 if( refItem && refItem->Type() == SCH_SYMBOL_T )
377                 {
378                     SCH_SYMBOL* refSymbol = static_cast<SCH_SYMBOL*>( refItem );
379                     token = refSymbol->GetRef( &refSheetPath, true ) + ":" + remainder;
380                 }
381             }
382 
383             newbuf.append( "${" + token + "}" );
384         }
385         else
386         {
387             newbuf.append( aSource[i] );
388         }
389     }
390 
391     return newbuf;
392 }
393 
394 
GetFullHierarchy() const395 SCH_SHEET_LIST& SCHEMATIC::GetFullHierarchy() const
396 {
397     static SCH_SHEET_LIST hierarchy;
398 
399     hierarchy.clear();
400     hierarchy.BuildSheetList( m_rootSheet, false );
401 
402     return hierarchy;
403 }
404