Merge pull request #462 from Kellermorph/makro-fix

follow up: wiring list
This commit is contained in:
Laurent Trinques
2026-05-19 13:15:14 +02:00
committed by GitHub
2 changed files with 163 additions and 125 deletions

View File

@@ -5,9 +5,7 @@
#include <QTextStream>
#include <QDomDocument>
#include <QFile>
#include <QRegularExpression>
#include <QQueue>
#include <QSet>
#include <algorithm>
WiringListExport::WiringListExport(QETProject *project, QWidget *parent) :
QObject(parent),
@@ -45,8 +43,27 @@ QDomElement WiringListExport::climbToDiagram(QDomNode node) const
QMap<QString, ElementInfo> WiringListExport::collectElementsInfo(const QDomElement &root) const
{
QMap<QString, ElementInfo> infoMap;
QDomNodeList elements = root.elementsByTagName("element");
QSet<QString> placeholderTypes;
QDomElement collection = root.firstChildElement("collection");
if (!collection.isNull()) {
QDomNodeList defs = collection.elementsByTagName("definition");
for (int i = 0; i < defs.size(); ++i) {
QDomElement def = defs.at(i).toElement();
QString ltype = def.attribute("link_type");
if (ltype == "next_report" || ltype == "previous_report") {
QDomElement parentEl = def.parentNode().toElement();
if (parentEl.tagName().toLower() == "element") {
QString name = parentEl.attribute("name");
if (!name.isEmpty()) {
placeholderTypes.insert(name);
}
}
}
}
}
QDomNodeList elements = root.elementsByTagName("element");
for (int i = 0; i < elements.size(); ++i) {
QDomElement el = elements.at(i).toElement();
QString uuid = normalizeUuid(el.attribute("uuid", el.attribute("id", "")));
@@ -75,10 +92,13 @@ QMap<QString, ElementInfo> WiringListExport::collectElementsInfo(const QDomEleme
}
}
QString typeVal = el.attribute("type").toLower();
if (typeVal.contains("naechste") || typeVal.contains("vorherige") ||
typeVal.contains("next") || typeVal.contains("previous")) {
QString typeVal = el.attribute("type");
info.isPlaceholder = false;
for (const QString &ptype : placeholderTypes) {
if (typeVal.endsWith(ptype)) {
info.isPlaceholder = true;
break;
}
}
infoMap.insert(uuid, info);
@@ -102,7 +122,15 @@ QList<ConductorData> WiringListExport::collectConductors(const QDomElement &root
data.el2_uuid = normalizeUuid(cond.attribute("element2", cond.attribute("element2id", "")));
data.element1_label = cond.attribute("element1_label");
if (data.element1_label.isEmpty()) {
data.element1_label = cond.attribute("element1_linked");
}
data.element2_label = cond.attribute("element2_label");
if (data.element2_label.isEmpty()) {
data.element2_label = cond.attribute("element2_linked");
}
data.terminalname1 = cond.attribute("terminalname1");
data.terminalname2 = cond.attribute("terminalname2");
data.tension_protocol = cond.attribute("tension_protocol");
@@ -119,101 +147,6 @@ QList<ConductorData> WiringListExport::collectConductors(const QDomElement &root
return conductors;
}
void WiringListExport::resolveEndpoints(QList<ConductorData> &conductors, const QMap<QString, ElementInfo> &elementsInfo) const
{
QRegularExpression numericLabelRe("^\\d+(\\.\\d+)?$");
QMap<QString, QList<ConductorData>> el_to_cons;
for (const ConductorData &c : conductors) {
if (!c.el1_uuid.isEmpty()) el_to_cons[c.el1_uuid].append(c);
if (!c.el2_uuid.isEmpty()) el_to_cons[c.el2_uuid].append(c);
}
for (int i = 0; i < conductors.size(); ++i) {
ConductorData &c = conductors[i];
auto resolveSide = [&](const QString &startUuid, QString &outLabel, QString &outTerminal) {
if (startUuid.isEmpty() || !elementsInfo.contains(startUuid)) return;
const ElementInfo &startInfo = elementsInfo[startUuid];
if (!startInfo.links.isEmpty() || startInfo.isPlaceholder) {
QQueue<QString> q;
QSet<QString> visited;
q.enqueue(startUuid);
visited.insert(startUuid);
int depth = 0;
while (!q.isEmpty() && depth < 3) {
int levelSize = q.size();
for (int k = 0; k < levelSize; ++k) {
QString curr = q.dequeue();
if (elementsInfo.contains(curr)) {
const ElementInfo &currInfo = elementsInfo[curr];
if (!currInfo.isPlaceholder && !currInfo.label.isEmpty() && !numericLabelRe.match(currInfo.label).hasMatch()) {
outLabel = currInfo.label;
return;
}
for (const QString &lnk : currInfo.links) {
if (!visited.contains(lnk)) {
visited.insert(lnk);
q.enqueue(lnk);
}
}
}
for (const ConductorData &cond : el_to_cons.value(curr)) {
if (cond.index == c.index) continue;
QString other;
QString terminalHint;
if (cond.el1_uuid == curr) {
other = cond.el2_uuid;
terminalHint = cond.terminalname2;
} else {
other = cond.el1_uuid;
terminalHint = cond.terminalname1;
}
if (!other.isEmpty() && !visited.contains(other)) {
if (elementsInfo.contains(other)) {
const ElementInfo &oInfo = elementsInfo[other];
if (!oInfo.isPlaceholder && !oInfo.label.isEmpty() && !numericLabelRe.match(oInfo.label).hasMatch()) {
outLabel = oInfo.label;
if (outTerminal.isEmpty()) outTerminal = terminalHint;
return;
}
}
visited.insert(other);
q.enqueue(other);
}
}
}
depth++;
}
} else {
if (outLabel.isEmpty()) {
outLabel = startInfo.label.isEmpty() ? startInfo.name : startInfo.label;
}
}
};
bool p1 = elementsInfo.value(c.el1_uuid).isPlaceholder;
bool p2 = elementsInfo.value(c.el2_uuid).isPlaceholder;
if (c.element1_label.isEmpty() || p1) {
if (p1) c.element1_label = "";
resolveSide(c.el1_uuid, c.element1_label, c.terminalname1);
}
if (c.element2_label.isEmpty() || p2) {
if (p2) c.element2_label = "";
resolveSide(c.el2_uuid, c.element2_label, c.terminalname2);
}
}
}
void WiringListExport::toCsv()
{
if (!m_project) return;
@@ -243,25 +176,140 @@ void WiringListExport::toCsv()
QMap<QString, ElementInfo> elementsInfo = collectElementsInfo(doc.documentElement());
QList<ConductorData> conductors = collectConductors(doc.documentElement());
resolveEndpoints(conductors, elementsInfo);
QList<ConductorData> uniqueConductors;
QSet<QString> seenConnections;
QMap<QString, ConductorData> partialWires;
for (const ConductorData &c : conductors) {
if (c.element1_label.isEmpty() && c.element2_label.isEmpty()) continue;
auto normalizePartial = [](ConductorData c, const QString &ph_uuid) {
if (c.el1_uuid == ph_uuid) {
std::swap(c.el1_uuid, c.el2_uuid);
std::swap(c.element1_label, c.element2_label);
std::swap(c.terminalname1, c.terminalname2);
}
return c;
};
QString sideA = c.element1_label + ":" + c.terminalname1;
QString sideB = c.element2_label + ":" + c.terminalname2;
auto mergeField = [](const QString &a, const QString &b) {
QString at = a.trimmed();
QString bt = b.trimmed();
if (at.isEmpty()) return bt;
if (bt.isEmpty()) return at;
if (at == bt) return at;
return at + ", " + bt;
};
QString key = (sideA < sideB) ? (sideA + "||" + sideB) : (sideB + "||" + sideA);
for (int i = 0; i < conductors.size(); ++i) {
ConductorData c = conductors[i];
if (!seenConnections.contains(key)) {
seenConnections.insert(key);
if (c.element1_label.isEmpty() && elementsInfo.contains(c.el1_uuid)) {
c.element1_label = elementsInfo[c.el1_uuid].label;
if (c.element1_label.isEmpty()) c.element1_label = elementsInfo[c.el1_uuid].name;
}
if (c.element2_label.isEmpty() && elementsInfo.contains(c.el2_uuid)) {
c.element2_label = elementsInfo[c.el2_uuid].label;
if (c.element2_label.isEmpty()) c.element2_label = elementsInfo[c.el2_uuid].name;
}
bool el1_ph = elementsInfo.value(c.el1_uuid).isPlaceholder;
bool el2_ph = elementsInfo.value(c.el2_uuid).isPlaceholder;
if (!el1_ph && !el2_ph) {
uniqueConductors.append(c);
continue;
}
if (el1_ph && el2_ph) {
uniqueConductors.append(c);
continue;
}
QString ph_uuid = el1_ph ? c.el1_uuid : c.el2_uuid;
ConductorData normC = normalizePartial(c, ph_uuid);
QString matching_ph_uuid;
if (!elementsInfo[ph_uuid].links.isEmpty()) {
matching_ph_uuid = elementsInfo[ph_uuid].links.first();
}
if (!matching_ph_uuid.isEmpty() && partialWires.contains(matching_ph_uuid)) {
ConductorData otherHalf = partialWires.take(matching_ph_uuid);
ConductorData merged;
merged.folio = mergeField(otherHalf.folio, normC.folio);
merged.el1_uuid = otherHalf.el1_uuid;
merged.element1_label = otherHalf.element1_label;
merged.terminalname1 = otherHalf.terminalname1;
merged.el2_uuid = normC.el1_uuid;
merged.element2_label = normC.element1_label;
merged.terminalname2 = normC.terminalname1;
merged.tension_protocol = mergeField(otherHalf.tension_protocol, normC.tension_protocol);
merged.conductor_color = mergeField(otherHalf.conductor_color, normC.conductor_color);
merged.conductor_section = mergeField(otherHalf.conductor_section, normC.conductor_section);
merged.function = mergeField(otherHalf.function, normC.function);
uniqueConductors.append(merged);
} else {
partialWires.insert(ph_uuid, normC);
}
}
for (const ConductorData &leftover : partialWires.values()) {
uniqueConductors.append(leftover);
}
for (ConductorData &c : uniqueConductors) {
if (!c.element2_label.isEmpty() && (c.element1_label.isEmpty() || c.element2_label.toLower() < c.element1_label.toLower())) {
std::swap(c.element1_label, c.element2_label);
std::swap(c.terminalname1, c.terminalname2);
std::swap(c.el1_uuid, c.el2_uuid);
}
}
std::sort(uniqueConductors.begin(), uniqueConductors.end(), [](const ConductorData &a, const ConductorData &b) {
QStringList partsA = a.folio.split(',');
QStringList partsB = b.folio.split(',');
int minLen = std::min(partsA.size(), partsB.size());
int folioCmp = 0;
for (int i = 0; i < minLen; ++i) {
bool okA, okB;
int numA = partsA[i].trimmed().toInt(&okA);
int numB = partsB[i].trimmed().toInt(&okB);
if (okA && okB) {
if (numA != numB) {
folioCmp = (numA < numB) ? -1 : 1;
break;
}
} else {
int strCmp = partsA[i].trimmed().compare(partsB[i].trimmed(), Qt::CaseInsensitive);
if (strCmp != 0) {
folioCmp = strCmp;
break;
}
}
}
if (folioCmp == 0 && partsA.size() != partsB.size()) {
folioCmp = (partsA.size() < partsB.size()) ? -1 : 1;
}
if (folioCmp != 0) return folioCmp < 0;
int el1Cmp = a.element1_label.toLower().compare(b.element1_label.toLower());
if (el1Cmp != 0) return el1Cmp < 0;
int el2Cmp = a.element2_label.toLower().compare(b.element2_label.toLower());
if (el2Cmp != 0) return el2Cmp < 0;
int term1Cmp = a.terminalname1.compare(b.terminalname1);
if (term1Cmp != 0) return term1Cmp < 0;
return a.terminalname2 < b.terminalname2;
});
QTextStream out(&file);
out << tr("Page", "Wiring list CSV header") << ";"
<< tr("Composant 1", "Wiring list CSV header") << ";"

View File

@@ -12,7 +12,6 @@ class QWidget;
class QDomElement;
class QDomNode;
// Internal data structures for parsing the XML graph
struct ElementInfo {
QString folio;
QStringList links;
@@ -34,18 +33,11 @@ struct ConductorData {
QString conductor_section;
QString function;
QString folio;
// Resolved endpoints
QString chosen_a_uuid;
QString chosen_a_label;
QString chosen_b_uuid;
QString chosen_b_label;
};
/**
* @brief The WiringListExport class
* Handles the export of the wiring list (Verdrahtungsplan) to a CSV file.
* Automatically resolves links and placeholders to find physical endpoints.
* Exports the wiring diagram from QElectroTech as a CSV file.
*/
class WiringListExport : public QObject
{
@@ -64,8 +56,6 @@ private:
QMap<QString, ElementInfo> collectElementsInfo(const QDomElement &root) const;
QList<ConductorData> collectConductors(const QDomElement &root) const;
void resolveEndpoints(QList<ConductorData> &conductors, const QMap<QString, ElementInfo> &elementsInfo) const;
};
#endif // WIRINGLISTEXPORT_H