Commit Graph

9760 Commits

Author SHA1 Message Date
ispyisail a4d8f19d9f Merge branch 'feat/script-conductor-uuid' into feat/qet-mcp-conductor-uuid 2026-09-29 06:34:16 +13:00
ispyisail 604c3b1cd7 Merge remote-tracking branch 'upstream/master' into feat/script-conductor-uuid
# Conflicts:
#	tests/qttest/CMakeLists.txt
2026-09-29 06:34:16 +13:00
Laurent Trinques bc5ef180b6 Merge pull request #1105 from ispyisail/feat/stable-symbol-uuid
Give symbols in older projects the same uuid every time they open
2026-09-28 16:42:25 +02:00
Laurent Trinques cdcff93190 Update CMakeLists.txt 2026-09-28 16:42:05 +02:00
Laurent Trinques 69a468af63 Merge branch 'master' into feat/stable-symbol-uuid 2026-09-28 16:33:24 +02:00
Laurent Trinques f981ec7dc8 Merge pull request #1111 from ispyisail/feat/1070-rotate-whole-group
Rotate a selected group as one piece
2026-09-28 14:55:04 +02:00
Laurent Trinques 7456a48681 Merge pull request #1110 from ispyisail/feat/1070-pick-one-member
Click again on an item of a selected group to select it alone
2026-09-28 14:54:27 +02:00
Laurent Trinques 5a09d67f64 Merge pull request #1109 from ispyisail/fix/second-save-changes-nothing
Fix a just-saved project changing again on its next save
2026-09-28 14:52:54 +02:00
ispyisail e25ae3cbfb Test the second save on every example, a single-space value and accents
Review of #1109 (scorpio810):

- tst_resaveunchanged now resaves every .qet in examples/ twice instead
  of two of them (24 rows, ~40 s). Dropping the load-time trim now also
  fails affuteuse_250h.qet, which the two-file version missed.
