mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-05-20 03:39:58 +02:00
Merge pull request #462 from Kellermorph/makro-fix
follow up: wiring list
This commit is contained in:
@@ -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") << ";"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user