KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_ref_suffix.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
24
26#include <qa_utils/file_utils.h>
27#include "eeschema_test_utils.h"
28
29#include <api/api_sch_utils.h>
30#include <api/schematic/schematic_types.pb.h>
33#include <downgrade_target.h>
34#include <eda_search_data.h>
35#include <erc/erc.h>
36#include <erc/erc_item.h>
40#include <reporter.h>
41#include <richio.h>
43#include <sch_commit.h>
44#include <sch_marker.h>
45#include <sch_reference_list.h>
47#include <tools/backannotate.h>
48#include <sch_screen.h>
49#include <sch_symbol.h>
50#include <sch_sheet.h>
51#include <sch_sheet_path.h>
52#include <schematic.h>
53#include <tool/tool_manager.h>
54#include <validators.h>
56
57#include <set>
58
59#include <wx/ffile.h>
60#include <wx/regex.h>
61#include <wx/tokenzr.h>
62
63
65{
66public:
68 {
69 if( m_dir.IsEmpty() )
70 return;
71
72 // Release the project lock file before its directory goes away
73 m_schematic.reset();
74 m_manager.UnloadProject( &m_manager.Prj(), false );
75 wxFileName::Rmdir( m_dir, wxPATH_RMDIR_RECURSIVE );
76 }
77
78 void Load( const wxString& aPath )
79 {
80 LoadSchematic( wxFileName( KI_TEST::GetEeschemaTestDataDir() + aPath + wxS( "." )
82 }
83
84 // Work on a copy of issue13212 so we can save it
85 void LoadCopy()
86 {
87 m_dir = wxFileName::CreateTempFileName( wxFileName::GetTempDir() + wxFileName::GetPathSeparator()
88 + wxS( "ref_suffix" ) );
89 wxRemoveFile( m_dir );
90 wxFileName::Mkdir( m_dir );
91
92 for( const wxString& name : { wxS( "issue13212" ), wxS( "issue13212_subsheet_1" ),
93 wxS( "issue13212_subsheet_2" ) } )
94 {
97 BOOST_REQUIRE( wxCopyFile( src.GetFullPath(), dst.GetFullPath() ) );
98 }
99
101 }
102
103 wxFileName RootFile() const
104 {
105 return wxFileName( m_dir, wxS( "issue13212" ), FILEEXT::KiCadSchematicFileExtension );
106 }
107
108 std::vector<SCH_SHEET_PATH> PathsOf( const wxString& aFile ) const
109 {
110 std::vector<SCH_SHEET_PATH> paths;
111
112 for( const SCH_SHEET_PATH& path : m_schematic->Hierarchy() )
113 {
114 if( path.Last()->GetFileName() == aFile )
115 paths.push_back( path );
116 }
117
118 return paths;
119 }
120
121 // Suffixes the two sheets of a reused file in page order; issue13212 reuses its subsheet_2
122 std::vector<SCH_SHEET_PATH> SuffixChannels( const wxString& aFirst = wxS( "_M1" ),
123 const wxString& aSecond = wxS( "_M2" ),
124 const wxString& aFile = wxS( "issue13212_subsheet_2.kicad_sch" ) )
125 {
126 std::vector<SCH_SHEET_PATH> channels = PathsOf( aFile );
127 BOOST_REQUIRE_EQUAL( channels.size(), 2 );
128
129 AddSuffixField( channels[0].Last(), aFirst );
130 AddSuffixField( channels[1].Last(), aSecond );
131 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = true;
132 m_schematic->RefreshHierarchy();
133 return channels;
134 }
135
136 // legacy_hierarchy reuses ampli_ht.kicad_sch twice, with separate numbering (U201 and U301)
137 std::vector<SCH_SHEET_PATH> LoadAmpliChannels()
138 {
139 Load( wxS( "legacy_hierarchy/legacy_hierarchy" ) );
140 return SuffixChannels( wxS( "_V" ), wxS( "_H" ), wxS( "ampli_ht.kicad_sch" ) );
141 }
142
143 static std::vector<SCH_SHEET*> SubSheets( SCH_SCREEN* aScreen )
144 {
145 std::vector<SCH_SHEET*> sheets;
146
147 for( SCH_ITEM* item : aScreen->Items().OfType( SCH_SHEET_T ) )
148 sheets.push_back( static_cast<SCH_SHEET*>( item ) );
149
150 std::sort( sheets.begin(), sheets.end(),
151 []( const SCH_SHEET* a, const SCH_SHEET* b )
152 {
153 return a->GetName() < b->GetName();
154 } );
155
156 return sheets;
157 }
158
159 static SCH_SYMBOL* FirstSymbol( const SCH_SHEET_PATH& aPath )
160 {
161 for( SCH_ITEM* item : aPath.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
162 return static_cast<SCH_SYMBOL*>( item );
163
164 return nullptr;
165 }
166
167 static SCH_SYMBOL* FindSymbol( const SCH_SHEET_PATH& aPath, const wxString& aRef, int aUnit = 1 )
168 {
169 for( SCH_ITEM* item : aPath.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
170 {
171 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
172
173 if( symbol->GetRef( &aPath ) == aRef && symbol->GetUnitSelection( &aPath ) == aUnit )
174 return symbol;
175 }
176
177 return nullptr;
178 }
179
180 static void AddSuffixField( SCH_SHEET* aSheet, const wxString& aText )
181 {
182 aSheet->AddField( SCH_FIELD( aSheet, FIELD_T::SHEET_USER, wxS( "Reference Suffix" ) ) )->SetText( aText );
183 }
184
185 // SCH_EDIT_FRAME::AnnotateSymbols in suffix mode once it has collected its scope
187 bool aResetAnnotation )
188 {
189 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = true;
191 AnnotateReferences( aRefs, aLocked, *m_schematic, aScope, UNSORTED, INCREMENTAL_BY_REF, 0, aResetAnnotation );
192 }
193
194 void AnnotateAll( bool aResetAnnotation = false )
195 {
196 SCH_SHEET_LIST sheets = m_schematic->Hierarchy();
199
200 sheets.GetSymbols( refs, SYMBOL_FILTER_NON_POWER );
202 Annotate( refs, locked, ANNOTATE_ALL, aResetAnnotation );
203 }
204
205 void AnnotateSelection( const std::vector<SCH_SYMBOL*>& aSymbols, const SCH_SHEET_PATH& aPath )
206 {
209
210 for( SCH_SYMBOL* symbol : aSymbols )
211 {
212 aPath.AppendSymbol( refs, symbol, SYMBOL_FILTER_NON_POWER );
214 }
215
216 Annotate( refs, locked, ANNOTATE_SELECTION, false );
217 }
218
219 void AnnotatePlaced( SCH_SYMBOL* aSymbol, const std::vector<SCH_SHEET_PATH>& aChannels )
220 {
221 SCH_SHEET_LIST hierarchy = m_schematic->Hierarchy();
222 SCH_REFERENCE_LIST existing;
223
224 hierarchy.GetSymbols( existing, SYMBOL_FILTER_ALL );
225 existing.SortByReferenceOnly();
226 AnnotateNewSymbol( aSymbol, aChannels, existing, m_schematic->Settings(), &hierarchy );
227 }
228
230 {
232 int count = 0;
233
234 m_schematic->Hierarchy().GetSymbols( refs, SYMBOL_FILTER_NON_POWER );
235 refs.CheckAnnotation(
236 [&]( ERCE_T aType, const wxString&, SCH_REFERENCE*, SCH_REFERENCE* )
237 {
238 count += aType == ERCE_DUPLICATE_REFERENCE;
239 } );
240
241 return count;
242 }
243
244 int CountErcMarkers( int aCode )
245 {
246 ERC_TESTER( m_schematic.get() ).RunTests( nullptr, nullptr, nullptr, &m_schematic->Project(), nullptr );
247
248 int count = 0;
249 SCH_SCREENS screens( m_schematic->Root() );
250
251 for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
252 {
253 for( SCH_ITEM* item : screen->Items().OfType( SCH_MARKER_T ) )
254 count += static_cast<SCH_MARKER*>( item )->GetRCItem()->GetErrorCode() == aCode;
255 }
256
257 return count;
258 }
259
260 wxString m_dir;
261};
262
263BOOST_FIXTURE_TEST_SUITE( RefSuffix, REF_SUFFIX_FIXTURE )
264
265
266BOOST_AUTO_TEST_CASE( SuffixTextRules )
267{
268 SCH_SHEET sheet;
269 SCH_FIELD field( &sheet, FIELD_T::SHEET_USER, wxS( "Reference Suffix" ) );
270
271 // Sheet Properties renumbers every non-mandatory field as SHEET_USER on OK
272 field.SetOrdinal( 42, FIELD_T::SHEET_USER );
274
275 field.SetName( wxS( "reference suffix" ) );
277
279
280 for( const wxString& bad : { wxS( "_M 1" ), wxS( "_M?" ), wxS( "_M1,_M2" ), wxS( "2" ) } )
281 BOOST_CHECK_MESSAGE( !GetFieldValidationErrorMessage( FIELD_T::SHEET_REF_SUFFIX, bad ).IsEmpty(), bad );
282
283 const std::set<wxString> inUse = { wxS( "_M1" ), wxS( "_M3" ), wxS( "_M09" ), wxS( "_K01" ), wxS( "_A" ),
284 wxS( "_AY" ), wxS( "_b" ), wxS( "_z" ), wxS( "_LEFT" ), wxS( "_N2X" ),
285 wxS( "_1ZZ" ), wxS( "_1AAA" ), wxS( "_W0" ),
286 wxS( "_W18446744073709551615" ) };
287
288 BOOST_CHECK_EQUAL( SCH_SHEET::NextRefSuffix( wxS( "_M1" ), inUse ), wxS( "_M10" ) );
289 BOOST_CHECK_EQUAL( SCH_SHEET::NextRefSuffix( wxS( "_A" ), inUse ), wxS( "_AZ" ) );
290 BOOST_CHECK_EQUAL( SCH_SHEET::NextRefSuffix( wxS( "_LEFT" ), inUse ), wxS( "_LEFT" ) );
291}
292
293
295{
296 LoadCopy();
297
298 std::vector<SCH_SHEET_PATH> channels = SuffixChannels();
299 SCH_SYMBOL* symbol = FirstSymbol( channels[0] );
300 const KIID uuid = symbol->m_Uuid;
301 const KIID_PATH paths[2] = { channels[0].Path(), channels[1].Path() };
302
303 symbol->SetRef( &channels[0], wxS( "R91" ) );
304 symbol->SetRef( &channels[1], wxS( "R92" ) );
305
306 // Older readers show the stored reference, so it must already carry the suffix
307 auto storesComposed =
308 []( const wxString& aFile )
309 {
310 wxString text;
311 BOOST_REQUIRE( wxFFile( aFile, wxS( "rb" ) ).ReadAll( &text ) );
312 return text.Contains( wxS( "(reference \"R91_M1\")" ) )
313 && text.Contains( wxS( "(reference \"R92_M2\")" ) );
314 };
315
317 io.SaveSchematicFile( RootFile().GetFullPath(), m_schematic->GetTopLevelSheets()[0], m_schematic.get() );
318 io.SaveSchematicFile( channels[0].LastScreen()->GetFileName(), channels[0].Last(), m_schematic.get() );
319 BOOST_CHECK( storesComposed( channels[0].LastScreen()->GetFileName() ) );
320
321 wxFileName downgraded( m_dir, wxS( "downgraded" ), FILEEXT::KiCadSchematicFileExtension );
322
323 for( const DOWNGRADE_TARGET& target : GetDowngradeTargets() )
324 {
325 BOOST_TEST_CONTEXT( target.m_name )
326 {
327 SaveSchematicForTarget( channels[0].Last(), m_schematic.get(), downgraded.GetFullPath(), target );
328 BOOST_CHECK( storesComposed( downgraded.GetFullPath() ) );
329 }
330 }
331
332 LoadSchematic( RootFile() );
333
334 std::optional<SCH_SHEET_PATH> first = m_schematic->Hierarchy().GetSheetPathByKIIDPath( paths[0] );
335 std::optional<SCH_SHEET_PATH> second = m_schematic->Hierarchy().GetSheetPathByKIIDPath( paths[1] );
336 SCH_SYMBOL* loaded = static_cast<SCH_SYMBOL*>( m_schematic->ResolveItem( uuid ) );
337 BOOST_REQUIRE( first && second && loaded );
338
339 BOOST_CHECK( first->Last()->GetField( FIELD_T::SHEET_REF_SUFFIX ) );
340 BOOST_CHECK_EQUAL( first->Last()->GetRefSuffix( m_schematic->Hierarchy()[0] ), wxS( "_M1" ) );
341 BOOST_CHECK( m_schematic->GetRefSuffix( *first ).IsEmpty() );
342 BOOST_CHECK_EQUAL( loaded->GetRef( &*first ), wxS( "R91" ) );
343
344 io.SaveSchematicFile( first->LastScreen()->GetFileName(), first->Last(), m_schematic.get() );
345 wxString plainFile;
346 BOOST_REQUIRE( wxFFile( first->LastScreen()->GetFileName(), wxS( "rb" ) ).ReadAll( &plainFile ) );
347 BOOST_CHECK( plainFile.Contains( wxS( "(reference \"R91\")" ) ) );
348 BOOST_CHECK( plainFile.Contains( wxS( "(reference \"R92\")" ) ) );
349 BOOST_CHECK( !plainFile.Contains( wxS( "(reference \"R91_M1\")" ) ) );
350
351 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = true;
352 m_schematic->RefreshRefSuffixes();
353 BOOST_CHECK_EQUAL( loaded->GetRef( &*first ), wxS( "R91_M1" ) );
354 BOOST_CHECK_EQUAL( loaded->GetBaseRef( &*first ), wxS( "R91" ) );
355 BOOST_CHECK_EQUAL( loaded->GetBaseRef( &*second ), wxS( "R92" ) );
356
357 // Importing the saved file as a sheet strips the source project's suffixes
358 SCHEMATIC host( &m_manager.Prj() );
359 host.Reset();
360 host.RefreshHierarchy();
361
362 auto sheet = std::make_unique<SCH_SHEET>( &host );
363 std::map<std::string, UTF8> props{ { "hierarchical_sheet_load", "1" } };
364
365 sheet->SetFileName( RootFile().GetFullPath() );
366 io.LoadSchematicFile( RootFile().GetFullPath(), &host, sheet.get(), &props );
367
368 SCH_SHEET_LIST imported( sheet.get() );
369 SCH_SHEET_PATH prefix = host.Hierarchy()[0];
370 std::set<wxString> refs;
371
372 imported.AddNewSymbolInstances( prefix, wxS( "host" ), true );
373
374 // The loaded root takes the new sheet's UUID, so each channel must find its own source instance below it
375 for( SCH_SHEET_PATH& path : imported )
376 {
377 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
378 {
379 SCH_SYMBOL_INSTANCE instance;
380
381 if( item->m_Uuid == uuid
382 && static_cast<SCH_SYMBOL*>( item )->GetInstance( instance, ( prefix + path ).Path() ) )
383 {
384 refs.insert( instance.m_Reference );
385 }
386 }
387 }
388
389 BOOST_CHECK( refs == std::set<wxString>( { wxS( "R91" ), wxS( "R92" ) } ) );
390}
391
392
393BOOST_AUTO_TEST_CASE( ComposesPerPath )
394{
395 Load( wxS( "issue25112/issue25112" ) );
396
397 // issue25112 reuses level1 twice and level1 reuses level2 twice, so four paths share one leaf
398 SCH_SHEET_PATH root = m_schematic->Hierarchy()[0];
399 std::vector<SCH_SHEET*> outer = SubSheets( root.LastScreen() );
400 BOOST_REQUIRE_EQUAL( outer.size(), 2 );
401
402 std::vector<SCH_SHEET*> inner = SubSheets( outer[0]->GetScreen() );
403 BOOST_REQUIRE_EQUAL( inner.size(), 2 );
404
405 AddSuffixField( outer[0], wxS( "_M1" ) );
406 AddSuffixField( outer[1], wxS( "_M2" ) );
407 outer[0]->AddField( SCH_FIELD( outer[0], FIELD_T::SHEET_USER, wxS( "CH" ) ) )->SetText( wxS( "a" ) );
408 outer[1]->AddField( SCH_FIELD( outer[1], FIELD_T::SHEET_USER, wxS( "CH" ) ) )->SetText( wxS( "b" ) );
409 AddSuffixField( inner[0], wxS( "_${CH}" ) );
410 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = true;
411 m_schematic->RefreshHierarchy();
412
413 std::set<wxString> composed;
414 SCH_SHEET_PATH leaf;
415
416 for( const SCH_SHEET_PATH& path : m_schematic->Hierarchy() )
417 {
418 if( path.Last() == inner[0] || path.Last() == inner[1] )
419 composed.insert( m_schematic->GetRefSuffix( path ) );
420
421 if( path.Last() == inner[0] && path.at( path.size() - 2 ) == outer[0] )
422 leaf = path;
423 }
424
425 BOOST_CHECK( composed == std::set<wxString>( { wxS( "_M1_a" ), wxS( "_M1" ), wxS( "_M2_b" ), wxS( "_M2" ) } ) );
426
427 // A path that differs from the hierarchy's only in the virtual root hashes apart but has the same suffix
428 SCH_SHEET_PATH rooted;
429 rooted.push_back( &m_schematic->Root() );
430
431 for( size_t i = 0; i < leaf.size(); i++ )
432 rooted.push_back( leaf.at( i ) );
433
434 BOOST_REQUIRE_NE( rooted.GetCurrentHash(), leaf.GetCurrentHash() );
435 BOOST_CHECK_EQUAL( m_schematic->GetRefSuffix( rooted ), wxS( "_M1_a" ) );
436
437 // Neither the suffix nor a field it reads takes a variant
438 m_schematic->AddVariant( wxS( "Alt" ) );
439 outer[0]->SetFieldText( wxS( "CH" ), wxS( "x" ), &root, wxS( "Alt" ) );
440 m_schematic->SetCurrentVariant( wxS( "Alt" ) );
441 m_schematic->RefreshHierarchy();
442 BOOST_CHECK_EQUAL( m_schematic->GetRefSuffix( leaf ), wxS( "_M1_a" ) );
443
444 outer[1]->SetFieldText( wxS( "Reference Suffix" ), wxS( "_X" ), &root, wxS( "Alt" ) );
445 BOOST_CHECK_EQUAL( outer[1]->GetRefSuffix( root ), wxS( "_X" ) );
446
447 // An override saved while the suffix was an ordinary field must not show through
448 SCH_SHEET_VARIANT stale( wxS( "Alt" ) );
449 stale.m_Fields[wxS( "Reference Suffix" )] = wxS( "_STALE" );
450 outer[1]->AddVariant( root, stale );
451
452 const SCH_FIELD* suffix = std::as_const( *outer[1] ).GetField( FIELD_T::SHEET_REF_SUFFIX );
453 BOOST_CHECK_EQUAL( suffix->GetShownText( &root, INTERNAL, wxS( "Alt" ) ), wxS( "_X" ) );
454
455 // Project, title block and file name values change outside commits and would leave the cache stale
456 m_schematic->Project().GetTextVars()[wxS( "LOT" )] = wxS( "7" );
457 outer[1]->AddField( SCH_FIELD( outer[1], FIELD_T::SHEET_USER, wxS( "ERC_WARNING_CH" ) ) )->SetText( wxS( "a" ) );
458 outer[1]->GetField( wxS( "Reference Suffix" ) )
459 ->SetText( wxS( "_${ERC_WARNING_CH}${LOT}${REVISION}${Sheetfile}" ) );
460
461 BOOST_CHECK_EQUAL( outer[1]->GetRefSuffix( root ), wxS( "_a${LOT}${REVISION}${Sheetfile}" ) );
462
463 // Autosave skips clean files, but the child file stores references composed with the edited suffix
464 SCH_SHEET before( *outer[0] );
465 SCH_SCREEN* child = outer[0]->GetScreen();
466 TOOL_MANAGER manager;
467
468 manager.SetEnvironment( m_schematic.get(), nullptr, nullptr, nullptr, nullptr );
469 child->SetContentModified( false );
470
471 SCH_COMMIT commit( &manager );
472 commit.Modify( outer[0], root.LastScreen() );
473 outer[0]->GetField( wxS( "CH" ) )->SetText( wxS( "c" ) );
474 BOOST_CHECK( before.HasHierarchyChanges( *outer[0] ) );
475
476 commit.Push( wxS( "Edit channel" ), SKIP_UNDO );
477 BOOST_CHECK( child->IsContentModified() );
478}
479
480
481BOOST_AUTO_TEST_CASE( ChannelRefsCompose )
482{
483 Load( wxS( "issue13212" ) );
484
485 std::vector<SCH_SHEET_PATH> channels = SuffixChannels();
486 SCH_SYMBOL* symbol = FirstSymbol( channels[0] );
487 wxString base = symbol->GetBaseRef( &channels[0] );
488
489 BOOST_CHECK_EQUAL( symbol->GetRef( &channels[0] ), base + wxS( "_M1" ) );
490 BOOST_CHECK_EQUAL( symbol->GetRef( &channels[1] ), symbol->GetBaseRef( &channels[1] ) + wxS( "_M2" ) );
491
492 // Commit and undo write the field text back through SetRef, so it must hold the base
493 channels[0].UpdateAllScreenReferences();
495
496 // Identical default net names would merge the channels' nets on the board
497 m_schematic->RebuildConnectivity();
498 std::vector<wxString> names = SCH_CONNECTIVITY::NAVIGATION_QUERY( *m_schematic ).NetNames();
499
500 for( const SCH_SHEET_PATH& channel : channels )
501 {
502 wxString prefix = wxS( "(" ) + symbol->GetRef( &channel ) + wxS( "-" );
503 BOOST_CHECK( std::ranges::any_of( names, [&]( const wxString& n ) { return n.Contains( prefix ); } ) );
504 }
505}
506
507
508BOOST_AUTO_TEST_CASE( UserTextMapsToBase )
509{
510 Load( wxS( "issue13212" ) );
511
512 std::vector<SCH_SHEET_PATH> channels = SuffixChannels();
513 SCH_SYMBOL* symbol = FirstSymbol( channels[1] );
515
516 channels[0].AppendSymbol( refs, symbol, SYMBOL_FILTER_ALL );
517 channels[1].AppendSymbol( refs, symbol, SYMBOL_FILTER_ALL );
518
519 // The board carries composed references, and channels of one file must not split onto two bases
520 std::vector<BACKANNOTATE_REF> same = ResolveBackannotatedRefs( { { &refs[0], wxS( "R7_M1" ) },
521 { &refs[1], wxS( "R7_M2" ) } }, true );
522 std::vector<BACKANNOTATE_REF> split = ResolveBackannotatedRefs( { { &refs[0], wxS( "R7_M1" ) },
523 { &refs[1], wxS( "R1_M2" ) } }, true );
524
525 BOOST_CHECK_EQUAL( same[0].m_base, wxS( "R7" ) );
526 BOOST_CHECK( !split[0].m_error.IsEmpty() );
527
528 SCH_SEARCH_DATA data;
529 data.replaceReferences = true;
531 data.findString = symbol->GetBaseRef( &channels[0] );
532 data.replaceString = wxS( "R99" );
533
534 symbol->GetField( FIELD_T::REFERENCE )->Replace( data, &channels[0] );
535 BOOST_CHECK_EQUAL( symbol->GetRef( &channels[0] ), wxS( "R99_M1" ) );
536
537 // Replacing into the suffix would store part of it in the base
538 data.findString = wxS( "_M1" );
539 data.replaceString = wxS( "_X" );
540 BOOST_CHECK( !symbol->GetField( FIELD_T::REFERENCE )->Replace( data, &channels[0] ) );
541
542 // Typing the shown reference must not double its suffix, and in suffix mode the edit reaches every channel
543 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = true;
544 m_schematic->SetCurrentSheet( channels[1] );
545 symbol->SetRefProp( wxS( "R77_M2" ) );
546 BOOST_CHECK_EQUAL( symbol->GetRef( &channels[0] ), wxS( "R77_M1" ) );
547
548 kiapi::schematic::types::SchematicSymbolInstance proto;
549 BOOST_REQUIRE( PackSymbol( &proto, symbol, channels[1] ) );
550 BOOST_CHECK_EQUAL( proto.reference_field().text().text(), "R77_M2" );
551
552 // A dialog writes back the base it loaded, and a base that happens to end in its suffix must survive that
553 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = false;
554 symbol->SetRef( &channels[1], wxS( "R5_M2" ) );
555 symbol->SetRefProp( symbol->GetBaseRef( &channels[1] ) );
556 BOOST_CHECK_EQUAL( symbol->GetBaseRef( &channels[1] ), wxS( "R5_M2" ) );
557
558 // A sibling suffix that ends in this channel's suffix still names the sibling
559 channels[0].Last()->GetField( FIELD_T::SHEET_REF_SUFFIX )->SetText( wxS( "_A" ) );
560 channels[1].Last()->GetField( FIELD_T::SHEET_REF_SUFFIX )->SetText( wxS( "_B_A" ) );
561 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = true;
562 m_schematic->RefreshHierarchy();
563
564 wxString base;
565 BOOST_CHECK( !symbol->BaseRefFromUser( channels[0], wxS( "R8_B_A" ), base ).IsEmpty() );
566
567 // The shown reference echoed back through the API keeps its base
568 kiapi::common::commands::ItemStatus status;
569 symbol->SetRef( &channels[0], wxS( "R8_B" ) );
570 BOOST_REQUIRE( PackSymbol( &proto, symbol, channels[0] ) );
571 BOOST_CHECK( ApplySymbolInstance( &status, symbol, proto, channels[0], m_schematic.get() ) );
572 BOOST_CHECK_EQUAL( symbol->GetBaseRef( &channels[0] ), wxS( "R8_B" ) );
573}
574
575
577{
578 LoadCopy();
579
580 // Without suffixes the two channels of one file cannot share a base
581 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = true;
582 BOOST_CHECK_EQUAL( CountErcMarkers( ERCE_DUPLICATE_SHEET_SUFFIX ), 1 );
583
584 // R7 and R8 are a valid separate numbering but disagree in suffix mode
585 std::vector<SCH_SHEET_PATH> channels = SuffixChannels();
586 const KIID_PATH firstPath = channels[0].Path();
587 BOOST_CHECK_EQUAL( CountErcMarkers( ERCE_REF_SUFFIX_MISMATCH ), 1 );
588
589 // Configured suffix collisions do not matter when separate numbering ignores them
590 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = false;
591 channels[1].Last()->GetField( FIELD_T::SHEET_REF_SUFFIX )->SetText( wxS( "_M1" ) );
592 m_schematic->RefreshHierarchy();
593 BOOST_CHECK_EQUAL( CountErcMarkers( ERCE_DUPLICATE_SHEET_SUFFIX ), 0 );
594
595 channels[1].Last()->GetField( FIELD_T::SHEET_REF_SUFFIX )->SetText( wxS( "_M2" ) );
596 m_schematic->RefreshHierarchy();
597
598 // A channel file saved before its parent gained a suffix keeps plain refs
599 SCH_IO_KICAD_SEXPR().SaveSchematicFile( RootFile().GetFullPath(), m_schematic->GetTopLevelSheets()[0],
600 m_schematic.get() );
601 LoadSchematic( RootFile() );
602 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = false;
603 m_schematic->RefreshRefSuffixes();
604 BOOST_CHECK_EQUAL( CountErcMarkers( ERCE_REF_SUFFIX_MISMATCH ), 0 );
605 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = true;
606 m_schematic->RefreshRefSuffixes();
607 BOOST_CHECK_EQUAL( CountErcMarkers( ERCE_REF_SUFFIX_MISMATCH ), 2 );
608
609 std::optional<SCH_SHEET_PATH> path = m_schematic->Hierarchy().GetSheetPathByKIIDPath( firstPath );
611
612 FirstSymbol( *path )->SetRef( &*path, wxS( "R99" ) );
613 BOOST_CHECK_EQUAL( CountErcMarkers( ERCE_REF_SUFFIX_MISMATCH ), 1 );
614}
615
616
617BOOST_AUTO_TEST_CASE( ErcChecksComposedPackageValues )
618{
619 std::vector<SCH_SHEET_PATH> channels = LoadAmpliChannels();
620 SCH_SHEET_PATH root = m_schematic->Hierarchy()[0];
621 SCH_SYMBOL* source = FindSymbol( channels[0], wxS( "U201_V" ), 1 );
623 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = false;
624
625 SCH_SYMBOL* unitA = new SCH_SYMBOL( *source->GetLibSymbolRef(), source->GetLibId(), &channels[0], 1 );
626 SCH_SYMBOL* unitB = new SCH_SYMBOL( *source->GetLibSymbolRef(), source->GetLibId(), &root, 2 );
627 channels[0].LastScreen()->Append( unitA );
628 root.LastScreen()->Append( unitB );
629 unitA->SetRef( &channels[0], wxS( "U777" ) );
630 unitA->SetRef( &channels[1], wxS( "U778" ) );
631 unitB->SetRef( &root, wxS( "U777_V2" ) );
632 unitA->GetField( FIELD_T::VALUE )->SetText( wxS( "first-value" ) );
633 unitB->GetField( FIELD_T::VALUE )->SetText( wxS( "second-value" ) );
634
635 channels[0].Last()->GetField( FIELD_T::SHEET_REF_SUFFIX )->SetText( wxS( "_V2" ) );
636 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = true;
637 m_schematic->RefreshHierarchy();
638
639 BOOST_REQUIRE_EQUAL( unitA->GetRef( &channels[0] ), unitB->GetRef( &root ) );
640 BOOST_CHECK_EQUAL( CountErcMarkers( ERCE_DIFFERENT_UNIT_VALUE ), 1 );
641
642 unitB->GetField( FIELD_T::VALUE )->SetText( wxS( "first-value" ) );
643 BOOST_CHECK_EQUAL( CountErcMarkers( ERCE_DIFFERENT_UNIT_VALUE ), 0 );
644}
645
646
647// The channel file starts with R7 and R8, which suffix mode must unify into one base
648BOOST_AUTO_TEST_CASE( SuffixModeSharesOneBase )
649{
650 Load( wxS( "issue13212" ) );
651
652 std::vector<SCH_SHEET_PATH> channels = SuffixChannels();
653 SCH_SYMBOL* symbol = FirstSymbol( channels[0] );
654
655 AnnotateAll( true );
656
657 BOOST_CHECK_EQUAL( symbol->GetRef( &channels[1] ), symbol->GetBaseRef( &channels[0] ) + wxS( "_M2" ) );
658
659 // Seven symbols take seven numbers, the channels' shared base among them
660 std::set<wxString> bases;
661
662 for( const SCH_SHEET_PATH& path : m_schematic->Hierarchy() )
663 {
664 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
665 bases.insert( static_cast<SCH_SYMBOL*>( item )->GetBaseRef( &path ) );
666 }
667
668 BOOST_CHECK( bases == std::set<wxString>( { wxS( "R1" ), wxS( "R2" ), wxS( "R3" ), wxS( "R4" ), wxS( "R5" ),
669 wxS( "R6" ), wxS( "R7" ) } ) );
670
671 // A symbol placed on a reused sheet takes one base for every channel
672 for( const SCH_SHEET_PATH& channel : channels )
673 symbol->SetRef( &channel, symbol->GetPrefix() + wxS( "?" ) );
674
675 AnnotatePlaced( symbol, channels );
676
677 BOOST_CHECK( !symbol->GetBaseRef( &channels[0] ).EndsWith( wxS( "?" ) ) );
678 BOOST_CHECK_EQUAL( symbol->GetBaseRef( &channels[1] ), symbol->GetBaseRef( &channels[0] ) );
679}
680
681
682BOOST_AUTO_TEST_CASE( SeparateNumberingIgnoresConfiguredSuffixes )
683{
684 Load( wxS( "issue13212" ) );
685 std::vector<SCH_SHEET_PATH> channels = SuffixChannels();
686 SCH_SYMBOL* symbol = FirstSymbol( channels[0] );
687 BOOST_REQUIRE( symbol );
688
689 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = false;
690 m_schematic->RefreshRefSuffixes();
691
692 BOOST_CHECK_EQUAL( channels[0].Last()->GetRefSuffix( m_schematic->Hierarchy()[0] ), wxS( "_M1" ) );
693 BOOST_CHECK( !m_schematic->HasRefSuffixes() );
694 BOOST_CHECK( m_schematic->GetRefSuffix( channels[0] ).IsEmpty() );
695 BOOST_CHECK_EQUAL( symbol->GetRef( &channels[0] ), symbol->GetBaseRef( &channels[0] ) );
696
697 symbol->SetRef( &channels[0], wxS( "R?" ) );
700 channels[0].AppendSymbol( refs, symbol, SYMBOL_FILTER_NON_POWER );
701 channels[0].AppendMultiUnitSymbol( locked, symbol, SYMBOL_FILTER_NON_POWER );
703 true );
704
705 BOOST_CHECK_EQUAL( symbol->GetRef( &channels[0] ), symbol->GetBaseRef( &channels[0] ) );
706 BOOST_CHECK( !symbol->GetRef( &channels[0] ).EndsWith( wxS( "_M1" ) ) );
707
708 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = true;
709 m_schematic->RefreshRefSuffixes();
710 BOOST_CHECK_EQUAL( symbol->GetRef( &channels[0] ), symbol->GetBaseRef( &channels[0] ) + wxS( "_M1" ) );
711}
712
713
714BOOST_AUTO_TEST_CASE( SeparateNumberingResetUsesDistinctPlainReferences )
715{
716 Load( wxS( "issue13212" ) );
717 std::vector<SCH_SHEET_PATH> channels = SuffixChannels();
718 SCH_SHEET_PATH root = m_schematic->Hierarchy()[0];
719 SCH_SYMBOL* channel = FirstSymbol( channels[0] );
720 SCH_SYMBOL* other = FindSymbol( root, wxS( "R1" ) );
721 BOOST_REQUIRE( channel && other );
722
723 other->SetRef( &root, wxS( "R1_M2" ) );
724 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = false;
725 m_schematic->RefreshRefSuffixes();
726
727 SCH_SHEET_LIST sheets = m_schematic->Hierarchy();
730 sheets.GetSymbols( refs, SYMBOL_FILTER_NON_POWER );
733
734 BOOST_CHECK_EQUAL( CountDuplicates(), 0 );
735 BOOST_CHECK_NE( channel->GetRef( &channels[0] ), channel->GetRef( &channels[1] ) );
736 BOOST_CHECK_EQUAL( channel->GetRef( &channels[0] ), channel->GetBaseRef( &channels[0] ) );
737 BOOST_CHECK_EQUAL( channel->GetRef( &channels[1] ), channel->GetBaseRef( &channels[1] ) );
738 BOOST_CHECK( !channel->GetRef( &channels[0] ).EndsWith( wxS( "_M1" ) ) );
739 BOOST_CHECK( !channel->GetRef( &channels[1] ).EndsWith( wxS( "_M2" ) ) );
740}
741
742
743BOOST_AUTO_TEST_CASE( SuffixModeNewReferenceAvoidsComposedCollision )
744{
745 Load( wxS( "issue13212" ) );
746
747 std::vector<SCH_SHEET_PATH> channels = SuffixChannels();
748 SCH_SHEET_PATH root = m_schematic->Hierarchy()[0];
749 SCH_SYMBOL* channel = FirstSymbol( channels[0] );
750 SCH_SYMBOL* other = FindSymbol( root, wxS( "R1" ) );
751 BOOST_REQUIRE( channel && other );
752
753 other->SetRef( &root, wxS( "R1_M2" ) );
754
755 for( const SCH_SHEET_PATH& path : channels )
756 channel->SetRef( &path, wxS( "R?" ) );
757
758 AnnotateSelection( { channel }, channels[0] );
759
760 BOOST_CHECK_EQUAL( other->GetRef( &root ), wxS( "R1_M2" ) );
761 BOOST_CHECK_EQUAL( channel->GetRef( &channels[0] ), wxS( "R7_M1" ) );
762 BOOST_CHECK_EQUAL( channel->GetRef( &channels[1] ), wxS( "R7_M2" ) );
763}
764
765
766// A variable can give a suffix that runs into the base number, so R91 with suffix 2 reads R912
767BOOST_AUTO_TEST_CASE( SuffixModeRenumbersComposedCollision )
768{
769 Load( wxS( "issue13212" ) );
770
771 for( const SCH_SHEET_PATH& path : PathsOf( wxS( "issue13212_subsheet_2.kicad_sch" ) ) )
772 path.Last()->AddField( SCH_FIELD( path.Last(), FIELD_T::SHEET_USER, wxS( "CH" ) ) )->SetText( wxS( "2" ) );
773
774 std::vector<SCH_SHEET_PATH> channels = SuffixChannels( wxS( "_A" ), wxS( "${CH}" ) );
775 std::vector<SCH_SHEET_PATH> singles = PathsOf( wxS( "issue13212_subsheet_1.kicad_sch" ) );
776 BOOST_REQUIRE( !singles.empty() && m_schematic->GetRefSuffix( channels[1] ) == wxS( "2" ) );
777
778 SCH_SHEET_PATH single = singles[0];
779 SCH_SYMBOL* channel = FirstSymbol( channels[0] );
780 SCH_SYMBOL* other = FirstSymbol( single );
781
782 channel->SetRef( &channels[0], wxS( "R91" ) );
783 channel->SetRef( &channels[1], wxS( "R92" ) );
784 other->SetRef( &single, wxS( "R912" ) );
785 AnnotateSelection( { channel }, channels[0] );
786
787 BOOST_CHECK_NE( channel->GetRef( &channels[1] ), other->GetRef( &single ) );
788
789 // R91 with suffix A1 and a bare R91A1 split into different prefixes yet show the same reference
790 channels[1].Last()->GetField( FIELD_T::SHEET_REF_SUFFIX )->SetText( wxS( "A1" ) );
791 m_schematic->RefreshHierarchy();
792
793 channel->SetRef( &channels[0], wxS( "R91" ) );
794 channel->SetRef( &channels[1], wxS( "R91" ) );
795 other->SetRef( &single, wxS( "R91A1" ) );
796 BOOST_CHECK_EQUAL( CountDuplicates(), 1 );
797
798 // Two kept bases in one run differ yet compose alike
799 channel->SetRef( &channels[1], wxS( "R92" ) );
800 AnnotateAll();
801
802 BOOST_CHECK_NE( channel->GetRef( &channels[1] ), other->GetRef( &single ) );
803}
804
805
806BOOST_AUTO_TEST_CASE( SuffixModeAnnotatesPackages )
807{
808 std::vector<SCH_SHEET_PATH> channels = LoadAmpliChannels();
809 SCH_SYMBOL* x = FindSymbol( channels[0], wxS( "R203_V" ) );
810 SCH_SYMBOL* y = FindSymbol( channels[0], wxS( "R213_V" ) );
811 SCH_SYMBOL* unitA = FindSymbol( channels[0], wxS( "U201_V" ), 1 );
812 SCH_SYMBOL* unitB = FindSymbol( channels[0], wxS( "U201_V" ), 2 );
813 BOOST_REQUIRE( x && y && unitA && unitB );
814
815 // Separate numbering may swap bases between channels, and sharing X's kept base must not land on Y
816 x->SetRef( &channels[1], wxS( "R213" ) );
817 y->SetRef( &channels[1], wxS( "R203" ) );
818 AnnotateSelection( { x }, channels[1] );
819
820 BOOST_CHECK_EQUAL( x->GetBaseRef( &channels[1] ), x->GetBaseRef( &channels[0] ) );
821 BOOST_CHECK_NE( x->GetRef( &channels[1] ), y->GetRef( &channels[1] ) );
822
823 // Only unit A's base and unit are taken on the other channel, so renumbering it must take unit B along
824 x->SetRef( &channels[1], wxS( "U201" ) );
825 AnnotateSelection( { unitA, unitB }, channels[0] );
826
827 BOOST_CHECK_EQUAL( unitA->GetBaseRef( &channels[0] ), unitB->GetBaseRef( &channels[0] ) );
828 BOOST_CHECK_NE( unitA->GetBaseRef( &channels[0] ), wxS( "U201" ) );
829
830 // A placed unit B joins the U1 package unit A holds on every channel
831 for( const SCH_SHEET_PATH& channel : channels )
832 {
833 unitA->SetRef( &channel, wxS( "U1" ) );
834 unitB->SetRef( &channel, wxS( "U?" ) );
835 }
836
837 m_schematic->Settings().m_AnnotateMethod = ANNOTATE_ALGO_T::INCREMENTAL_BY_REF;
838 m_schematic->Settings().m_AnnotateStartNum = 0;
839 AnnotatePlaced( unitB, channels );
840
841 BOOST_CHECK_EQUAL( unitB->GetBaseRef( &channels[0] ), wxS( "U1" ) );
842 BOOST_CHECK_EQUAL( unitB->GetBaseRef( &channels[1] ), wxS( "U1" ) );
843
844 // Copies that select different units pair into packages instead of sharing a base
845 unitA->SetUnitSelection( &channels[1], 2 );
846 unitB->SetUnitSelection( &channels[1], 1 );
847 unitA->ClearAnnotation( nullptr, false );
848 unitB->ClearAnnotation( nullptr, false );
849 AnnotateAll();
850
851 std::map<wxString, std::set<int>> packages;
852
853 for( SCH_SYMBOL* symbol : { unitA, unitB } )
854 {
855 for( const SCH_SHEET_PATH& channel : channels )
856 packages[symbol->GetRef( &channel )].insert( symbol->GetUnitSelection( &channel ) );
857 }
858
859 BOOST_CHECK_EQUAL( packages.size(), 2 );
860
861 for( const auto& [package, units] : packages )
862 BOOST_CHECK_MESSAGE( !package.Contains( wxS( "?" ) ) && units == std::set<int>( { 1, 2 } ), package );
863}
864
865
866// Older versions store every unit of a package with its lowest suffix, which loading must strip
867BOOST_AUTO_TEST_CASE( LoadStripsDowngradedPackageSuffix )
868{
869 std::vector<SCH_SHEET_PATH> channels = LoadAmpliChannels();
870 SCH_SYMBOL* unitA = FindSymbol( channels[0], wxS( "U201_V" ), 1 );
871 SCH_SYMBOL* unitB = FindSymbol( channels[0], wxS( "U201_V" ), 2 );
872 BOOST_REQUIRE( unitA && unitB );
873
874 // As a v9 file stores U9 (unit A on _H, unit B on _V) and U8 (unit A on _V, unit B on _H)
875 unitA->SetRef( &channels[1], wxS( "U9_H" ) );
876 unitB->SetRef( &channels[0], wxS( "U9_H" ) );
877 unitA->SetRef( &channels[0], wxS( "U8_H" ) );
878 unitB->SetRef( &channels[1], wxS( "U8_H" ) );
879
880 m_schematic->Hierarchy().StripRefSuffixes();
881
882 BOOST_CHECK_EQUAL( unitB->GetBaseRef( &channels[0] ), wxS( "U9" ) );
883 BOOST_CHECK_EQUAL( unitA->GetBaseRef( &channels[0] ), wxS( "U8" ) );
884
885 // Each top level sheet strips its references alone, so a package spanning two recovers once both exist.
886 // U9 (A on _H2_V, B on _V) keeps U9_H2_V whole and U7 (B on _H2_V, A on _V) strips U7_H2_V to U7_H2.
887 channels[1].Last()->GetField( wxS( "Reference Suffix" ) )->SetText( wxS( "_H2_V" ) );
888 m_schematic->RefreshHierarchy();
889 unitA->SetRef( &channels[1], wxS( "U9" ) );
890 unitB->SetRef( &channels[0], wxS( "U9_H2_V" ) );
891 unitB->SetRefLacksSuffix( channels[0].Path() );
892 unitB->SetRef( &channels[1], wxS( "U7" ) );
893 unitA->SetRef( &channels[0], wxS( "U7_H2" ) );
894
895 SCH_SHEET* other = new SCH_SHEET( m_schematic.get() );
896 SCH_SCREEN* screen = new SCH_SCREEN( m_schematic.get() );
897 other->SetScreen( screen );
898 screen->SetFileName( wxS( "other.kicad_sch" ) );
899
900 m_schematic->SetTopLevelSheets( { m_schematic->GetTopLevelSheets()[0], other } );
901
902 BOOST_CHECK_EQUAL( unitB->GetBaseRef( &channels[0] ), wxS( "U9" ) );
903 BOOST_CHECK_EQUAL( unitA->GetBaseRef( &channels[0] ), wxS( "U7" ) );
904}
905
906
907// Every output names a part by the reference the schematic composes for it
908BOOST_AUTO_TEST_CASE( OutputsAgreeOnComposedRefs )
909{
910 std::vector<SCH_SHEET_PATH> channels = LoadAmpliChannels();
911 SCH_SYMBOL* unitA = FindSymbol( channels[0], wxS( "U201_V" ), 1 );
912 SCH_SYMBOL* unitB = FindSymbol( channels[0], wxS( "U201_V" ), 2 );
913 SCH_SYMBOL* low = FindSymbol( channels[0], wxS( "R212_V" ) );
914 BOOST_REQUIRE( unitA && unitB && low );
915
916 KI_TEST::SCOPED_TEMP_DIR temp( "ref-suffix-outputs" );
918 const wxString netFile = temp.PathStr() + wxS( "/netlist.net" );
919 const wxString xmlFile = temp.PathStr() + wxS( "/netlist.xml" );
920
921 auto read =
922 []( const wxString& aFile )
923 {
924 wxString text;
925 BOOST_REQUIRE( wxFFile( aFile, wxS( "r" ) ).ReadAll( &text ) );
926 return text;
927 };
928
929 // Allegro groups like parts by prefix and number, and R30_V sorts after R301_H as text
930 low->SetRef( &channels[0], wxS( "R30" ) );
931 BOOST_REQUIRE( NETLIST_EXPORTER_ALLEGRO( m_schematic.get(), nullptr ).WriteNetlist( netFile, 0, reporter ) );
932 BOOST_CHECK( read( netFile ).Contains( wxS( "; R30_V,\n\tR201_V,\n\tR208_V,\n\tR301_H," ) ) );
933
934 // Suffix mode gives both channels one base, so U201 is a dual op amp in each channel
935 m_schematic->Settings().m_AnnotateReusedSheetSuffixes = true;
936
937 for( SCH_ITEM* item : channels[0].LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
938 {
939 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
940
941 if( !symbol->GetRef( &channels[0] ).StartsWith( wxS( "#" ) ) )
942 symbol->SetRef( &channels[1], symbol->GetBaseRef( &channels[0] ) );
943 }
944
945 BOOST_CHECK_EQUAL( CountDuplicates(), 0 );
946
947 // Annotation never shares power symbol refs, so their per-channel numbers are not a mismatch
948 BOOST_CHECK_EQUAL( CountErcMarkers( ERCE_REF_SUFFIX_MISMATCH ), 0 );
949
951 m_schematic->Hierarchy().GetMultiUnitSymbols( packages, SYMBOL_FILTER_NON_POWER );
952 BOOST_CHECK_EQUAL( packages[wxS( "U201_V" )].GetCount(), 2 );
953 BOOST_CHECK_EQUAL( packages[wxS( "U201_H" )].GetCount(), 2 );
954
955 // U9 spans the channels, so one footprint U9_H holds unit A on _H and unit B on _V
956 unitA->SetRef( &channels[0], wxS( "U8" ) );
957 unitB->SetRef( &channels[1], wxS( "U8" ) );
958 unitA->SetRef( &channels[1], wxS( "U9" ) );
959 unitB->SetRef( &channels[0], wxS( "U9" ) );
960
961 BOOST_CHECK_EQUAL( unitB->GetRef( &channels[0] ), wxS( "U9_H" ) );
962 BOOST_CHECK_EQUAL( unitB->GetRef( &channels[0], true ), wxS( "U9B_V" ) );
963
964 std::set<wxString> expected;
965
966 for( const SCH_SHEET_PATH& path : m_schematic->Hierarchy() )
967 {
968 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
969 {
970 const wxString ref = static_cast<SCH_SYMBOL*>( item )->GetRef( &path );
971
972 if( !ref.StartsWith( wxS( "#" ) ) )
973 expected.insert( ref );
974 }
975 }
976
977 auto refsIn =
978 []( const wxString& aText, const wxString& aPattern )
979 {
980 std::set<wxString> refs;
981 wxRegEx re( aPattern, wxRE_ADVANCED );
982 wxString rest = aText;
983 size_t start = 0;
984 size_t len = 0;
985
986 while( re.Matches( rest ) && re.GetMatch( &start, &len, 0 ) )
987 {
988 refs.insert( re.GetMatch( rest, 1 ) );
989 rest = rest.Mid( start + len );
990 }
991
992 return refs;
993 };
994
995 // Update PCB reads this netlist
996 STRING_FORMATTER formatter;
997 NETLIST_EXPORTER_KICAD( m_schematic.get(), nullptr ).Format( &formatter, GNL_ALL | GNL_OPT_KICAD );
998 BOOST_CHECK( refsIn( formatter.GetString(), wxS( "\\(comp \\(ref \"([^\"]+)\"\\)" ) ) == expected );
999
1000 BOOST_REQUIRE( NETLIST_EXPORTER_XML( m_schematic.get(), nullptr ).WriteNetlist( xmlFile, 0, reporter ) );
1001 BOOST_CHECK( refsIn( read( xmlFile ), wxS( "<comp ref=\"([^\"]+)\"" ) ) == expected );
1002
1003 // Allegro starts each package's references after "; " and puts the rest on tab-indented lines
1004 BOOST_REQUIRE( NETLIST_EXPORTER_ALLEGRO( m_schematic.get(), nullptr ).WriteNetlist( netFile, 0, reporter ) );
1005
1006 wxString allegro = read( netFile );
1007 allegro = allegro.Mid( allegro.Find( wxS( "$PACKAGES\n" ) ) + 10 );
1008 BOOST_CHECK( refsIn( allegro.Left( allegro.Find( wxS( "$" ) ) ), wxS( "(?:; |\\n\\t)([^,\\s]+)" ) ) == expected );
1009
1010 SCH_REFERENCE_LIST refs;
1011 m_schematic->Hierarchy().GetSymbols( refs, SYMBOL_FILTER_NON_POWER );
1012
1014 bom.AddColumn( GetDefaultFieldName( FIELD_T::REFERENCE, UNTRANSLATED ), wxS( "Reference" ), false );
1015 bom.SetGroupingEnabled( false );
1016 bom.RebuildRows();
1017
1018 std::set<wxString> listed;
1019
1020 for( int row = 0; row < bom.GetNumberRows(); ++row )
1021 {
1022 wxStringTokenizer tokens( bom.GetValue( row, 0 ), wxS( ", " ), wxTOKEN_STRTOK );
1023
1024 while( tokens.HasMoreTokens() )
1025 listed.insert( tokens.GetNextToken() );
1026 }
1027
1028 BOOST_CHECK( listed == expected );
1029
1030 // With the unit that names the package left off the board, the other unit still places it
1031 channels[1].Last()->GetField( wxS( "Reference Suffix" ) )->SetText( wxS( "_Z" ) );
1032 m_schematic->RefreshHierarchy();
1033 unitB->SetExcludedFromBoard( true, &channels[0] );
1034
1035 formatter.Clear();
1036 NETLIST_EXPORTER_KICAD( m_schematic.get(), nullptr ).Format( &formatter, GNL_ALL | GNL_OPT_KICAD );
1037
1038 wxString netlist = formatter.GetString();
1039 int comp = netlist.Find( wxS( "(comp (ref \"U9_V\")" ) );
1040 BOOST_REQUIRE( comp != wxNOT_FOUND );
1041
1042 wxString block = netlist.Mid( comp + 1 );
1043 block = block.Left( block.Find( wxS( "(comp " ) ) );
1044 BOOST_CHECK( block.Contains( wxS( "(tstamps \"" ) + channels[1].PathAsString() + wxS( "\")" ) ) );
1045}
1046
1047
KICAD_PLUGIN_EXPORT SCENEGRAPH * Load(char const *aFileName)
Read a model file and creates a generic display structure.
const char * name
bool ApplySymbolInstance(kiapi::common::commands::ItemStatus *aStatus, SCH_SYMBOL *aSymbol, const kiapi::schematic::types::SchematicSymbolInstance &aInput, const SCH_SHEET_PATH &aPath, SCHEMATIC *aSchematic)
Apply placement-specific data to an aSymbol at aPath: reference, unit, and the per-placement attribut...
bool PackSymbol(kiapi::schematic::types::SchematicSymbolInstance *aOutput, const SCH_SYMBOL *aInput, const SCH_SHEET_PATH &aPath, const SCHEMATIC *aSchematic)
std::vector< BACKANNOTATE_REF > ResolveBackannotatedRefs(const std::vector< std::pair< const SCH_REFERENCE *, wxString > > &aRequests, bool aShareBases)
Turn the references footprints carry into the base references of their schematic instances.
bool IsContentModified() const
Definition base_screen.h:56
void SetContentModified(bool aModified=true)
Definition base_screen.h:55
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
const KIID m_Uuid
Definition eda_item.h:599
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
void RunTests(DS_PROXY_VIEW_ITEM *aDrawingSheet, SCH_EDIT_FRAME *aEditFrame, KIFACE *aCvPcb, PROJECT *aProject, PROGRESS_REPORTER *aProgressReporter)
Definition erc.cpp:3499
void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser) override
wxString GetValue(int aRow, int aCol) override
Definition kiid.h:46
A generic fixture for loading schematics and associated settings for qa tests.
std::unique_ptr< SCHEMATIC > m_schematic
virtual void LoadSchematic(const wxFileName &aFn)
wxString PathStr() const
Get the path to the temporary directory as a wxString.
Definition file_utils.h:64
Generate a netlist compatible with Allegro.
bool WriteNetlist(const wxString &aOutFileName, unsigned aNetlistOptions, REPORTER &aReporter)
Write to specified output file.
Generate the KiCad netlist format supported by Pcbnew.
void Format(OUTPUTFORMATTER *aOutputFormatter, int aCtl)
Output this s-expression netlist into aOutputFormatter.
Generate a generic XML based netlist file.
A singleton reporter that reports to nowhere.
Definition reporter.h:267
static std::vector< SCH_SHEET * > SubSheets(SCH_SCREEN *aScreen)
void AnnotateAll(bool aResetAnnotation=false)
int CountErcMarkers(int aCode)
std::vector< SCH_SHEET_PATH > LoadAmpliChannels()
void Annotate(SCH_REFERENCE_LIST &aRefs, const SCH_MULTI_UNIT_REFERENCE_MAP &aLocked, ANNOTATE_SCOPE_T aScope, bool aResetAnnotation)
std::vector< SCH_SHEET_PATH > SuffixChannels(const wxString &aFirst=wxS("_M1"), const wxString &aSecond=wxS("_M2"), const wxString &aFile=wxS("issue13212_subsheet_2.kicad_sch"))
wxFileName RootFile() const
static SCH_SYMBOL * FirstSymbol(const SCH_SHEET_PATH &aPath)
void AnnotateSelection(const std::vector< SCH_SYMBOL * > &aSymbols, const SCH_SHEET_PATH &aPath)
std::vector< SCH_SHEET_PATH > PathsOf(const wxString &aFile) const
static void AddSuffixField(SCH_SHEET *aSheet, const wxString &aText)
static SCH_SYMBOL * FindSymbol(const SCH_SHEET_PATH &aPath, const wxString &aRef, int aUnit=1)
void AnnotatePlaced(SCH_SYMBOL *aSymbol, const std::vector< SCH_SHEET_PATH > &aChannels)
void Load(const wxString &aPath)
~REF_SUFFIX_FIXTURE() override
Holds all the data relating to one schematic.
Definition schematic.h:153
void Reset()
Initialize this schematic to a blank one, unloading anything existing.
SCH_SHEET_LIST Hierarchy() const
Return the full schematic flattened hierarchical sheet list.
void RefreshHierarchy()
Commit for the schematic and symbol editors.
Definition sch_commit.h:54
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
One UI operation's hierarchy index.
std::vector< wxString > NetNames() const
bool Replace(const EDA_SEARCH_DATA &aSearchData, void *aAuxData=nullptr) override
Perform a text replace using the find and replace criteria in aSearchData on items that support text ...
void SetOrdinal(int aOrdinal, FIELD_T aType)
virtual const wxString & GetText() const override
Return the string associated with the text object.
Definition sch_field.h:139
FIELD_T GetId() const
Definition sch_field.h:143
wxString GetShownText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString, int aDepth=0) const
void SetName(const wxString &aName)
void SetText(const wxString &aText) override
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:170
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
void SortByReferenceOnly()
Sort the list of references by reference.
int CheckAnnotation(ANNOTATION_ERROR_HANDLER aErrorHandler)
Check for annotations errors.
A helper to define a symbol's reference designator in a schematic.
Container class that holds multiple SCH_SCREEN objects in a hierarchy.
Definition sch_screen.h:826
SCH_SCREEN * GetNext()
SCH_SCREEN * GetFirst()
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
void GetMultiUnitSymbols(SCH_MULTI_UNIT_REFERENCE_MAP &aRefList, SYMBOL_FILTER aSymbolFilter) const
Add a SCH_REFERENCE_LIST object to aRefList for each same-reference set of multi-unit parts in the li...
void AddNewSymbolInstances(const SCH_SHEET_PATH &aPrefixSheetPath, const wxString &aProjectName, bool aStripRefSuffixes=false)
Attempt to add new symbol instances for all symbols in this list of sheet paths prefixed with aPrefix...
void GetSymbols(SCH_REFERENCE_LIST &aReferences, SYMBOL_FILTER aSymbolFilter, bool aForceIncludeOrphanSymbols=false) const
Add a SCH_REFERENCE object to aReferences for each symbol in the list of sheets.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
void AppendSymbol(SCH_REFERENCE_LIST &aReferences, SCH_SYMBOL *aSymbol, SYMBOL_FILTER aSymbolFilter, bool aForceIncludeOrphanSymbols=false) const
Append a SCH_REFERENCE object to aReferences based on aSymbol.
size_t GetCurrentHash() const
SCH_SCREEN * LastScreen()
SCH_SHEET * at(size_t aIndex) const
Forwarded method from std::vector.
void AppendMultiUnitSymbol(SCH_MULTI_UNIT_REFERENCE_MAP &aRefList, SCH_SYMBOL *aSymbol, SYMBOL_FILTER aSymbolFilter) const
Append a SCH_REFERENCE_LIST object to aRefList based on aSymbol, storing same-reference set of multi-...
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
size_t size() const
Forwarded method from std::vector.
Variant information for a schematic sheet.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:50
static wxString NextRefSuffix(const wxString &aSuffix, const std::set< wxString > &aInUse, const std::function< bool(const wxString &)> &aIsTaken=nullptr)
Return the suffix a pasted copy of a sheet should take.
bool HasHierarchyChanges(const SCH_SHEET &aOther) const
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
SCH_FIELD * AddField(const SCH_FIELD &aField)
Add aFieldName to the list of fields.
Schematic symbol object.
Definition sch_symbol.h:76
void SetRefProp(const wxString &aRef)
wxString BaseRefFromUser(const SCH_SHEET_PATH &aSheet, const wxString &aText, wxString &aBase, const SCHEMATIC *aSchematic=nullptr) const
Convert a reference typed on aSheet to the base reference.
wxString GetBaseRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const
void SetRefLacksSuffix(const KIID_PATH &aPath, bool aLacksSuffix=true)
Flag the reference loaded for aPath as saved without its sheet suffix.
void SetRef(const SCH_SHEET_PATH *aSheet, const wxString &aReference)
Set the base reference for the given sheet path for this symbol.
void ClearAnnotation(const SCH_SHEET_PATH *aSheetPath, bool aResetPrefix)
Clear exiting symbol annotation.
bool GetInstance(SCH_SYMBOL_INSTANCE &aInstance, const KIID_PATH &aSheetPath, bool aTestFromEnd=false) const
int GetUnitSelection(const SCH_SHEET_PATH *aSheet) const
Return the instance-specific unit selection for the given sheet path.
void SetUnitSelection(const SCH_SHEET_PATH *aSheet, int aUnitSelection)
Set the selected unit of this symbol on one sheet.
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
wxString GetPrefix() const
Definition sch_symbol.h:241
void SetExcludedFromBoard(bool aEnable, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
Implement an OUTPUTFORMATTER to a memory buffer.
Definition richio.h:430
const std::string & GetString()
Definition richio.h:453
void Clear()
Clear the buffer and empties the internal string.
Definition richio.h:443
Master controller class:
void SetEnvironment(EDA_ITEM *aModel, KIGFX::VIEW *aView, KIGFX::VIEW_CONTROLS *aViewControls, APP_SETTINGS_BASE *aSettings, TOOLS_HOLDER *aFrame)
Set the work environment (model, view, view controls and the parent window).
std::map< wxString, wxString > m_Fields
@ INTERNAL
Definition common.h:96
const std::vector< DOWNGRADE_TARGET > & GetDowngradeTargets()
Supported downgrade targets, newest first.
ERCE_T
ERC error codes.
@ ERCE_REF_SUFFIX_MISMATCH
Saved reference lacks the suffix of its reused sheet.
@ ERCE_DUPLICATE_REFERENCE
More than one symbol with the same reference.
@ ERCE_DUPLICATE_SHEET_SUFFIX
Two uses of one sheet file share a reference suffix.
@ ERCE_DIFFERENT_UNIT_VALUE
Units of same symbol have different values.
static const std::string KiCadSchematicFileExtension
std::string source
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
#define GNL_ALL
@ GNL_OPT_KICAD
#define SKIP_UNDO
Definition sch_commit.h:38
void SaveSchematicForTarget(SCH_SHEET *aSheet, SCHEMATIC *aSchematic, const wxString &aPath, const DOWNGRADE_TARGET &aTarget)
Save a schematic sheet file stamped for the target, using the writer for that version.
static void LoadSchematic(SCHEMATIC *aSchematic, SCH_SHEET *aRootSheet, const wxString &aFileName, REPORTER *aReporter)
std::set< const SCH_SCREEN * > AnnotateReferences(SCH_REFERENCE_LIST &aRefs, SCH_MULTI_UNIT_REFERENCE_MAP aLocked, const SCHEMATIC &aSchematic, ANNOTATE_SCOPE_T aScope, ANNOTATE_ORDER_T aSortOption, ANNOTATE_ALGO_T aAlgoOption, int aStartNumber, bool aResetAnnotation, const std::function< void(SCH_REFERENCE &)> &aBeforeAnnotate)
Annotate aRefs, the references in aScope, around every other symbol of aSchematic.
void AnnotateNewSymbol(SCH_SYMBOL *aSymbol, const std::vector< SCH_SHEET_PATH > &aPaths, SCH_REFERENCE_LIST &aExistingRefs, const SCHEMATIC_SETTINGS &aSettings, SCH_SHEET_LIST *aHierarchy)
Annotate a newly placed symbol on each of aPaths in turn, avoiding aExistingRefs and adding the resul...
std::optional< std::pair< SCH_SHEET_PATH, SCH_SHEET_PATH > > FindRefSuffixCollision(const SCH_REFERENCE_LIST &aRefs, const SCHEMATIC &aSchematic)
Find two instances of a reused sheet that aRefs reaches and that compose the same reference suffix.
@ UNSORTED
Annotate by position of symbol in the schematic sheet object list.
@ SORT_BY_X_POSITION
Annotate by X position from left to right.
ANNOTATE_SCOPE_T
Schematic annotation scope options.
@ ANNOTATE_SELECTION
Annotate the selection.
@ ANNOTATE_ALL
Annotate the full schematic.
@ INCREMENTAL_BY_REF
Annotate incrementally using the first free reference number.
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
std::map< wxString, SCH_REFERENCE_LIST > SCH_MULTI_UNIT_REFERENCE_MAP
Container to map reference designators for multi-unit parts.
@ SYMBOL_FILTER_NON_POWER
@ SYMBOL_FILTER_ALL
static std::vector< std::string > split(const std::string &aStr, const std::string &aDelim)
Split the input string into a vector of output strings.
An older KiCad release we can export a project back to.
EDA_SEARCH_MATCH_MODE matchMode
A simple container for schematic symbol instance information.
wxString GetDefaultFieldName(FIELD_T aFieldId, TRANSLATION aTranslation)
Return a default symbol field name for a mandatory field type.
@ SHEET_REF_SUFFIX
Optional sheet field appended to the references of the sheet's symbols.
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
@ UNTRANSLATED
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_CHECK(res.column_datatype(0)==SQL_NUMERIC||res.column_datatype(0)==SQL_DECIMAL)
BOOST_REQUIRE_EQUAL(rows.size(), 1)
BOOST_CHECK_EQUAL(std::any_cast< std::string >(rows[0].at("val")), "12345678901234567.89")
BOOST_REQUIRE(res.columns()==1)
BOOST_AUTO_TEST_SUITE_END()
std::string netlist
std::string path
IbisParser parser & reporter
KIBIS_COMPONENT * comp
VECTOR3I expected(15, 30, 45)
BOOST_AUTO_TEST_CASE(SuffixTextRules)
BOOST_TEST_CONTEXT("Test Clearance")
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_MARKER_T
Definition typeinfo.h:154
wxString GetFieldValidationErrorMessage(FIELD_T aFieldId, const wxString &aValue)
Return the error message if aValue is invalid for aFieldId.
Custom text control validator definitions.
Definition of file extensions used in Kicad.