diff --git a/sources/cli_export.cpp b/sources/cli_export.cpp index 44aa2b8d8..a6b85e580 100644 --- a/sources/cli_export.cpp +++ b/sources/cli_export.cpp @@ -19,9 +19,11 @@ #include "bordertitleblock.h" #include "conductornumexport.h" +#include "conductorproperties.h" #include "dataBase/projectdatabase.h" #include "diagram.h" #include "diagramcontext.h" +#include "qetgraphicsitem/conductor.h" #include "qetgraphicsitem/element.h" #include "qetgraphicsitem/terminal.h" #include "qetproject.h" @@ -37,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -57,12 +60,22 @@ const QHash &exportFlags() {"--export-cables", "cables"}, {"--export-wires", "wires"}, {"--export-bom", "bom"}, + {"--export-nets", "nets"}, + {"--export-links", "links"}, {"--info", "info"}, {"--check-elements", "check"}, + {"--resave", "resave"}, }; return flags; } +/// Device tag of an element ("K1", "Q55"), falling back to its name. +QString elementLabel(Element *element) +{ + const QString label = element->elementInformations()["label"].toString(); + return label.isEmpty() ? element->name() : label; +} + /// Pixel rect of a diagram's border + title block (the printable page area). QRect diagramRect(Diagram *diagram) { @@ -426,6 +439,164 @@ int checkElements(const QString &path) return failures > 0 ? 1 : 0; } +/// Map every element in the project to its 1-based folio (page) position. +QHash folioIndex(QETProject &project) +{ + QHash folio; + int index = 0; + const QList diagrams = project.diagrams(); + for (Diagram *diagram : diagrams) { + ++index; + const QList elements = diagram->elements(); + for (Element *e : elements) + folio.insert(e, index); + } + return folio; +} + +/// Electrical nets: groups of terminals joined into one potential. +/// Walks QET's own potential graph, so each net is a connected component +/// of terminals across all folios. The ground truth for connectivity. +int exportNets(QETProject &project, const QString &output) +{ + const QHash folio = folioIndex(project); + + QList all_conductors; + const QList diagrams = project.diagrams(); + for (Diagram *diagram : diagrams) + all_conductors << diagram->conductors(); + + QSet visited; + QJsonArray nets; + int net_no = 0; + for (Conductor *c : all_conductors) { + if (visited.contains(c)) + continue; + + // The whole potential this conductor belongs to. relatedPotential- + // Conductors() also fills t_list with every terminal in the net + // (following folio reports and terminal blocks too). + QList t_list; + QSet group = c->relatedPotentialConductors(true, &t_list); + group.insert(c); + for (Conductor *g : group) + visited.insert(g); + if (c->terminal1) t_list << c->terminal1; + if (c->terminal2) t_list << c->terminal2; + + // Wire number: smallest non-empty conductor text (deterministic). + QStringList wire_nos; + for (Conductor *g : group) + if (!g->properties().text.isEmpty()) + wire_nos << g->properties().text; + wire_nos.sort(); + + ++net_no; + QJsonArray terminals; + QSet seen; + for (Terminal *t : t_list) { + if (!t || seen.contains(t)) + continue; + seen.insert(t); + Element *pe = t->parentElement(); + QJsonObject to; + to["element"] = pe ? elementLabel(pe) : QString(); + to["terminal"] = t->name(); + to["folio"] = pe ? folio.value(pe, 0) : 0; + terminals.append(to); + } + QJsonObject net; + net["net"] = net_no; + net["wire_no"] = wire_nos.value(0); + net["terminals"] = terminals; + nets.append(net); + } + + QJsonObject root; + root["project"] = project.title(); + root["nets"] = nets.size(); + root["list"] = nets; + + QFile file(output); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + err << "Cannot open '" << output << "' for writing.\n"; + return 1; + } + file.write(QJsonDocument(root).toJson(QJsonDocument::Indented)); + file.close(); + out << "Exported " << nets.size() << " net(s) -> " << output << "\n"; + return 0; +} + +/// Cross-references: each linkable element (coil / contact / report) and the +/// elements it links to, flagging masters/slaves with no link as unresolved. +int exportLinks(QETProject &project, const QString &output) +{ + const QHash folio = folioIndex(project); + + QString csv("element;link_type;linked_to;folio;status\n"); + int linkable = 0, unresolved = 0; + + const QList diagrams = project.diagrams(); + for (Diagram *diagram : diagrams) { + const QList elements = diagram->elements(); + for (Element *e : elements) { + if (e->linkType() == Element::Simple) + continue; + ++linkable; + + const QList linked = e->linkedElements(); + QStringList names; + for (Element *le : linked) + names << elementLabel(le) % "(f" + % QString::number(folio.value(le, 0)) % ")"; + + QString status = "linked"; + if ((e->linkType() == Element::Master + || e->linkType() == Element::Slave) + && linked.isEmpty()) { + status = "UNRESOLVED"; + ++unresolved; + } + + csv += csvField(elementLabel(e)) % ";" + % e->linkTypeToString() % ";" + % csvField(names.join(", ")) % ";" + % QString::number(folio.value(e, 0)) % ";" + % status % "\n"; + } + } + + QFile file(output); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + err << "Cannot open '" << output << "' for writing.\n"; + return 1; + } + QTextStream fout(&file); + fout << csv; + file.close(); + out << "Exported " << linkable << " linkable element(s), " + << unresolved << " unresolved -> " << output << "\n"; + return 0; +} + +/// Round-trip: load the project and write its XML back out, so an external +/// diff can reveal markup QET silently normalises (tolerated-but-invalid XML). +int resaveProject(QETProject &project, const QString &output) +{ + const QDomDocument doc = project.toXml(); + QFile file(output); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + err << "Cannot open '" << output << "' for writing.\n"; + return 1; + } + QTextStream fout(&file); + fout << doc.toString(4); + file.close(); + out << "Re-saved project -> " << output << "\n"; + return 0; +} + } // anonymous namespace namespace CLIExport { @@ -495,6 +666,12 @@ int run(const QStringList &args) return exportCsv(project, format, output); if (format == "bom") return exportBom(project, output); + if (format == "nets") + return exportNets(project, output); + if (format == "links") + return exportLinks(project, output); + if (format == "resave") + return resaveProject(project, output); return exportImages(project, format, output); } diff --git a/sources/cli_export.h b/sources/cli_export.h index 149a45107..5abe194ca 100644 --- a/sources/cli_export.h +++ b/sources/cli_export.h @@ -48,17 +48,24 @@ namespace CLIExport { qelectrotech --export-cables qelectrotech --export-wires qelectrotech --export-bom + qelectrotech --export-nets + qelectrotech --export-links qelectrotech --info [output.json] qelectrotech --check-elements + qelectrotech --resave PDF: one multi-page document (one diagram per page). PNG/SVG: one file per diagram, named /_.<ext>. cables: wiring list (one row per conductor) as CSV. wires: list of distinct wire numbers as CSV. bom: bill of materials (one row per element) as CSV. + nets: electrical nets (connected-terminal groups) as JSON. + links: element cross-references (coil/contact) as CSV, with + unresolved links flagged. info: structural project summary as JSON (stdout, or a file) — per-page element / conductor counts and unconnected terminals. check-elements: validate .elmt file(s) against the element schema. + resave: load and rewrite the project XML (round-trip integrity). */ int run(const QStringList &args);