KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_altium_sch_import.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 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
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
22
23#include <lib_id.h>
24#include <schematic.h>
26#include <sch_label.h>
27#include <sch_line.h>
28#include <sch_screen.h>
29#include <sch_sheet.h>
30#include <sch_sheet_path.h>
31#include <sch_symbol.h>
33
34#include <map>
35#include <optional>
36#include <set>
37#include <vector>
38
39
40namespace
41{
42
43struct ALTIUM_SCH_IMPORT_FIXTURE
44{
45 ALTIUM_SCH_IMPORT_FIXTURE() : m_schematic( nullptr )
46 {
47 m_settingsManager.LoadProject( "" );
48 m_schematic.SetProject( &m_settingsManager.Prj() );
49 m_schematic.Reset();
50 }
51
52 ~ALTIUM_SCH_IMPORT_FIXTURE() { m_schematic.Reset(); }
53
54 wxString dataFile( const wxString& aName ) const
55 {
56 return wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir()
57 + "/plugins/altium/issue22943/" )
58 + aName;
59 }
60
61 wxString issue24861DataFile( const wxString& aName ) const
62 {
63 return wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir()
64 + "/plugins/altium/issue24861/" )
65 + aName;
66 }
67
68 wxString ticket1303DataFile( const wxString& aName ) const
69 {
70 return wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir()
71 + "/plugins/altium/ticket1303/" )
72 + aName;
73 }
74
75 wxString eDPAdapterDataFile( const wxString& aName ) const
76 {
77 return wxString::FromUTF8( KI_TEST::GetTestDataRootDir()
78 + "pcbnew/plugins/altium/eDP_adapter_dvt1_source/" )
79 + aName;
80 }
81
82 SETTINGS_MANAGER m_settingsManager;
83 SCHEMATIC m_schematic;
84};
85
86} // namespace
87
88
89BOOST_FIXTURE_TEST_SUITE( AltiumSchImport, ALTIUM_SCH_IMPORT_FIXTURE )
90
91
92// https://gitlab.com/kicad/code/kicad/-/issues/22943
93// A component placed from an external Altium library must be addressed by a well-formed library
94// id (source library nickname + real library reference) so it resolves against the library that
95// project import registers, instead of the importer's internal per-placement name.
96BOOST_AUTO_TEST_CASE( Issue22943_SourceLibrarySymbolLibId )
97{
98 SCH_IO_ALTIUM plugin;
99
100 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( dataFile( "1_cover.SchDoc" ), &m_schematic );
101 BOOST_REQUIRE( rootSheet );
102 BOOST_REQUIRE( rootSheet->GetScreen() );
103
104 // The real reference names of every symbol contained in the source library.
105 wxArrayString libNames;
106 plugin.EnumerateSymbolLib( libNames, dataFile( "mounting_holes.SchLib" ) );
107
108 std::set<wxString> libSymbolNames( libNames.begin(), libNames.end() );
109 BOOST_REQUIRE( libSymbolNames.count( wxT( "MH M3" ) ) );
110
111 std::vector<SCH_SYMBOL*> fromMountingHoles;
112
113 for( SCH_ITEM* item : rootSheet->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
114 {
115 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
116
117 if( sym->GetLibId().GetUniStringLibNickname() == wxT( "mounting_holes" ) )
118 fromMountingHoles.push_back( sym );
119 }
120
121 BOOST_REQUIRE( !fromMountingHoles.empty() );
122
123 // Every symbol drawn from the source library must name an item that actually exists there.
124 for( SCH_SYMBOL* sym : fromMountingHoles )
125 {
126 const LIB_ID& libId = sym->GetLibId();
127 BOOST_CHECK( libId.IsValid() );
128 BOOST_CHECK_MESSAGE( libSymbolNames.count( libId.GetUniStringLibItemName() ),
129 "Library id '" << libId.Format().wx_str()
130 << "' does not resolve in mounting_holes.SchLib" );
131 }
132}
133
134
135// https://gitlab.com/kicad/code/kicad/-/issues/24861
136BOOST_AUTO_TEST_CASE( Issue24861_RepeatedSchematicChannels )
137{
138 SCH_IO_ALTIUM plugin;
139
140 std::map<std::string, UTF8> properties;
141 properties.emplace( "project_file", UTF8( issue24861DataFile( wxT( "Repeated_Schematic.PrjPcb" ) ) ) );
142 properties.emplace( "sch0", UTF8( issue24861DataFile( wxT( "Repeated_Schematic.SchDoc" ) ) ) );
143 properties.emplace( "sch1", UTF8( issue24861DataFile( wxT( "Channel.SchDoc" ) ) ) );
144
145 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( wxEmptyString, &m_schematic, nullptr, &properties );
146 BOOST_REQUIRE( rootSheet );
147
148 const std::vector<SCH_SHEET*> topLevelSheets = m_schematic.GetTopLevelSheets();
149 BOOST_REQUIRE_EQUAL( topLevelSheets.size(), 1 );
150 BOOST_CHECK_EQUAL( topLevelSheets.front()->GetName(), wxT( "Repeated_Schematic" ) );
151
152 std::optional<SCH_SHEET_PATH> topLevelPath;
153 std::map<wxString, SCH_SHEET_PATH> channelPaths;
154
155 for( const SCH_SHEET_PATH& sheetPath : m_schematic.Hierarchy() )
156 {
157 SCH_SHEET* sheet = sheetPath.Last();
158
159 if( sheet && sheet->GetName() == wxT( "Repeated_Schematic" ) )
160 topLevelPath = sheetPath;
161 else if( sheet && sheet->GetName().StartsWith( wxT( "CH" ) ) )
162 channelPaths.emplace( sheet->GetName(), sheetPath );
163 }
164
165 BOOST_REQUIRE( topLevelPath );
166 BOOST_CHECK_EQUAL( topLevelPath->GetPageNumber(), wxT( "1" ) );
167
168 BOOST_REQUIRE_EQUAL( channelPaths.size(), 3 );
169 BOOST_CHECK_EQUAL( channelPaths.at( wxT( "CH1" ) ).GetPageNumber(), wxT( "2" ) );
170 BOOST_CHECK_EQUAL( channelPaths.at( wxT( "CH2" ) ).GetPageNumber(), wxT( "3" ) );
171 BOOST_CHECK_EQUAL( channelPaths.at( wxT( "CH3" ) ).GetPageNumber(), wxT( "4" ) );
172
173 std::set<wxString> ledReferences;
174 std::set<wxString> resistorReferences;
175
176 for( const auto& [channelName, sheetPath] : channelPaths )
177 {
178 for( SCH_ITEM* item : sheetPath.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
179 {
180 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
181 wxString ref = symbol->GetRef( &sheetPath );
182
183 if( ref.StartsWith( wxT( "LED" ) ) )
184 ledReferences.insert( ref );
185 else if( ref.StartsWith( wxT( "R" ) ) )
186 resistorReferences.insert( ref );
187 }
188 }
189
190 BOOST_CHECK( ledReferences == std::set<wxString>( { wxT( "LED1_CH1" ), wxT( "LED1_CH2" ),
191 wxT( "LED1_CH3" ) } ) );
192 BOOST_CHECK( resistorReferences == std::set<wxString>( { wxT( "R1_CH1" ), wxT( "R1_CH2" ),
193 wxT( "R1_CH3" ) } ) );
194}
195
196
197// Support ticket #1303: an OrCad-derived Altium sheet symbol references several source files
198// through a single semicolon-separated filename ("pagea.SchDoc;pageb.SchDoc"), the two pages of one
199// multi-page block. The pages cross-reference each other (and one references itself). Every page
200// must be merged into the one sub-sheet screen, and the cyclic cross-references must not create a
201// recursive hierarchy that trips SCH_SHEET_LIST::BuildSheetList.
202BOOST_AUTO_TEST_CASE( Ticket1303_MultiPageBlock )
203{
204 SCH_IO_ALTIUM plugin;
205
206 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( ticket1303DataFile( wxT( "overview.SchDoc" ) ),
207 &m_schematic, nullptr, nullptr );
208 BOOST_REQUIRE( rootSheet );
209 BOOST_REQUIRE( rootSheet->GetScreen() );
210
211 // Walking the hierarchy must not trip the recursion guard in SCH_SHEET_LIST::BuildSheetList.
212 // Before the fix this raised a wxASSERT (the pages resolved to sub-sheets whose remapped
213 // filenames collided in the ancestry).
214 SCH_SHEET_LIST hierarchy = m_schematic.Hierarchy();
215
216 // Both pages of the block land in a single merged sub-sheet screen; a regression drops one (or
217 // both), leaving the sheet empty. The screen also holds no residual sub-sheet symbols, since the
218 // cross-page references were pruned rather than descended into.
219 bool foundMergedScreen = false;
220
221 for( const SCH_SHEET_PATH& sheetPath : hierarchy )
222 {
223 SCH_SCREEN* screen = sheetPath.LastScreen();
224
225 if( !screen || screen == rootSheet->GetScreen() )
226 continue;
227
228 std::optional<VECTOR2I> posA;
229 std::optional<VECTOR2I> posB;
230
231 for( SCH_ITEM* item : screen->Items().OfType( SCH_LABEL_T ) )
232 {
233 SCH_LABEL* label = static_cast<SCH_LABEL*>( item );
234
235 if( label->GetText() == wxT( "ONLY_A" ) )
236 posA = label->GetPosition();
237 else if( label->GetText() == wxT( "ONLY_B" ) )
238 posB = label->GetPosition();
239 }
240
241 if( !posA || !posB )
242 continue;
243
244 foundMergedScreen = true;
245
246 // The cross-page references were pruned, not descended into: the merged screen holds no
247 // residual sub-sheet symbols.
248 int subSheetCount = 0;
249
250 for( SCH_ITEM* item : screen->Items().OfType( SCH_SHEET_T ) )
251 {
252 (void) item;
253 subSheetCount++;
254 }
255
256 BOOST_CHECK_EQUAL( subSheetCount, 0 );
257
258 // Page B is tiled below page A rather than superimposed, even though both labels sit at the
259 // same coordinates in their source files.
260 BOOST_CHECK_MESSAGE( *posA != *posB, "Merged pages must be tiled, not overlapping" );
261
262 // The pruned cross-reference sheet's pin was converted to a hierarchical label so the wire
263 // that terminated on it still connects by name.
264 bool foundSigLabel = false;
265
266 for( SCH_ITEM* item : screen->Items().OfType( SCH_HIER_LABEL_T ) )
267 {
268 if( static_cast<SCH_HIERLABEL*>( item )->GetText() == wxT( "SIG" ) )
269 foundSigLabel = true;
270 }
271
272 BOOST_CHECK_MESSAGE( foundSigLabel,
273 "Pruned sheet pin must survive as a hierarchical label" );
274 }
275
276 BOOST_CHECK_MESSAGE( foundMergedScreen,
277 "Both pages of the multi-file sheet symbol must load into one screen" );
278}
279
280
281// https://gitlab.com/kicad/code/kicad/-/issues/24843
282// Altium encodes symbol rotation as quarter turns that map one-for-one onto KiCad's
283// SYM_ORIENT_* angles. The importer must store the same angle Altium shows so a later
284// "Update Symbols from Library" against a canonical upright symbol does not rotate the
285// placement. The reference symbols below are non-mirrored so the stored angle reads back
286// directly, without GetOrientation()'s mirror normalization.
287BOOST_AUTO_TEST_CASE( Issue24843_SymbolOrientationMatchesAltium )
288{
289 SCH_IO_ALTIUM plugin;
290
291 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( eDPAdapterDataFile( "power.SchDoc" ),
292 &m_schematic );
293 BOOST_REQUIRE( rootSheet );
294
295 m_schematic.RefreshHierarchy();
296
297 // Reference designator -> Altium orientation angle read straight from the source records.
298 const std::map<wxString, SYMBOL_ORIENTATION_PROP> expected = {
299 { wxT( "L10P" ), SYMBOL_ORIENTATION_PROP::SYMBOL_ANGLE_0 }, // Altium ORIENTATION 0
300 { wxT( "C10P" ), SYMBOL_ORIENTATION_PROP::SYMBOL_ANGLE_90 }, // Altium ORIENTATION 1
301 { wxT( "R10P" ), SYMBOL_ORIENTATION_PROP::SYMBOL_ANGLE_180 }, // Altium ORIENTATION 2
302 { wxT( "C13P" ), SYMBOL_ORIENTATION_PROP::SYMBOL_ANGLE_270 }, // Altium ORIENTATION 3
303 };
304
305 std::map<wxString, SYMBOL_ORIENTATION_PROP> actual;
306
307 for( const SCH_SHEET_PATH& sheetPath : m_schematic.Hierarchy() )
308 {
309 for( SCH_ITEM* item : sheetPath.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
310 {
311 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
312 wxString ref = symbol->GetRef( &sheetPath );
313
314 if( expected.count( ref ) )
315 actual[ref] = symbol->GetOrientationProp();
316 }
317 }
318
319 for( const auto& [ref, angle] : expected )
320 {
321 BOOST_REQUIRE_MESSAGE( actual.count( ref ), "Symbol '" << ref << "' not found on import" );
322 BOOST_CHECK_MESSAGE( actual.at( ref ) == angle,
323 "Symbol '" << ref << "' imported with orientation " << actual.at( ref )
324 << ", expected " << angle );
325 }
326}
327
328
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:110
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:221
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
bool IsValid() const
Check if this LID_ID is valid.
Definition lib_id.h:168
UTF8 Format() const
Definition lib_id.cpp:132
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
Definition lib_id.h:108
const wxString GetUniStringLibNickname() const
Definition lib_id.h:84
SCH_SHEET * LoadSchematicFile(const wxString &aFileName, SCHEMATIC *aSchematic, SCH_SHEET *aAppendToMe=nullptr, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load information from some input file format that this SCH_IO implementation knows about,...
void EnumerateSymbolLib(wxArrayString &aSymbolNameList, const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Populate a list of LIB_SYMBOL alias names contained within the library aLibraryPath.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:162
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:115
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
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:44
wxString GetName() const
Definition sch_sheet.h:136
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:139
Schematic symbol object.
Definition sch_symbol.h:69
SYMBOL_ORIENTATION_PROP GetOrientationProp() const
Definition sch_symbol.h:298
const LIB_ID & GetLibId() const override
Definition sch_symbol.h:158
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
VECTOR2I GetPosition() const override
Definition sch_text.h:146
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition utf8.h:67
wxString wx_str() const
Definition utf8.cpp:41
std::string GetTestDataRootDir()
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
@ SYMBOL_ANGLE_180
Definition symbol.h:50
@ SYMBOL_ANGLE_0
Definition symbol.h:48
@ SYMBOL_ANGLE_90
Definition symbol.h:49
@ SYMBOL_ANGLE_270
Definition symbol.h:51
BOOST_AUTO_TEST_CASE(Issue22943_SourceLibrarySymbolLibId)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
int actual
BOOST_CHECK_EQUAL(result, "25.4")
@ SCH_SYMBOL_T
Definition typeinfo.h:169
@ SCH_LABEL_T
Definition typeinfo.h:164
@ SCH_SHEET_T
Definition typeinfo.h:172
@ SCH_HIER_LABEL_T
Definition typeinfo.h:166