KiCad PCB EDA Suite
Loading...
Searching...
No Matches
refdes_tracker.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 * KiCad is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * KiCad 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 KiCad. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <regex>
21#include <algorithm>
22#include <iostream>
23
24#include <sch_reference_list.h>
25
26#include "refdes_tracker.h"
27
28REFDES_TRACKER::REFDES_TRACKER( bool aThreadSafe ) :
29 m_threadSafe( aThreadSafe ), m_reuseRefDes( true )
30{
31}
32
33bool REFDES_TRACKER::Insert( const std::string& aRefDes )
34{
35 std::unique_lock<std::mutex> lock;
36
37 if( m_threadSafe )
38 lock = std::unique_lock<std::mutex>( m_mutex );
39
40 return insertImpl( aRefDes );
41}
42
43bool REFDES_TRACKER::insertImpl( const std::string& aRefDes )
44{
45 if( m_allRefDes.find( aRefDes ) != m_allRefDes.end() )
46 return false;
47
48 auto [prefix, number] = parseRefDes( aRefDes );
49
50 m_allRefDes.insert( aRefDes );
51
52 // Insert the number and update caches
53 return insertNumber( prefix, number );
54}
55
56bool REFDES_TRACKER::insertNumber( const std::string& aPrefix, int aNumber )
57{
58 PREFIX_DATA& data = m_prefixData[aPrefix];
59
60 if( data.m_usedNumbers.find( aNumber ) != data.m_usedNumbers.end() )
61 return false;
62
63 data.m_usedNumbers.insert( aNumber );
64
65 if( aNumber > 0 )
66 updateCacheOnInsert( data, aNumber );
67
68 return true;
69}
70
71
72bool REFDES_TRACKER::containsImpl( const std::string& aRefDes ) const
73{
74 return m_allRefDes.contains( aRefDes );
75}
76
77bool REFDES_TRACKER::Contains( const std::string& aRefDes ) const
78{
79 std::unique_lock<std::mutex> lock;
80
81 if( m_threadSafe )
82 lock = std::unique_lock<std::mutex>( m_mutex );
83
84 return containsImpl( aRefDes );
85}
86
87
89 const std::map<int, std::vector<SCH_REFERENCE>>& aRefNumberMap,
90 const std::vector<int>& aRequiredUnits,
91 int aMinValue )
92{
93 std::unique_lock<std::mutex> lock;
94
95 if( m_threadSafe )
96 lock = std::unique_lock<std::mutex>( m_mutex );
97
98 // Filter out negative unit numbers
99 std::vector<int> validUnits;
100 std::copy_if( aRequiredUnits.begin(), aRequiredUnits.end(),
101 std::back_inserter( validUnits ),
102 []( int unit ) { return unit >= 0; } );
103
104 int candidate = aMinValue;
105
106 while( true )
107 {
108 // Check if this candidate number is currently in use
109 auto mapIt = aRefNumberMap.find( candidate );
110
111 if( mapIt == aRefNumberMap.end() )
112 {
113 // Not currently in use - check if it was previously used
114 std::string candidateRefDes = aRef.GetRef().ToStdString() + std::to_string( candidate );
115
116 if( m_reuseRefDes || !containsImpl( candidateRefDes ) )
117 {
118 // Completely unused - this is our answer
119 insertNumber( aRef.GetRefStr(), candidate );
120 m_allRefDes.insert( candidateRefDes );
121 return candidate;
122 }
123 else
124 {
125 // Previously used but no longer active - skip to next candidate
126 candidate++;
127 continue;
128 }
129 }
130 else
131 {
132 // Currently in use - check if required units are available
133 if( validUnits.empty() )
134 {
135 // Need completely unused reference, but this one is in use
136 candidate++;
137 continue;
138 }
139
140 // Check if required units are available
141 bool unitsAvailable;
143 {
144 // Use external units checker if available
145 unitsAvailable = m_externalUnitsChecker( aRef, mapIt->second, validUnits );
146 }
147 else
148 {
149 // Use default implementation
150 unitsAvailable = areUnitsAvailable( aRef, mapIt->second, validUnits );
151 }
152
153 if( unitsAvailable )
154 {
155 // All required units are available - this is our answer
156 // Note: Don't insert into tracker since reference is already in use
157 return candidate;
158 }
159 else
160 {
161 // Some required units are not available - try next candidate
162 candidate++;
163 continue;
164 }
165 }
166 }
167}
168
169
171 const std::vector<SCH_REFERENCE>& aRefVector,
172 const std::vector<int>& aRequiredUnits ) const
173{
174 for( const int& unit : aRequiredUnits )
175 {
176 for( const SCH_REFERENCE& ref : aRefVector )
177 {
178 // If we have a different library or different value,
179 // we cannot share a reference designator. Also, if the unit matches,
180 // the reference designator + unit is already in use.
181 if( ref.CompareLibName( aRef ) != 0
182 || ref.CompareValue( aRef ) != 0
183 || ref.GetUnit() == unit )
184 {
185 return false; // Conflict found
186 }
187 }
188 }
189
190 return true; // All required units are available
191}
192
193
194std::pair<std::string, int> REFDES_TRACKER::parseRefDes( const std::string& aRefDes ) const
195{
196 if( aRefDes.empty() )
197 return { "", 0 };
198
199 // Find the last sequence of digits at the end
200 std::regex pattern( R"(^([A-Za-z]+)(\d+)?$)" );
201 std::smatch match;
202
203 if( std::regex_match( aRefDes, match, pattern ) )
204 {
205 std::string prefix = match[1].str();
206 if( match[2].matched )
207 {
208 int number = std::stoi( match[2].str() );
209 return { prefix, number };
210 }
211 else
212 {
213 return { prefix, 0 }; // No number suffix
214 }
215 }
216
217 // If it doesn't match our expected pattern, treat the whole thing as prefix
218 return { aRefDes, 0 };
219}
220
222{
223 if( aData.m_cacheValid )
224 return;
225
226 // Find the first gap in the sequence starting from 1
227 int candidate = 1;
228 for( int used : aData.m_usedNumbers )
229 {
230 if( used <= 0 )
231 continue; // Skip non-positive numbers (like our 0 marker)
232 if( used == candidate )
233 {
234 candidate++;
235 }
236 else if( used > candidate )
237 {
238 break; // Found a gap
239 }
240 }
241
242 aData.m_baseNext = candidate;
243 aData.m_cacheValid = true;
244}
245
246void REFDES_TRACKER::updateCacheOnInsert( PREFIX_DATA& aData, int aInsertedNumber ) const
247{
248 // Update base next cache if it's valid and affected
249 if( aData.m_cacheValid )
250 {
251 if( aInsertedNumber == aData.m_baseNext )
252 {
253 // The base next was just used, find the new next
254 int candidate = aData.m_baseNext + 1;
255 while( aData.m_usedNumbers.find( candidate ) != aData.m_usedNumbers.end() )
256 {
257 candidate++;
258 }
259 aData.m_baseNext = candidate;
260 }
261 // If aInsertedNumber > m_baseNext, base cache is still valid
262 // If aInsertedNumber < m_baseNext, base cache is still valid
263 }
264
265 for( auto cacheIt = aData.m_nextCache.begin(); cacheIt != aData.m_nextCache.end(); ++cacheIt )
266 {
267 int cachedNext = cacheIt->second;
268
269 if( aInsertedNumber == cachedNext )
270 {
271 // This cached value was just used, need to update it
272 int candidate = cachedNext + 1;
273
274 while( aData.m_usedNumbers.contains( candidate ) )
275 candidate++;
276
277 cacheIt->second = candidate;
278 }
279 }
280}
281
282int REFDES_TRACKER::findNextAvailable( const PREFIX_DATA& aData, int aMinValue ) const
283{
284 if( auto cacheIt = aData.m_nextCache.find( aMinValue ); cacheIt != aData.m_nextCache.end() )
285 return cacheIt->second;
286
287 updateBaseNext( const_cast<PREFIX_DATA&>( aData ) );
288
289 int candidate;
290
291 if( aMinValue <= 1 )
292 {
293 candidate = aData.m_baseNext;
294 }
295 else
296 {
297 // Start search from aMinValue
298 candidate = aMinValue;
299
300 while( aData.m_usedNumbers.find( candidate ) != aData.m_usedNumbers.end() )
301 candidate++;
302 }
303
304 // Cache the result
305 aData.m_nextCache[aMinValue] = candidate;
306
307 return candidate;
308}
309
310std::string REFDES_TRACKER::Serialize() const
311{
312 std::unique_lock<std::mutex> lock;
313 if( m_threadSafe )
314 lock = std::unique_lock<std::mutex>( m_mutex );
315
316 std::ostringstream result;
317 bool first = true;
318
319 for( const auto& [prefix, data] : m_prefixData )
320 {
321 if( !first )
322 result << ",";
323 first = false;
324
325 std::string escapedPrefix = escapeForSerialization( prefix );
326
327 // Separate numbers from prefix-only entries
328 std::vector<int> numbers;
329 bool hasPrefix = false;
330
331 for( int num : data.m_usedNumbers )
332 {
333 if( num > 0 )
334 numbers.push_back( num );
335 else if( num == 0 )
336 hasPrefix = true;
337 }
338
339 if( numbers.empty() && !hasPrefix )
340 continue; // No data for this prefix
341
342 // Create ranges for numbered entries
343 std::vector<std::pair<int, int>> ranges;
344
345 if( !numbers.empty() )
346 {
347 int start = numbers[0];
348 int end = numbers[0];
349
350 for( size_t i = 1; i < numbers.size(); ++i )
351 {
352 if( numbers[i] == end + 1 )
353 {
354 end = numbers[i];
355 }
356 else
357 {
358 ranges.push_back( { start, end } );
359 start = end = numbers[i];
360 }
361 }
362 ranges.push_back( { start, end } );
363 }
364
365 bool firstRange = true;
366 for( const auto& [start, end] : ranges )
367 {
368 if( !firstRange )
369 result << ",";
370 firstRange = false;
371
372 result << escapedPrefix;
373 if( start == end )
374 {
375 result << start;
376 }
377 else
378 {
379 result << start << "-" << end;
380 }
381 }
382
383 // Add prefix-only entry if it exists
384 if( hasPrefix )
385 {
386 if( !firstRange )
387 result << ",";
388 result << escapedPrefix;
389 }
390 }
391
392 return result.str();
393}
394
395bool REFDES_TRACKER::Deserialize( const std::string& aData )
396{
397 std::unique_lock<std::mutex> lock;
398
399 if( m_threadSafe )
400 lock = std::unique_lock<std::mutex>( m_mutex );
401
402 clearImpl();
403
404 if( aData.empty() )
405 return true;
406
407 auto parts = splitString( aData, ',' );
408
409 for( const std::string& part : parts )
410 {
411 std::string unescaped = unescapeFromSerialization( part );
412
413 // Parse each part
414 std::regex rangePattern( R"(^([A-Za-z]+)(\d+)(?:-(\d+))?$)" );
415 std::regex prefixOnlyPattern( R"(^([A-Za-z]+)$)" );
416 std::smatch match;
417
418 if( std::regex_match( unescaped, match, rangePattern ) )
419 {
420 std::string prefix = match[1].str();
421 int start = std::stoi( match[2].str() );
422 int end = match[3].matched ? std::stoi( match[3].str() ) : start;
423
424 for( int i = start; i <= end; ++i )
425 {
426 if( !insertImpl( prefix + std::to_string( i ) ) )
427 {
428 // Note: insertImpl might fail if number already exists for prefix
429 // but that's okay during deserialization of valid data
430 }
431 }
432 }
433 else if( std::regex_match( unescaped, match, prefixOnlyPattern ) )
434 {
435 std::string prefix = match[1].str();
436 if( !insertImpl( prefix ) )
437 {
438 // Note: insertImpl might fail if prefix already exists
439 // but that's okay during deserialization of valid data
440 }
441 }
442 else
443 {
444 // Invalid format
445 clearImpl();
446 return false;
447 }
448 }
449
450 return true;
451}
452
454{
455 std::unique_lock<std::mutex> lock;
456
457 if( m_threadSafe )
458 lock = std::unique_lock<std::mutex>( m_mutex );
459
460 clearImpl();
461}
462
463
465{
466 m_prefixData.clear();
467 m_allRefDes.clear();
468}
469
471{
472 std::unique_lock<std::mutex> lock;
473
474 if( m_threadSafe )
475 lock = std::unique_lock<std::mutex>( m_mutex );
476
477 return m_allRefDes.size();
478}
479
480std::string REFDES_TRACKER::escapeForSerialization( const std::string& aStr ) const
481{
482 std::string result;
483 result.reserve( aStr.length() * 2 ); // Reserve space to avoid frequent reallocations
484
485 for( char c : aStr )
486 {
487 if( c == '\\' || c == ',' || c == '-' )
488 result += '\\';
489 result += c;
490 }
491 return result;
492}
493
494std::string REFDES_TRACKER::unescapeFromSerialization( const std::string& aStr ) const
495{
496 std::string result;
497 result.reserve( aStr.length() );
498
499 bool escaped = false;
500 for( char c : aStr )
501 {
502 if( escaped )
503 {
504 result += c;
505 escaped = false;
506 }
507 else if( c == '\\' )
508 {
509 escaped = true;
510 }
511 else
512 {
513 result += c;
514 }
515 }
516 return result;
517}
518
519std::vector<std::string> REFDES_TRACKER::splitString( const std::string& aStr, char aDelimiter ) const
520{
521 std::vector<std::string> result;
522 std::string current;
523 bool escaped = false;
524
525 for( char c : aStr )
526 {
527 if( escaped )
528 {
529 current += c;
530 escaped = false;
531 }
532 else if( c == '\\' )
533 {
534 escaped = true;
535 current += c;
536 }
537 else if( c == aDelimiter )
538 {
539 result.push_back( current );
540 current.clear();
541 }
542 else
543 {
544 current += c;
545 }
546 }
547
548 if( !current.empty() )
549 result.push_back( current );
550
551 return result;
552}
553
554
556{
557 std::unique_lock<std::mutex> lock;
558
559 if( m_threadSafe )
560 lock = std::unique_lock<std::mutex>( m_mutex );
561
562 m_externalUnitsChecker = aChecker;
563}
564
565
567{
568 std::unique_lock<std::mutex> lock;
569
570 if( m_threadSafe )
571 lock = std::unique_lock<std::mutex>( m_mutex );
572
573 m_externalUnitsChecker = nullptr;
574}
void updateBaseNext(PREFIX_DATA &aData) const
std::string escapeForSerialization(const std::string &aStr) const
Escape special characters for serialization.
bool insertNumber(const std::string &aPrefix, int aNumber)
Insert a number for a specific prefix, updating internal structures.
void ClearUnitsChecker()
Clear the external units checker, reverting to default behavior.
bool Deserialize(const std::string &aData)
Deserialize tracker data from string representation.
std::vector< std::string > splitString(const std::string &aStr, char aDelimiter) const
Split string by delimiter, handling escaped characters.
bool m_reuseRefDes
If true, allows reusing existing reference designators.
std::mutex m_mutex
Mutex for thread safety.
int GetNextRefDesForUnits(const SCH_REFERENCE &aRef, const std::map< int, std::vector< SCH_REFERENCE > > &aRefNumberMap, const std::vector< int > &aRequiredUnits, int aMinValue)
Get the next available reference designator number for multi-unit symbols.
std::unordered_set< std::string > m_allRefDes
bool Insert(const std::string &aRefDes)
Insert a reference designator into the tracker.
void clearImpl()
Clear all internal data structures without locking.
size_t Size() const
Get the total count of stored reference designators.
std::string unescapeFromSerialization(const std::string &aStr) const
Unescape special characters from serialization.
bool insertImpl(const std::string &aRefDes)
Internal implementation of Insert without locking.
int findNextAvailable(const PREFIX_DATA &aData, int aMinValue) const
Find next available number for a prefix starting from a minimum value.
REFDES_TRACKER(bool aThreadSafe=false)
Constructor.
bool areUnitsAvailable(const SCH_REFERENCE &aRef, const std::vector< SCH_REFERENCE > &aRefVector, const std::vector< int > &aRequiredUnits) const
Check if all required units are available for a given reference number.
void SetUnitsChecker(const UNITS_CHECKER_FUNC< SCH_REFERENCE > &aChecker)
Set an external units checker function for SCH_REFERENCE objects.
void updateCacheOnInsert(PREFIX_DATA &aData, int aInsertedNumber) const
Update cached next available values when a number is inserted.
std::string Serialize() const
Serialize the tracker data to a compact string representation.
std::unordered_map< std::string, PREFIX_DATA > m_prefixData
Map from prefix to its tracking data.
bool m_threadSafe
True if thread safety is enabled.
bool Contains(const std::string &aRefDes) const
Check if a reference designator exists in the tracker.
std::pair< std::string, int > parseRefDes(const std::string &aRefDes) const
Parse a reference designator into prefix and numerical suffix.
bool containsImpl(const std::string &aRefDes) const
Check if a reference designator exists in the tracker without locking.
UNITS_CHECKER_FUNC< SCH_REFERENCE > m_externalUnitsChecker
External units checker function (optional)
void Clear()
Clear all stored reference designators.
A helper to define a symbol's reference designator in a schematic.
wxString GetRef() const
const char * GetRefStr() const
std::function< bool(const T &aTestRef, const std::vector< T > &aExistingRefs, const std::vector< int > &aRequiredUnits)> UNITS_CHECKER_FUNC
Function type for external units availability checking.
Data structure for tracking used numbers and caching next available values.
std::set< int > m_usedNumbers
Sorted set of used numbers for this prefix.
bool m_cacheValid
True if m_baseNext cache is valid.
int m_baseNext
Next available from 1 (cached)
std::map< int, int > m_nextCache
Cache of next available number for given min values.
VECTOR2I end