- New case singleSpaceValueKept: a title-block property set to one
  space survives two saves (#973), and an accented property comes back
  unchanged. Fails if qetproject.cpp stops parsing with
  PreserveSpacingOnlyNodes.
- New tst_diagramcontext: the QDom reader (projects) and the pugixml
  reader (element definitions in the collection) return the same value
  for plain, stray-spaced, accented and non-Latin text. Fails if the
  pugixml path decodes as Latin-1 or either reader stops trimming.

The pugixml reader still reads a single-space value as "": pugixml drops
whitespace-only text unless parse_ws_pcdata is set, as it did before
this PR. That reader only sees element definitions, never a project, so
#973's title-block values do not go through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:29:51 +13:00
ispyisail e7745afd18 qet-mcp: answer review on naming a conductor by uuid
- An end conductorEnds() reports as "?" is never picked.
- An empty "conductor" says why: an older project's conductors have no
  saved uuid, so qet_conductors reports it empty.
- The op description and README say set_conductor still changes the
  whole potential when given a uuid, and that older projects' conductors
  are named by element + terminal until #1103.
- test_conductor_by_uuid covers move_conductor_segment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:25:05 +13:00
ispyisail af6c2f0dde Merge branch 'feat/script-conductor-uuid' into feat/qet-mcp-conductor-uuid 2026-09-28 23:21:51 +13:00
ispyisail 3d85fc013d Scripting: keep conductors()'s doc comment on conductors()
The anonymous namespace sat between the comment and the function, so
Doxygen attached the comment to describeEnd().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:21:38 +13:00
ispyisail a8f940505d Rotate a selected group as one piece
Discussion #1070 proposed that rotate, like move, copy and delete, works on
the whole group once one of its items is clicked. Rotate (Space) turned
each member on its own spot instead, so rotating a group pulled it apart:
two grouped texts side by side ended up each turned in place, no longer
side by side.

When the selection is exactly one whole group -- wires aside, which follow
their symbols -- Rotate now turns it as one piece around its centre, as
"Pivoter le groupe" (Shift+Space) already does
(ItemGroups::soleWholeGroup()). Any other selection, including a single
member picked out of its group, rotates as before.

In the GUI, on two grouped texts selected by one click: Space on master
leaves both where they were, turned; here it gives exactly what
Shift+Space gives on both (both texts swung around the group's centre).
tst_itemgroups: 4 new checks; without the whole-group condition, a
picked member counts as a group and fails. ctest 24/24.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:39:03 +13:00
ispyisail 6ed41bbfd0 Click again on a member of a selected group to pick it on its own
Clicking an item of a group selects the whole group (#1070). Clicking
again on one of its items is meant to select just that item, to edit it
on its own -- what discussion #1070 proposed -- but the second click
selected the whole group again: Qt left only the clicked item selected on
release, and the group completion pulled the others back in.

A press on a member of a group that is selected whole now notes that
member (ItemGroups::memberToPick(), which also finds the member when the
click lands on a symbol's own text). If the click ends without a drag and
Qt has left only that member selected, the selection stays so. A drag
still moves the whole group; Ctrl+click keeps its meaning; a group of one
is not picked from.

In the GUI, on two grouped texts: one click then Delete removes both
(master and this); click, click again, Delete removes only the clicked
text here, both on master; dragging after one click moves both texts by
the same amount on both. tst_itemgroups: 5 new checks; removing the
whole-group or the group-of-one condition fails one each. ctest 24/24.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:33:28 +13:00
ispyisail 4dcb54a3a2 Merge branch 'feat/script-conductor-uuid' into feat/qet-mcp-conductor-uuid 2026-09-28 22:04:33 +13:00
ispyisail 9db6084387 Build tst_scriptconductoruuid only where --run exists
The test runs a script through --run, which a build without the Qt Qml
module does not have (QET_HAS_SCRIPTING). Linux CI installs no Qml package,
so the binary took the script for a project to open and the test waited
out its 60 s. The test is now built only when scripting is: a configure
with Qt6Qml disabled lists 24 tests, without it; with Qml it runs and
passes as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:04:30 +13:00
ispyisail 75450d7102 Make saving a just-saved project change nothing
Saving a project that had just been saved changed it again in 18 of the
24 example projects, so a project kept in version control showed changes
nobody made. Both causes were cleanup done on save but not on load:

- Symbol information whose values were all empty was written as an empty
  <elementInformations/> block (DiagramContext::toXml() skips empty
  values, Element::toXml() wrote the block anyway). The next load read it
  as no information and the next save dropped it. The block is now written
  only when something went into it.
- Information values were trimmed on save but not on load, so a label with
  stray spaces (" PRISE") kept them in memory and in its displayed copy
  until the project was opened again. The same rule, kept in one place,
  now applies when reading: stray whitespace around real content trimmed,
  a value that is only whitespace kept (#973).

All 24 examples now save identically a second time (master: 6), and each
one's first save is byte-for-byte what master wrote only on its second.
A title-block property set to a single space keeps it through two saves.

tst_resaveunchanged runs --resave twice on Projet_vierge.qet and
m_000.qet; both fail without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:59:02 +13:00
ispyisail e12410e439 Never derive a uuid the file already carries
A symbol keeps its derived uuid once saved, even when it is moved. A
symbol saved without a uuid that later turns up on the spot it left -- a
hand edit, an older version, another tool writing the file -- derived the
same uuid, and the project had two symbols with one identity.

readDiagramsXml() now collects every symbol and wire uuid the file
carries, on any folio, before a folio loads; derivedItemUuid() moves to
the next counter value while a candidate is among them. The result still
depends on the file alone. Renamed from derivedUuid(), which QETProject
already has for the project's own uuid.

tst_derivedsymboluuid: newcomerOnAMovedSymbolsSpotGetsAnotherUuid fails
with the check switched off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:13:02 +13:00
ispyisail 0721b42e21 Give symbols saved without a uuid the same one on every load
A symbol saved without a uuid got a random one from Element::fromXml()
on every load, and the next save wrote it out: two loads of the same file
gave the same symbol two identities, and anything pointing at it by uuid
(a script, a comparison of two versions, a wire's identity) could not
follow it from one session to the next.

When a folio is loaded, such a symbol now gets a UUID v5 derived from
what it is and where it sits: its type, its position on the folio and its
orientation. Never the folio's index, so inserting or moving a folio does
not change it. Identical symbols stacked on one spot, or a copied folio,
are told apart by a counter kept per project (QETProject::derivedUuid()),
in load order among those symbols alone. A paste still renews uuids.

Symbols that have a uuid in the file keep it. All 24 example projects
already have one for every symbol, so they are unchanged; with the
symbols' uuids stripped, each saves byte-for-byte the same twice (master:
different every time).

tst_derivedsymboluuid runs --resave on a fixture with its uuids stripped:
same uuids on every load, same after a folio is inserted in front, saved
uuids kept, stacked copies differ. The first two fail without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:13:02 +13:00
ispyisail 9b967707a0 qet-mcp: qet_edit names a conductor by its uuid
set_conductor, move_conductor_segment and delete_conductor named a
conductor by one of its terminals, which had to carry exactly one
conductor: where two meet at a terminal, neither could be named from it.

Each now also takes "conductor": "{uuid}" (as qet_conductors reports it)
in place of element + terminal. The generated script asks
qet.conductorEnds() for the conductor's two ends and passes the one whose
terminal carries only that conductor. Where both ends are shared, or no
conductor has that uuid, the op fails and its "note" says which. The
lookup is required only when a uuid is used; giving both forms is an
error.

Tests: the script generated for each form and the argument errors; on a
folio where one terminal carries two conductors, deleting either by uuid
leaves exactly the other; an unknown uuid is reported. With the end chosen
without checking its terminal carries only that conductor, the terminal
test fails. 248/248 with a build carrying qet.conductorEnds().

Stacked on the conductorUuids()/conductorEnds() scripting change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:12:55 +13:00
ispyisail 353dbd3317 Scripting: list a folio's conductors by uuid and find their ends
A script could name a conductor only by one of its ends, "the conductor on
terminal N of element X", which fails where two conductors meet at one
terminal and cannot follow a conductor that is re-connected.

qet.conductorUuids(folio) lists the folio's conductor uuids, in the order
qet.conductors() lists them. qet.conductorEnds(folio, uuid) returns that
conductor's two ends as "{element uuid} terminal N" -- the form
conductors() prints and the conductor calls take -- or an empty list if
the folio has no such conductor. The end formatting conductors() already
did is shared rather than copied.

Conductors of older projects have no saved uuid yet, so theirs change
from one load to the next until that is settled (discussion #1103); new
conductors keep theirs.

tst_scriptconductoruuid runs a script through --run on a fixture: every
conductor has a distinct uuid, and its ends match the conductors() line
at the same position; an unknown uuid, a malformed one and a folio that
does not exist give empty lists. It fails with the two ends swapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:07:16 +13:00
Laurent Trinques 5ca047a05d Merge pull request #1104 from Alfmat01/master
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m29s
Complete Spanish translations
2026-09-28 09:40:57 +02:00
Alfmat01 db3ca9eb6d Merge pull request #1 from Alfmat01/Alfmat01-Complete-Spanish-translations
Complete Spanish translations

Complete 1,383 pending entries in lang/qet_es.ts, using terminology appropriate for electrical schematics, wiring and control panels.

Translate missing entries and review unfinished drafts.

Preserve placeholders, plural forms, links and markup.

Leave previously completed entries unchanged.

Validation: XML structure checked and no pending translations remain in the submitted file. Not tested in the running application.
2026-09-28 09:33:08 +02:00
Alfmat01 257ec77136 Complete Spanish translations
Complete Spanish translations
2026-09-28 09:27:43 +02:00
ispyisail 1586aaec75 Merge pull request #1097 from ispyisail/fix/qet-mcp-free-texts-only
Fix qet_diff counting a symbol's own texts as free texts
2026-09-28 16:48:53 +13:00
ispyisail 4ad352126b Merge master into fix/qet-mcp-free-texts-only
Conflict in _extras(): keep master's tables and folio uuids and _angle(),
and read only the folio's own inputs/shapes/images as this branch does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:26:31 +13:00
ispyisail 2e56001c9d Merge pull request #1102 from ispyisail/fix/legacy-symbol-input-texts
Fix schema_indus.qet example showing no device labels
2026-09-28 16:21:27 +13:00
ispyisail 65f10f6c86 Merge pull request #1092 from ispyisail/fix/search-list-dark-icons
Fix element icons that are dark on a dark theme in search results
2026-09-28 16:20:42 +13:00
ispyisail 5585d87ea5 Merge pull request #1089 from ispyisail/fix/1069-align-lone-shape
Fix Align centre commands when a group is left with only a shape
2026-09-28 16:20:11 +13:00
ispyisail 83da997c83 Merge pull request #1100 from ispyisail/feat/qet-mcp-items
Add qet_items: list drawn items with their uuids
2026-09-28 16:15:03 +13:00
ispyisail 049a4ba673 Merge pull request #1101 from ispyisail/feat/qet-mcp-edit-table-text-uuid
Let qet_edit address tables and symbol text fields by uuid
2026-09-28 16:14:41 +13:00
ispyisail f14bc0de28 Merge pull request #1098 from ispyisail/feat/script-table-text-uuid-index
Add scripting lookups for tables and symbol text fields by uuid
2026-09-28 16:14:09 +13:00
ispyisail 6d62140445 Merge pull request #1096 from ispyisail/fix/qet-mcp-diff-rotation
Fix qet_diff not reporting a rotated symbol
2026-09-28 16:13:34 +13:00
ispyisail d04eee756a Merge pull request #1095 from ispyisail/test/qet-mcp-read-tool-contracts
Add exact-answer tests for the MCP read tools and qet_diff
2026-09-28 16:13:02 +13:00
ispyisail d8c309afce Merge pull request #1094 from ispyisail/fix/qet-mcp-autonum-not-a-wire
Fix the MCP server counting a wire-numbering rule as a wire
2026-09-28 16:12:37 +13:00
ispyisail b1689fb760 Merge pull request #1093 from ispyisail/fix/qet-mcp-continuity-folio
Fix qet_continuity reporting a missing folio as clean
2026-09-28 16:12:13 +13:00
ispyisail 2529b11d69 Convert examples/schema_indus.qet to the current format; its labels show again
schema_indus.qet was saved by QElectroTech 0.3, where each placed symbol
kept its own values for its definition's old text fields, as
<element><inputs><input text="T1" .../>. Loading those was deliberately
dropped in 1f53c3929 ("Remove retro compatibility of element text item
prior to qet 0.7"), so since 2021 this shipped example has shown none of
its per-symbol texts: 122 texts on 44 of its 48 symbols, including every
device label (Q1, KM1, KM2, T1, M1, S1-S5, H1, H2, the X terminals) and
ratings such as "24VAC" and "F0 am 0,5A".

This commit changes the example file only, not the loader. It was
converted by loading it once with a local build that had 1f53c3929
reverted (and the converted label kept when the symbol's own label was
empty, as that code intended), and saving it. That build is not proposed.

Checked with a build of current master: every one of the 44 symbols shows
exactly the texts its <inputs> held; the 48 symbols (type, position,
rotation), the 69 wires (ends and numbers) and the folio are unchanged; the
two free texts only gain their font written out, as any save does. Like any
save, the file also gains uuids and the current version number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:01:31 +13:00
ispyisail 503c8c69b5 qet-mcp: qet_edit addresses tables and symbol text fields by uuid
set_table_position and delete_table took a table's index, and
set_element_text / delete_element_text a text field's index. An index
shifts when an earlier item is deleted, so a run that deletes table 0 and
then moves "table 1" moved the wrong table.

Each now also takes the item's uuid, resolved at run time through
qet.tableIndex() and qet.elementTextIndex() (the previous commit), as
texts, shapes and pictures already are through textIndex() and friends. A
text field's lookup is scoped to the op's element, since copies of a symbol
share their fields' uuids. An index still works, and a build without the
lookups is refused with the usual missing-methods hint only when a uuid is
actually used.

Tests: the generated script (no binary), and end to end: delete one table
then move the other, both by uuid; and edit one copy's shared field by uuid,
leaving the other copy's untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:49:36 +13:00
ispyisail 9232775a25 Merge branch 'feat/qet-mcp-diff-uuids' into feat/qet-mcp-items 2026-09-28 13:31:54 +13:00
ispyisail d92a0781b3 qet-mcp: test the text-field uuid guard on each side of a diff
A field with a uuid on a symbol with none cannot be keyed by
(symbol, field); the mutation audit showed either side's guard could be
dropped unnoticed. Now tested with the symbol uuid missing on one side,
then the other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:31:54 +13:00
ispyisail fd962619b5 Scripting: look up a table or a symbol text field by its uuid
qet.textIndex(), shapeIndex() and imageIndex() turn a free text's, shape's
or picture's uuid into the index the other calls take. Tables and symbol
text fields had no such lookup, so a script could only name them by index,
and an index shifts when an earlier item is deleted: a script that deletes
table 0 and then moves "table 1" moves the wrong table.

- qet.tableIndex(folio, uuid): the table's current index in tables(folio),
  or -1.
- qet.elementTextIndex(folio, elementUuid, textUuid): the field's current
  index in elementTexts(folio, elementUuid), or -1. The element is part of
  the address because a field's uuid is unique only within its element:
  copying an element keeps its fields' uuids (2612_ats_singlephase.qet has
  one field uuid on 20 copies).

Tests in misc/qet-mcp's integration suite, which drives these through
--run: a table followed across a deletion of the one before it, and the
same field uuid resolving on two copies to each copy's own field. Both
fail against a build without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:29:24 +13:00
ispyisail fd8b816475 qet-mcp: qet_items test fixture wraps its table in <tables>, as saved files do
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:20:23 +13:00
ispyisail bbe1903ec8 qet-mcp: read a folio's tables from its <tables> block only (as #1097 does for texts)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:20:22 +13:00
ispyisail 56a23d85dc Merge branch 'feat/qet-mcp-diff-uuids' into feat/qet-mcp-items 2026-09-28 13:20:22 +13:00
ispyisail a89a57bb10 qet-mcp: a symbol's own texts are not free texts on the folio
qet_diff's texts, shapes and pictures sections collected every <input>,
<shape> and <image> under a folio with iter(). Symbols in older files carry
their own <inputs><input> texts, so those were counted as the folio's free
texts: schema_indus.qet folio 1 showed 124 where QElectroTech has 2, and
editing one of those symbol texts would have been reported as a free text
changing.

Only the folio's direct <inputs>, <shapes> and <images> children are read
now. Shapes and pictures have no nested copies in the shipped examples;
they are changed too so the three stay alike. Found by comparing the
tools' answers with QElectroTech's own lists over the shipped examples.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:19:54 +13:00
ispyisail 1de307c13c Merge branch 'feat/qet-mcp-diff-uuids' into feat/qet-mcp-items 2026-09-28 13:15:21 +13:00
ispyisail 279c98f10a qet-mcp: add qet_items, listing drawn items with their uuids
qet_edit addresses free texts, shapes, pictures and tables by uuid, and
qet_diff reports them by uuid, but no tool listed them: the only way to
learn an item's uuid was to read the .qet. qet_items lists every free
text, shape, picture, table and symbol text field per folio (counted from
1, as qet_elements), with its uuid and main fields; filter by folio and
kind; default limit 500.

Also a test that every tool argument holding a data path is in the
workspace policy (_DATA_PATHS). Nothing checked that direction: a new tool
left out of the policy would have read or written anywhere with every test
passing, as removing qet_items' entry showed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:15:21 +13:00
ispyisail 8fd692783c qet-mcp: key symbol text fields by symbol and field uuid; refuse shared uuids
A symbol text field's uuid is unique only within its symbol: copying a
symbol keeps them, so 7 of the 24 shipped examples repeat one, up to 20
times (2612_ats_singlephase.qet). Keyed on the field uuid alone, those
fields merged and an edit to one copy could be reported on another. They
are now keyed on (symbol uuid, field uuid).

More generally, uuids are used as keys only when present on every item and
unique on each side; otherwise the old position/ends matching is kept, for
texts, shapes, pictures, tables, conductors and folios alike. Found by the
seeded-edit invariants (an untouched symbol's field reported changed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:15:12 +13:00
ispyisail f5888e8b56 qet-mcp: qet_diff matches conductors, folios, text fields and tables by uuid
QElectroTech now saves a uuid on conductors (those created since, or
loaded with one), folios, symbol text fields and tables, but qet_diff still
matched them by position or by ends:

- conductors: a rewire read as one wire removed and another added; by
  uuid it is that conductor with changed "ends".
- folios: a reorder read as every later folio changing its fields; by uuid
  it is one "reordered" entry, plus "added"/"removed" folios.
- symbol text fields: deleting the first of two read as the second
  changing; by uuid it is that field removed.
- tables (<graphics_table>) were not compared at all; now a section of
  their own.

Each is matched by uuid only when every item of that kind on both sides
has one; otherwise the old matching is kept, and each section says which
in "keyed_by", since older files and their first re-save mix the two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:03:06 +13:00
ispyisail e64ce0a6ba qet-mcp: qet_diff reports rotated symbols; equal angles compare equal
Rotating a symbol changes only its orientation attribute (quarter turns),
which qet_diff did not read, so a pure rotation reported no change in any
section. The elements section now has "rotated": each symbol whose
orientation changed, with the value before and after.

Rotating and undoing leaves QElectroTech writing text-field rotations as
"-270" where they were "90" (or "-90" for "270"); compared as strings that
read as a change. Rotations of texts, shapes, pictures and element text
fields are now compared reduced to [0, 360).

Found by a seeded-edit invariant run over the shipped examples: 51
rotations unreported across 20 projects, and 7 undo sequences reported as
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:55:46 +13:00