KiCad PCB EDA Suite
Loading...
Searching...
No Matches
net_settings.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 (C) 2020 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Jon Evans <[email protected]>
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <algorithm>
23#include <limits>
24
25#include <json_common.h>
26
29#include <settings/parameters.h>
31#include <string_utils.h>
32#include <base_units.h>
33#include <unordered_set>
34
35
36// const int netSettingsSchemaVersion = 0;
37// const int netSettingsSchemaVersion = 1; // new overbar syntax
38// const int netSettingsSchemaVersion = 2; // exclude buses from netclass members
39// const int netSettingsSchemaVersion = 3; // netclass assignment patterns
40// const int netSettingsSchemaVersion = 4; // netclass ordering
41const int netSettingsSchemaVersion = 5; // Tuning profile names
42
43
44static std::optional<int> getInPcbUnits( const nlohmann::json& aObj, const std::string& aKey,
45 std::optional<int> aDefault = std::optional<int>() )
46{
47 if( aObj.contains( aKey ) && aObj[aKey].is_number() )
48 return pcbIUScale.mmToIU( aObj[aKey].get<double>() );
49 else
50 return aDefault;
51};
52
53
54static std::optional<int> getInSchUnits( const nlohmann::json& aObj, const std::string& aKey,
55 std::optional<int> aDefault = std::optional<int>() )
56{
57 if( aObj.contains( aKey ) && aObj[aKey].is_number() )
58 return schIUScale.MilsToIU( aObj[aKey].get<double>() );
59 else
60 return aDefault;
61};
62
63
64NET_SETTINGS::NET_SETTINGS( JSON_SETTINGS* aParent, const std::string& aPath ) :
65 NESTED_SETTINGS( "net_settings", netSettingsSchemaVersion, aParent, aPath, false )
66{
67 m_defaultNetClass = std::make_shared<NETCLASS>( NETCLASS::Default, true );
68 m_defaultNetClass->SetDescription( _( "This is the default net class." ) );
69 m_defaultNetClass->SetPriority( std::numeric_limits<int>::max() );
70
71 auto saveNetclass =
72 []( nlohmann::json& json_array, const std::shared_ptr<NETCLASS>& nc )
73 {
74 // Note: we're in common/, but we do happen to know which of these
75 // fields are used in which units system.
76 nlohmann::json nc_json = { { "name", nc->GetName().ToUTF8() },
77 { "priority", nc->GetPriority() },
78 { "schematic_color", nc->GetSchematicColor( true ) },
79 { "pcb_color", nc->GetPcbColor( true ) },
80 { "tuning_profile", nc->GetTuningProfile() } };
81
82 auto saveInPcbUnits =
83 []( nlohmann::json& json, const std::string& aKey, int aValue )
84 {
85 json.push_back( { aKey, pcbIUScale.IUTomm( aValue ) } );
86 };
87
88 if( nc->HasWireWidth() )
89 nc_json.push_back(
90 { "wire_width", schIUScale.IUToMils( nc->GetWireWidth() ) } );
91
92 if( nc->HasBusWidth() )
93 nc_json.push_back( { "bus_width", schIUScale.IUToMils( nc->GetBusWidth() ) } );
94
95 if( nc->HasLineStyle() )
96 nc_json.push_back( { "line_style", nc->GetLineStyle() } );
97
98 if( nc->HasClearance() )
99 saveInPcbUnits( nc_json, "clearance", nc->GetClearance() );
100
101 if( nc->HasTrackWidth() )
102 saveInPcbUnits( nc_json, "track_width", nc->GetTrackWidth() );
103
104 if( nc->HasViaDiameter() )
105 saveInPcbUnits( nc_json, "via_diameter", nc->GetViaDiameter() );
106
107 if( nc->HasViaDrill() )
108 saveInPcbUnits( nc_json, "via_drill", nc->GetViaDrill() );
109
110 if( nc->HasuViaDiameter() )
111 saveInPcbUnits( nc_json, "microvia_diameter", nc->GetuViaDiameter() );
112
113 if( nc->HasuViaDrill() )
114 saveInPcbUnits( nc_json, "microvia_drill", nc->GetuViaDrill() );
115
116 if( nc->HasDiffPairWidth() )
117 saveInPcbUnits( nc_json, "diff_pair_width", nc->GetDiffPairWidth() );
118
119 if( nc->HasDiffPairGap() )
120 saveInPcbUnits( nc_json, "diff_pair_gap", nc->GetDiffPairGap() );
121
122 if( nc->HasDiffPairViaGap() )
123 saveInPcbUnits( nc_json, "diff_pair_via_gap", nc->GetDiffPairViaGap() );
124
125 json_array.push_back( nc_json );
126 };
127
128 auto readNetClass =
129 []( const nlohmann::json& entry )
130 {
131 wxString name = entry["name"];
132
133 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( name, false );
134
135 if( entry.contains( "priority" ) && entry["priority"].is_number() )
136 nc->SetPriority( entry["priority"].get<int>() );
137
138 if( entry.contains( "tuning_profile" ) && entry["tuning_profile"].is_string() )
139 nc->SetTuningProfile( entry["tuning_profile"].get<wxString>() );
140
141 if( auto value = getInPcbUnits( entry, "clearance" ) )
142 nc->SetClearance( *value );
143
144 if( auto value = getInPcbUnits( entry, "track_width" ) )
145 nc->SetTrackWidth( *value );
146
147 if( auto value = getInPcbUnits( entry, "via_diameter" ) )
148 nc->SetViaDiameter( *value );
149
150 if( auto value = getInPcbUnits( entry, "via_drill" ) )
151 nc->SetViaDrill( *value );
152
153 if( auto value = getInPcbUnits( entry, "microvia_diameter" ) )
154 nc->SetuViaDiameter( *value );
155
156 if( auto value = getInPcbUnits( entry, "microvia_drill" ) )
157 nc->SetuViaDrill( *value );
158
159 if( auto value = getInPcbUnits( entry, "diff_pair_width" ) )
160 nc->SetDiffPairWidth( *value );
161
162 if( auto value = getInPcbUnits( entry, "diff_pair_gap" ) )
163 nc->SetDiffPairGap( *value );
164
165 if( auto value = getInPcbUnits( entry, "diff_pair_via_gap" ) )
166 nc->SetDiffPairViaGap( *value );
167
168 if( auto value = getInSchUnits( entry, "wire_width" ) )
169 nc->SetWireWidth( *value );
170
171 if( auto value = getInSchUnits( entry, "bus_width" ) )
172 nc->SetBusWidth( *value );
173
174 if( entry.contains( "line_style" ) && entry["line_style"].is_number() )
175 nc->SetLineStyle( entry["line_style"].get<int>() );
176
177 if( entry.contains( "pcb_color" ) && entry["pcb_color"].is_string() )
178 nc->SetPcbColor( entry["pcb_color"].get<KIGFX::COLOR4D>() );
179
180 if( entry.contains( "schematic_color" )
181 && entry["schematic_color"].is_string() )
182 {
183 nc->SetSchematicColor( entry["schematic_color"].get<KIGFX::COLOR4D>() );
184 }
185
186 return nc;
187 };
188
189 m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "classes",
190 [&]() -> nlohmann::json
191 {
192 nlohmann::json ret = nlohmann::json::array();
193
195 saveNetclass( ret, m_defaultNetClass );
196
197 for( const auto& [name, netclass] : m_netClasses )
198 saveNetclass( ret, netclass );
199
200 return ret;
201 },
202 [&]( const nlohmann::json& aJson )
203 {
204 if( !aJson.is_array() )
205 return;
206
207 m_netClasses.clear();
208
209 for( const nlohmann::json& entry : aJson )
210 {
211 if( !entry.is_object() || !entry.contains( "name" ) )
212 continue;
213
214 std::shared_ptr<NETCLASS> nc = readNetClass( entry );
215
216 if( nc->IsDefault() )
218 else
219 m_netClasses[nc->GetName()] = nc;
220 }
221 },
222 {} ) );
223
224 m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "net_colors",
225 [&]() -> nlohmann::json
226 {
227 nlohmann::json ret = {};
228
229 for( const auto& [netname, color] : m_netColorAssignments )
230 {
231 std::string key( netname.ToUTF8() );
232 ret[ std::move( key ) ] = color;
233 }
234
235 return ret;
236 },
237 [&]( const nlohmann::json& aJson )
238 {
239 if( !aJson.is_object() )
240 return;
241
242 m_netColorAssignments.clear();
243
244 for( const auto& pair : aJson.items() )
245 {
246 wxString key( pair.key().c_str(), wxConvUTF8 );
247 m_netColorAssignments[std::move( key )] = pair.value().get<KIGFX::COLOR4D>();
248 }
249 },
250 {} ) );
251
252 m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "net_chain_classes",
253 [&]() -> nlohmann::json
254 {
255 // Force object type so an empty map round-trips as {} rather than null;
256 // the reader rejects non-objects, which would otherwise leave stale
257 // chain assignments in place after the user clears them all.
258 nlohmann::json ret = nlohmann::json::object();
259
260 for( const auto& [chain, className] : m_netChainClasses )
261 ret[ std::string( chain.ToUTF8() ) ] = std::string( className.ToUTF8() );
262
263 return ret;
264 },
265 [&]( const nlohmann::json& aJson )
266 {
267 if( !aJson.is_object() )
268 return;
269
270 m_netChainClasses.clear();
271
272 for( const auto& pair : aJson.items() )
273 {
274 wxString chain( pair.key().c_str(), wxConvUTF8 );
275 wxString className = pair.value().get<wxString>();
276
277 if( !className.IsEmpty() )
278 m_netChainClasses[ std::move( chain ) ] = std::move( className );
279 }
280 },
281 {} ) );
282
283 m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "netclass_assignments",
284 [&]() -> nlohmann::json
285 {
286 nlohmann::json ret = {};
287
288 for( const auto& [netname, netclassNames] : m_netClassLabelAssignments )
289 {
290 nlohmann::json netclassesJson = nlohmann::json::array();
291
292 for( const auto& netclass : netclassNames )
293 {
294 std::string netclassStr( netclass.ToUTF8() );
295 netclassesJson.push_back( std::move( netclassStr ) );
296 }
297
298 std::string key( netname.ToUTF8() );
299 ret[std::move( key )] = netclassesJson;
300 }
301
302 return ret;
303 },
304 [&]( const nlohmann::json& aJson )
305 {
306 if( !aJson.is_object() )
307 return;
308
310
311 for( const auto& pair : aJson.items() )
312 {
313 wxString key( pair.key().c_str(), wxConvUTF8 );
314
315 for( const auto& netclassName : pair.value() )
316 m_netClassLabelAssignments[key].insert( netclassName.get<wxString>() );
317 }
318 },
319 {} ) );
320
321 m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "netclass_patterns",
322 [&]() -> nlohmann::json
323 {
324 nlohmann::json ret = nlohmann::json::array();
325
326 for( const auto& [matcher, netclassName] : m_netClassPatternAssignments )
327 {
328 nlohmann::json pattern_json = {
329 { "pattern", matcher->GetPattern().ToUTF8() },
330 { "netclass", netclassName.ToUTF8() }
331 };
332
333 ret.push_back( std::move( pattern_json ) );
334 }
335
336 return ret;
337 },
338 [&]( const nlohmann::json& aJson )
339 {
340 if( !aJson.is_array() )
341 return;
342
344
345 for( const nlohmann::json& entry : aJson )
346 {
347 if( !entry.is_object() )
348 continue;
349
350 if( entry.contains( "pattern" ) && entry["pattern"].is_string()
351 && entry.contains( "netclass" ) && entry["netclass"].is_string() )
352 {
353 wxString pattern = entry["pattern"].get<wxString>();
354 wxString netclass = entry["netclass"].get<wxString>();
355
356 // Expand bus patterns so individual bus member nets can be matched
357 ForEachBusMember( pattern,
358 [&]( const wxString& memberPattern )
359 {
360 addSinglePatternAssignment( memberPattern, netclass );
361 } );
362 }
363 }
364 },
365 {} ) );
366
367 registerMigration( 0, 1, std::bind( &NET_SETTINGS::migrateSchema0to1, this ) );
368 registerMigration( 1, 2, std::bind( &NET_SETTINGS::migrateSchema1to2, this ) );
369 registerMigration( 2, 3, std::bind( &NET_SETTINGS::migrateSchema2to3, this ) );
370 registerMigration( 3, 4, std::bind( &NET_SETTINGS::migrateSchema3to4, this ) );
371 registerMigration( 4, 5, std::bind( &NET_SETTINGS::migrateSchema4to5, this ) );
372}
373
374
376{
377 // Release early before destroying members
378 if( m_parent )
379 {
380 m_parent->ReleaseNestedSettings( this );
381 m_parent = nullptr;
382 }
383}
384
385
386bool NET_SETTINGS::operator==( const NET_SETTINGS& aOther ) const
387{
388 // m_netClasses maps name -> shared_ptr<NETCLASS>. The default pair operator==
389 // would compare the shared_ptrs by pointer identity, missing in-place edits
390 // to the underlying NETCLASS (the UI's normal edit path mutates through the
391 // shared_ptr rather than swapping it). Compare names and contents instead.
392 auto netclassEntryEqual = []( const auto& aLhs, const auto& aRhs )
393 {
394 if( aLhs.first != aRhs.first )
395 return false;
396
397 if( aLhs.second.get() == aRhs.second.get() )
398 return true;
399
400 if( !aLhs.second || !aRhs.second )
401 return false;
402
403 return aLhs.second->EqualsByPersistedFields( *aRhs.second );
404 };
405
406 if( !std::equal( std::begin( m_netClasses ), std::end( m_netClasses ),
407 std::begin( aOther.m_netClasses ), std::end( aOther.m_netClasses ),
408 netclassEntryEqual ) )
409 return false;
410
411 // m_defaultNetClass is held by shared_ptr and (per board.cpp / kicad_sexpr_parser.cpp
412 // setup paths) is mutated in place via GetDefaultNetclass()->SetClearance( ... ).
413 // shared_ptr identity is preserved across those edits, so a pointer-only check
414 // would silently drop the most common project edit. Compare contents.
415 if( static_cast<bool>( m_defaultNetClass ) != static_cast<bool>( aOther.m_defaultNetClass ) )
416 return false;
417
419 && !m_defaultNetClass->EqualsByPersistedFields( *aOther.m_defaultNetClass ) )
420 {
421 return false;
422 }
423
424 // m_netClassPatternAssignments stores std::unique_ptr<EDA_COMBINED_MATCHER>, so a naive
425 // std::equal would compare matcher pointer identity and report two settings instances built
426 // from identical input as unequal. Compare pattern text plus the assigned netclass name.
427 auto patternEqual = []( const auto& aLhs, const auto& aRhs )
428 {
429 if( !aLhs.first || !aRhs.first )
430 return aLhs.first.get() == aRhs.first.get() && aLhs.second == aRhs.second;
431
432 return aLhs.first->GetPattern() == aRhs.first->GetPattern() && aLhs.second == aRhs.second;
433 };
434
435 if( !std::equal( std::begin( m_netClassPatternAssignments ),
437 std::begin( aOther.m_netClassPatternAssignments ),
438 std::end( aOther.m_netClassPatternAssignments ),
439 patternEqual ) )
440 return false;
441
442 // m_netClassChainPatternAssignments is derived state, rebuilt from m_netChainClasses and
443 // board NETINFO on every netlist update. Equality is defined by persisted inputs only;
444 // including the derived list here would mark the project dirty whenever a rebuild produced
445 // a transient ordering difference.
446
447 if( !std::equal( std::begin( m_netClassLabelAssignments ),
448 std::end( m_netClassLabelAssignments ),
449 std::begin( aOther.m_netClassLabelAssignments ),
450 std::end( aOther.m_netClassLabelAssignments ) ) )
451 return false;
452
453 if( !std::equal( std::begin( m_netColorAssignments ), std::end( m_netColorAssignments ),
454 std::begin( aOther.m_netColorAssignments ),
455 std::end( aOther.m_netColorAssignments ) ) )
456 return false;
457
459 return false;
460
461 return true;
462}
463
464
466{
467 if( &aOther == this )
468 return;
469
470 // Flush the source's live field state into its JSON cache so the cache
471 // mirrors what we want to copy.
472 aOther.Store();
473
474 // CloneFrom() copies the JSON tree but leaves m_parent / m_path untouched,
475 // which is what we need: this instance must stay attached to its host
476 // project file so SaveToFile() walks the right m_nested_settings entry.
477 Internals()->CloneFrom( *aOther.Internals() );
478
479 // Repopulate our in-memory NETCLASS / pattern / color state from the cloned
480 // JSON. Load() rebuilds m_defaultNetClass, m_netClasses, fresh
481 // EDA_COMBINED_MATCHER instances for m_netClassPatternAssignments, and the
482 // remaining maps, decoupling our instance from aOther's lifetime.
483 Load();
484
485 // Load() repopulates persisted state but doesn't clear the derived caches.
486 // Drop them so stale lookups from this instance's pre-CopyFrom life
487 // (composite resolutions, implicit netclasses, chain-derived patterns
488 // rebuilt on every netlist update, and the per-net effective-class cache)
489 // don't survive the swap.
490 m_compositeNetClasses.clear();
491 m_impicitNetClasses.clear();
494}
495
496
498{
499 if( m_internals->contains( "classes" ) && m_internals->At( "classes" ).is_array() )
500 {
501 for( auto& netClass : m_internals->At( "classes" ).items() )
502 {
503 if( netClass.value().contains( "nets" ) && netClass.value()["nets"].is_array() )
504 {
505 nlohmann::json migrated = nlohmann::json::array();
506
507 for( auto& net : netClass.value()["nets"].items() )
508 migrated.push_back( ConvertToNewOverbarNotation( net.value().get<wxString>() ) );
509
510 netClass.value()["nets"] = migrated;
511 }
512 }
513 }
514
515 return true;
516}
517
518
520{
521 return true;
522}
523
524
526{
527 if( m_internals->contains( "classes" ) && m_internals->At( "classes" ).is_array() )
528 {
529 nlohmann::json patterns = nlohmann::json::array();
530
531 for( auto& netClass : m_internals->At( "classes" ).items() )
532 {
533 if( netClass.value().contains( "name" )
534 && netClass.value().contains( "nets" )
535 && netClass.value()["nets"].is_array() )
536 {
537 wxString netClassName = netClass.value()["name"].get<wxString>();
538
539 for( auto& net : netClass.value()["nets"].items() )
540 {
541 nlohmann::json pattern_json = {
542 { "pattern", net.value().get<wxString>() },
543 { "netclass", netClassName }
544 };
545
546 patterns.push_back( pattern_json );
547 }
548 }
549 }
550
551 m_internals->SetFromString( "netclass_patterns", patterns );
552 }
553
554 return true;
555}
556
557
559{
560 // Add priority field to netclasses
561 if( m_internals->contains( "classes" ) && m_internals->At( "classes" ).is_array() )
562 {
563 int priority = 0;
564
565 for( auto& netClass : m_internals->At( "classes" ).items() )
566 {
567 if( netClass.value()["name"].get<wxString>() == NETCLASS::Default )
568 netClass.value()["priority"] = std::numeric_limits<int>::max();
569 else
570 netClass.value()["priority"] = priority++;
571 }
572 }
573
574 // Move netclass assignments to a list
575 if( m_internals->contains( "netclass_assignments" )
576 && m_internals->At( "netclass_assignments" ).is_object() )
577 {
578 nlohmann::json migrated = {};
579
580 for( const auto& pair : m_internals->At( "netclass_assignments" ).items() )
581 {
582 nlohmann::json netclassesJson = nlohmann::json::array();
583
584 if( pair.value().get<wxString>() != wxEmptyString )
585 netclassesJson.push_back( pair.value() );
586
587 migrated[pair.key()] = netclassesJson;
588 }
589
590 m_internals->SetFromString( "netclass_assignments", migrated );
591 }
592
593 return true;
594}
595
596
598{
599 // Add tuning profile name field to netclasses
600 if( m_internals->contains( "classes" ) && m_internals->At( "classes" ).is_array() )
601 {
602 const wxString emptyStr = "";
603
604 for( auto& netClass : m_internals->At( "classes" ).items() )
605 netClass.value()["tuning_profile"] = emptyStr.ToUTF8();
606 }
607
608 return true;
609}
610
611
612void NET_SETTINGS::SetDefaultNetclass( std::shared_ptr<NETCLASS> netclass )
613{
614 m_defaultNetClass = netclass;
615}
616
617
618std::shared_ptr<NETCLASS> NET_SETTINGS::GetDefaultNetclass() const
619{
620 return m_defaultNetClass;
621}
622
623
624bool NET_SETTINGS::HasNetclass( const wxString& netclassName ) const
625{
626 return m_netClasses.find( netclassName ) != m_netClasses.end();
627}
628
629
630void NET_SETTINGS::SetNetclass( const wxString& netclassName, std::shared_ptr<NETCLASS>& netclass )
631{
632 m_netClasses[netclassName] = netclass;
633}
634
635
636void NET_SETTINGS::SetNetclasses( const std::map<wxString, std::shared_ptr<NETCLASS>>& netclasses )
637{
638 m_netClasses = netclasses;
640}
641
642
643const std::map<wxString, std::shared_ptr<NETCLASS>>& NET_SETTINGS::GetNetclasses() const
644{
645 return m_netClasses;
646}
647
648
649const std::map<wxString, std::shared_ptr<NETCLASS>>& NET_SETTINGS::GetCompositeNetclasses() const
650{
652}
653
654
656{
657 m_netClasses.clear();
658 m_impicitNetClasses.clear();
660}
661
662
663const std::map<wxString, std::set<wxString>>& NET_SETTINGS::GetNetclassLabelAssignments() const
664{
666}
667
668
673
674
675void NET_SETTINGS::ClearNetclassLabelAssignment( const wxString& netName )
676{
677 m_netClassLabelAssignments.erase( netName );
678}
679
680
681void NET_SETTINGS::SetNetclassLabelAssignment( const wxString& netName,
682 const std::set<wxString>& netclasses )
683{
684 m_netClassLabelAssignments[netName] = netclasses;
685}
686
687
689 const std::set<wxString>& netclasses )
690{
691 m_netClassLabelAssignments[netName].insert( netclasses.begin(), netclasses.end() );
692}
693
694
695bool NET_SETTINGS::HasNetclassLabelAssignment( const wxString& netName ) const
696{
697 return m_netClassLabelAssignments.find( netName ) != m_netClassLabelAssignments.end();
698}
699
700
701void NET_SETTINGS::SetNetclassPatternAssignment( const wxString& pattern, const wxString& netclass )
702{
703 // Expand bus patterns (vector buses and bus groups) to individual member patterns.
704 // This is necessary because the regex/wildcard matchers interpret brackets and braces
705 // as special characters, not as bus notation.
706 ForEachBusMember( pattern,
707 [&]( const wxString& memberPattern )
708 {
709 addSinglePatternAssignment( memberPattern, netclass );
710 } );
711
713}
714
715
716void NET_SETTINGS::addSinglePatternAssignment( const wxString& pattern, const wxString& netclass )
717{
718 // Avoid exact duplicates - these shouldn't cause problems, due to later de-duplication
719 // but they are unnecessary.
720 for( auto& assignment : m_netClassPatternAssignments )
721 {
722 if( assignment.first->GetPattern() == pattern && assignment.second == netclass )
723 return;
724 }
725
726 // No assignment, add a new one
728 { std::make_unique<EDA_COMBINED_MATCHER>( pattern, CTX_NETCLASS ), netclass } );
729}
730
731
733 std::vector<std::pair<std::unique_ptr<EDA_COMBINED_MATCHER>, wxString>>&& netclassPatterns )
734{
735 m_netClassPatternAssignments = std::move( netclassPatterns );
737}
738
739
740std::vector<std::pair<std::unique_ptr<EDA_COMBINED_MATCHER>, wxString>>&
745
746
751
752
753void NET_SETTINGS::SetChainPatternAssignment( const wxString& pattern, const wxString& netclass )
754{
755 ForEachBusMember( pattern,
756 [&]( const wxString& memberPattern )
757 {
758 addSingleChainPatternAssignment( memberPattern, netclass );
759 } );
760
762}
763
764
766 const wxString& netclass )
767{
768 for( auto& assignment : m_netClassChainPatternAssignments )
769 {
770 if( !assignment.first )
771 continue;
772
773 if( assignment.first->GetPattern() == pattern && assignment.second == netclass )
774 return;
775 }
776
778 { std::make_unique<EDA_COMBINED_MATCHER>( pattern, CTX_NETCLASS ), netclass } );
779}
780
781
787
788
789void NET_SETTINGS::ClearCacheForNet( const wxString& netName )
790{
791 if( m_effectiveNetclassCache.count( netName ) )
792 {
793 wxString compositeNetclassName = m_effectiveNetclassCache[netName]->GetName();
794 m_compositeNetClasses.erase( compositeNetclassName );
795 m_effectiveNetclassCache.erase( netName );
796 }
797}
798
799
805
806
807void NET_SETTINGS::SetNetColorAssignment( const wxString& netName, const KIGFX::COLOR4D& color )
808{
809 m_netColorAssignments[netName] = color;
810}
811
812
813const std::map<wxString, KIGFX::COLOR4D>& NET_SETTINGS::GetNetColorAssignments() const
814{
816}
817
818
823
824
825bool NET_SETTINGS::RenameNetPathPrefix( const wxString& aOldPrefix, const wxString& aNewPrefix )
826{
827 if( aOldPrefix.IsEmpty() || aOldPrefix == aNewPrefix )
828 return false;
829
830 bool changed = false;
831
832 // Patterns hold the path as plain text, so swap the leading prefix and rebuild the matcher.
833 for( auto& [matcher, netclass] : m_netClassPatternAssignments )
834 {
835 const wxString pattern = matcher->GetPattern();
836
837 if( pattern.StartsWith( aOldPrefix ) )
838 {
839 wxString updated = aNewPrefix + pattern.Mid( aOldPrefix.length() );
840 matcher = std::make_unique<EDA_COMBINED_MATCHER>( updated, CTX_NETCLASS );
841 changed = true;
842 }
843 }
844
845 // Net color keys are full net names, which carry the path too.
846 std::map<wxString, KIGFX::COLOR4D> updatedColors;
847
848 for( const auto& [netName, color] : m_netColorAssignments )
849 {
850 if( netName.StartsWith( aOldPrefix ) )
851 {
852 updatedColors[aNewPrefix + netName.Mid( aOldPrefix.length() )] = color;
853 changed = true;
854 }
855 else
856 {
857 updatedColors[netName] = color;
858 }
859 }
860
861 if( changed )
862 {
863 m_netColorAssignments = std::move( updatedColors );
865 }
866
867 return changed;
868}
869
870
871bool NET_SETTINGS::HasEffectiveNetClass( const wxString& aNetName ) const
872{
873 return m_effectiveNetclassCache.count( aNetName ) > 0;
874}
875
876
877std::shared_ptr<NETCLASS> NET_SETTINGS::GetCachedEffectiveNetClass( const wxString& aNetName ) const
878{
879 return m_effectiveNetclassCache.at( aNetName );
880}
881
882
883std::shared_ptr<NETCLASS> NET_SETTINGS::GetEffectiveNetClass( const wxString& aNetName )
884{
885 // Lambda to fetch an explicit netclass. Returns a nullptr if not found
886 auto getExplicitNetclass =
887 [this]( const wxString& netclass ) -> std::shared_ptr<NETCLASS>
888 {
889 if( netclass == NETCLASS::Default )
890 return m_defaultNetClass;
891
892 auto ii = m_netClasses.find( netclass );
893
894 if( ii == m_netClasses.end() )
895 return {};
896 else
897 return ii->second;
898 };
899
900 // Lambda to fetch or create an implicit netclass (defined with a label, but not configured)
901 // These are needed as while they do not provide any netclass parameters, they do now appear in
902 // DRC matching strings as an assigned netclass.
903 auto getOrAddImplicitNetcless =
904 [this]( const wxString& netclass ) -> std::shared_ptr<NETCLASS>
905 {
906 auto ii = m_impicitNetClasses.find( netclass );
907
908 if( ii == m_impicitNetClasses.end() )
909 {
910 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( netclass, false );
911 nc->SetPriority( std::numeric_limits<int>::max() - 1 ); // Priority > default netclass
912 m_impicitNetClasses[netclass] = nc;
913 return nc;
914 }
915 else
916 {
917 return ii->second;
918 }
919 };
920
921 // <no net> is forced to be part of the default netclass.
922 if( aNetName.IsEmpty() )
923 return m_defaultNetClass;
924
925 // First check if we have a cached resolved netclass
926 auto cacheItr = m_effectiveNetclassCache.find( aNetName );
927
928 if( cacheItr != m_effectiveNetclassCache.end() )
929 return cacheItr->second;
930
931 // No cache found - build a vector of all netclasses assigned to or matching this net
932 std::unordered_set<std::shared_ptr<NETCLASS>> resolvedNetclasses;
933
934 // First find explicit netclass assignments
935 auto it = m_netClassLabelAssignments.find( aNetName );
936
937 if( it != m_netClassLabelAssignments.end() && it->second.size() > 0 )
938 {
939 for( const wxString& netclassName : it->second )
940 {
941 std::shared_ptr<NETCLASS> netclass = getExplicitNetclass( netclassName );
942
943 if( netclass )
944 {
945 resolvedNetclasses.insert( std::move( netclass ) );
946 }
947 else
948 {
949 resolvedNetclasses.insert( getOrAddImplicitNetcless( netclassName ) );
950 }
951 }
952 }
953
954 // Now find any pattern-matched netclass assignments (user + chain-derived)
955 auto applyPatternList =
956 [&]( const std::vector<std::pair<std::unique_ptr<EDA_COMBINED_MATCHER>, wxString>>&
957 patterns )
958 {
959 for( const auto& [matcher, netclassName] : patterns )
960 {
961 if( matcher->StartsWith( aNetName ) )
962 {
963 std::shared_ptr<NETCLASS> netclass = getExplicitNetclass( netclassName );
964
965 if( netclass )
966 resolvedNetclasses.insert( std::move( netclass ) );
967 else
968 resolvedNetclasses.insert( getOrAddImplicitNetcless( netclassName ) );
969 }
970 }
971 };
972
973 applyPatternList( m_netClassPatternAssignments );
974 applyPatternList( m_netClassChainPatternAssignments );
975
976 // Handle zero resolved netclasses
977 if( resolvedNetclasses.size() == 0 )
978 {
979 // For bus patterns, check if all members share the same netclass.
980 // If they do, the bus inherits that netclass for coloring purposes.
981 std::shared_ptr<NETCLASS> sharedNetclass;
982 bool allSameNetclass = true;
983 bool isBusPattern = false;
984
985 ForEachBusMember( aNetName,
986 [&]( const wxString& member )
987 {
988 // If ForEachBusMember gives us back the same name, it's not a bus.
989 // Skip to avoid infinite recursion.
990 if( member == aNetName )
991 return;
992
993 isBusPattern = true;
994
995 if( !allSameNetclass )
996 return;
997
998 std::shared_ptr<NETCLASS> memberNc = GetEffectiveNetClass( member );
999
1000 if( !sharedNetclass )
1001 {
1002 sharedNetclass = memberNc;
1003 }
1004 else if( memberNc->GetName() != sharedNetclass->GetName() )
1005 {
1006 allSameNetclass = false;
1007 }
1008 } );
1009
1010 if( isBusPattern && allSameNetclass && sharedNetclass
1011 && sharedNetclass->GetName() != NETCLASS::Default )
1012 {
1013 m_effectiveNetclassCache[aNetName] = sharedNetclass;
1014 return sharedNetclass;
1015 }
1016
1018
1019 return m_defaultNetClass;
1020 }
1021
1022 // Make and cache the effective netclass. Note that makeEffectiveNetclass will add the default
1023 // netclass to resolvedNetclasses if it is needed to complete the netclass paramters set. It
1024 // will also sort resolvedNetclasses by priority order.
1025 std::vector<NETCLASS*> netclassPtrs;
1026
1027 for( const std::shared_ptr<NETCLASS>& nc : resolvedNetclasses )
1028 netclassPtrs.push_back( nc.get() );
1029
1030 wxString name;
1031 name.Printf( "Effective for net: %s", aNetName );
1032 std::shared_ptr<NETCLASS> effectiveNetclass = std::make_shared<NETCLASS>( name, false );
1033 makeEffectiveNetclass( effectiveNetclass, netclassPtrs );
1034
1035 if( netclassPtrs.size() == 1 )
1036 {
1037 // No defaults were added - just return the primary netclass
1038 m_effectiveNetclassCache[aNetName] = *resolvedNetclasses.begin();
1039 return *resolvedNetclasses.begin();
1040 }
1041 else
1042 {
1043 effectiveNetclass->SetConstituentNetclasses( std::move( netclassPtrs ) );
1044
1045 m_compositeNetClasses[effectiveNetclass->GetName()] = effectiveNetclass;
1046 m_effectiveNetclassCache[aNetName] = effectiveNetclass;
1047
1048 return effectiveNetclass;
1049 }
1050}
1051
1052
1054{
1055 for( auto& [ncName, nc] : m_compositeNetClasses )
1056 {
1057 // Note this needs to be a copy in case we now need to add the default netclass
1058 std::vector<NETCLASS*> constituents = nc->GetConstituentNetclasses();
1059
1060 wxASSERT( constituents.size() > 0 );
1061
1062 // If the last netclass is Default, remove it (it will be re-added if still needed)
1063 if( ( *constituents.rbegin() )->GetName() == NETCLASS::Default )
1064 {
1065 constituents.pop_back();
1066 }
1067
1068 // Remake the netclass from original constituents
1069 nc->ResetParameters();
1070 makeEffectiveNetclass( nc, constituents );
1071 nc->SetConstituentNetclasses( std::move( constituents ) );
1072 }
1073}
1074
1075
1076void NET_SETTINGS::makeEffectiveNetclass( std::shared_ptr<NETCLASS>& effectiveNetclass,
1077 std::vector<NETCLASS*>& constituentNetclasses ) const
1078{
1079 // Sort the resolved netclasses by priority (highest first), with same-priority netclasses
1080 // ordered alphabetically
1081 std::sort( constituentNetclasses.begin(), constituentNetclasses.end(),
1082 []( NETCLASS* nc1, NETCLASS* nc2 )
1083 {
1084 int p1 = nc1->GetPriority();
1085 int p2 = nc2->GetPriority();
1086
1087 if( p1 < p2 )
1088 return true;
1089
1090 if (p1 == p2)
1091 return nc1->GetName().Cmp( nc2->GetName() ) < 0;
1092
1093 return false;
1094 } );
1095
1096 // Iterate from lowest priority netclass and fill effective netclass parameters
1097 for( auto itr = constituentNetclasses.rbegin(); itr != constituentNetclasses.rend(); ++itr )
1098 {
1099 NETCLASS* nc = *itr;
1100
1101 if( nc->HasClearance() )
1102 {
1103 effectiveNetclass->SetClearance( nc->GetClearance() );
1104 effectiveNetclass->SetClearanceParent( nc );
1105 }
1106
1107 if( nc->HasTrackWidth() )
1108 {
1109 effectiveNetclass->SetTrackWidth( nc->GetTrackWidth() );
1110 effectiveNetclass->SetTrackWidthParent( nc );
1111 }
1112
1113 if( nc->HasViaDiameter() )
1114 {
1115 effectiveNetclass->SetViaDiameter( nc->GetViaDiameter() );
1116 effectiveNetclass->SetViaDiameterParent( nc );
1117 }
1118
1119 if( nc->HasViaDrill() )
1120 {
1121 effectiveNetclass->SetViaDrill( nc->GetViaDrill() );
1122 effectiveNetclass->SetViaDrillParent( nc );
1123 }
1124
1125 if( nc->HasuViaDiameter() )
1126 {
1127 effectiveNetclass->SetuViaDiameter( nc->GetuViaDiameter() );
1128 effectiveNetclass->SetuViaDiameterParent( nc );
1129 }
1130
1131 if( nc->HasuViaDrill() )
1132 {
1133 effectiveNetclass->SetuViaDrill( nc->GetuViaDrill() );
1134 effectiveNetclass->SetuViaDrillParent( nc );
1135 }
1136
1137 if( nc->HasDiffPairWidth() )
1138 {
1139 effectiveNetclass->SetDiffPairWidth( nc->GetDiffPairWidth() );
1140 effectiveNetclass->SetDiffPairWidthParent( nc );
1141 }
1142
1143 if( nc->HasDiffPairGap() )
1144 {
1145 effectiveNetclass->SetDiffPairGap( nc->GetDiffPairGap() );
1146 effectiveNetclass->SetDiffPairGapParent( nc );
1147 }
1148
1149 if( nc->HasDiffPairViaGap() )
1150 {
1151 effectiveNetclass->SetDiffPairViaGap( nc->GetDiffPairViaGap() );
1152 effectiveNetclass->SetDiffPairViaGapParent( nc );
1153 }
1154
1155 if( nc->HasWireWidth() )
1156 {
1157 effectiveNetclass->SetWireWidth( nc->GetWireWidth() );
1158 effectiveNetclass->SetWireWidthParent( nc );
1159 }
1160
1161 if( nc->HasBusWidth() )
1162 {
1163 effectiveNetclass->SetBusWidth( nc->GetBusWidth() );
1164 effectiveNetclass->SetBusWidthParent( nc );
1165 }
1166
1167 if( nc->HasLineStyle() )
1168 {
1169 effectiveNetclass->SetLineStyle( nc->GetLineStyle() );
1170 effectiveNetclass->SetLineStyleParent( nc );
1171 }
1172
1173 COLOR4D pcbColor = nc->GetPcbColor();
1174
1175 if( pcbColor != COLOR4D::UNSPECIFIED )
1176 {
1177 effectiveNetclass->SetPcbColor( pcbColor );
1178 effectiveNetclass->SetPcbColorParent( nc );
1179 }
1180
1181 COLOR4D schColor = nc->GetSchematicColor();
1182
1183 if( schColor != COLOR4D::UNSPECIFIED )
1184 {
1185 effectiveNetclass->SetSchematicColor( schColor );
1186 effectiveNetclass->SetSchematicColorParent( nc );
1187 }
1188
1189 if( nc->HasTuningProfile() )
1190 {
1191 effectiveNetclass->SetTuningProfile( nc->GetTuningProfile() );
1192 effectiveNetclass->SetTuningProfileParent( nc );
1193 }
1194 }
1195
1196 // Fill in any required defaults
1197 if( addMissingDefaults( effectiveNetclass.get() ) )
1198 constituentNetclasses.push_back( m_defaultNetClass.get() );
1199}
1200
1201
1203{
1204 bool addedDefault = false;
1205
1206 if( !nc->HasClearance() )
1207 {
1208 addedDefault = true;
1209 nc->SetClearance( m_defaultNetClass->GetClearance() );
1211 }
1212
1213 if( !nc->HasTrackWidth() )
1214 {
1215 addedDefault = true;
1216 nc->SetTrackWidth( m_defaultNetClass->GetTrackWidth() );
1218 }
1219
1220 if( !nc->HasViaDiameter() )
1221 {
1222 addedDefault = true;
1223 nc->SetViaDiameter( m_defaultNetClass->GetViaDiameter() );
1225 }
1226
1227 if( !nc->HasViaDrill() )
1228 {
1229 addedDefault = true;
1230 nc->SetViaDrill( m_defaultNetClass->GetViaDrill() );
1232 }
1233
1234 if( !nc->HasuViaDiameter() )
1235 {
1236 addedDefault = true;
1237 nc->SetuViaDiameter( m_defaultNetClass->GetuViaDiameter() );
1239 }
1240
1241 if( !nc->HasuViaDrill() )
1242 {
1243 addedDefault = true;
1244 nc->SetuViaDrill( m_defaultNetClass->GetuViaDrill() );
1246 }
1247
1248 if( !nc->HasDiffPairWidth() )
1249 {
1250 addedDefault = true;
1251 nc->SetDiffPairWidth( m_defaultNetClass->GetDiffPairWidth() );
1253 }
1254
1255 if( !nc->HasDiffPairGap() )
1256 {
1257 addedDefault = true;
1258 nc->SetDiffPairGap( m_defaultNetClass->GetDiffPairGap() );
1260 }
1261
1262 // Currently this is only on the default netclass, and not editable in the setup panel
1263 // if( !nc->HasDiffPairViaGap() )
1264 // {
1265 // addedDefault = true;
1266 // nc->SetDiffPairViaGap( m_defaultNetClass->GetDiffPairViaGap() );
1267 // nc->SetDiffPairViaGapParent( m_defaultNetClass.get() );
1268 // }
1269
1270 if( !nc->HasWireWidth() )
1271 {
1272 addedDefault = true;
1273 nc->SetWireWidth( m_defaultNetClass->GetWireWidth() );
1275 }
1276
1277 if( !nc->HasBusWidth() )
1278 {
1279 addedDefault = true;
1280 nc->SetBusWidth( m_defaultNetClass->GetBusWidth() );
1282 }
1283
1284 // The tuning profile can be empty - only fill if a default tuning profile is set
1285 if( !nc->HasTuningProfile() && m_defaultNetClass->HasTuningProfile() )
1286 {
1287 addedDefault = true;
1288 nc->SetTuningProfile( m_defaultNetClass->GetTuningProfile() );
1290 }
1291
1292 return addedDefault;
1293}
1294
1295
1296std::shared_ptr<NETCLASS> NET_SETTINGS::GetNetClassByName( const wxString& aNetClassName ) const
1297{
1298 auto ii = m_netClasses.find( aNetClassName );
1299
1300 if( ii == m_netClasses.end() )
1301 return m_defaultNetClass;
1302 else
1303 return ii->second;
1304}
1305
1306
1307static bool isSuperSubOverbar( wxChar c )
1308{
1309 return c == '_' || c == '^' || c == '~';
1310}
1311
1312
1320static bool isEscaped( const wxString& aStr, size_t aPos )
1321{
1322 if( aPos == 0 )
1323 return false;
1324
1325 // Count consecutive backslashes before this position
1326 int backslashCount = 0;
1327 size_t pos = aPos;
1328
1329 while( pos > 0 && aStr[pos - 1] == '\\' )
1330 {
1331 backslashCount++;
1332 pos--;
1333 }
1334
1335 // If odd number of backslashes, the character is escaped
1336 return ( backslashCount % 2 ) == 1;
1337}
1338
1339
1340bool NET_SETTINGS::ParseBusVector( const wxString& aBus, wxString* aName,
1341 std::vector<wxString>* aMemberList )
1342{
1343 auto isDigit =
1344 []( wxChar c )
1345 {
1346 static wxString digits( wxT( "0123456789" ) );
1347 return digits.Contains( c );
1348 };
1349
1350 size_t busLen = aBus.length();
1351 size_t i = 0;
1352 wxString prefix;
1353 wxString suffix;
1354 wxString tmp;
1355 long begin = 0;
1356 long end = 0;
1357 int braceNesting = 0;
1358 bool fmtWrapsName = false;
1359 bool inQuotes = false;
1360
1361 prefix.reserve( busLen );
1362
1363 // Parse prefix
1364 //
1365 // Formatting markers (^{}, _{}, ~{}) can appear either as part of the prefix name
1366 // (e.g. I^{2}C[0..7]) or wrapping the range specifier (e.g. D_{[1..2]}).
1367 // We preserve formatting in the prefix and only strip it when the range bracket
1368 // appears inside formatting braces, indicating the formatting wraps the range.
1369 //
1370 for( ; i < busLen; ++i )
1371 {
1372 // Handle quoted strings (allows spaces inside)
1373 if( aBus[i] == '"' && !isEscaped( aBus, i ) )
1374 {
1375 inQuotes = !inQuotes;
1376 continue;
1377 }
1378
1379 if( inQuotes )
1380 {
1381 // Inside quotes, add characters directly (including spaces)
1382 if( aBus[i] == '\\' && i + 1 < busLen )
1383 {
1384 // Handle escaped characters inside quotes
1385 prefix += aBus[++i];
1386 }
1387 else
1388 {
1389 prefix += aBus[i];
1390 }
1391
1392 continue;
1393 }
1394
1395 if( aBus[i] == '{' )
1396 {
1397 if( i > 0 && isSuperSubOverbar( aBus[i-1] ) )
1398 {
1399 braceNesting++;
1400 prefix += wxT( '{' );
1401 continue;
1402 }
1403 else
1404 return false;
1405 }
1406 else if( aBus[i] == '}' )
1407 {
1408 braceNesting--;
1409 prefix += wxT( '}' );
1410 continue;
1411 }
1412
1413 // Handle backslash-escaped spaces
1414 if( aBus[i] == '\\' && i + 1 < busLen && aBus[i + 1] == ' ' )
1415 {
1416 prefix += aBus[++i];
1417 continue;
1418 }
1419
1420 // Unescaped space or ] in bus vector prefix is not allowed
1421 if( aBus[i] == ' ' || aBus[i] == ']' )
1422 return false;
1423
1424 if( aBus[i] == '[' )
1425 {
1426 if( braceNesting > 0 )
1427 {
1428 size_t fmtStart = prefix.rfind( wxT( '{' ) );
1429
1430 if( fmtStart != wxString::npos && fmtStart > 0
1431 && isSuperSubOverbar( prefix[fmtStart - 1] ) )
1432 {
1433 if( fmtStart == prefix.length() - 1 )
1434 {
1435 // '{' immediately precedes '[' (e.g. D_{[1..2]}).
1436 // The formatting decorates the range indices, not the
1437 // name itself.
1438 prefix.erase( fmtStart - 1 );
1439 }
1440 else
1441 {
1442 // Name characters exist between '{' and '[' (e.g.
1443 // ~{BE[0..3]}). The formatting wraps the signal name,
1444 // not the range.
1445 fmtWrapsName = true;
1446 }
1447 }
1448 }
1449
1450 break;
1451 }
1452
1453 prefix += aBus[i];
1454 }
1455
1456 // Parse start number
1457 //
1458 i++; // '[' character
1459
1460 if( i >= busLen )
1461 return false;
1462
1463 for( ; i < busLen; ++i )
1464 {
1465 if( aBus[i] == '.' && i + 1 < busLen && aBus[i+1] == '.' )
1466 {
1467 tmp.ToLong( &begin );
1468 i += 2;
1469 break;
1470 }
1471
1472 if( !isDigit( aBus[i] ) )
1473 return false;
1474
1475 tmp += aBus[i];
1476 }
1477
1478 // Parse end number
1479 //
1480 tmp = wxEmptyString;
1481
1482 if( i >= busLen )
1483 return false;
1484
1485 for( ; i < busLen; ++i )
1486 {
1487 if( aBus[i] == ']' )
1488 {
1489 tmp.ToLong( &end );
1490 ++i;
1491 break;
1492 }
1493
1494 if( !isDigit( aBus[i] ) )
1495 return false;
1496
1497 tmp += aBus[i];
1498 }
1499
1500 // Parse suffix
1501 //
1502 for( ; i < busLen; ++i )
1503 {
1504 if( aBus[i] == '}' )
1505 {
1506 braceNesting--;
1507
1508 if( fmtWrapsName )
1509 suffix += aBus[i];
1510 }
1511 else if( aBus[i] == '+' || aBus[i] == '-' || aBus[i] == 'P' || aBus[i] == 'N' )
1512 {
1513 suffix += aBus[i];
1514 }
1515 else
1516 {
1517 return false;
1518 }
1519 }
1520
1521 if( braceNesting != 0 )
1522 return false;
1523
1524 if( begin == end )
1525 return false;
1526 else if( begin > end )
1527 std::swap( begin, end );
1528
1529 if( aName )
1530 *aName = prefix;
1531
1532 if( aMemberList )
1533 {
1534 for( long idx = begin; idx <= end; ++idx )
1535 {
1536 wxString str = prefix;
1537 str << idx;
1538 str << suffix;
1539
1540 aMemberList->emplace_back( str );
1541 }
1542 }
1543
1544 return true;
1545}
1546
1547
1548bool NET_SETTINGS::ParseBusGroup( const wxString& aGroup, wxString* aName,
1549 std::vector<wxString>* aMemberList )
1550{
1551 size_t groupLen = aGroup.length();
1552 size_t i = 0;
1553 wxString prefix;
1554 wxString tmp;
1555 int braceNesting = 0;
1556 bool inQuotes = false;
1557
1558 prefix.reserve( groupLen );
1559
1560 // Escape spaces in member names so recursive parsing by ForEachBusMember works correctly.
1561 // Both quoted strings and backslash-escaped spaces collapse to bare spaces during parsing,
1562 // so we must re-escape them for subsequent ParseBusVector/ParseBusGroup calls.
1563 auto escapeSpacesForBus =
1564 []( const wxString& aMember ) -> wxString
1565 {
1566 wxString escaped;
1567 escaped.reserve( aMember.length() * 2 );
1568
1569 for( wxUniChar c : aMember )
1570 {
1571 if( c == ' ' )
1572 escaped += wxT( "\\ " );
1573 else
1574 escaped += c;
1575 }
1576
1577 return escaped;
1578 };
1579
1580 // Parse prefix
1581 //
1582 // Formatting markers (^{}, _{}, ~{}) in the prefix are part of the group name
1583 // and must be preserved. The member-list opening brace is distinguished by NOT
1584 // being preceded by a formatting character.
1585 //
1586 for( ; i < groupLen; ++i )
1587 {
1588 // Handle quoted strings (allows spaces inside)
1589 if( aGroup[i] == '"' && !isEscaped( aGroup, i ) )
1590 {
1591 inQuotes = !inQuotes;
1592 continue;
1593 }
1594
1595 if( inQuotes )
1596 {
1597 // Inside quotes, add characters directly (including spaces)
1598 if( aGroup[i] == '\\' && i + 1 < groupLen )
1599 {
1600 // Handle escaped characters inside quotes
1601 prefix += aGroup[++i];
1602 }
1603 else
1604 {
1605 prefix += aGroup[i];
1606 }
1607
1608 continue;
1609 }
1610
1611 if( aGroup[i] == '{' )
1612 {
1613 if( i > 0 && isSuperSubOverbar( aGroup[i-1] ) )
1614 {
1615 braceNesting++;
1616 prefix += wxT( '{' );
1617 continue;
1618 }
1619 else
1620 break;
1621 }
1622 else if( aGroup[i] == '}' )
1623 {
1624 braceNesting--;
1625 prefix += wxT( '}' );
1626 continue;
1627 }
1628
1629 // Handle backslash-escaped spaces
1630 if( aGroup[i] == '\\' && i + 1 < groupLen && aGroup[i + 1] == ' ' )
1631 {
1632 prefix += aGroup[++i];
1633 continue;
1634 }
1635
1636 // Unescaped space, [, or ] in bus group prefix is not allowed
1637 if( aGroup[i] == ' ' || aGroup[i] == '[' || aGroup[i] == ']' )
1638 return false;
1639
1640 prefix += aGroup[i];
1641 }
1642
1643 if( braceNesting != 0 )
1644 return false;
1645
1646 if( aName )
1647 *aName = prefix;
1648
1649 // Parse members
1650 //
1651 i++; // '{' character
1652
1653 if( i >= groupLen )
1654 return false;
1655
1656 inQuotes = false;
1657
1658 for( ; i < groupLen; ++i )
1659 {
1660 // Handle quoted strings (allows spaces inside member names)
1661 if( aGroup[i] == '"' && !isEscaped( aGroup, i ) )
1662 {
1663 inQuotes = !inQuotes;
1664 continue;
1665 }
1666
1667 if( inQuotes )
1668 {
1669 // Inside quotes, add characters directly (including spaces)
1670 if( aGroup[i] == '\\' && i + 1 < groupLen )
1671 {
1672 // Handle escaped characters inside quotes
1673 tmp += aGroup[++i];
1674 }
1675 else
1676 {
1677 tmp += aGroup[i];
1678 }
1679
1680 continue;
1681 }
1682
1683 if( aGroup[i] == '{' )
1684 {
1685 if( i > 0 && isSuperSubOverbar( aGroup[i-1] ) )
1686 {
1687 braceNesting++;
1688
1689 // Keep the full formatting notation (e.g. ~{CAS}) in the member name.
1690 // A net named ~{CAS} is distinct from CAS, and stripping the marker
1691 // would lose that identity. Vector bus members like D_{[1..2]} also
1692 // preserve their subscript so recursive ForEachBusMember can parse them.
1693 tmp += wxT( '{' );
1694 continue;
1695 }
1696 else
1697 return false;
1698 }
1699 else if( aGroup[i] == '}' )
1700 {
1701 if( braceNesting )
1702 {
1703 braceNesting--;
1704 tmp += wxT( '}' );
1705 continue;
1706 }
1707 else
1708 {
1709 if( aMemberList && !tmp.IsEmpty() )
1710 aMemberList->push_back( EscapeString( escapeSpacesForBus( tmp ), CTX_NETNAME ) );
1711
1712 return true;
1713 }
1714 }
1715
1716 // Handle backslash-escaped spaces in member names
1717 if( aGroup[i] == '\\' && i + 1 < groupLen && aGroup[i + 1] == ' ' )
1718 {
1719 tmp += aGroup[++i];
1720 continue;
1721 }
1722
1723 // Unescaped space or comma separates members
1724 if( aGroup[i] == ' ' || aGroup[i] == ',' )
1725 {
1726 if( aMemberList && !tmp.IsEmpty() )
1727 aMemberList->push_back( EscapeString( escapeSpacesForBus( tmp ), CTX_NETNAME ) );
1728
1729 tmp.Clear();
1730 continue;
1731 }
1732
1733 tmp += aGroup[i];
1734 }
1735
1736 return false;
1737}
1738
1739
1740void NET_SETTINGS::ForEachBusMember( const wxString& aBusPattern,
1741 const std::function<void( const wxString& )>& aFunction )
1742{
1743 std::vector<wxString> members;
1744
1745 if( ParseBusVector( aBusPattern, nullptr, &members ) )
1746 {
1747 // Vector bus: call function for each expanded member
1748 for( const wxString& member : members )
1749 aFunction( member );
1750 }
1751 else if( ParseBusGroup( aBusPattern, nullptr, &members ) )
1752 {
1753 // Bus group: recursively expand each member (which may itself be a vector or group)
1754 for( const wxString& member : members )
1755 ForEachBusMember( member, aFunction );
1756 }
1757 else
1758 {
1759 // Not a bus pattern: call function with the original pattern
1760 aFunction( aBusPattern );
1761 }
1762}
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:398
void CloneFrom(const JSON_SETTINGS_INTERNALS &aOther)
virtual void Load()
Updates the parameters of this object based on the current JSON document contents.
std::vector< PARAM_BASE * > m_params
The list of parameters (owned by this object)
void registerMigration(int aOldSchemaVersion, int aNewSchemaVersion, std::function< bool(void)> aMigrator)
Registers a migration from one schema version to another.
JSON_SETTINGS_INTERNALS * Internals()
JSON_SETTINGS(const wxString &aFilename, SETTINGS_LOC aLocation, int aSchemaVersion)
std::unique_ptr< JSON_SETTINGS_INTERNALS > m_internals
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
JSON_SETTINGS * m_parent
A pointer to the parent object to load and store from.
NESTED_SETTINGS(const std::string &aName, int aSchemaVersion, JSON_SETTINGS *aParent, const std::string &aPath, bool aLoadFromFile=true)
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:38
void SetViaDiameter(int aDia)
Definition netclass.h:141
void SetViaDrill(int aSize)
Definition netclass.h:149
bool HasLineStyle() const
Definition netclass.h:239
int GetViaDiameter() const
Definition netclass.h:139
int GetViaDrill() const
Definition netclass.h:147
void SetWireWidthParent(NETCLASS *parent)
Definition netclass.h:214
static const char Default[]
the name of the default NETCLASS
Definition netclass.h:40
void SetuViaDrillParent(NETCLASS *parent)
Definition netclass.h:167
bool HasBusWidth() const
Definition netclass.h:217
bool HasuViaDrill() const
Definition netclass.h:162
void SetDiffPairWidthParent(NETCLASS *parent)
Definition netclass.h:175
void SetuViaDiameter(int aSize)
Definition netclass.h:157
void SetDiffPairWidth(int aSize)
Definition netclass.h:173
int HasViaDrill() const
Definition netclass.h:146
int GetDiffPairViaGap() const
Definition netclass.h:187
void SetViaDrillParent(NETCLASS *parent)
Definition netclass.h:151
wxString GetTuningProfile() const
Definition netclass.h:252
void SetDiffPairGapParent(NETCLASS *parent)
Definition netclass.h:183
void SetTuningProfileParent(NETCLASS *aParent)
Definition netclass.h:253
int GetDiffPairGap() const
Definition netclass.h:179
int GetuViaDrill() const
Definition netclass.h:163
bool HasViaDiameter() const
Definition netclass.h:138
int GetLineStyle() const
Definition netclass.h:240
bool HasDiffPairWidth() const
Definition netclass.h:170
bool HasuViaDiameter() const
Definition netclass.h:154
void SetTrackWidthParent(NETCLASS *parent)
Definition netclass.h:135
int GetuViaDiameter() const
Definition netclass.h:155
bool HasTrackWidth() const
Definition netclass.h:130
void SetViaDiameterParent(NETCLASS *parent)
Definition netclass.h:143
int GetDiffPairWidth() const
Definition netclass.h:171
void SetuViaDrill(int aSize)
Definition netclass.h:165
int GetWireWidth() const
Definition netclass.h:210
void SetDiffPairGap(int aSize)
Definition netclass.h:181
void SetBusWidthParent(NETCLASS *parent)
Definition netclass.h:222
void SetClearance(int aClearance)
Definition netclass.h:125
COLOR4D GetPcbColor(bool aIsForSave=false) const
Definition netclass.h:195
bool HasDiffPairGap() const
Definition netclass.h:178
COLOR4D GetSchematicColor(bool aIsForSave=false) const
Definition netclass.h:225
void SetBusWidth(int aWidth)
Definition netclass.h:220
void SetClearanceParent(NETCLASS *parent)
Definition netclass.h:127
int GetTrackWidth() const
Definition netclass.h:131
void SetWireWidth(int aWidth)
Definition netclass.h:212
void SetTuningProfile(const wxString &aTuningProfile)
Definition netclass.h:251
bool HasTuningProfile() const
Definition netclass.h:250
bool HasWireWidth() const
Definition netclass.h:209
int GetClearance() const
Definition netclass.h:123
void SetuViaDiameterParent(NETCLASS *parent)
Definition netclass.h:159
void SetTrackWidth(int aWidth)
Definition netclass.h:133
bool HasDiffPairViaGap() const
Definition netclass.h:186
int GetBusWidth() const
Definition netclass.h:218
bool HasClearance() const
Definition netclass.h:122
void ClearAllCaches()
Clears the effective netclass cache for all nets.
std::map< wxString, std::shared_ptr< NETCLASS > > m_compositeNetClasses
Map of netclass names to netclass definitions for.
bool addMissingDefaults(NETCLASS *nc) const
Adds any missing fields to the given netclass from the default netclass.
void ClearNetColorAssignments()
Clears all net name to color assignments Calling user is responsible for resetting the effective netc...
void ClearChainPatternAssignments()
Clears all chain-derived pattern assignments.
bool operator==(const NET_SETTINGS &aOther) const
void ClearCacheForNet(const wxString &netName)
Clears effective netclass cache for the given net.
std::shared_ptr< NETCLASS > GetEffectiveNetClass(const wxString &aNetName)
Fetches the effective (may be aggregate) netclass for the given net name.
bool HasEffectiveNetClass(const wxString &aNetName) const
Determines if an effective netclass for the given net name has been cached.
void addSinglePatternAssignment(const wxString &pattern, const wxString &netclass)
Adds a single pattern assignment without bus expansion (internal helper)
void ClearNetclassLabelAssignments()
Clears all net name to netclasses assignments Calling user is responsible for resetting the effective...
bool RenameNetPathPrefix(const wxString &aOldPrefix, const wxString &aNewPrefix)
Retarget netclass patterns and net colors after a path prefix changes (sheet rename).
void ClearNetclassLabelAssignment(const wxString &netName)
Clears a specific net name to netclass assignment Calling user is responsible for resetting the effec...
void ClearNetclassPatternAssignments()
Clears all netclass pattern assignments.
std::map< wxString, KIGFX::COLOR4D > m_netColorAssignments
A map of fully-qualified net names to colors used in the board context.
void SetNetclasses(const std::map< wxString, std::shared_ptr< NETCLASS > > &netclasses)
Sets all netclass Calling this method will reset the effective netclass calculation caches.
bool HasNetclassLabelAssignment(const wxString &netName) const
Determines if a given net name has netclasses assigned.
void SetNetclassLabelAssignment(const wxString &netName, const std::set< wxString > &netclasses)
Sets a net name to netclasses assignment Calling user is responsible for resetting the effective netc...
std::shared_ptr< NETCLASS > m_defaultNetClass
The default netclass.
virtual ~NET_SETTINGS()
static bool ParseBusGroup(const wxString &aGroup, wxString *name, std::vector< wxString > *aMemberList)
Parse a bus group label into the name and a list of components.
void ClearNetclasses()
Clears all netclasses Calling this method will reset the effective netclass calculation caches.
std::map< wxString, std::shared_ptr< NETCLASS > > m_impicitNetClasses
Map of netclass names to netclass definitions for implicit netclasses.
const std::map< wxString, std::shared_ptr< NETCLASS > > & GetCompositeNetclasses() const
Gets all composite (multiple assignment / missing defaults) netclasses.
std::vector< std::pair< std::unique_ptr< EDA_COMBINED_MATCHER >, wxString > > m_netClassPatternAssignments
List of net class pattern assignments.
bool migrateSchema3to4()
bool migrateSchema0to1()
std::map< wxString, std::shared_ptr< NETCLASS > > m_effectiveNetclassCache
Cache of nets to pattern-matched netclasses.
void SetNetclassPatternAssignments(std::vector< std::pair< std::unique_ptr< EDA_COMBINED_MATCHER >, wxString > > &&netclassPatterns)
Sets all netclass pattern assignments Calling user is responsible for resetting the effective netclas...
void SetNetclassPatternAssignment(const wxString &pattern, const wxString &netclass)
Sets a netclass pattern assignment Calling this method will reset the effective netclass calculation ...
bool migrateSchema2to3()
std::map< wxString, std::shared_ptr< NETCLASS > > m_netClasses
Map of netclass names to netclass definitions.
void addSingleChainPatternAssignment(const wxString &pattern, const wxString &netclass)
Adds a single chain-derived pattern assignment without bus expansion (internal helper)
const std::map< wxString, std::set< wxString > > & GetNetclassLabelAssignments() const
Gets all current net name to netclasses assignments.
const std::map< wxString, std::shared_ptr< NETCLASS > > & GetNetclasses() const
Gets all netclasses.
std::vector< std::pair< std::unique_ptr< EDA_COMBINED_MATCHER >, wxString > > m_netClassChainPatternAssignments
List of chain-derived netclass pattern assignments.
std::shared_ptr< NETCLASS > GetDefaultNetclass() const
Gets the default netclass for the project.
void SetChainPatternAssignment(const wxString &pattern, const wxString &netclass)
Sets a chain-derived netclass pattern assignment.
static bool ParseBusVector(const wxString &aBus, wxString *aName, std::vector< wxString > *aMemberList)
Parse a bus vector (e.g.
const std::map< wxString, KIGFX::COLOR4D > & GetNetColorAssignments() const
Gets all net name to color assignments.
bool migrateSchema1to2()
void CopyFrom(NET_SETTINGS &aOther)
Deep-copy the persisted contents of aOther into this instance.
std::vector< std::pair< std::unique_ptr< EDA_COMBINED_MATCHER >, wxString > > & GetNetclassPatternAssignments()
Gets the netclass pattern assignments.
void RecomputeEffectiveNetclasses()
Recomputes the internal values of all aggregate effective netclasses Called when a value of a user-de...
std::shared_ptr< NETCLASS > GetCachedEffectiveNetClass(const wxString &aNetName) const
Returns an already cached effective netclass for the given net name.
bool migrateSchema4to5()
std::map< wxString, std::set< wxString > > m_netClassLabelAssignments
Map of net names to resolved netclasses.
void SetNetclass(const wxString &netclassName, std::shared_ptr< NETCLASS > &netclass)
Sets the given netclass Calling user is responsible for resetting the effective netclass calculation ...
void makeEffectiveNetclass(std::shared_ptr< NETCLASS > &effectiveNetclass, std::vector< NETCLASS * > &netclasses) const
Creates an effective aggregate netclass from the given constituent netclasses.
void AppendNetclassLabelAssignment(const wxString &netName, const std::set< wxString > &netclasses)
Apppends to a net name to netclasses assignment Calling user is responsible for resetting the effecti...
void SetDefaultNetclass(std::shared_ptr< NETCLASS > netclass)
Sets the default netclass for the project Calling user is responsible for resetting the effective net...
static void ForEachBusMember(const wxString &aBusPattern, const std::function< void(const wxString &)> &aFunction)
Call a function for each member of an expanded bus pattern.
std::shared_ptr< NETCLASS > GetNetClassByName(const wxString &aNetName) const
Get a NETCLASS object from a given Netclass name string.
void SetNetColorAssignment(const wxString &netName, const KIGFX::COLOR4D &color)
Sets a net to color assignment Calling user is responsible for resetting the effective netclass calcu...
NET_SETTINGS(JSON_SETTINGS *aParent, const std::string &aPath)
bool HasNetclass(const wxString &netclassName) const
Determines if the given netclass exists.
std::map< wxString, wxString > m_netChainClasses
Map of net-chain name -> chain-class name.
Like a normal param, but with custom getter and setter functions.
Definition parameters.h:297
bool isDigit(char cc)
Definition dsnlexer.cpp:465
#define _(s)
@ CTX_NETCLASS
nlohmann::json json
Definition gerbview.cpp:49
static bool isSuperSubOverbar(wxChar c)
const int netSettingsSchemaVersion
static bool isEscaped(const wxString &aStr, size_t aPos)
Check if a character at the given position is escaped by a backslash.
static std::optional< int > getInSchUnits(const nlohmann::json &aObj, const std::string &aKey, std::optional< int > aDefault=std::optional< int >())
static std::optional< int > getInPcbUnits(const nlohmann::json &aObj, const std::string &aKey, std::optional< int > aDefault=std::optional< int >())
wxString ConvertToNewOverbarNotation(const wxString &aOldStr)
Convert the old ~...~ overbar notation to the new ~{...} one.
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
@ CTX_NETNAME
const SHAPE_LINE_CHAIN chain
VECTOR2I end