Altium import, nets, canonical pins, component kinds, set/$var, scrollback, source modal.
Major additions, all wired end-to-end with doctest coverage:
- Altium netlist importer (`imports/import_altium.{hpp,cpp}`): two-pass
parser for `[ ]` parts and `( )` signals; `System::Load` no longer has
the IMPORT_ALTIUM hole.
- `duplicate <src> <dst>` deep-copies a module (signals, parts, pins,
rewired signals); connections excluded by design.
- Nets (`system/nets.{hpp,cpp}`): BFS over `Connection::pin_map` to
return the transitive (Module, Signal) closure. `verify` extended with
a second pass flagging Power↔GndShield inconsistencies in bridged
nets; new `net <module> <signal>` command for inspection.
- Canonical pin names (`system/pin_name.{hpp,cpp}`): zero-padded digit
suffix lets A1 ↔ A001 pair via `IdentityTransform` and
`CheckIdentityCompatible` without losing the imported notation.
- Component classification (`system/component_kind.{hpp,cpp}`):
`Part::kind` inferred at construction from the reference-designator
prefix (longest-match: LED/TP/SW/FB/MK/MP/MH/HS/RA/RN/RP/RV first,
then R/C/L/F/D/Q/U/J/P/Y/X/S).
- Identity wiring tolerance: `CheckIdentityCompatible` accepts the
subset case (typical when one importer drops NC pins, e.g. Altium)
and surfaces orphans as an info string. `FillIdentityNCs`
materialises orphan canonical positions as NC pins on the missing
side at connect time.
- Connector layout preparation: `pin_layout(kind)` and
`FillPartFromLayout(part, kind)` stubs in `pin_role`, called from
`set-type`. Empty today; populate alongside `vpx_3u_role`.
- TUI scrollback: PageUp/PageDown step 10 lines, Home/End jump to
ends; `Print()` snaps back to the tail.
- `set <name> <value>` declares session variables; `$name` / `${name}`
expanded inside `Finalize` between canonical-form recording and the
action call — history and script-save preserve `$var` references.
- Long `source` scripts now show a centred "Computing…" modal with a
N/M progress counter. Driven by a ticker thread that posts one
paced `Event::Special` per processed line, ack'd by the main thread,
so heavy lines don't backlog ticks and freeze the counter.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "system/connect.hpp"
|
||||
#include "system/modules.hpp"
|
||||
#include "system/nets.hpp"
|
||||
#include "system/parts.hpp"
|
||||
#include "system/persist.hpp"
|
||||
#include "system/pin_role.hpp"
|
||||
@@ -32,7 +33,8 @@ void Tui::RegisterCommands() {
|
||||
+ std::string(maxw - kv.first.size() + 2, ' ')
|
||||
+ kv.second.description);
|
||||
}
|
||||
Print("Keys: Esc cancels a multi-step prompt; Tab completes commands or paths.");
|
||||
Print("Keys: Esc cancels a multi-step prompt; Tab completes commands or paths;");
|
||||
Print(" PageUp/PageDown scroll output (10 lines), Home/End jump to top/bottom.");
|
||||
return;
|
||||
}
|
||||
const std::string &name = args[0];
|
||||
@@ -66,8 +68,42 @@ void Tui::RegisterCommands() {
|
||||
commands["new"] = { {}, [this](auto &) {
|
||||
sys = std::make_unique<System>();
|
||||
recorded.clear();
|
||||
vars.clear();
|
||||
Print("system created.");
|
||||
}, true, "create a new (empty) system; resets the script-save buffer" };
|
||||
}, true, "create a new (empty) system; resets the script-save buffer and $vars" };
|
||||
|
||||
commands["set"] = {
|
||||
{{"name", Completion::None},
|
||||
{"value", Completion::None}},
|
||||
[this](const std::vector<std::string> &args) {
|
||||
if (args.empty()) {
|
||||
if (vars.empty()) { Print("(no variables defined)"); return; }
|
||||
for (const auto &kv : vars)
|
||||
Print(" $" + kv.first + " = " + kv.second);
|
||||
return;
|
||||
}
|
||||
if (args.size() != 2) {
|
||||
Print("usage: set <name> <value> (or no args to list)");
|
||||
return;
|
||||
}
|
||||
const std::string &name = args[0];
|
||||
if (name.empty()) { Print("set: empty name"); return; }
|
||||
for (size_t i = 0; i < name.size(); ++i) {
|
||||
char c = name[i];
|
||||
bool ok = std::isalnum((unsigned char)c) || c == '_';
|
||||
bool first_ok = i == 0 ? !std::isdigit((unsigned char)c) : true;
|
||||
if (!ok || !first_ok) {
|
||||
Print("set: invalid name '" + name
|
||||
+ "' (must match [A-Za-z_][A-Za-z0-9_]*)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
vars[name] = args[1];
|
||||
},
|
||||
/*prompt_for_missing=*/ false,
|
||||
"define a $variable for substitution in subsequent commands "
|
||||
"(no args = list defined vars)",
|
||||
};
|
||||
|
||||
commands["load"] = {
|
||||
{{"module name", Completion::None},
|
||||
@@ -180,10 +216,60 @@ void Tui::RegisterCommands() {
|
||||
}
|
||||
}
|
||||
}
|
||||
Print("verify: " + std::to_string(mismatches) + " mismatch(es) over "
|
||||
Print("verify: " + std::to_string(mismatches) + " local mismatch(es) over "
|
||||
+ std::to_string(checked) + " typed pin(s).");
|
||||
|
||||
auto nets = compute_all_nets(sys.get());
|
||||
int bridged = 0, inconsistent = 0;
|
||||
for (const auto &n : nets) {
|
||||
if (n.members.size() < 2) continue;
|
||||
++bridged;
|
||||
SignalType dom;
|
||||
if (net_type_consistent(n, dom)) continue;
|
||||
++inconsistent;
|
||||
std::string line = " net mixes Power and GndShield:";
|
||||
for (const auto &mp : n.members) {
|
||||
line += " " + mp.first->name + "/" + mp.second->name
|
||||
+ "(" + signal_type_name(mp.second->type) + ")";
|
||||
}
|
||||
Print(line);
|
||||
}
|
||||
Print("verify: " + std::to_string(inconsistent) + " inconsistent net(s) over "
|
||||
+ std::to_string(bridged) + " bridged net(s) ("
|
||||
+ std::to_string(nets.size()) + " total).");
|
||||
}, true,
|
||||
"check that each pin's connected signal matches its connector_type's expected role" };
|
||||
"check pin roles locally and signal-type consistency across bridged nets" };
|
||||
|
||||
commands["net"] = {
|
||||
{{"module", Completion::None},
|
||||
{"signal name", Completion::None}},
|
||||
[this](const std::vector<std::string> &args) {
|
||||
if (!sys) { Print("no system: run 'new' first."); return; }
|
||||
Module *mod;
|
||||
try { mod = sys->modules()->get(args[0]); }
|
||||
catch (const std::exception &) {
|
||||
Print("unknown module: " + args[0]); return;
|
||||
}
|
||||
Signal *sig;
|
||||
try { sig = mod->signals->get(args[1]); }
|
||||
catch (const std::exception &) {
|
||||
Print("unknown signal: " + mod->name + "/" + args[1]); return;
|
||||
}
|
||||
Net n = find_net(sys.get(), mod, sig);
|
||||
SignalType dom;
|
||||
bool ok = net_type_consistent(n, dom);
|
||||
Print("net containing " + mod->name + "/" + sig->name
|
||||
+ " — " + std::to_string(n.members.size()) + " signal(s)"
|
||||
+ (ok ? "" : " [INCONSISTENT]")
|
||||
+ ", dominant: " + signal_type_name(dom));
|
||||
for (const auto &mp : n.members) {
|
||||
Print(" " + mp.first->name + "/" + mp.second->name
|
||||
+ " (" + signal_type_name(mp.second->type) + ")");
|
||||
}
|
||||
},
|
||||
/*prompt_for_missing=*/ true,
|
||||
"show all signals reachable from <module>/<signal> through connections",
|
||||
};
|
||||
|
||||
commands["set-signal-type"] = {
|
||||
{{"module", Completion::None},
|
||||
@@ -267,10 +353,14 @@ void Tui::RegisterCommands() {
|
||||
return;
|
||||
}
|
||||
prt->connector_type = args[2];
|
||||
int filled = FillPartFromLayout(prt, args[2]);
|
||||
for (auto &kv : *prt)
|
||||
kv.second->expected_signal_type = pin_role(args[2], kv.first);
|
||||
Print(mod->name + "/" + prt->name + ": connector_type = "
|
||||
+ (args[2].empty() ? "(none)" : args[2]));
|
||||
if (filled > 0)
|
||||
Print("set-type: materialised " + std::to_string(filled)
|
||||
+ " NC pin(s) from connector layout");
|
||||
},
|
||||
/*prompt_for_missing=*/ false,
|
||||
"tag a part's connector type for transform lookup",
|
||||
@@ -376,11 +466,19 @@ void Tui::RegisterCommands() {
|
||||
+ "'. Set matching types via 'set-type' first.");
|
||||
return;
|
||||
}
|
||||
std::string err = CheckIdentityCompatible(p1, p2);
|
||||
std::string info;
|
||||
std::string err = CheckIdentityCompatible(p1, p2, &info);
|
||||
if (!err.empty()) {
|
||||
Print("connect refused: " + err);
|
||||
return;
|
||||
}
|
||||
if (!info.empty()) {
|
||||
int added = FillIdentityNCs(p1, p2);
|
||||
Print("connect: " + info);
|
||||
if (added > 0)
|
||||
Print("connect: materialised " + std::to_string(added)
|
||||
+ " NC pin(s) so both sides match");
|
||||
}
|
||||
}
|
||||
auto pin_map = t->apply(p1, p2);
|
||||
|
||||
@@ -478,4 +576,58 @@ void Tui::RegisterCommands() {
|
||||
/*prompt_for_missing=*/ false,
|
||||
"list parts/signals matching a pattern (interactive screen if no args)",
|
||||
};
|
||||
|
||||
commands["duplicate"] = {
|
||||
{{"source module", Completion::None},
|
||||
{"new module name", Completion::None}},
|
||||
[this](const std::vector<std::string> &args) {
|
||||
if (!sys) { Print("no system: run 'new' first."); return; }
|
||||
|
||||
Module *src;
|
||||
try { src = sys->modules()->get(args[0]); }
|
||||
catch (const std::exception &) {
|
||||
Print("unknown module: " + args[0]); return;
|
||||
}
|
||||
if (sys->modules()->exists(args[1])) {
|
||||
Print("duplicate refused: module '" + args[1] + "' already exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
Module *dst = new Module(args[1]);
|
||||
|
||||
// 1. Copy signals (preserve type overrides).
|
||||
for (auto &skv : *src->signals) {
|
||||
Signal *ss = skv.second;
|
||||
Signal *ds = new Signal(ss->name);
|
||||
ds->type = ss->type;
|
||||
dst->signals->add(ds);
|
||||
}
|
||||
|
||||
// 2. Copy parts, pins, and re-wire pin→signal.
|
||||
for (auto &pkv : *src) {
|
||||
Part *sp = pkv.second;
|
||||
Part *dp = new Part(sp->name);
|
||||
dp->connector_type = sp->connector_type;
|
||||
for (auto &nkv : *sp) {
|
||||
Pin *sn = nkv.second;
|
||||
Pin *dn = new Pin(sn->name);
|
||||
dn->expected_signal_type = sn->expected_signal_type;
|
||||
dp->add(dn);
|
||||
if (sn->signal()) {
|
||||
Signal *ds = dst->signals->get(sn->signal()->name);
|
||||
ds->add(dn);
|
||||
dn->connect(ds);
|
||||
}
|
||||
}
|
||||
dst->add(dp);
|
||||
}
|
||||
|
||||
sys->modules()->add(dst);
|
||||
Print("duplicate: '" + args[0] + "' → '" + args[1] + "'"
|
||||
+ " (" + std::to_string(dst->size()) + " part(s), "
|
||||
+ std::to_string(dst->signals->size()) + " signal(s))");
|
||||
},
|
||||
/*prompt_for_missing=*/ true,
|
||||
"clone a module under a new name (parts, pins, signals; no connections)",
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user