diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index 138e1356c..37bb3d2e6 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -259,12 +259,13 @@ void projectDataBase::addConductor(Conductor *conductor) return; } - //A conductor whose terminal(s) predate terminal uuids (legacy - //elements not yet re-saved by a uuid-aware element editor) can't - //be given a stable identity here -- omitted the same way - //element_nomenclature_view already omits exclude_from_bom elements, - //rather than fabricating one. - if (conductor->terminal1->uuid().isNull() || conductor->terminal2->uuid().isNull()) { + //Both endpoints must belong to an element: the terminal table is keyed + //on (terminal, element) and a terminal with no parent has no identity + //to key on. Terminals whose *definition* predates terminal uuids are + //fine -- Terminal::stableUuid() derives one from the terminal's local + //position, which is what the project format itself matches on. + if (!conductor->terminal1->parentElement() + || !conductor->terminal2->parentElement()) { return; } @@ -366,9 +367,9 @@ void projectDataBase::bindConductorValues(QSqlQuery &query, Conductor *conductor { query.bindValue(QStringLiteral(":uuid"), conductor->uuid().toString()); query.bindValue(QStringLiteral(":diagram_uuid"), diagram->uuid().toString()); - query.bindValue(QStringLiteral(":terminal1_uuid"), conductor->terminal1->uuid().toString()); + query.bindValue(QStringLiteral(":terminal1_uuid"), conductor->terminal1->stableUuid().toString()); query.bindValue(QStringLiteral(":terminal1_element_uuid"), conductor->terminal1->parentElement()->uuid().toString()); - query.bindValue(QStringLiteral(":terminal2_uuid"), conductor->terminal2->uuid().toString()); + query.bindValue(QStringLiteral(":terminal2_uuid"), conductor->terminal2->stableUuid().toString()); query.bindValue(QStringLiteral(":terminal2_element_uuid"), conductor->terminal2->parentElement()->uuid().toString()); query.bindValue(QStringLiteral(":text"), conductor->properties().text); } @@ -730,9 +731,10 @@ void projectDataBase::populateConductorTable() const auto conductor_list = diagram->conductors(); for (auto *conductor : conductor_list) { - //See addConductor() for why terminals without a uuid - //(legacy elements) are omitted rather than fabricating one. - if (conductor->terminal1->uuid().isNull() || conductor->terminal2->uuid().isNull()) { + //See addConductor(): only a terminal with no parent element is + //skipped. A missing terminal uuid is handled by stableUuid(). + if (!conductor->terminal1->parentElement() + || !conductor->terminal2->parentElement()) { continue; } @@ -756,7 +758,7 @@ void projectDataBase::populateConductorTable() */ void projectDataBase::insertTerminal(Terminal *terminal) { - m_insert_terminal_query.bindValue(":uuid", terminal->uuid().toString()); + m_insert_terminal_query.bindValue(":uuid", terminal->stableUuid().toString()); m_insert_terminal_query.bindValue(":element_uuid", terminal->parentElement()->uuid().toString()); m_insert_terminal_query.bindValue(":name", terminal->name()); if (!m_insert_terminal_query.exec()) { diff --git a/sources/qetgraphicsitem/terminal.cpp b/sources/qetgraphicsitem/terminal.cpp index ee041c632..e4e5563bb 100644 --- a/sources/qetgraphicsitem/terminal.cpp +++ b/sources/qetgraphicsitem/terminal.cpp @@ -817,6 +817,60 @@ QUuid Terminal::uuid() const return d->m_uuid; } +/** + @brief Terminal::stableUuid + An identity for this terminal that exists on every element, not only on + those saved by a uuid-aware element editor. + + uuid() comes from the catalog .elmt definition and is empty for every + element authored before that field existed -- which is most of the + installed base. Anything keyed on uuid() alone therefore cannot see those + elements at all. + + When there is no uuid, derive one from the terminal's local position and + orientation inside its element. That is not an arbitrary choice: it is the + same thing the project format itself uses to match a conductor back to a + terminal ("each connection is made by using the local position of the + terminal and a dynamic id" -- TerminalData::m_uuid). m_pos is the position + read from the definition and is not touched by moving the element on the + folio, so the result is stable across loads, saves and folio moves, and it + is unique within an element except where a definition genuinely declares + two terminals at the same point -- three cases in the whole example corpus, + and harmless, because two terminals sharing a position and orientation are + indistinguishable in every observable respect: they merge to one terminal + row and every conductor on either of them still resolves to the right + element and name. + + Derived values are UUID v5 in a fixed namespace, so they are reproducible + without being written to the file, and cannot collide with the v4 uuids + the element editor generates. + + @return the terminal's own uuid when it has one, otherwise a derived one +*/ +QUuid Terminal::stableUuid() const +{ + if (!d->m_uuid.isNull()) { + return d->m_uuid; + } + + //Fixed namespace for terminal identities derived from geometry. + static const QUuid derived_ns(QStringLiteral("{6b1f6d1e-6a1a-5f7e-9a3d-9c0a5b2d7e11}")); + + //Position and orientation only. The name is deliberately excluded: it + //is not stable across a save cycle -- QET rewrites a terminal named + //"_" as unnamed, which would silently change the identity of 1421 of + //industrial.qet's 1790 terminals on the first resave. It is also not + //needed: keying on geometry alone produces exactly the same number of + //collisions across the example corpus, and it means renaming a + //terminal does not change what it is. + const QString key = QStringLiteral("%1|%2|%3") + .arg(d->m_pos.x(), 0, 'f', 4) + .arg(d->m_pos.y(), 0, 'f', 4) + .arg(static_cast(d->m_orientation)); + + return QUuid::createUuidV5(derived_ns, key); +} + QString Terminal::name() const { if (d->m_use_master_label && parent_element_) { diff --git a/sources/qetgraphicsitem/terminal.h b/sources/qetgraphicsitem/terminal.h index 53606b698..a6eae784c 100644 --- a/sources/qetgraphicsitem/terminal.h +++ b/sources/qetgraphicsitem/terminal.h @@ -75,6 +75,7 @@ class Terminal : public QGraphicsObject Diagram *diagram () const; Element *parentElement () const; QUuid uuid () const; + QUuid stableUuid () const; QString name () const; QString baseName () const; TerminalData::Type terminalType() const;