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>
94 lines
2.9 KiB
C++
94 lines
2.9 KiB
C++
#include "import_altium.hpp"
|
|
|
|
#include "system/parts.hpp"
|
|
#include "system/pins.hpp"
|
|
#include "system/signals.hpp"
|
|
|
|
#include <cctype>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
std::string strip(const std::string &s) {
|
|
size_t i = 0, j = s.size();
|
|
while (i < j && std::isspace((unsigned char)s[i])) ++i;
|
|
while (j > i && std::isspace((unsigned char)s[j - 1])) --j;
|
|
return s.substr(i, j - i);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ImportAltium::ImportAltium(std::string filename) : ImportBase(filename) {}
|
|
|
|
// Altium netlist text format:
|
|
// * Parts section: blocks delimited by `[` and `]`. First non-empty line
|
|
// inside a block is the part name.
|
|
// * Signals section: blocks delimited by `(` and `)`. First non-empty line
|
|
// is the signal name; subsequent lines are `partname-pinname` entries.
|
|
//
|
|
// Both sections may interleave; we make two passes — parts first so signals
|
|
// can resolve their part references.
|
|
void ImportAltium::parse(Signals *signals) {
|
|
std::vector<std::string> lines;
|
|
std::string raw;
|
|
while (std::getline(file_lines, raw)) lines.push_back(raw);
|
|
|
|
enum class State { Out, In };
|
|
|
|
// Pass 1: parts.
|
|
{
|
|
State sta = State::Out;
|
|
int lnum = 0;
|
|
for (const auto &l : lines) {
|
|
std::string t = strip(l);
|
|
if (t == "]") { sta = State::Out; continue; }
|
|
if (sta == State::Out) {
|
|
if (t == "[") { sta = State::In; lnum = 0; }
|
|
continue;
|
|
}
|
|
++lnum;
|
|
if (lnum == 1 && !t.empty() && !prts->exists(t)) {
|
|
prts->add(new Part(t));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 2: signals + pins.
|
|
{
|
|
State sta = State::Out;
|
|
int lnum = 0;
|
|
Signal *sig = nullptr;
|
|
for (const auto &l : lines) {
|
|
std::string t = strip(l);
|
|
if (t == ")") { sta = State::Out; sig = nullptr; continue; }
|
|
if (sta == State::Out) {
|
|
if (t == "(") { sta = State::In; lnum = 0; sig = nullptr; }
|
|
continue;
|
|
}
|
|
++lnum;
|
|
if (t.empty()) continue;
|
|
if (lnum == 1) {
|
|
sig = signals->merge(t);
|
|
continue;
|
|
}
|
|
if (!sig) continue;
|
|
// Split on first '-' so pin names containing dashes survive.
|
|
auto dash = t.find('-');
|
|
if (dash == std::string::npos) continue;
|
|
std::string pname = strip(t.substr(0, dash));
|
|
std::string pinname = strip(t.substr(dash + 1));
|
|
if (pname.empty() || pinname.empty()) continue;
|
|
Part *prt = nullptr;
|
|
try { prt = prts->get(pname); }
|
|
catch (...) { continue; }
|
|
if (prt->exists(pinname)) continue;
|
|
Pin *pin = new Pin(pinname);
|
|
try { prt->add(pin); }
|
|
catch (...) { delete pin; continue; }
|
|
sig->add(pin);
|
|
pin->connect(sig);
|
|
}
|
|
}
|
|
}
|