From 65b10b01d9d8a0c708bebce40fc07ecd6f227b39 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Mon, 12 Oct 2026 12:20:46 +1300 Subject: [PATCH] Export the wiring list without building every folio (stage D5) The wiring list export read the project as QETProject::toXml() writes it, which builds every folio. It now reads a document made without building: a built folio written as saving writes it (QETProject::folioXml(), which keeps its symbols' links to partners not built), a folio not built copied from its XML, and what QElectroTech computes for a wire's ends -- its symbols' labels, their last partner's label, the terminals' names -- filled in from the symbols built and from the project database. A symbol still waiting for a partner not built takes its partners from the database, saved order. 136-folio .qetz, wiring list from the command line: 30.1 s with every folio built, 4.5 s lazily, same file. tst_lazyfolios: the wiring list of a .qet and of a .qetz as when built, building no folio. New corpus gate (it fails on all 6 projects tried with terminal names broken): --export-cables and --export-wires lazily from the .qet and the .qetz = eager on 342 projects; one (forum f3386) differed until waiting symbols took their partners from the database. ctest 96/96 eager and lazy (one earlier run had modal_quit_regression and tst_resaveunchanged fail once each, both passing on rerun in both modes); 342 corpus projects save the same in all four arms. Co-Authored-By: Claude Opus 5.5 --- sources/dataBase/projectdatabase.cpp | 28 +++++++++ sources/dataBase/projectdatabase.h | 2 + sources/diagram.h | 2 + sources/qetproject.cpp | 14 +++++ sources/qetproject.h | 1 + sources/wiringlistexport.cpp | 88 +++++++++++++++++++++++++++- sources/wiringlistexport.h | 1 + tests/qttest/lazy_folios_probe.cpp | 28 +++++---- 8 files changed, 153 insertions(+), 11 deletions(-) diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index 460e68b29..74e391b5f 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -1289,6 +1289,34 @@ QList projectDataBase::wiresOnFolios(const QList wiresOnFolios(const QList &folios) const; + QString elementLabel(const QUuid &element) const; + QString terminalName(const QUuid &terminal, const QUuid &element) const; static QVariant sequenceValue(const autonum::sequentialNumbers &sequence); static autonum::sequentialNumbers sequenceFromValue(const QVariant &value); diff --git a/sources/diagram.h b/sources/diagram.h index d79439795..b29730aa4 100644 --- a/sources/diagram.h +++ b/sources/diagram.h @@ -229,6 +229,8 @@ class Diagram : public QGraphicsScene /// Symbols built as partners (buildSymbols()) changed since: /// the folio must be built to be saved bool partialChanged() const {return m_partial_changed;} + /// The symbols built as partners (buildSymbols()), by uuid + QHash partialElements() const {return m_partial_elements;} QString headerXml(); /// The folio's text in the file it was read from, while it is /// not built (QETProject::toXml()) diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index 34e46e67d..b788313c9 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -2978,6 +2978,20 @@ bool QETProject::usesElement(const ElementsLocation &location) const return(unbuiltFolioElements().contains(location)); } +/** + @brief QETProject::folioXml + @return the built folio @p folio written as saving writes it: its + symbols keep the links to partners on folios not built yet + (QET_LAZY_FOLIOS), which Diagram::toXml() alone leaves out +*/ +QDomDocument QETProject::folioXml(Diagram *folio) +{ + m_waiting_link_targets = unloadedFolioCount() ? symbolsOnUnbuiltFolios() : QSet(); + const QDomDocument xml = folio->toXml(); + m_waiting_link_targets.clear(); + return xml; +} + /** @brief QETProject::buildFoliosHoldingTables Build the folios not built yet (QET_LAZY_FOLIOS) that hold a drawn diff --git a/sources/qetproject.h b/sources/qetproject.h index 509a532a8..8d8d8bded 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -140,6 +140,7 @@ class QETProject : public QObject /// While saving: the symbols on folios not built a built symbol /// may be saved linked to (Element::toXml()) QSet waitingLinkTargets() const {return m_waiting_link_targets;} + QDomDocument folioXml(Diagram *folio); QSet tableChainFolios(Diagram *folio) const; void loadFolios() const; void buildFolios(const QList &folios) const; diff --git a/sources/wiringlistexport.cpp b/sources/wiringlistexport.cpp index 39282ac69..c69df19f8 100644 --- a/sources/wiringlistexport.cpp +++ b/sources/wiringlistexport.cpp @@ -1,6 +1,9 @@ #include "wiringlistexport.h" #include "qetproject.h" #include "diagram.h" +#include "dataBase/projectdatabase.h" +#include "ElementsCollection/xmlelementcollection.h" +#include "qetgraphicsitem/element.h" #include #include #include @@ -48,6 +51,89 @@ QDomElement WiringListExport::climbToDiagram(QDomNode node) const return QDomElement(); } +/** + The project as the export reads it: its symbol collection and its folios + in order, each with its "order". A folio not built (QET_LAZY_FOLIOS) is + not built for it: its XML is copied, and what QElectroTech computes for + its wires' ends -- the labels of the symbols they end on and of their + partners, the terminals' names -- is filled in from the symbols built + (on built folios or alone, as partners) and from the project database. +*/ +QDomDocument WiringListExport::projectDocument() const +{ + QDomDocument document; + QDomElement root = document.createElement(QStringLiteral("project")); + document.appendChild(root); + if (XmlElementCollection *collection = m_project->embeddedElementCollection()) + root.appendChild(document.importNode(collection->root(), true)); + + //A folio whose partners built alone were linked or unlinked since + //is not what its XML says: built, as saving builds it + for (Diagram *diagram : m_project->folios()) + if (!diagram->isLoaded() && diagram->partialChanged()) + m_project->buildFolios({diagram}); + + QHash built; + for (Diagram *diagram : m_project->folios()) { + if (diagram->isLoaded()) { + for (Element *element : diagram->elements()) built.insert(element->uuid(), element); + } else { + const QHash partial = diagram->partialElements(); + for (auto it = partial.cbegin() ; it != partial.cend() ; ++it) built.insert(it.key(), it.value()); + } + } + projectDataBase *database = m_project->dataBase(); + auto label = [&built, database](const QUuid &uuid) { + Element *element = built.value(uuid); + return element ? element->actualLabel() : database->elementLabel(uuid); + }; + //Conductor::toXml(): the label of the last of the symbol's partners. + //A symbol still waiting for a partner not built has its links as + //saved, in the database + auto linked = [&built, database, &label](const QUuid &uuid) -> std::optional { + Element *element = built.value(uuid); + if (element && element->pendingLinks().isEmpty()) { + if (element->linkedElements().isEmpty()) return std::nullopt; + return element->linkedElements().last()->actualLabel(); + } + const QList links = database->linkedSymbols(uuid); + if (links.isEmpty()) return std::nullopt; + return label(links.last()); + }; + + const QList folios = m_project->folios(); + for (int i = 0 ; i < folios.size() ; ++i) { + Diagram *diagram = folios.at(i); + QDomElement folio; + const bool is_built = diagram->isLoaded(); + folio = document.importNode(is_built ? m_project->folioXml(diagram).documentElement() + : diagram->deferredXml(), true).toElement(); + //A built folio's wires describe their ends but a partner not + //built yet; a folio not built has none of it in its part (.qetz) + //or as read, before any change (.qet) + QDomNodeList wires = folio.elementsByTagName(QStringLiteral("conductor")); + for (int w = 0 ; w < wires.size() ; ++w) { + QDomElement wire = wires.at(w).toElement(); + for (const QString end : {QStringLiteral("1"), QStringLiteral("2")}) { + const QUuid element(wire.attribute(QStringLiteral("element") + end)); + if (element.isNull()) continue; //an end saved the older way + if (const auto partner = linked(element)) + wire.setAttribute(QStringLiteral("element") + end + QStringLiteral("_linked"), *partner); + else + wire.removeAttribute(QStringLiteral("element") + end + QStringLiteral("_linked")); + if (is_built) continue; + wire.setAttribute(QStringLiteral("element") + end + QStringLiteral("_label"), label(element)); + wire.setAttribute(QStringLiteral("terminalname") + end, + database->terminalName(QUuid(wire.attribute(QStringLiteral("terminal") + end)), + element)); + } + } + folio.setAttribute(QStringLiteral("order"), i + 1); + root.appendChild(folio); + } + return document; +} + QMap WiringListExport::collectElementsInfo(const QDomElement &root) const { QMap infoMap; @@ -191,7 +277,7 @@ QString WiringListExport::toCsvString() const { if (!m_project) return QString(); - QDomDocument doc = m_project->toXml(); + QDomDocument doc = projectDocument(); if (doc.isNull()) return QString(); QSet conductorDefinitionTypes; diff --git a/sources/wiringlistexport.h b/sources/wiringlistexport.h index 8f1696b29..759e099a2 100644 --- a/sources/wiringlistexport.h +++ b/sources/wiringlistexport.h @@ -60,6 +60,7 @@ private: QString normalizeUuid(const QString &u) const; QString findDiagramFolio(const QDomElement &diagramElem) const; QDomElement climbToDiagram(QDomNode node) const; + QDomDocument projectDocument() const; QMap collectElementsInfo(const QDomElement &root) const; QList collectConductors(const QDomElement &root) const; diff --git a/tests/qttest/lazy_folios_probe.cpp b/tests/qttest/lazy_folios_probe.cpp index 2a809693c..4c1a24e96 100644 --- a/tests/qttest/lazy_folios_probe.cpp +++ b/tests/qttest/lazy_folios_probe.cpp @@ -385,8 +385,16 @@ int main(int argc, char **argv) QETProject exported(path); check(ConductorNumExport(&exported).wiresNum() == ConductorNumExport(&eager).wiresNum(), "wire numbers of a .qet exported as when built"); + const QString wiring = WiringListExport(&exported).toCsvString(), usual_wiring = WiringListExport(&eager).toCsvString(); + if (wiring != usual_wiring) { + const QStringList a = wiring.split('\n'), b = usual_wiring.split('\n'); + for (const QString &line : b) if (!a.contains(line)) std::printf(" wiring when built: %s\n", qPrintable(line)); + for (const QString &line : a) if (!b.contains(line)) std::printf(" wiring lazily: %s\n", qPrintable(line)); + std::printf(" %d lines lazily, %d when built\n", int(a.size()), int(b.size())); + } + check(wiring == usual_wiring, "wiring list of a .qet exported as when built"); check(exported.unloadedFolioCount() == folios.size() - strip_folios.size(), - "exporting a .qet's wire numbers builds no folio"); + "exporting a .qet's wire numbers and wiring list builds no folio"); } //A symbol placed on a folio, numbered by a scheme others follow QString location, title; @@ -707,13 +715,6 @@ int main(int argc, char **argv) ++continued; } check(continued > 0, "a folio whose table continues another"); - { - //The wiring list export writes the whole project out - //(toXml()), building every folio: as when built - QETProject exported(zipped); - check(WiringListExport(&exported).toCsvString() == WiringListExport(&eager).toCsvString(), - "wiring list exported as when built"); - } //Numbering, the searches for symbols and the unused symbols //are answered from the database: no folio file is read @@ -799,7 +800,7 @@ int main(int argc, char **argv) check(listed.unloadedFolioCount() > 0, "listing the tables does not build every folio"); } - //The wire numbers export and the terminal + //The wire numbers and wiring list exports, and the terminal //numbering dialog: as when built, building only the folios //holding a terminal (D5) { @@ -807,7 +808,14 @@ int main(int argc, char **argv) const int unbuilt = exported.unloadedFolioCount(); check(ConductorNumExport(&exported).wiresNum() == ConductorNumExport(&eager).wiresNum(), "wire numbers exported as when built"); - check(exported.unloadedFolioCount() == unbuilt, "exporting wire numbers builds no folio"); + const QString wiring = WiringListExport(&exported).toCsvString(), usual_wiring = WiringListExport(&eager).toCsvString(); + if (wiring != usual_wiring) { + const QStringList a = wiring.split('\n'), b = usual_wiring.split('\n'); + for (const QString &line : b) if (!a.contains(line)) std::printf(" wiring when built: %s\n", qPrintable(line)); + for (const QString &line : a) if (!b.contains(line)) std::printf(" wiring lazily: %s\n", qPrintable(line)); + } + check(wiring == usual_wiring, "wiring list exported as when built"); + check(exported.unloadedFolioCount() == unbuilt, "exporting wire numbers and the wiring list builds no folio"); { TerminalNumberingDialog dialog(nullptr, &exported); } int with_terminals = 0; for (Diagram *folio : folios)