mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-03 01:04:13 +02:00
Merge pull request #1248 from ispyisail/feature/wiring-terminal-identity
Add terminal index and uuid to the wiring list export
This commit is contained in:
@@ -542,6 +542,15 @@ Python, plus the hang guard on `addConductor` and the database refresh in
|
||||
`qet.terminalIndex()` in the build. A symbol file saved without terminal
|
||||
uuids lists them empty; QElectroTech gives the terminals of every
|
||||
project's copy of it a uuid on opening (#1118), written on the next save.
|
||||
- **The `wiring` export names unnamed terminals.** Most shipped symbols
|
||||
leave their terminals unnamed, so `from_terminal`/`to_terminal` are often
|
||||
empty. Each row also ends with `from_terminal_index`,
|
||||
`from_terminal_uuid`, `to_terminal_index` and `to_terminal_uuid`: the
|
||||
index `add_conductor` takes, and the uuid the `.qet` names the terminal
|
||||
by (as `qet_edit` accepts in place of the index). The index is empty for
|
||||
a terminal sharing its point with another, where the order is undefined;
|
||||
the uuid tells those apart. `wiring_list_view` carries the same four
|
||||
columns for `qet_query`.
|
||||
- **`qet_export` isolates its launch.** SingleApplication keys its socket
|
||||
on `applicationFilePath()`, so a second launch of the same binary path
|
||||
forwards its request to an already-running instance and returns *that*
|
||||
|
||||
@@ -22,6 +22,7 @@ away: a check that has never been seen to fail is not evidence.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -3675,6 +3676,44 @@ class Integration(unittest.TestCase):
|
||||
note = r["operations"][1].get("note", "")
|
||||
self.assertIn("from_terminal: no terminal", note)
|
||||
self.assertIn("to_terminal: no terminal", note)
|
||||
def test_wiring_export_names_unnamed_terminals(self):
|
||||
"""--export-wiring gives each end the index add_conductor took and
|
||||
the uuid the file names the terminal by, even where the terminal
|
||||
has no name. This strip's file lists its terminals in another order
|
||||
than the index (top to bottom, then left to right), so a column
|
||||
holding the file's order would fail here."""
|
||||
strip = ("common://10_electric/10_allpole/130_terminals_terminal_strips/"
|
||||
"90_terminal_strips_diagram/90-10-0211.elmt")
|
||||
base = self.sb.new()
|
||||
r = self.ok(self.sb.edit(base, [
|
||||
{"op": "add_element", "id": "a", "folio": 0, "path": strip, "x": 100, "y": 200},
|
||||
{"op": "add_element", "id": "b", "folio": 0, "path": strip, "x": 400, "y": 200},
|
||||
{"op": "add_conductor", "folio": 0, "from": "$a", "from_terminal": 2,
|
||||
"to": "$b", "to_terminal": 3},
|
||||
{"op": "add_conductor", "folio": 0, "from": "$a", "from_terminal": 1,
|
||||
"to": "$b", "to_terminal": 0}]))
|
||||
a_uuid, b_uuid = (o["result"] for o in r["operations"][:2])
|
||||
out = self.sb.p("wiring.csv")
|
||||
e = m.tool_export(BINARY, r["output"], "wiring", out)
|
||||
self.assertTrue(e["ok"], e)
|
||||
with open(out, encoding="utf-8-sig", newline="") as fh:
|
||||
rows = list(csv.DictReader(fh, delimiter=";"))
|
||||
self.assertEqual(len(rows), 2)
|
||||
# the conductor in the file: which uuid it names each end by
|
||||
in_file = {c.get("uuid"): {(c.get("element1"), c.get("terminal1")),
|
||||
(c.get("element2"), c.get("terminal2"))}
|
||||
for c in ET.parse(r["output"]).getroot().iter("conductor")
|
||||
if c.get("element1")}
|
||||
got = set()
|
||||
for row in rows:
|
||||
self.assertEqual((row["from_terminal"], row["to_terminal"]), ("", ""))
|
||||
ends = {(int(row["from_terminal_index"]), row["from_terminal_uuid"]),
|
||||
(int(row["to_terminal_index"]), row["to_terminal_uuid"])}
|
||||
self.assertEqual({u for _, u in ends},
|
||||
{t for _, t in in_file[row["conductor_uuid"]]})
|
||||
got.add(frozenset(i for i, _ in ends))
|
||||
self.assertEqual(got, {frozenset({2, 3}), frozenset({1, 0})})
|
||||
|
||||
def test_folio_ref_survives_an_insert_before_it(self):
|
||||
"""add_folio names a folio "$f"; inserting another at position 0
|
||||
moves it from index 1 to 2. "$f" must still name it."""
|
||||
|
||||
@@ -620,6 +620,12 @@ QHash<Element *, int> folioIndex(QETProject &project)
|
||||
/// From-to wiring list: one row per conductor, each endpoint resolved to its
|
||||
/// element label and terminal name.
|
||||
///
|
||||
/// Most symbols leave their terminals unnamed, so each end also carries the
|
||||
/// terminal's index -- the one the scripting API's addConductor() takes,
|
||||
/// empty for two terminals at one point -- and its uuid, the one the project
|
||||
/// file names the terminal by. These come last, after the columns the list
|
||||
/// has always had, so a reader of those is not disturbed.
|
||||
///
|
||||
/// Reads wiring_list_view out of the project database. --export-cables produces
|
||||
/// the same logical list from the document XML instead, and the two are meant
|
||||
/// to agree: running both and diffing them is a direct check that the database
|
||||
@@ -632,7 +638,8 @@ int exportWiring(QETProject &project, const QString &output)
|
||||
|
||||
static const QStringList columns {
|
||||
"wire_number", "from_element_label", "from_terminal",
|
||||
"to_element_label", "to_terminal", "diagram_position", "conductor_uuid"
|
||||
"to_element_label", "to_terminal", "diagram_position", "conductor_uuid",
|
||||
"from_terminal_index", "from_terminal_uuid", "to_terminal_index", "to_terminal_uuid"
|
||||
};
|
||||
|
||||
QSqlQuery query = project.dataBase()->newQuery(
|
||||
|
||||
@@ -180,8 +180,37 @@ struct DocumentTerminal
|
||||
QString uuid;
|
||||
QString name;
|
||||
bool master_label = false;
|
||||
QVariant index;
|
||||
};
|
||||
|
||||
//The index each of @p points has in Element::terminals() -- the index
|
||||
//the scripting API's addConductor() and conductor calls take -- or a
|
||||
//null QVariant for a point another terminal shares. Element::
|
||||
//parseTerminal() sorts the list top to bottom, then left to right, on
|
||||
//each terminal's position in its definition, and the sort is not
|
||||
//stable, so which of two terminals at the same point comes first is
|
||||
//not defined: no index is given rather than one that can change.
|
||||
QList<QVariant> terminalIndexes(const QList<QPointF> &points)
|
||||
{
|
||||
QList<int> order;
|
||||
for (int i = 0 ; i < points.size() ; ++i) {
|
||||
order << i;
|
||||
}
|
||||
std::stable_sort(order.begin(), order.end(), [&points](int a, int b) {
|
||||
if (points.at(a).y() == points.at(b).y()) {
|
||||
return points.at(a).x() < points.at(b).x();
|
||||
}
|
||||
return points.at(a).y() < points.at(b).y();
|
||||
});
|
||||
QList<QVariant> indexes(points.size());
|
||||
for (int i = 0 ; i < order.size() ; ++i) {
|
||||
if (points.count(points.at(order.at(i))) == 1) {
|
||||
indexes[order.at(i)] = i;
|
||||
}
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
|
||||
struct DocumentDefinition
|
||||
{
|
||||
QString type;
|
||||
@@ -391,6 +420,25 @@ bool projectDataBase::populateFromDocument(const QDomDocument &document, QString
|
||||
data.fromXml(definition);
|
||||
read.type = data.typeToString();
|
||||
read.sub_type = data.masterTypeToString();
|
||||
//Every terminal the element is built with counts towards
|
||||
//the indexes, as Element::parseTerminal() keeps every one
|
||||
//whose position reads, in every <description>.
|
||||
QList<QDomElement> parsed;
|
||||
QList<QPointF> points;
|
||||
for (QDomElement d = definition.firstChildElement(QStringLiteral("description")) ;
|
||||
!d.isNull() ; d = d.nextSiblingElement(QStringLiteral("description"))) {
|
||||
for (QDomElement t = d.firstChildElement(QStringLiteral("terminal")) ;
|
||||
!t.isNull() ; t = t.nextSiblingElement(QStringLiteral("terminal")))
|
||||
{
|
||||
qreal x, y;
|
||||
if (QET::attributeIsAReal(t, QStringLiteral("x"), &x)
|
||||
&& QET::attributeIsAReal(t, QStringLiteral("y"), &y)) {
|
||||
parsed << t;
|
||||
points << QPointF(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
const QList<QVariant> indexes = terminalIndexes(points);
|
||||
const QDomElement description = definition.firstChildElement(QStringLiteral("description"));
|
||||
for (QDomElement t = description.firstChildElement(QStringLiteral("terminal")) ;
|
||||
!t.isNull() ; t = t.nextSiblingElement(QStringLiteral("terminal")))
|
||||
@@ -403,6 +451,10 @@ bool projectDataBase::populateFromDocument(const QDomDocument &document, QString
|
||||
terminal.uuid = terminal_uuid.toString();
|
||||
terminal.name = t.attribute(QStringLiteral("name"));
|
||||
terminal.master_label = t.attribute(QStringLiteral("use_master_label")) == QLatin1String("true");
|
||||
const int place = parsed.indexOf(t);
|
||||
if (place >= 0) {
|
||||
terminal.index = indexes.at(place);
|
||||
}
|
||||
read.terminals.insert(terminal_uuid, terminal);
|
||||
}
|
||||
known = definitions.insert(type, read);
|
||||
@@ -582,8 +634,8 @@ bool projectDataBase::populateFromDocument(const QDomDocument &document, QString
|
||||
for (const auto &end : {std::make_pair(conductor.element1, conductor.terminal1),
|
||||
std::make_pair(conductor.element2, conductor.terminal2)}) {
|
||||
const DocumentElement &owner = elements.at(element_index.value(end.first));
|
||||
insertTerminal(end.second, end.first,
|
||||
owner.terminals.value(QUuid(end.second)).name);
|
||||
const DocumentTerminal &terminal = owner.terminals.value(QUuid(end.second));
|
||||
insertTerminal(end.second, end.first, terminal.name, terminal.index);
|
||||
}
|
||||
m_insert_conductor_query.bindValue(QStringLiteral(":uuid"), conductor.uuid);
|
||||
m_insert_conductor_query.bindValue(QStringLiteral(":diagram_uuid"), conductor.diagram_uuid);
|
||||
@@ -1560,6 +1612,7 @@ bool projectDataBase::createDataBase()
|
||||
"uuid VARCHAR(50) NOT NULL, "
|
||||
"element_uuid VARCHAR(50) NOT NULL,"
|
||||
"name VARCHAR(50),"
|
||||
"terminal_index INTEGER,"
|
||||
"PRIMARY KEY (uuid, element_uuid),"
|
||||
"FOREIGN KEY (element_uuid) REFERENCES element (uuid)"
|
||||
")");
|
||||
@@ -1812,7 +1865,11 @@ void projectDataBase::createWiringListView()
|
||||
"t2.element_uuid AS to_element_uuid,"
|
||||
"ei2.label AS to_element_label,"
|
||||
"t2.name AS to_terminal,"
|
||||
"d.pos AS diagram_position"
|
||||
"d.pos AS diagram_position,"
|
||||
"t1.uuid AS from_terminal_uuid,"
|
||||
"t1.terminal_index AS from_terminal_index,"
|
||||
"t2.uuid AS to_terminal_uuid,"
|
||||
"t2.terminal_index AS to_terminal_index"
|
||||
" FROM conductor c"
|
||||
" JOIN terminal t1 ON c.terminal1_uuid = t1.uuid AND c.terminal1_element_uuid = t1.element_uuid"
|
||||
" JOIN terminal t2 ON c.terminal2_uuid = t2.uuid AND c.terminal2_element_uuid = t2.element_uuid"
|
||||
@@ -1999,9 +2056,15 @@ void projectDataBase::populateConductorTable()
|
||||
*/
|
||||
void projectDataBase::insertTerminal(Terminal *terminal)
|
||||
{
|
||||
const QList<Terminal *> terminals = terminal->parentElement()->terminals();
|
||||
QList<QPointF> points;
|
||||
for (const Terminal *t : terminals) {
|
||||
points << terminal->parentElement()->mapFromScene(t->dockConductor());
|
||||
}
|
||||
insertTerminal(terminal->stableUuid().toString(),
|
||||
terminal->parentElement()->uuid().toString(),
|
||||
terminal->name());
|
||||
terminal->name(),
|
||||
terminalIndexes(points).value(terminals.indexOf(terminal)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2009,11 +2072,12 @@ void projectDataBase::insertTerminal(Terminal *terminal)
|
||||
insertTerminal(Terminal *) from values rather than a live terminal.
|
||||
*/
|
||||
void projectDataBase::insertTerminal(const QString &uuid, const QString &element_uuid,
|
||||
const QString &name)
|
||||
const QString &name, const QVariant &index)
|
||||
{
|
||||
m_insert_terminal_query.bindValue(":uuid", uuid);
|
||||
m_insert_terminal_query.bindValue(":element_uuid", element_uuid);
|
||||
m_insert_terminal_query.bindValue(":name", name);
|
||||
m_insert_terminal_query.bindValue(":terminal_index", index);
|
||||
if (!m_insert_terminal_query.exec()) {
|
||||
qDebug() << "projectDataBase::insertTerminal insert error : " << m_insert_terminal_query.lastError();
|
||||
}
|
||||
@@ -2146,7 +2210,7 @@ void projectDataBase::prepareQuery()
|
||||
|
||||
//INSERT TERMINAL
|
||||
m_insert_terminal_query = QSqlQuery(m_data_base);
|
||||
m_insert_terminal_query.prepare("INSERT OR IGNORE INTO terminal (uuid, element_uuid, name) VALUES (:uuid, :element_uuid, :name)");
|
||||
m_insert_terminal_query.prepare("INSERT OR IGNORE INTO terminal (uuid, element_uuid, name, terminal_index) VALUES (:uuid, :element_uuid, :name, :terminal_index)");
|
||||
|
||||
//INSERT CONDUCTOR
|
||||
m_insert_conductor_query = QSqlQuery(m_data_base);
|
||||
|
||||
@@ -150,7 +150,7 @@ class projectDataBase : public QObject
|
||||
void watchConductor(Conductor *conductor);
|
||||
void insertTerminal(Terminal *terminal);
|
||||
void insertTerminal(const QString &uuid, const QString &element_uuid,
|
||||
const QString &name);
|
||||
const QString &name, const QVariant &index);
|
||||
void prepareQuery();
|
||||
static QHash<QString, QString> elementInfoToString(
|
||||
Element *elmt);
|
||||
|
||||
Reference in New Issue
Block a user