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 <connection_graph.h>
24#include <erc/erc.h>
25#include <lib_id.h>
26#include <reporter.h>
27#include <schematic.h>
29#include <sch_label.h>
30#include <sch_no_connect.h>
31#include <sch_line.h>
32#include <sch_screen.h>
33#include <sch_sheet.h>
34#include <sch_sheet_path.h>
35#include <sch_symbol.h>
37
38#include <map>
39#include <optional>
40#include <set>
41#include <vector>
42
43
44namespace
45{
46
47struct ALTIUM_SCH_IMPORT_FIXTURE
48{
49 ALTIUM_SCH_IMPORT_FIXTURE() :
50 m_loadInfoScope( &NULL_REPORTER::GetInstance() ),
51 m_schematic( nullptr )
52 {
53 m_settingsManager.LoadProject( "" );
54 m_schematic.SetProject( &m_settingsManager.Prj() );
55 m_schematic.Reset();
56 }
57
58 ~ALTIUM_SCH_IMPORT_FIXTURE() { m_schematic.Reset(); }
59
60 wxString dataFile( const wxString& aName ) const
61 {
62 return wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir()
63 + "/plugins/altium/issue22943/" )
64 + aName;
65 }
66
67 wxString issue24861DataFile( const wxString& aName ) const
68 {
69 return wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir()
70 + "/plugins/altium/issue24861/" )
71 + aName;
72 }
73
74 wxString issue22908DataFile( const wxString& aName ) const
75 {
76 return wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir()
77 + "/plugins/altium/issue22908/" )
78 + aName;
79 }
80
81 wxString ticket1303DataFile( const wxString& aName ) const
82 {
83 return wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir()
84 + "/plugins/altium/ticket1303/" )
85 + aName;
86 }
87
88 wxString issue16903DataFile( const wxString& aName ) const
89 {
90 return wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir()
91 + "/plugins/altium/issue16903/" )
92 + aName;
93 }
94
95 wxString eDPAdapterDataFile( const wxString& aName ) const
96 {
97 return wxString::FromUTF8( KI_TEST::GetTestDataRootDir()
98 + "pcbnew/plugins/altium/eDP_adapter_dvt1_source/" )
99 + aName;
100 }
101
102 // The fixture project has no symbol libraries, so every symbol link fails by design
103 LOAD_INFO_REPORTER_SCOPE m_loadInfoScope;
104 SETTINGS_MANAGER m_settingsManager;
105 SCHEMATIC m_schematic;
106};
107
108} // namespace
109
110
111BOOST_FIXTURE_TEST_SUITE( AltiumSchImport, ALTIUM_SCH_IMPORT_FIXTURE )
112
113
114// https://gitlab.com/kicad/code/kicad/-/issues/22943
115// A component placed from an external Altium library must be addressed by a well-formed library
116// id (source library nickname + real library reference) so it resolves against the library that
117// project import registers, instead of the importer's internal per-placement name.
118BOOST_AUTO_TEST_CASE( Issue22943_SourceLibrarySymbolLibId )
119{
120 SCH_IO_ALTIUM plugin;
121
122 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( dataFile( "1_cover.SchDoc" ), &m_schematic );
123 BOOST_REQUIRE( rootSheet );
124 BOOST_REQUIRE( rootSheet->GetScreen() );
125
126 // The real reference names of every symbol contained in the source library.
127 wxArrayString libNames;
128 plugin.EnumerateSymbolLib( libNames, dataFile( "mounting_holes.SchLib" ) );
129
130 std::set<wxString> libSymbolNames( libNames.begin(), libNames.end() );
131 BOOST_REQUIRE( libSymbolNames.count( wxT( "MH M3" ) ) );
132
133 std::vector<SCH_SYMBOL*> fromMountingHoles;
134
135 for( SCH_ITEM* item : rootSheet->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
136 {
137 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
138
139 if( sym->GetLibId().GetUniStringLibNickname() == wxT( "mounting_holes" ) )
140 fromMountingHoles.push_back( sym );
141 }
142
143 BOOST_REQUIRE( !fromMountingHoles.empty() );
144
145 // Every symbol drawn from the source library must name an item that actually exists there.
146 for( SCH_SYMBOL* sym : fromMountingHoles )
147 {
148 const LIB_ID& libId = sym->GetLibId();
149 BOOST_CHECK( libId.IsValid() );
150 BOOST_CHECK_MESSAGE( libSymbolNames.count( libId.GetUniStringLibItemName() ),
151 "Library id '" << libId.Format().wx_str()
152 << "' does not resolve in mounting_holes.SchLib" );
153 }
154}
155
156
157// https://gitlab.com/kicad/code/kicad/-/issues/24861
158BOOST_AUTO_TEST_CASE( Issue24861_RepeatedSchematicChannels )
159{
160 SCH_IO_ALTIUM plugin;
161
162 std::map<std::string, UTF8> properties;
163 properties.emplace( "project_file", UTF8( issue24861DataFile( wxT( "Repeated_Schematic.PrjPcb" ) ) ) );
164 properties.emplace( "sch0", UTF8( issue24861DataFile( wxT( "Repeated_Schematic.SchDoc" ) ) ) );
165 properties.emplace( "sch1", UTF8( issue24861DataFile( wxT( "Channel.SchDoc" ) ) ) );
166
167 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( wxEmptyString, &m_schematic, nullptr, &properties );
168 BOOST_REQUIRE( rootSheet );
169
170 const std::vector<SCH_SHEET*> topLevelSheets = m_schematic.GetTopLevelSheets();
171 BOOST_REQUIRE_EQUAL( topLevelSheets.size(), 1 );
172 BOOST_CHECK_EQUAL( topLevelSheets.front()->GetName(), wxT( "Repeated_Schematic" ) );
173
174 std::optional<SCH_SHEET_PATH> topLevelPath;
175 std::map<wxString, SCH_SHEET_PATH> channelPaths;
176
177 for( const SCH_SHEET_PATH& sheetPath : m_schematic.Hierarchy() )
178 {
179 SCH_SHEET* sheet = sheetPath.Last();
180
181 if( sheet && sheet->GetName() == wxT( "Repeated_Schematic" ) )
182 topLevelPath = sheetPath;
183 else if( sheet && sheet->GetName().StartsWith( wxT( "CH" ) ) )
184 channelPaths.emplace( sheet->GetName(), sheetPath );
185 }
186
187 BOOST_REQUIRE( topLevelPath );
188 BOOST_CHECK_EQUAL( topLevelPath->GetPageNumber(), wxT( "1" ) );
189
190 BOOST_REQUIRE_EQUAL( channelPaths.size(), 3 );
191 BOOST_CHECK_EQUAL( channelPaths.at( wxT( "CH1" ) ).GetPageNumber(), wxT( "2" ) );
192 BOOST_CHECK_EQUAL( channelPaths.at( wxT( "CH2" ) ).GetPageNumber(), wxT( "3" ) );
193 BOOST_CHECK_EQUAL( channelPaths.at( wxT( "CH3" ) ).GetPageNumber(), wxT( "4" ) );
194
195 std::set<wxString> ledReferences;
196 std::set<wxString> resistorReferences;
197
198 for( const auto& [channelName, sheetPath] : channelPaths )
199 {
200 for( SCH_ITEM* item : sheetPath.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
201 {
202 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
203 wxString ref = symbol->GetRef( &sheetPath );
204
205 if( ref.StartsWith( wxT( "LED" ) ) )
206 ledReferences.insert( ref );
207 else if( ref.StartsWith( wxT( "R" ) ) )
208 resistorReferences.insert( ref );
209 }
210 }
211
212 BOOST_CHECK( ledReferences == std::set<wxString>( { wxT( "LED1_CH1" ), wxT( "LED1_CH2" ),
213 wxT( "LED1_CH3" ) } ) );
214 BOOST_CHECK( resistorReferences == std::set<wxString>( { wxT( "R1_CH1" ), wxT( "R1_CH2" ),
215 wxT( "R1_CH3" ) } ) );
216}
217
218
219// https://gitlab.com/kicad/code/kicad/-/issues/22908
220// Only the 34 Generic No ERC crosses are no-connects, the four Specific No ERC checkboxes are not
221BOOST_AUTO_TEST_CASE( Issue22908_SpecificNoErcIsNotNoConnect )
222{
223 SCH_IO_ALTIUM plugin;
224
225 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( issue22908DataFile( wxT( "3_mpcie.SchDoc" ) ), &m_schematic );
226 BOOST_REQUIRE( rootSheet );
227
228 SCH_SCREEN* screen = rootSheet->GetScreen();
229 BOOST_REQUIRE( screen );
230
231 // Altium places these in 10 mil units with the Y axis flipped about the page bottom
232 static const std::vector<VECTOR2I> crosses = {
233 { 360, 500 }, { 360, 540 }, { 360, 610 }, { 360, 600 }, { 360, 620 }, { 360, 530 }, { 360, 460 },
234 { 360, 450 }, { 170, 550 }, { 170, 540 }, { 170, 520 }, { 170, 510 }, { 170, 610 }, { 170, 590 },
235 { 170, 580 }, { 880, 500 }, { 880, 540 }, { 880, 610 }, { 880, 600 }, { 880, 620 }, { 880, 530 },
236 { 880, 460 }, { 880, 450 }, { 690, 550 }, { 690, 540 }, { 690, 520 }, { 690, 510 }, { 690, 610 },
237 { 690, 590 }, { 690, 580 }, { 480, 650 }, { 1000, 650 }, { 290, 340 }, { 810, 340 }
238 };
239
240 const int gridPitch = m_schematic.Settings().m_ConnectionGridSize;
241 const int pageHeight = screen->GetPageSettings().GetHeightIU( schIUScale.IU_PER_MILS );
242 const int flipY = ( pageHeight / gridPitch ) * gridPitch;
243
244 std::set<std::pair<int, int>> expected;
245
246 for( const VECTOR2I& cross : crosses )
247 expected.emplace( schIUScale.MilsToIU( cross.x * 10 ), flipY - schIUScale.MilsToIU( cross.y * 10 ) );
248
249 std::set<std::pair<int, int>> actual;
250
251 size_t actualCount = 0;
252
253 for( SCH_ITEM* item : screen->Items().OfType( SCH_NO_CONNECT_T ) )
254 {
255 ++actualCount;
256 actual.emplace( item->GetPosition().x, item->GetPosition().y );
257 }
258
259 BOOST_CHECK_EQUAL( actualCount, crosses.size() );
260 BOOST_CHECK( actual == expected );
261}
262
263
264// Support ticket #1303: an OrCad-derived Altium sheet symbol references several source files
265// through a single semicolon-separated filename ("pagea.SchDoc;pageb.SchDoc"), the two pages of one
266// multi-page block. The pages cross-reference each other (and one references itself). Every page
267// must be merged into the one sub-sheet screen, and the cyclic cross-references must not create a
268// recursive hierarchy that trips SCH_SHEET_LIST::BuildSheetList.
269BOOST_AUTO_TEST_CASE( Ticket1303_MultiPageBlock )
270{
271 SCH_IO_ALTIUM plugin;
272
273 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( ticket1303DataFile( wxT( "overview.SchDoc" ) ),
274 &m_schematic, nullptr, nullptr );
275 BOOST_REQUIRE( rootSheet );
276 BOOST_REQUIRE( rootSheet->GetScreen() );
277
278 // Walking the hierarchy must not trip the recursion guard in SCH_SHEET_LIST::BuildSheetList.
279 // Before the fix this raised a wxASSERT (the pages resolved to sub-sheets whose remapped
280 // filenames collided in the ancestry).
281 SCH_SHEET_LIST hierarchy = m_schematic.Hierarchy();
282
283 // Both pages of the block land in a single merged sub-sheet screen; a regression drops one (or
284 // both), leaving the sheet empty. The screen also holds no residual sub-sheet symbols, since the
285 // cross-page references were pruned rather than descended into.
286 bool foundMergedScreen = false;
287
288 for( const SCH_SHEET_PATH& sheetPath : hierarchy )
289 {
290 SCH_SCREEN* screen = sheetPath.LastScreen();
291
292 if( !screen || screen == rootSheet->GetScreen() )
293 continue;
294
295 std::optional<VECTOR2I> posA;
296 std::optional<VECTOR2I> posB;
297
298 for( SCH_ITEM* item : screen->Items().OfType( SCH_LABEL_T ) )
299 {
300 SCH_LABEL* label = static_cast<SCH_LABEL*>( item );
301
302 if( label->GetText() == wxT( "ONLY_A" ) )
303 posA = label->GetPosition();
304 else if( label->GetText() == wxT( "ONLY_B" ) )
305 posB = label->GetPosition();
306 }
307
308 if( !posA || !posB )
309 continue;
310
311 foundMergedScreen = true;
312
313 // The cross-page references were pruned, not descended into: the merged screen holds no
314 // residual sub-sheet symbols.
315 int subSheetCount = 0;
316
317 for( SCH_ITEM* item : screen->Items().OfType( SCH_SHEET_T ) )
318 {
319 (void) item;
320 subSheetCount++;
321 }
322
323 BOOST_CHECK_EQUAL( subSheetCount, 0 );
324
325 // Page B is tiled below page A rather than superimposed, even though both labels sit at the
326 // same coordinates in their source files.
327 BOOST_CHECK_MESSAGE( *posA != *posB, "Merged pages must be tiled, not overlapping" );
328
329 // The pruned cross-reference sheet's pin was converted to a hierarchical label so the wire
330 // that terminated on it still connects by name.
331 bool foundSigLabel = false;
332
333 for( SCH_ITEM* item : screen->Items().OfType( SCH_HIER_LABEL_T ) )
334 {
335 if( static_cast<SCH_HIERLABEL*>( item )->GetText() == wxT( "SIG" ) )
336 foundSigLabel = true;
337 }
338
339 BOOST_CHECK_MESSAGE( foundSigLabel,
340 "Pruned sheet pin must survive as a hierarchical label" );
341 }
342
343 BOOST_CHECK_MESSAGE( foundMergedScreen,
344 "Both pages of the multi-file sheet symbol must load into one screen" );
345}
346
347
348// https://gitlab.com/kicad/code/kicad/-/issues/24843
349// Altium encodes symbol rotation as quarter turns that map one-for-one onto KiCad's
350// SYM_ORIENT_* angles. The importer must store the same angle Altium shows so a later
351// "Update Symbols from Library" against a canonical upright symbol does not rotate the
352// placement. The reference symbols below are non-mirrored so the stored angle reads back
353// directly, without GetOrientation()'s mirror normalization.
354BOOST_AUTO_TEST_CASE( Issue24843_SymbolOrientationMatchesAltium )
355{
356 SCH_IO_ALTIUM plugin;
357
358 // The source embeds images by absolute Windows paths that cannot resolve here
360
361 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( eDPAdapterDataFile( "power.SchDoc" ),
362 &m_schematic );
363 BOOST_REQUIRE( rootSheet );
364
365 m_schematic.RefreshHierarchy();
366
367 // Reference designator -> Altium orientation angle read straight from the source records.
368 const std::map<wxString, SYMBOL_ORIENTATION_PROP> expected = {
369 { wxT( "L10P" ), SYMBOL_ORIENTATION_PROP::SYMBOL_ANGLE_0 }, // Altium ORIENTATION 0
370 { wxT( "C10P" ), SYMBOL_ORIENTATION_PROP::SYMBOL_ANGLE_90 }, // Altium ORIENTATION 1
371 { wxT( "R10P" ), SYMBOL_ORIENTATION_PROP::SYMBOL_ANGLE_180 }, // Altium ORIENTATION 2
372 { wxT( "C13P" ), SYMBOL_ORIENTATION_PROP::SYMBOL_ANGLE_270 }, // Altium ORIENTATION 3
373 };
374
375 std::map<wxString, SYMBOL_ORIENTATION_PROP> actual;
376
377 for( const SCH_SHEET_PATH& sheetPath : m_schematic.Hierarchy() )
378 {
379 for( SCH_ITEM* item : sheetPath.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
380 {
381 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
382 wxString ref = symbol->GetRef( &sheetPath );
383
384 if( expected.count( ref ) )
385 actual[ref] = symbol->GetOrientationProp();
386 }
387 }
388
389 for( const auto& [ref, angle] : expected )
390 {
391 BOOST_REQUIRE_MESSAGE( actual.count( ref ), "Symbol '" << ref << "' not found on import" );
392 BOOST_CHECK_MESSAGE( actual.at( ref ) == angle,
393 "Symbol '" << ref << "' imported with orientation " << actual.at( ref )
394 << ", expected " << angle );
395 }
396}
397
398
399// https://gitlab.com/kicad/code/kicad/-/issues/16903
400// Every connectable coordinate in this design sits on a 50 mil grid in Altium's own frame, so the
401// importer's Y flip must preserve that grid phase rather than shift the sheet off grid.
402BOOST_AUTO_TEST_CASE( Issue16903_ImportKeepsGeometryOnGrid )
403{
404 SCH_IO_ALTIUM plugin;
405
406 SCH_SHEET* rootSheet =
407 plugin.LoadSchematicFile( issue16903DataFile( wxT( "hierarchical_schematic_top.SchDoc" ) ),
408 &m_schematic );
409 BOOST_REQUIRE( rootSheet );
410
411 m_schematic.RefreshHierarchy();
412
413 ERC_TESTER tester( &m_schematic );
415}
416
417
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:130
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:118
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
Runs the electrical rules checks and adds a SCH_MARKER for each violation.
Definition erc.h:60
int TestOffGridEndpoints()
Test pins and wire ends for being off grid.
Definition erc.cpp:3049
virtual void SetReporter(REPORTER *aReporter)
Set an optional reporter for warnings/errors.
Definition io_base.h:89
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
static REPORTER & GetInstance()
Definition reporter.cpp:207
int GetHeightIU(double aIUScale) const
Get the page height in IU.
Definition page_info.h:163
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:170
const PAGE_INFO & GetPageSettings() const
Definition sch_screen.h:144
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
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:48
wxString GetName() const
Definition sch_sheet.h:142
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
Schematic symbol object.
Definition sch_symbol.h:73
SYMBOL_ORIENTATION_PROP GetOrientationProp() const
Definition sch_symbol.h:305
const LIB_ID & GetLibId() const override
Definition sch_symbol.h:163
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
VECTOR2I GetPosition() const override
Definition sch_text.h:143
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:51
@ SYMBOL_ANGLE_0
Definition symbol.h:49
@ SYMBOL_ANGLE_90
Definition symbol.h:50
@ SYMBOL_ANGLE_270
Definition symbol.h:52
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_NO_CONNECT_T
Definition typeinfo.h:156
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_LABEL_T
Definition typeinfo.h:163
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_HIER_LABEL_T
Definition typeinfo.h:165
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708