Commit Graph

9815 Commits

Author SHA1 Message Date
ispyisail a0d5c4fa9c Fill the project database from the file as it is read
The project database -- behind parts lists, summary tables and the wiring
list -- was filled by walking the built folios: every element and
conductor object of every scene. Filling it from the document the project
was just read from instead is what the database needs before a project
could be opened without building every folio (qelectrotech-docker
DB-FROM-XML-SCOPE.md).

updateDB(document), called by readProjectXml(), fills diagram,
diagram_info, element, element_info, terminal and conductor from the
document, through the code the folios use: a BorderTitleBlock read from
each folio's XML gives the title-block values and each element's grid
cell, the embedded collection gives each definition, and the binders are
shared with the live path. Shapes, texts and pictures still come from the
folios (their boxes need fonts and pens).

It falls back to the folios, saying why in the log, when the document
does not carry what that needs: an item without a saved uuid (older files
-- the folios derive them on load), a conductor naming its ends the older
way, a %autonum folio number, a terminal showing its master's label, a
missing or unbuildable definition. A symbol label computed from a formula
is the one saved in the file, which QElectroTech writes as it computes it
on every save. QET_DATABASE_FROM_FOLIOS=1 forces the folio path.

tst_databasefromdocument saves every example once, then fills both ways
and requires identical tables (24/24, filled from the document each
time), and checks that an older file falls back with its reason. Red when
the document path is made to write a wrong grid cell. Database phase of
loading unchanged: industrial.qet 0.146 s vs 0.160 s, Polonez 0.040 s vs
0.038 s (median of 5).

