A project saved as "my.project.qet" showed "my" in every title block
cell using %{projectfilename}, and "my" again in %{savedfilename} after
a save. QETProject used QFileInfo::baseName(), which stops at the first
"."; completeBaseName() strips only the ".qet" suffix. Same fix as
#725 made for the PDF export's file name.
Verified headlessly on examples/industrial.qet copied to
my.project.qet, with a title block cell set to %{projectfilename}:
--export-svg shows "my" on master and "my.project" with this change,
on all 50 folios. For %{savedfilename}, a --run script calling
qet.save("") twice writes "my" on master and "my.project" with this
change. ctest 32/32.
Not changed: write() updates the saved* variables after the file is
written, so the values stored in a file are those of the save before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
"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>
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>
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>
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>
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>
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>
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.
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>
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>
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>
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>