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