KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_design_block_duplicate.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, see <https://www.gnu.org/licenses/>.
18 */
19
28
29#include <boost/test/unit_test.hpp>
30#include <eeschema_test_utils.h>
31
33#include <sch_screen.h>
34#include <sch_sheet.h>
35#include <sch_group.h>
36#include <sch_line.h>
37#include <sch_junction.h>
38#include <schematic.h>
39#include <kiid.h>
41
42#include <wx/filename.h>
43#include <wx/stdpaths.h>
44
45
47{
50 {
51 wxString tempDir = wxStandardPaths::Get().GetTempDir();
52 wxString projectPath = tempDir + wxFileName::GetPathSeparator()
53 + wxT( "test_design_block.kicad_pro" );
54 m_tempFiles.push_back( projectPath );
55
56 m_settingsManager.LoadProject( projectPath.ToStdString() );
57 m_schematic = std::make_unique<SCHEMATIC>( nullptr );
59 m_schematic->SetProject( m_project );
60 }
61
63 {
64 for( const wxString& file : m_tempFiles )
65 {
66 if( wxFileExists( file ) )
67 wxRemoveFile( file );
68 }
69
70 m_schematic.reset();
71 }
72
73 wxString GetTempFileName( const wxString& aPrefix )
74 {
75 wxString tempDir = wxStandardPaths::Get().GetTempDir();
76 wxString fileName = wxFileName::CreateTempFileName(
77 tempDir + wxFileName::GetPathSeparator() + aPrefix );
78 m_tempFiles.push_back( fileName );
79 return fileName;
80 }
81
85 void CreateDesignBlockContent( SCH_SCREEN* aScreen, SCH_GROUP** aOutGroup,
86 const VECTOR2I& aOffset )
87 {
88 // Create a wire
89 SCH_LINE* wire = new SCH_LINE( aOffset + VECTOR2I( 0, 0 ), LAYER_WIRE );
90 wire->SetEndPoint( aOffset + VECTOR2I( 1000000, 0 ) ); // 10mm wire
91 aScreen->Append( wire );
92
93 // Create a junction at the wire endpoint
94 SCH_JUNCTION* junction = new SCH_JUNCTION( aOffset + VECTOR2I( 1000000, 0 ) );
95 aScreen->Append( junction );
96
97 // Create a group containing both items
98 SCH_GROUP* group = new SCH_GROUP( aScreen );
99 group->SetName( "DesignBlock" );
100 group->AddItem( wire );
101 group->AddItem( junction );
102 aScreen->Append( group );
103
104 if( aOutGroup )
105 *aOutGroup = group;
106 }
107
109 std::unique_ptr<SCHEMATIC> m_schematic;
111 std::vector<wxString> m_tempFiles;
112};
113
114
115BOOST_FIXTURE_TEST_SUITE( DesignBlockDuplicate, DESIGN_BLOCK_FIXTURE )
116
117
118
122BOOST_AUTO_TEST_CASE( TestMultipleDesignBlocksGroupIntegrity )
123{
124 // Create a simple schematic
125 m_schematic->CreateDefaultScreens();
126
127 std::vector<SCH_SHEET*> topSheets = m_schematic->GetTopLevelSheets();
128 BOOST_REQUIRE( !topSheets.empty() );
129
130 SCH_SCREEN* screen = topSheets[0]->GetScreen();
131 BOOST_REQUIRE( screen != nullptr );
132
133 screen->SetFileName( "test_design_block.kicad_sch" );
134
135 // Create first "design block" instance
136 SCH_GROUP* group1 = nullptr;
137 CreateDesignBlockContent( screen, &group1, VECTOR2I( 0, 0 ) );
138 BOOST_REQUIRE( group1 != nullptr );
139
140 KIID group1Uuid = group1->m_Uuid;
141 size_t group1MemberCount = group1->GetItems().size();
142 BOOST_CHECK_EQUAL( group1MemberCount, 2 ); // Wire + junction
143
144 // Create second "design block" instance at a different position
145 SCH_GROUP* group2 = nullptr;
146 CreateDesignBlockContent( screen, &group2, VECTOR2I( 5000000, 0 ) ); // 50mm offset
147 BOOST_REQUIRE( group2 != nullptr );
148
149 KIID group2Uuid = group2->m_Uuid;
150 size_t group2MemberCount = group2->GetItems().size();
151 BOOST_CHECK_EQUAL( group2MemberCount, 2 ); // Wire + junction
152
153 // Verify both groups exist and are distinct
154 BOOST_CHECK( group1Uuid != group2Uuid );
155
156 // Count total groups before save
157 int groupCountBefore = 0;
158
159 for( SCH_ITEM* item : screen->Items().OfType( SCH_GROUP_T ) )
160 {
161 groupCountBefore++;
162 SCH_GROUP* group = static_cast<SCH_GROUP*>( item );
163 BOOST_TEST_MESSAGE( "Group before save: " << group->GetName().ToStdString()
164 << " UUID: " << group->m_Uuid.AsString().ToStdString()
165 << " Members: " << group->GetItems().size() );
166 }
167
168 BOOST_CHECK_EQUAL( groupCountBefore, 2 );
169
170 // Save the schematic
171 wxString fileName = GetTempFileName( "test_design_block" );
172 fileName += ".kicad_sch";
173 m_tempFiles.push_back( fileName );
174
176 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( fileName, topSheets[0], m_schematic.get() ) );
177 BOOST_CHECK( wxFileExists( fileName ) );
178
179 // Reset and reload
180 m_schematic->Reset();
181 SCH_SHEET* defaultSheet = m_schematic->GetTopLevelSheet( 0 );
182 SCH_SHEET* loadedSheet = nullptr;
183
184 BOOST_CHECK_NO_THROW( loadedSheet = io.LoadSchematicFile( fileName, m_schematic.get() ) );
185 BOOST_REQUIRE( loadedSheet != nullptr );
186
187 m_schematic->AddTopLevelSheet( loadedSheet );
188 m_schematic->RemoveTopLevelSheet( defaultSheet );
189 delete defaultSheet;
190
191 SCH_SCREEN* loadedScreen = loadedSheet->GetScreen();
192 BOOST_REQUIRE( loadedScreen != nullptr );
193
194 // Verify groups after reload
195 int groupCountAfter = 0;
196 int totalMembersAfter = 0;
197
198 for( SCH_ITEM* item : loadedScreen->Items().OfType( SCH_GROUP_T ) )
199 {
200 groupCountAfter++;
201 SCH_GROUP* group = static_cast<SCH_GROUP*>( item );
202
203 BOOST_TEST_MESSAGE( "Group after load: " << group->GetName().ToStdString()
204 << " UUID: " << group->m_Uuid.AsString().ToStdString()
205 << " Members: " << group->GetItems().size() );
206
207 // Each group should still have exactly 2 members
208 BOOST_CHECK_EQUAL( group->GetItems().size(), 2 );
209 totalMembersAfter += group->GetItems().size();
210
211 // Verify members are actual items on the screen
212 for( EDA_ITEM* member : group->GetItems() )
213 {
214 bool found = false;
215
216 for( SCH_ITEM* screenItem : loadedScreen->Items() )
217 {
218 if( screenItem == member )
219 {
220 found = true;
221 break;
222 }
223 }
224
225 BOOST_CHECK_MESSAGE( found, "Group member should exist on screen" );
226 }
227 }
228
229 // CRITICAL: We should have 2 groups, each with 2 members
230 BOOST_CHECK_EQUAL( groupCountAfter, 2 );
231 BOOST_CHECK_EQUAL( totalMembersAfter, 4 );
232
233 BOOST_TEST_MESSAGE( "Test passed: Groups maintained integrity after save/reload" );
234}
235
236
242BOOST_AUTO_TEST_CASE( TestDesignBlockDuplicateUuidHandling )
243{
244 m_schematic->CreateDefaultScreens();
245
246 std::vector<SCH_SHEET*> topSheets = m_schematic->GetTopLevelSheets();
247 BOOST_REQUIRE( !topSheets.empty() );
248
249 SCH_SCREEN* mainScreen = topSheets[0]->GetScreen();
250 BOOST_REQUIRE( mainScreen != nullptr );
251 mainScreen->SetFileName( "test_duplicate_uuid.kicad_sch" );
252
253 // To properly simulate design block placement, we simulate what happens when
254 // the same design block file is loaded twice: groups are created pointing to
255 // the items they were loaded with, then items are moved to the target screen.
256 //
257 // We'll create two sets of items with the SAME UUIDs (as if loaded from
258 // the same design block file), with groups properly pointing to their own items.
259
260 // Fixed UUIDs to simulate loading from same source file
261 KIID wireUuid1( "11111111-1111-1111-1111-111111111111" );
262 KIID junctionUuid1( "22222222-2222-2222-2222-222222222222" );
263 KIID groupUuid1( "33333333-3333-3333-3333-333333333333" );
264
265 // First "design block placement" - create items at origin
266 SCH_LINE* wire1 = new SCH_LINE( VECTOR2I( 0, 0 ), LAYER_WIRE );
267 wire1->SetEndPoint( VECTOR2I( 1000000, 0 ) );
268 const_cast<KIID&>( wire1->m_Uuid ) = wireUuid1;
269 mainScreen->Append( wire1 );
270
271 SCH_JUNCTION* junction1 = new SCH_JUNCTION( VECTOR2I( 1000000, 0 ) );
272 const_cast<KIID&>( junction1->m_Uuid ) = junctionUuid1;
273 mainScreen->Append( junction1 );
274
275 SCH_GROUP* group1 = new SCH_GROUP( mainScreen );
276 group1->SetName( "DesignBlock" );
277 const_cast<KIID&>( group1->m_Uuid ) = groupUuid1;
278 group1->AddItem( wire1 );
279 group1->AddItem( junction1 );
280 mainScreen->Append( group1 );
281
282 // Second "design block placement" - create items at offset with SAME UUIDs
283 // (simulating loading same design block file again)
284 SCH_LINE* wire2 = new SCH_LINE( VECTOR2I( 5000000, 0 ), LAYER_WIRE );
285 wire2->SetEndPoint( VECTOR2I( 6000000, 0 ) );
286 const_cast<KIID&>( wire2->m_Uuid ) = wireUuid1; // SAME UUID!
287 mainScreen->Append( wire2 );
288
289 SCH_JUNCTION* junction2 = new SCH_JUNCTION( VECTOR2I( 6000000, 0 ) );
290 const_cast<KIID&>( junction2->m_Uuid ) = junctionUuid1; // SAME UUID!
291 mainScreen->Append( junction2 );
292
293 SCH_GROUP* group2 = new SCH_GROUP( mainScreen );
294 group2->SetName( "DesignBlock" );
295 const_cast<KIID&>( group2->m_Uuid ) = groupUuid1; // SAME UUID!
296 group2->AddItem( wire2 ); // Points to wire2, not wire1
297 group2->AddItem( junction2 ); // Points to junction2, not junction1
298 mainScreen->Append( group2 );
299
300 // Verify we have duplicate UUIDs
301 std::map<KIID, int> uuidCounts;
302
303 for( SCH_ITEM* item : mainScreen->Items() )
304 uuidCounts[item->m_Uuid]++;
305
306 int duplicatesFound = 0;
307
308 for( const auto& pair : uuidCounts )
309 {
310 if( pair.second > 1 )
311 {
312 duplicatesFound += pair.second - 1;
313 BOOST_TEST_MESSAGE( "Found duplicate UUID: " << pair.first.AsString().ToStdString()
314 << " count: " << pair.second );
315 }
316 }
317
318 // Should have 3 duplicate pairs: wire, junction, group
319 BOOST_CHECK_EQUAL( duplicatesFound, 3 );
320
321 // This is the critical test: ReplaceDuplicateTimeStamps should now handle
322 // ALL item types, not just hierarchical items
323 SCH_SCREENS screens( topSheets[0] );
324 int replaced = screens.ReplaceDuplicateTimeStamps();
325
326 BOOST_TEST_MESSAGE( "ReplaceDuplicateTimeStamps replaced: " << replaced );
327
328 // Should replace exactly 3 items (one from each duplicate pair)
329 BOOST_CHECK_EQUAL( replaced, 3 );
330
331 // Verify UUIDs are now unique
332 uuidCounts.clear();
333
334 for( SCH_ITEM* item : mainScreen->Items() )
335 uuidCounts[item->m_Uuid]++;
336
337 for( const auto& pair : uuidCounts )
338 {
339 BOOST_CHECK_MESSAGE( pair.second == 1,
340 "UUID should be unique after ReplaceDuplicateTimeStamps: "
341 << pair.first.AsString().ToStdString() );
342 }
343
344 // Verify both groups still have their correct members
345 int groupCount = 0;
346
347 for( SCH_ITEM* item : mainScreen->Items().OfType( SCH_GROUP_T ) )
348 {
349 groupCount++;
350 SCH_GROUP* group = static_cast<SCH_GROUP*>( item );
351
352 BOOST_TEST_MESSAGE( "Group: " << group->GetName().ToStdString()
353 << " UUID: " << group->m_Uuid.AsString().ToStdString()
354 << " Members: " << group->GetItems().size() );
355
356 // Each group should have exactly 2 members
357 BOOST_CHECK_EQUAL( group->GetItems().size(), 2 );
358
359 // Verify all members are on the screen
360 for( EDA_ITEM* member : group->GetItems() )
361 {
362 bool memberOnScreen = false;
363
364 for( SCH_ITEM* screenItem : mainScreen->Items() )
365 {
366 if( screenItem == member )
367 {
368 memberOnScreen = true;
369 break;
370 }
371 }
372
373 BOOST_CHECK_MESSAGE( memberOnScreen,
374 "Group member should be on screen. Member UUID: "
375 << member->m_Uuid.AsString().ToStdString() );
376 }
377 }
378
379 BOOST_CHECK_EQUAL( groupCount, 2 );
380
381 // Save and reload to test full round-trip
382 wxString fileName = GetTempFileName( "test_dup_uuid" );
383 fileName += ".kicad_sch";
384 m_tempFiles.push_back( fileName );
385
387 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( fileName, topSheets[0], m_schematic.get() ) );
388
389 m_schematic->Reset();
390 SCH_SHEET* defaultSheet = m_schematic->GetTopLevelSheet( 0 );
391 SCH_SHEET* loadedSheet = io.LoadSchematicFile( fileName, m_schematic.get() );
392 BOOST_REQUIRE( loadedSheet != nullptr );
393
394 m_schematic->AddTopLevelSheet( loadedSheet );
395 m_schematic->RemoveTopLevelSheet( defaultSheet );
396 delete defaultSheet;
397
398 SCH_SCREEN* loadedScreen = loadedSheet->GetScreen();
399
400 // Verify groups after reload - each should have 2 members
401 groupCount = 0;
402 int membersWithValidPointers = 0;
403
404 for( SCH_ITEM* item : loadedScreen->Items().OfType( SCH_GROUP_T ) )
405 {
406 groupCount++;
407 SCH_GROUP* group = static_cast<SCH_GROUP*>( item );
408
409 BOOST_TEST_MESSAGE( "Loaded group: " << group->GetName().ToStdString()
410 << " Members: " << group->GetItems().size() );
411
412 // CRITICAL CHECK: Each group should have 2 members after reload
413 BOOST_CHECK_EQUAL( group->GetItems().size(), 2 );
414
415 for( EDA_ITEM* member : group->GetItems() )
416 {
417 bool memberOnScreen = false;
418
419 for( SCH_ITEM* screenItem : loadedScreen->Items() )
420 {
421 if( screenItem == member )
422 {
423 memberOnScreen = true;
424 membersWithValidPointers++;
425 break;
426 }
427 }
428
429 BOOST_CHECK_MESSAGE( memberOnScreen,
430 "Loaded group member should be on screen" );
431 }
432 }
433
434 BOOST_CHECK_EQUAL( groupCount, 2 );
435 BOOST_CHECK_EQUAL( membersWithValidPointers, 4 ); // 2 groups * 2 members each
436
437 BOOST_TEST_MESSAGE( "Test passed: Duplicate UUIDs correctly handled and groups preserved" );
438}
439
440
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:50
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:58
void SetName(const wxString &aName)
Definition eda_group.h:48
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:96
const KIID m_Uuid
Definition eda_item.h:531
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:221
Definition kiid.h:44
Container for project specific data.
Definition project.h:62
A set of SCH_ITEMs (i.e., without duplicates).
Definition sch_group.h:48
A SCH_IO derivation for loading schematic files using the new s-expression file format.
void SaveSchematicFile(const wxString &aFileName, SCH_SHEET *aSheet, SCHEMATIC *aSchematic, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aSchematic to a storage file in a format that this SCH_IO implementation knows about,...
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,...
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:162
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:38
void SetEndPoint(const VECTOR2I &aPosition)
Definition sch_line.h:145
Container class that holds multiple SCH_SCREEN objects in a hierarchy.
Definition sch_screen.h:746
int ReplaceDuplicateTimeStamps()
Test all sheet and symbol objects in the schematic for duplicate time stamps and replaces them as nec...
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:115
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:44
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:139
@ LAYER_WIRE
Definition layer_ids.h:450
Class to handle a set of SCH_ITEMs.
std::unique_ptr< SCHEMATIC > m_schematic
std::vector< wxString > m_tempFiles
void CreateDesignBlockContent(SCH_SCREEN *aScreen, SCH_GROUP **aOutGroup, const VECTOR2I &aOffset)
Create a simple design block content: a wire and junction in a group.
wxString GetTempFileName(const wxString &aPrefix)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_CASE(TestMultipleDesignBlocksGroupIntegrity)
Test that multiple design block instances maintain correct group membership after save/reload This is...
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
BOOST_CHECK_EQUAL(result, "25.4")
@ SCH_GROUP_T
Definition typeinfo.h:170
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683