QETProject::projectWideProperties() is split out of
updateDiagramsFolioData() so both fills use the same title-block context.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 15:38:20 +13:00
ispyisail 2801d93afd Merge pull request #1134 from ispyisail/fix/database-folio-date-century
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m31s
Fix folio dates a century early in the project database
2026-09-29 14:35:41 +13:00
ispyisail d0a439a448 Merge pull request #1133 from ispyisail/test/qet-mcp-audit-element-tools
Add exact-answer tests for qet_check, qet_continuity and the symbol-file tools
2026-09-29 14:23:13 +13:00
ispyisail 0392d74e15 Merge pull request #1131 from ispyisail/fix/qet-mcp-folio-ref-uuid
Fix qet_edit's folio "$id" naming the wrong folio after an insert
2026-09-29 14:22:54 +13:00
ispyisail 37eb7ce5ac Merge pull request #1130 from ispyisail/test/qet-mcp-audit-survivors
Add qet-mcp tests for what a second mutation audit found untested
2026-09-29 14:22:35 +13:00
ispyisail 2f655dfbc7 Merge pull request #1129 from ispyisail/fix/qet-mcp-pin-binary
Fix the MCP server running any program an assistant names
2026-09-29 14:22:15 +13:00
ispyisail d331a79e8b Merge pull request #1124 from ispyisail/fix/935-english-wording
Fix broken and untranslated English strings (#935, phases 1–2)
2026-09-29 14:21:56 +13:00
ispyisail 3139fa54f7 Fix folio dates a century early in the project database
diagram_info.date -- and project_summary_view.date, which the summary
table dialog queries -- held the folio date read back from the title
block's text: QLocale::system().toDate() of the string
updateDiagramContextForTitleBlock() writes in the locale's short format.
Where that format has a two-digit year (en_US "M/d/yy") Qt reads the year
back as 19xx, so 741.qet's 2010-09-21 became 1910-09-21, and so did every
dated folio of the 24 examples (109 of 109).

bindDiagramInfoValues() now binds the folio's own date,
BorderTitleBlock::date(), the value that text was made from. A folio set
to show the current date or none gives the same date as before.

tst_dbfoliodate runs --run on examples/741.qet under LC_ALL=en_US.UTF-8
and expects 2010-09-21 from project_summary_view: 1910-09-21 without this
change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 14:14:15 +13:00
ispyisail 0075ee208f qet-mcp: exact-answer tests for qet_check, qet_continuity and the .elmt tools
A mutation audit of the functions no audit had covered (tests needing no
QElectroTech): these 13 caught 153 of 337 planted bugs (45 %). With these
tests, 322 (96 %). qet_check was at 26/59 and qet_continuity at 24/37
even with the binary tests, since those look at a finding or two.

- qet_check / qet_continuity: _run_qet replaced by a stub returning chosen
  log lines, so the whole answer is compared -- summary counts, "ok",
  passed, check_failures, each finding's count, note and sampled rows,
  sorting by severity, folio_number, the launch hint carried through,
  the folio argument in the script, folio bounds, and lines that only
  look like ours.
- qet_element_build / qet_element_info: the written header, names, kind
  information and terminals, and the reported result, key for key; the
  refusals; geometry worked out by hand; every part kind's extent and
  written attributes; number formatting.
- qet_element_search and its index: the index entry key for key, the
  cache, ranking (exact name, then first word, then length), the default
  and given limits.

The 15 left are equivalent: timeouts and output limits, a ranking
constant that only has to exceed 0, and a containment check the 5-unit
margin keeps from ever failing. Tests only; 282/282.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 14:00:02 +13:00
ispyisail 124b7e4f6e qet-mcp: stop a tool call choosing the program the server runs
qet_export, qet_edit, qet_query, qet_continuity, qet_check and
qet_project_new took the QElectroTech executable as a per-call argument
and ran whatever executable file it named, with the call's own paths as
arguments. The workspace policy exempted it as configuration, but it is
chosen by the model on every call, so text inside a project could steer
an assistant into starting another program.

The server now finds QElectroTech itself: QET_BINARY, then the install it
ships in (<prefix>/share/qelectrotech/mcp/), then PATH. "binary" becomes
optional; when given it must be that same file (after resolving
symlinks) or one listed in QET_MCP_BINARIES. QET_MCP_ALLOW_ANY_BINARY=1
restores the old behaviour, as QET_MCP_ALLOW_ANY_PATH does for paths.
"elements_dir" defaults to the installed collection and, when given, must
be in the workspace, that collection, or QET_MCP_ELEMENTS.

Both checks live in enforce_path_policy(), the one place tool arguments
enter. test_configuration_paths_are_exempt asserted the old exemption and
is replaced by BinaryPolicy (13 tests) and a stdio test of the original
reproduction. Each new check was removed in turn and the tests failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:11:03 +13:00
ispyisail fdef38eba8 qet-mcp: tests for what a second mutation audit found untested
tools/qet-mcp-audit/mutate.py over the code changed since #1095
(_build_script, the diff helpers, _parse_script_output, tool_items and
the wire-end keys of #1118), tests needing no QElectroTech only: master
f530b7636's suite noticed 531 of 578 planted bugs (92 %). With these
tests, 568 (98 %). The 10 left are equivalent: rsplit("/", 1) vs 2, a
1e-6 tolerance compared with < or <=, a one-letter orientation sliced
[:1] or [:2], branches that only touch parts without terminals, and the
placeholder terminal 0 a conductor uuid overwrites.

- Every argument kind refuses what it cannot take, before any launch:
  malformed indices, bool, points and nodes, search_and_replace with an
  unknown kind or conductor field or an empty element_info field,
  "conductor" given with "element" or "terminal" alone, an op that is not
  an object -- and the same kinds accept what they should.
- _parse_script_output reports capabilities (none logged = unknown, not
  "nothing missing"), notes on the right op, save and stopped_early;
  ignores a line without the marker even when it would parse, and a line
  of a kind it does not know.
- A uuid wire end in a column of terminals (same x) keys like the
  numbered one: the match is on x, y and orientation together.

Tests only; 264/264.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:09:08 +13:00
Laurent Trinques fdcdfd749e Merge pull request #1127 from ispyisail/fix/bugtracker-105-diagonal-wire
Fix bugtracker #105: straight wire drawn diagonally after a move
2026-09-29 01:01:36 +02:00
Laurent Trinques 041e0fc2a6 Merge pull request #1123 from arummler/cpp-standard-raise
Fix small depreciations in C++20
2026-09-29 01:01:14 +02:00
ispyisail 94b0d8d06d qet-mcp: a folio's "$id" keeps naming it after a later insert or removal
The "$id" of an add_folio or insert_folio held the index the folio had
when it was made, so a later insert_folio or remove_folio in the same
qet_edit run shifted it, and ops naming "$id" edited the wrong folio --
the case #1115 fixed for folios given by uuid, left open for these.

The script now also keeps such a folio's uuid (qet.folioUuid()) and
resolves "$id", where an op takes a folio, through qet.folioIndex() at
the moment it is used. On a build without folioUuid() it falls back to
the stored index, as before, and nothing new is required of the binary.
The op's reported result is still the index.

Test: add_folio "$f" (index 1), insert_folio at 0, set_folio_title
"$f": the title lands on the third folio -- on the second without this
change. Two script-generation tests updated for the new expression.
255/255.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:46:18 +13:00
ispyisail f530b7636e Merge pull request #1128 from ispyisail/feature/qet-mcp-call
Add a one-shot --call to qet-mcp and document desktop app setup
2026-09-29 11:37:14 +13:00
ispyisail 080317485b Merge pull request #1126 from ispyisail/feat/qet-mcp-terminal-uuid
Let qet_edit name a terminal by its uuid
2026-09-29 11:36:51 +13:00
ispyisail 8f253be4ce Merge pull request #1125 from ispyisail/feat/script-terminal-uuid
Add a scripting lookup for terminals by uuid
2026-09-29 11:36:31 +13:00
ispyisail 03fc51d0fe qet-mcp: add --call, and document using it from the Claude desktop app
A web chat in a browser cannot start a local program, so it cannot run
this server. Two ways round that, documented in the README:

- the Claude desktop app runs stdio servers; a step-by-step setup;
- a chat that can execute Python can upload qet_mcp.py and run
  `--call <tool> '<json>'` (or `-` to read the arguments from stdin).

--call goes through the same dispatcher as the stdio server, so the
workspace policy applies unchanged. Exit status 0 success, 1 the tool
reported an error, 2 the call was malformed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:15:26 +13:00
ispyisail 2ea279cc2b Fix bugtracker #105: straight wire drawn diagonally after a move
updateConductorPath() fits a stored profile to the new terminal
positions by sharing the horizontal difference over the profile's
horizontal segments and the vertical one over its vertical segments.
When a profile has no segment of non-zero length along an axis, the
difference along that axis was dropped and the last point joined the
terminal diagonally. On save that diagonal was written as one
axis-aligned segment, so on reopen pathFromXml() found the lengths
incoherent and rerouted the wire.

Every straight wire with a stored path hits this after a reload, as a
zero-length segment is saved as horizontal. Moving one end at a right
angle to the wire, in the direction that keeps its path type, showed it.

Generate a new path in that case, as is already done when there is no
profile for the path type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:09:27 +13:00
ispyisail 5683a53536 English: fix broken and untranslated strings (#935, phases 1 and 2)
Translation text only; no source string, no other language touched.
"folio" is kept, as decided in #935, and the two "on the current sheet"
strings whose source says folio now say folio too.

- Broken wording: "N° scheme", "Export fully folio", "Title of folio",
  "Label folio", "N° of folio", "Dimensions of folio", "Folio Untitled",
  "Move up  this folio", "Title block informations", the table
  "lines are missing %1" warning.
- Folio report elements get one name, "folio reference", which
  DiagramView already used (was: folio referencing(s), reference folio
  following, previous reference folio, folio reports).
- Machine-translation damage: "</ b>" broke the folio-reference error
  message's HTML, and two variable help texts listed "% F", "% l" etc.,
  which are not variables.
- Plurals still showing French or wrong: conductor colour undo entry
  (empty, so French was shown), "%n erreur", "%n forme", "%n item placed"
  for both forms, "redesigned" for redrawn, and "bornes" rendered as
  "boundaries" in the element-reload message.
- Cross-reference spelled one way; "Réf." and "Numéro : %1" translated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:00:58 +13:00
ispyisail ccc063a19e qet-mcp: report every wrong terminal uuid of an op, each by its argument
Review of the previous commit: an add_conductor with both terminal uuids
wrong logged two notes under one op index and the second replaced the
first, and neither said which end it was about. Notes of one op are now
joined, and a terminal note starts with its argument ("from_terminal:",
"to_terminal:", "terminal:"). The integration test's both-ends case fails
with the old replacing behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:00:16 +13:00
ispyisail 3112727ee7 elementTerminals(): keep a "%3" or "%4" in a terminal's name as written
Review of the previous commit: the line was built with chained arg()
calls, so a "%3" in a terminal's name was replaced by the conductor
count -- as before -- and now a "%4" by the uuid as well. One
multi-argument arg() substitutes each placeholder of the pattern once.

percentInNameKept renames a terminal of perceuse.qet "x%3y%4": listed as
"x1y{uuid}" before, "x%3y%4" now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:00:16 +13:00
ispyisail f58bf7502e qet-mcp: qet_edit names a terminal by its uuid
"terminal", "from_terminal" and "to_terminal" take the terminal's uuid
(as qet_element_info lists it) in place of its index. It names a
terminal of the op's own element -- for add_conductor, of that end's
element, "$name" references included -- and is turned at run time into
the index the call takes by qet.terminalIndex(); if the element has no
such terminal the op fails with a note. Unlike the index, a sort by
position, it is defined between two terminals at the same point. The
lookup is required only when a uuid is used, so index-only edits still
run on older builds.

README: the terminal uuids; wires of older projects now get a lasting
uuid (#1107) instead of staying unnamed.

Tests: script generation for every terminal-taking op, "$name" and folio
uuid resolution, the index still passed through; through the binary,
bobine_ka_a_remanence (file order A2, A1; index order A1, A2) wired A2 to
A1 by uuid lands on exactly those ends, and an unknown uuid stops the run
with its note. 255/255 against a build with qet.terminalIndex(); a build
without it is reported as missing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:00:16 +13:00
ispyisail 9fdd6ac333 Scripting: find a terminal by its uuid
The conductor calls name a wire end as element uuid + terminal index.
The index is the terminal's place in Element::terminals(), a sort by
position that is undefined between two terminals at the same point, and
the documentation ruled terminal uuids out as "empty for most of the
installed base". Since #1118 every terminal of an opened project has one.

- elementTerminals() ends each line with the terminal's uuid
  (Terminal::stableUuid()); the text before it is unchanged.
- terminalIndex(folio, elementUuid, terminalUuid) returns the index the
  calls take, or -1 if the element or terminal is not there, or if two of
  the element's terminals carry that uuid.
- The class documentation says what does address a terminal: its uuid
  together with its element's.

tst_scriptterminaluuid runs --run on perceuse.qet (552 elements, two
terminals at one point in some): every terminal listed with a uuid,
distinct within its element, found again at its own index; -1 for an
unknown or malformed uuid, an unknown element and a bad folio. Red when
terminalIndex() returns the wrong index. qet-mcp suite 253/253.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:00:16 +13:00
Andre Rummler 91ef85f891 Unify Qt6:: usage. 2026-09-28 23:38:01 +02:00
Laurent Trinques b411a08340 git submodule update --remote elements 2026-09-28 23:34:05 +02:00
Laurent Trinques d3ca8483cf Merge pull request #1122 from ispyisail/feat/editor-derived-terminal-uuids
Give old symbols' terminals their lasting uuid in the symbol editor
2026-09-28 23:30:27 +02:00
Laurent Trinques 3df6d450fe Merge pull request #1121 from ispyisail/fix/titleblock-custom-vars-1113
Fix #1113: title-block variables shown by name and missing in Replace
2026-09-28 23:28:52 +02:00
Andre Rummler a72384850a Replace depreciated aAdConst with STL. 2026-09-28 23:19:46 +02:00
Laurent Trinques a00e9439b7 Merge pull request #1120 from ispyisail/fix/1117-collection-icon-after-save
Fix #1117: element icon disappears from the collection after saving
2026-09-28 23:18:59 +02:00
ispyisail 22f1455868 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 <noreply@anthropic.com>
2026-09-29 10:08:39 +13:00
ispyisail 2815dbaf47 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 <noreply@anthropic.com>
2026-09-29 10:08:39 +13:00
Laurent Trinques 90084ecf40 Merge pull request #1119 from Kellermorph/fix-material
Fix material list reading, selection and article fields
2026-09-28 23:04:32 +02:00
Andre Rummler 2b813a0d91 Fix Qt6 package name which was forgotten when removing the variable used to differentiate between Qt5 nd Qt6. 2026-09-28 23:03:11 +02:00
Andre Rummler 574d6dcd56 Fix captor operator [=] which needs to be written explciitly [this] in C++20. 2026-09-28 23:01:42 +02:00
Andre Rummler 7facb1cbf8 Fix cast which becomes a warning in C++20. 2026-09-28 23:00:58 +02:00
ispyisail 08bce54a2f Fix #1113: custom title-block variables in Find/Replace and unset placeholders
Two problems with a custom variable newly added to a title-block template:

1. Find/Replace > Folio > Custom showed an empty table, so the user had to
   know and type each variable name. It now lists every custom variable the
   folios already carry plus those their templates use, with empty values.
   An empty value now means "leave unchanged", like every field of the main
   tab; only the variables actually filled in are written to the folios.

2. The title block showed the variable's own name ("%doc-type") until the
   folio's properties were opened, because interpreteVariables() only
   replaces names present in the context. Placeholders in the template text
   that no key resolves now render blank, as auto-added unset ones already
   did since #973. Only the template's own text is considered, so a value
   that contains "%something" is never touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:59:21 +13:00
ispyisail 34a2ceff70 Fix #1117: element icon vanishes from the collection after saving
Saving an element from the element editor calls
ElementsCollectionWidget::locationWasSaved(), which runs clearData() on
the panel item: the icon is set to a null QIcon. The icon only comes
back through FileElementCollectionItem::setUpIcon(), but since the
#633 recursion guard that returns for good once m_icon_initialized is
set, and nothing ever reset it. The row stayed without an icon until
the whole collection was reloaded.

#1008 refreshed the picture caches on save, but no one asked them for
the new picture, so it could not fix this. Resetting the flag in
clearData() lets the next paint rebuild the icon, which then comes
from those refreshed caches, so the panel shows the new drawing.

The flag is reset after the base clearData(): its setIcon() emits
dataChanged(), which re-enters setUpIcon() and must still return early.
setUpIcon() sets the flag before its own setIcon(), so the #633 guard
is unchanged.

The same reset should also bring back a folder's icon after editing its
properties (editDirectory() calls clearData() too); read, not tested.
Project collection items were never affected: their setUpIcon() guards
on icon().isNull().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:55:10 +13:00
Laurent Trinques df2395e5c6 Merge pull request #1118 from ispyisail/feat/fill-terminal-uuids
Give terminals in older projects a lasting uuid
2026-09-28 22:42:22 +02:00
Kellermorph 8b54ea400b Fix material list reading, selection and article fields
Follow-up to #1088, covering its review comments and one behaviour
change found while testing the feature:

- MaterialList::load() names the columns from the machine key line when
  the file has one, instead of from the translated label line above it.
  A catalogue written in another language fills the element fields
  again, and saving no longer replaces the key line with labels.
- The entry picked after "New entry" is found by comparing the columns
  one by one instead of comparing the two maps as a whole: the entry
  form leaves the empty columns out, so the record never matched the
  line that had just been written and the search was cleared for
  nothing. The search is now only given up when it really hides the new
  line.
- Applying a catalogue entry pushes an undo command only when the live
  edit is on. In the properties window, where there is none, the fields
  wait for "Apply", so "Cancel" gives the element its own values back
  instead of leaving the picked part in place.
- Cells a file holds past the header are kept in MaterialRecord::extra
  and written back, so appending an article never shortens a line.
- An empty cell of a column describing the article itself (MaterialList::
  isArticleBound: description, designation, manufacturer, order number,
  supplier, model, ratings, dimensions, auxiliary block) clears the
  field, so an element never keeps the manufacturer of the part picked
  before. An empty cell of any other column (function, comment, notes,
  plant, location, quantity, unity), and any column the file does not
  hold at all, leaves the field alone.
- Comments left where the review asked for them: the corner button
  lookup, the ';' separator fallback, the search filter cost.
2026-09-28 22:38:41 +02:00
Laurent Trinques 56e69d5956 Merge pull request #1116 from ispyisail/fix/replace-symbol-keeps-wires
Fix wires lost after replacing a symbol already in the project
2026-09-28 22:30:09 +02:00
ispyisail eb3b10a48e Tell apart two terminals at one point; qet_diff: same wire in both forms
Review of the previous commit:
- The load fallback compared a saved uuid with occurrence 0 only, so a
  wire on the second of two terminals at one point of a symbol was lost
  once the definition was replaced, and one on the first could go to
  either of the pair (Element::m_terminals is sorted, not in definition
  order). Element::parseTerminal() now records each terminal's rank among
  the terminals of the definition at the same point, derivedUuid() uses
  it, and fillMissing() starts from the same rank.
  derivedUuidFoundAfterReplacement runs on perceuse.qet and industrial.qet
  too: 154/156 and 670/671 wires without the rank, all with it.

qet-mcp: the first save of an older project now rewrites its wires from
the numbered form to the uuid form, and qet_diff keyed the two forms
differently, so an untouched resave showed every wire removed and added
(4 failures in test_qet_mcp.py). A uuid end is now resolved to the same
key as a numbered one: the terminal's definition position, moved to where
the wire docks, is the placed symbol's <terminal> record.
test_conductor_key_same_in_both_forms fails without it; 253/253 pass on
this build and on the previous stage's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:10:38 +13:00
ispyisail 4e6f59e011 Give terminals in older projects a lasting uuid
Most symbols stored in older projects have no uuid on their terminals
(706 of the 900 in the 24 examples), so a terminal's identity is worked
out from where it sits in its symbol on every load (stableUuid()). That
is only sound while nothing keyed on it is kept between loads.

- On opening a project, every terminal of its embedded symbols without a
  uuid gets that same derived value (TerminalUuids::fillMissing(), from
  XmlElementCollection's loading constructor, before any folio is
  built). The next save writes it, and the wires on it in the form that
  names terminals by uuid, which QElectroTech reads since 0.8.0.
- The recipe moves to TerminalUuids::derived(), which stableUuid() now
  calls, so the two cannot drift apart. A second terminal at the same
  point of a symbol gets the next occurrence, and no value is given
  twice within a symbol.
- findTerminal(): a wire whose terminal uuid is not found is matched to
  the terminal whose derived value it is, so a saved uuid still finds its
  terminal after the symbol's definition was replaced by one whose
  terminals carry other uuids.

The project database's terminal and conductor tables are identical before
and after on all 24 examples except the 3 terminals that share a point
with another in their symbol (industrial.qet 1, perceuse.qet 2), which
now have an identity of their own. Every example keeps every wire through
a resave, and a second save changes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 08:50:27 +13:00
ispyisail 079d085a87 Keep terminal uuids when a whole category is replaced; never duplicate one
Review of the previous commit:
- keep() could give an old uuid to a new terminal while another terminal
  of the new definition already carried it (a moved terminal), leaving
  two terminals with one uuid. A terminal carrying any old uuid is now
  left alone, and an old uuid already in use is never handed out.
- copyDirectory() replaced a whole category of the embedded collection
  (drag a folder onto the project's folder of the same name) without
  carrying terminal uuids over. keepInDirectory() walks both trees by
  name and calls keep() on each symbol.
- The "wire(s) not loaded" log line repeated the folio's list on every
  paste; it is now written only when a folio is loaded.

tst_terminaluuids: 3 new cases, each red on the previous keep().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 08:11:15 +13:00
ispyisail e6ac117f9b Keep wires attached when a project's copy of a symbol is replaced
A wire whose terminals have uuids is saved against them, and on load it
is reattached to a terminal with that uuid or dropped, with only a qDebug
line. Re-importing a changed symbol and choosing "replace" swapped the
project's definition for one whose terminal uuids differ; the placed
symbols kept the old ones until the project was reopened, so every wire
on them was lost at the next open, silently, and gone for good at the
next save.

- XmlElementCollection::copyElement(), where an embedded definition is
  overwritten, carries each old terminal uuid onto the new terminal at
  the same place and orientation (TerminalUuids::keep()). Terminals that
  moved, and new ones, keep their own.
- Diagram::fromXml() records wires it could not reattach, logs them, and
  the editor lists them in one warning after opening a project.

Measured on 2612_ats_singlephase.qet with the stored splice's terminal
uuids made to differ from the collection's: replace, save, reopen loads
34 of 131 wires on master, 131 with this change (GUI, both arms).
tst_terminaluuids covers keep() and runs the real loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 08:11:15 +13:00
Laurent Trinques 9cbf26dcc9 Merge pull request #1115 from ispyisail/feat/qet-mcp-edit-folio-uuid
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m10s
Let qet_edit name a folio by its uuid
2026-09-28 20:54:04 +02:00
Laurent Trinques 4bf02cd936 Merge branch 'master' into feat/qet-mcp-edit-folio-uuid 2026-09-28 20:53:51 +02:00
Laurent Trinques 9fe30c9081 Merge pull request #1114 from ispyisail/feat/script-folio-uuid
Add scripting calls to find a folio by its uuid
2026-09-28 20:28:58 +02:00
Laurent Trinques 045d2edc27 Merge pull request #1108 from ispyisail/feat/qet-mcp-conductor-uuid
Let qet_edit name a wire by its uuid
2026-09-28 20:27:05 +02:00
Laurent Trinques c1332adce5 Merge pull request #1107 from ispyisail/feat/lasting-wire-uuid
Give wires in older projects a lasting uuid, from what they connect
2026-09-28 20:19:03 +02:00