From 2815dbaf47f508b437e5798b830ad342d98ef3af Mon Sep 17 00:00:00 2001 From: ispyisail Date: Tue, 29 Sep 2026 09:37:57 +1300 Subject: [PATCH 1/2] Symbol editor: give old terminals their derived uuid, not a random one A symbol file whose terminals have no uuid got a random one for each terminal when opened in the element editor (PartTerminal's constructor), written on save. Every copy of the same old symbol therefore ended up with different terminal uuids, none of them the one a project gives the same terminals on opening (TerminalUuids::fillMissing()). ElementScene::loadContent() now reads a copy of the definition filled by TerminalUuids::fillMissingInDefinition(), the same rule as a project, including the next occurrence for the second of two terminals at one point. Terminals that have a uuid keep it; a paste still renews them all (PastePartsCommand). Checked in the editor: 6es7_212-1ae40-0xb0__p3.elmt (no terminal uuids in the collection) and tm3saf5r_layout.elmt with its uuids stripped (two terminals at one point), opened, nudged back and forth, saved: this build writes exactly the derived values (computed independently in Python), the previous one random ones. Select all, copy, paste, save: 6 terminals, 6 distinct uuids. Co-Authored-By: Claude Opus 5.5 --- sources/ElementsCollection/terminaluuids.cpp | 36 +++++++++++++++----- sources/ElementsCollection/terminaluuids.h | 1 + sources/editor/elementscene.cpp | 8 +++++ tests/qttest/tst_terminaluuids.cpp | 23 +++++++++++++ 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/sources/ElementsCollection/terminaluuids.cpp b/sources/ElementsCollection/terminaluuids.cpp index fe9141f47..c5b7ba098 100644 --- a/sources/ElementsCollection/terminaluuids.cpp +++ b/sources/ElementsCollection/terminaluuids.cpp @@ -28,12 +28,12 @@ namespace { @return the of the definition held by the embedded collection element @p collection_element */ -QList terminalsOf(const QDomElement &collection_element) + //The of the @p definition +QList definitionTerminals(const QDomElement &definition) { QList terminals; - const QDomElement description = collection_element - .firstChildElement(QStringLiteral("definition")) - .firstChildElement(QStringLiteral("description")); + const QDomElement description = + definition.firstChildElement(QStringLiteral("description")); for (QDomElement t = description.firstChildElement(QStringLiteral("terminal")); !t.isNull(); t = t.nextSiblingElement(QStringLiteral("terminal"))) { @@ -42,6 +42,12 @@ QList terminalsOf(const QDomElement &collection_element) return terminals; } +QList terminalsOf(const QDomElement &collection_element) +{ + return definitionTerminals( + collection_element.firstChildElement(QStringLiteral("definition"))); +} + //Place and orientation, "10" and "10.0" being the same place QString terminalPlace(const QDomElement &terminal) { @@ -61,10 +67,10 @@ int orientationOf(const QDomElement &terminal) return 0; } - //The of one element definition get a uuid where missing -int fillDefinition(const QDomElement &collection_element) + //The of one get a uuid where missing +int fillDefinition(const QDomElement &definition) { - const QList terminals = terminalsOf(collection_element); + const QList terminals = definitionTerminals(definition); QSet taken; for (const QDomElement &t : terminals) { const QUuid uuid(t.attribute(QStringLiteral("uuid"))); @@ -105,7 +111,8 @@ int fillDirectory(const QDomElement &directory) if (child.tagName() == QLatin1String("category")) { filled += fillDirectory(child); } else if (child.tagName() == QLatin1String("element")) { - filled += fillDefinition(child); + filled += fillDefinition( + child.firstChildElement(QStringLiteral("definition"))); } } return filled; @@ -252,3 +259,16 @@ int TerminalUuids::fillMissing(const QDomElement &collection_root) { return fillDirectory(collection_root); } + +/** + @brief TerminalUuids::fillMissingInDefinition + fillMissing() for a single element definition (@p definition is its + ), as the element editor reads it from a file: an old + symbol opened and saved there gets the uuids a project gives the same + terminals, not random ones that would make every copy of it differ. + @return the number of terminals given a uuid +*/ +int TerminalUuids::fillMissingInDefinition(const QDomElement &definition) +{ + return fillDefinition(definition); +} diff --git a/sources/ElementsCollection/terminaluuids.h b/sources/ElementsCollection/terminaluuids.h index d5c94f517..f9892b0a6 100644 --- a/sources/ElementsCollection/terminaluuids.h +++ b/sources/ElementsCollection/terminaluuids.h @@ -26,6 +26,7 @@ namespace TerminalUuids { QUuid derived(qreal x, qreal y, int orientation, int occurrence = 0); int fillMissing(const QDomElement &collection_root); + int fillMissingInDefinition(const QDomElement &definition); void keep(const QDomElement &old_element, QDomElement &new_element); void keepInDirectory(const QDomElement &old_directory, QDomElement &new_directory); diff --git a/sources/editor/elementscene.cpp b/sources/editor/elementscene.cpp index 534d435c9..c0105ae83 100644 --- a/sources/editor/elementscene.cpp +++ b/sources/editor/elementscene.cpp @@ -17,6 +17,7 @@ */ #include "elementscene.h" +#include "../ElementsCollection/terminaluuids.h" #include "../NameList/ui/namelistdialog.h" #include "../NameList/ui/namelistwidget.h" #include "../QPropertyUndoCommand/qpropertyundocommand.h" @@ -1196,6 +1197,13 @@ ElementContent ElementScene::loadContent(const QDomDocument &xml_document) if (root.tagName() != "definition" || root.attribute("type") != "element") return(loaded_parts); + //Terminals saved without a uuid get the one a project gives them + //(see TerminalUuids::fillMissing()), not a random one: the same old + //symbol then has the same terminal uuids wherever it is copied. + //On a copy, the document is the caller's. A paste renews them all. + root = root.cloneNode(true).toElement(); + TerminalUuids::fillMissingInDefinition(root); + //Load the graphic description of the element for (QDomNode node = root.firstChild() ; !node.isNull() ; node = node.nextSibling()) { diff --git a/tests/qttest/tst_terminaluuids.cpp b/tests/qttest/tst_terminaluuids.cpp index b9a7fe1fd..35caba47f 100644 --- a/tests/qttest/tst_terminaluuids.cpp +++ b/tests/qttest/tst_terminaluuids.cpp @@ -300,6 +300,29 @@ private slots: QCOMPARE(TerminalUuids::fillMissing(root), 0); } + // The element editor fills a definition read from a file with exactly + // what a project fills the same definition with. + void fillMissingInDefinitionMatchesProject() + { + QDomDocument doc; + const QString own = QStringLiteral("{0f5d4b0c-2f7e-4a55-9a51-8c3a3e1c2d11}"); + QDomElement in_project = symbol(doc, {{"0", "10", "s"}, + {"0", "10", "s"}, + {"5", "0", "e", own}, + {"-3", "0", "w"}}); + QDomElement root = doc.createElement(QStringLiteral("collection")); + root.appendChild(in_project); + QDomElement definition = in_project.firstChildElement(QStringLiteral("definition")) + .cloneNode(true).toElement(); + + QCOMPARE(TerminalUuids::fillMissingInDefinition(definition), 3); + QCOMPARE(TerminalUuids::fillMissing(root), 3); + QCOMPARE(uuids(definition), uuids(in_project)); + QCOMPARE(uuids(definition).at(2), own); + QCOMPARE(uuids(definition).at(1), TerminalUuids::derived(0, 10, 2, 1).toString()); + QCOMPARE(TerminalUuids::fillMissingInDefinition(definition), 0); + } + // An example whose symbols have no terminal uuids and whose wires are // all in the numbered form: once saved, every terminal has a uuid, // every wire names its ends by uuid, nothing is lost, and saving again From 22f1455868f0cc88df4317ac703a429a85525466 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Tue, 29 Sep 2026 09:55:27 +1300 Subject: [PATCH 2/2] Symbol editor: imported elements get new terminal uuids Review of the previous commit: "Import scaled element" and "Import DXF" add a definition to an open symbol through OpenElmtCommand, which, unlike a paste, kept the imported terminals' uuids. With derived uuids, an old symbol imported into itself (or into another with a terminal at the same point) gave two terminals one uuid; importing any symbol whose terminals already carried uuids did the same before this series. OpenElmtCommand now renews the imported terminals' uuids, as PastePartsCommand does. Checked in the editor with a stand-in scaler at scale 1: open 6es7_212-1ae40-0xb0__p3.elmt, import it into itself, save -- 6 terminals, 3 distinct uuids without this commit, 6 with it, the original 3 keeping their derived values. Co-Authored-By: Claude Opus 5.5 --- sources/editor/UndoCommand/openelmtcommand.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sources/editor/UndoCommand/openelmtcommand.cpp b/sources/editor/UndoCommand/openelmtcommand.cpp index f94ab4c2f..ff28b4adc 100644 --- a/sources/editor/UndoCommand/openelmtcommand.cpp +++ b/sources/editor/UndoCommand/openelmtcommand.cpp @@ -17,6 +17,7 @@ */ #include "openelmtcommand.h" #include "../elementscene.h" +#include "../graphicspart/partterminal.h" #include #include @@ -52,6 +53,15 @@ void OpenElmtCommand::redo() if (m_first_redo) { m_scene->fromXml(m_document, QPointF(), true, &m_graphics_item); + //The terminals are added to an element that may already hold + //the same ones -- importing the same symbol twice, or two old + //symbols whose terminals get the same derived uuid -- so they + //get new uuids, as a paste gives them (PastePartsCommand). + for (QGraphicsItem *qgi : std::as_const(m_graphics_item)) { + if (qgi->type() == PartTerminal::Type) { + static_cast(qgi)->setNewUuid(); + } + } m_scene->qgiManager().manage(m_graphics_item); m_first_redo = false;