Commit Graph

9853 Commits

Author SHA1 Message Date
ispyisail 46598c4ed2 Merge pull request #1161 from ispyisail/fix/wire-nonfinite-values
Reject non-finite values when loading a wire
2026-09-30 11:32:53 +13:00
plc-user bae336ef5b Update define_definitions.cmake – fix only whitespace
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m27s
2026-09-29 21:57:43 +02:00
ispyisail 721bd155f5 Reject non-finite values when loading a wire
A wire read "nan" or "inf" for its position, its segment lengths and
its label position without checking them: QString::toDouble() accepts
all three as numbers. PR #792 and #996 closed this gap for elements,
terminals and texts; wires were left out.

The worst case is a segment length: a single "nan" passes the check
that the segments add up to the distance between the two terminals,
because every comparison with NaN is false. The whole path then turns
to NaN: on 2612_ats_singlephase.qet one bad length out of six was saved
back as six "nan" vertical segments, and the next save dropped them and
routed the wire automatically, so each save changed the file.

Now:
- a non-finite segment length is skipped like any unreadable one, so
  the path fails the coherence check and the wire is routed
  automatically at load, as already happens for "inf";
- a non-finite wire position is read as 0;
- a non-finite label position or rotation is ignored, as if the label
  had not been moved or rotated.

Found by the mutation sweep (python3 -m simulator sweep, mutators
inject_nan_coordinate / inject_inf_coordinate). The 24 example projects
resave byte-identical with and without this change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 08:42:12 +13:00
ispyisail 08271cb771 Merge pull request #1160 from ispyisail/fix/1155-multiline-text-alignment
Fix #1155: lines of a multi-line symbol text ignore its alignment
2026-09-30 08:14:52 +13:00
ispyisail 0d869b7e50 Align the lines of a multi-line symbol text to its alignment
The lines of a wrapped or multi-line dynamic text were always centred,
whatever horizontal alignment the user picked, on the folio and in the
symbol editor alike. Follow the horizontal alignment instead.

The alignment still also sets the anchor point, as before, and saved
positions are unchanged. Existing multi-line texts set to the default
left alignment are now drawn left-aligned instead of centred.

Issue #1155

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 07:05:54 +13:00
plc-user f15736bff7 Improve German wording 2026-09-29 18:39:04 +02:00
Laurent Trinques 0592b661df Merge pull request #1156 from Kellermorph/ger-translation
German Translation
2026-09-29 16:20:34 +02:00
Kellermorph 10d7dae5d0 German Translation 2026-09-29 14:14:02 +02:00
Laurent Trinques 0f1717c636 Merge pull request #1153 from ispyisail/feat/1069-align-icons
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m39s
Add icons for the Align commands
2026-09-29 13:01:19 +02:00
Laurent Trinques a16bb938f5 Merge pull request #1154 from ispyisail/fix/1145-shape-drag-snap
Fix #1145: drag a shape onto the grid by its corner
2026-09-29 12:59:21 +02:00
ispyisail cc0521bfe5 Drag a shape onto the grid by its corner, not its hidden origin (#1145)
A dragged shape put its pos() on the grid. pos() cannot be seen, and it
is off the drawn corner whenever the shape was drawn or resized with
Ctrl held, or rotated, so such a shape stayed off the grid however it
was dragged. Worse, Snap to grid (previous commit) moves pos() off the
grid to put the corner on it, so the next drag undid the snap.

QetShapeItem now overrides setPos(), which only the drag calls through
the virtual: dragged alone or with other shapes only, the top-left
corner of the drawn outline goes on the grid. Dragged together with
anything else it snaps by pos() as before, because the rest of the
selection follows this shape's movement and a corner correction would
take the symbols off the grid. Ctrl still drags freely: the snap goes
through Diagram::snapToGrid(), which reads it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 23:39:23 +13:00
ispyisail 1fdb414006 Give the Align commands icons
The seven Edit > Align commands (Snap to grid and the six align
commands, discussion #1069) had no icons: the menu, the context menu,
command search and the shortcut bar showed them as bare text.

They are pixel-grid SVGs in ico/scalable/ drawn like the folio and
Add PDF icons: 24 pixel canvas, art on the inner 22 pixel grid, one
currentColor ink so misc/make_icon_themes.py writes the dark copies.
Each align icon is a guide line with two boxes of different lengths
against it; the centre ones show the guide only between the boxes.
Snap to grid is a faint grid with one box sitting exactly on a cell.
The boxes have a solid outline and a 35 % tinted inside, which keeps
the guide the strongest mark at 16 pixels.

Names follow the freedesktop icon names (align-horizontal-left, ...).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 23:37:40 +13:00
Laurent Trinques 367efb5589 Merge pull request #1152 from ispyisail/fix/1145-align-shapes
Fix #1145: let Align and Snap to grid move drawn shapes
2026-09-29 12:31:24 +02:00
Laurent Trinques a4f6dfe8b4 Merge pull request #1151 from ispyisail/fix/1146-locked-group-member
Fix a group splitting apart when one of its items is locked (#1146)
2026-09-29 12:27:05 +02:00
Laurent Trinques b094ff328c Merge pull request #1150 from ispyisail/fix/1144-group-menu-refresh
Fix right-click menu offering Group again after grouping (#1144)
2026-09-29 12:26:52 +02:00
ispyisail 795484ff4f Let Align and Snap to grid move drawn shapes (#1145)
Rectangles, ellipses, lines and polygons were left out of the Align
submenu: with only shapes selected every command was greyed out, and a
shape outside a group was ignored when aligning it with a symbol.

Shapes now take part like pictures. Their edges are the shape as drawn
(the new QetShapeItem::sceneOutlineRect(), without the pen, the 6 px
selection margin or the wider hover outline; the old code used
sceneBoundingRect() for grouped shapes and so aligned them 6 px off).
The point that goes on the grid is the top-left corner of that box: a
rectangle's corner, an ellipse's box. pos() is not used, because a
shape drawn with Ctrl held or rotated has its corners off the grid
while pos() is on it.

The menu's enable rule now counts what the command counts, a group as
one. Before, two symbols in one group enabled the six align commands,
which then did nothing and only said so in the status bar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 23:24:50 +13:00
ispyisail 2dbaa69186 Fix a group splitting apart when one of its items is locked (#1146)
Grouping two symbols and then locking one of them (Lock position in its
properties) left the group in a broken state: dragging the unlocked symbol
pulled it away while the locked one stayed, and dragging the locked one
did nothing. The move simply dropped locked items, so the rest of the
group went without them.

A group with a locked member now does not move at all, whichever member
is dragged, and the status bar says why. This is the rule the item-groups
proposal (discussion #1070) set out for this case. The same rule applies
to the arrow keys and to the Align commands, which share
DiagramContent::removeNonMovableItems().

Also fixed on the way, for a plain selection with a locked symbol: a wire
between the locked symbol and one being dragged kept its user-placed text
moving with the dragged end. Such a wire is now redrawn only, as a wire to
an unselected symbol already is.

Checked in the GUI on two symbols joined by a wire (grafcet example),
master against this branch, positions read from the saved file:
- drag the unlocked member: master moves it 190 px, this branch moves
  nothing and shows the message
- arrow keys on the selected group (3 runs each): master moves the
  unlocked member, this branch nothing
- the same two symbols ungrouped: both move the unlocked one, as before
- user-placed wire text: master shifts it 190 px, this branch keeps it
ctest: 34/34.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:23:32 +13:00
ispyisail 1d816f432c Fix the right-click menu offering Group again after grouping (#1144)
Group and Ungroup are enabled in slot_updateComplexActions(), which ran
only when the selection changed. Grouping, ungrouping and undoing either
leave the selection as it is, and so does right-clicking an item that is
already selected, so the actions kept the state from before. The
right-click menu hides disabled actions, so it went on showing Group on a
selection that was now one group (and Ungroup after ungrouping), and the
one that would work was not in the menu at all.

Diagram::setItemGroup() is the one place an item's group changes (group,
ungroup, undo, redo, paste), so it now emits itemGroupChanged() and the
editor refreshes its actions on it, beside the selectionChanged
connection.

Checked in the GUI on two free texts, saving after each step: right-click
> Group, right-click again, Ctrl+Z, right-click again. Before, the second
and third right-clicks offered Group again and did nothing (both saves
still grouped). After, they offered Ungroup and ungrouped (0 grouped
texts in both saves); Group, Ungroup, Group also round-trips.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 23:09:57 +13:00
ispyisail cbbb9fd0ac Merge pull request #1149 from ispyisail/fix/register-filetypes-no-admin
Fix .qet file association from the Windows ZIP without admin rights
2026-09-29 22:31:03 +13:00
ispyisail df442c81e3 Fix register_filetypes.bat for the portable ZIP and for non-admin users
The Windows portable ZIP ships "Lancer QET_qt6.bat", but the script only
looked for "Lancer QET.bat", so it stopped at its first check and never
registered anything. It now tries the ZIP's name first and falls back to
the installer's.

It also wrote to HKEY_CLASSES_ROOT, which a standard (non-admin) Windows
account cannot create keys in. It now writes to
HKEY_CURRENT_USER\Software\Classes: per-user, no elevation needed, and
Windows merges it into HKEY_CLASSES_ROOT for that user.

qet_uninstall_file_associations.reg removes the HKCU keys first, then the
old HKEY_CLASSES_ROOT ones as before.

Tested under Wine 10 on the git10275 nightly ZIP: the old script aborts
("Lancer QET.bat ... n'a pas ete trouve"), the new one registers .qet,
.elmt and .titleblock under HKCU with nothing under HKLM, the uninstall
file removes them, and the "Lancer QET.bat" fallback works. Wine does not
enforce admin rights, so the non-admin case itself is not proven there.

Reported in #1148.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 22:00:06 +13:00
ispyisail 48446e01e4 Merge pull request #1142 from ispyisail/feat/database-from-document
Fill the project database from the file, not from the built folios
2026-09-29 20:57:16 +13:00
ispyisail 8fdff95732 Merge remote-tracking branch 'upstream/master' into feat/database-from-document
# Conflicts:
#	sources/qetproject.cpp
2026-09-29 19:00:16 +13:00
ispyisail 33a4ef5bad Merge pull request #1140 from ispyisail/fix/project-filename-first-dot
Fix title blocks cutting project file names at the first dot
2026-09-29 18:53:54 +13:00
ispyisail 79717ccd61 Merge pull request #1138 from ispyisail/feature/connect-ai-assistant
Add Help → Connect an AI assistant
2026-09-29 18:53:29 +13:00
ispyisail fd7aec4071 Merge pull request #1137 from ispyisail/feature/qet-mcp-windows-python
Offer Python with the Windows installer, for the MCP server
2026-09-29 18:53:01 +13:00
ispyisail 2511566d8b Merge pull request #1136 from ispyisail/feature/qet-mcp-install
Install the MCP server with QElectroTech
2026-09-29 18:52:36 +13:00
ispyisail fb61e03bd7 Merge pull request #1135 from ispyisail/fix/qet-mcp-windows-launch
Fix the MCP server failing to start QElectroTech on Windows
2026-09-29 18:52:10 +13:00
ispyisail b1abab1241 Work out formula labels and wire texts from the file, with the folios' code
The first fill from the document stored the label and wire text saved in
the file. QElectroTech saves what it last worked out, not what the formula
gives now: K%total-%id saved as K1-1 on a 3-folio project is K3-1 on the
folios, and the same for a wire's formula. So the document fill disagreed
with the folio fill on any file whose folios had been added or moved.

AssignVariables now works from a FormulaContext: the folio's number,
index, total, plant, location, title-block and project variables, and the
element's grid cell and prefix or the wire's four properties. The Diagram
overload fills one from a built folio, the database fills one from the
file, and both call the same evaluation.

Also brought in line with Diagram::fromXml(): an element Element::valideXml()
rejects is skipped with its wires; a folio where two elements number their
terminals alike (Element::fromXml() refuses one, by geometry), a frozen
formula wire text, and the older sequential-number attributes are left to
the folios. Each folio's border now starts from the defaults a new Diagram
has, not the previous folio's values.

tst_databasefromdocument: formulasAreWorkedOut (every variable kind, stale
saved values), unbuiltElementIsLeftOut, clashingTerminalIdsFallBack. Each
fails with its fix removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 16:08:45 +13:00
ispyisail 42fab5ad78 Fix title blocks cutting project file names at the first dot
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>
2026-09-29 15:56:32 +13:00
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 ddc645174f Connect an AI assistant: say when Python is missing
The configuration names "python" or "python3", or the Python the Windows
installer can add. When none of those can run, the assistant fails with
an error far from the cause, so the dialog now says so in bold, with what
to do: tick "Python pour l'assistant IA" in the installer or install
Python (Windows), or install Python 3 from the system's packages.

Windows 10 and 11 put a python.exe in WindowsApps that only opens the
Microsoft Store, so a PATH search alone reports Python where there is
none. A result there is reported as possibly that shortcut. Backslashes
are converted explicitly, not with QDir::fromNativeSeparators(), which
leaves them alone off Windows and so could not be tested here.

tst_aiassistantsetup: 17 cases (8 new: the bundled Python is taken
without looking on PATH; none, python.org, the Store shortcut with either
separator, and a Linux folder named WindowsApps). Each of the 4 new rules
was removed in turn and the test failed. ctest 32/32. In the GUI, the
same install layout shows the warning with no python3 on PATH and no
warning with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:17:16 +13:00
ispyisail 95914c7ee1 Merge remote-tracking branch 'upstream/master' into feature/connect-ai-assistant 2026-09-29 14:40:38 +13:00
ispyisail 9778dc0a86 Merge branch 'feature/qet-mcp-install' into feature/qet-mcp-windows-python 2026-09-29 14:37:53 +13:00
ispyisail c4b1e02810 Merge remote-tracking branch 'upstream/master' into feature/qet-mcp-install 2026-09-29 14:37:43 +13:00
ispyisail 7fa2643ac5 Merge remote-tracking branch 'upstream/master' into fix/qet-mcp-windows-launch
# Conflicts:
#	misc/qet-mcp/test_qet_mcp.py
2026-09-29 14:37:36 +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 06fc5153c1 qet-mcp: make QElectroTech launches work on Windows
On Windows every tool that starts QElectroTech (qet_export, qet_edit,
qet_query, qet_check, qet_continuity, qet_project_new) failed, for two
reasons:

- The server ran a copy of the executable from a temporary folder, to get
  its own SingleApplication key. A Windows program loads its DLLs from its
  own folder, so the copy died before main() with 0xC0000135 (DLL not
  found). Every flag the server passes is a CLI export flag or --run, and
  main.cpp handles both before it constructs SingleApplication, so on
  Windows the original is now run. The copy stays elsewhere.
- It set QT_QPA_PLATFORM=offscreen. The Windows packages ship only the
  qwindows platform plugin, so Qt found none and stopped at a message box
  nobody could close: every call hung until its timeout. Windows now keeps
  its default platform; the export flags and --run open no window.

Checked under Wine (qet-wine-smoke) on the fork's CI Windows build, run
through python.org's embeddable Python: before, qet_export ended with
exit 3221225781; after, a PDF export, a qet_query (98 elements, as on
Linux) and a qet_edit placing a common:// element all succeed. The hang
was isolated by launching the same export from bash (works) and from
Python with one change at a time: only dropping QT_QPA_PLATFORM made it
work. Four unit tests pin both choices per platform; each fails with its
fix removed. Suite 262/262 none skipped on Linux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 14:25:06 +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 c76ef10e0b Add Help > Connect an AI assistant
A dialog showing the configuration an AI assistant needs to start
QElectroTech's MCP server (misc/qet-mcp), with this installation's paths
filled in: pick the assistant (Claude Desktop, Claude Code, GitHub
Copilot in VS Code, Cursor, Gemini CLI, Codex CLI, LM Studio), the
drawings folder the assistant may use, and whether it may edit (off by
default). It says where the text goes, warns that text inside a project
from someone else can steer an assistant, and copies the text. It writes
nothing and starts nothing.

AiAssistantSetup finds the server beside the running program, in the two
places the packages put it (<root>/mcp beside <root>/bin on Windows,
<prefix>/share/qelectrotech/mcp beside <prefix>/bin otherwise), and the
Python the Windows installer can add. When there is none, the dialog says
the server is not installed with this version and links the guide.
The configuration is written with QJsonDocument, so Windows paths are
escaped correctly; Codex CLI gets TOML.

tst_aiassistantsetup (9 cases): both layouts, no server, the bundled
Python on Windows only, valid JSON for every JSON client with paths that
parse back unchanged, the "servers" key for VS Code, "type" only where
wanted, editing off unless allowed, escaped TOML. Each of 8 rules was
removed in turn and the test failed. ctest 32/32.

Checked in the GUI (Xvfb): Help menu opens it; from a build tree it
reports the server missing and disables Copy; from an install layout it
fills the paths, and the text Copy put on the clipboard, used unchanged
as a Claude Code configuration, ran a real qet_project_info call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:29:50 +13:00
ispyisail 6b9c5189b1 Offer Python with the Windows installer, for the MCP server
Most Windows users have no Python, which the MCP server needs. The
installer now offers "Python for the AI assistant", unticked by default:
the official embeddable package from python.org (3.14.7, ~12 MB, no
registry, removed with QElectroTech), in <folder>\mcp\python. The
portable folder and the MSI, which pack all of files/, carry it.

The workflow downloads it pinned by version and by python.org's own
sha256 for the file (checked against python.org's download API), and
fails the build on a mismatch. The script stays plain text beside it.

Checked under Wine (64-bit, qet-wine-smoke image), on an installer built
with makensis 3.10 (0 warnings, strings in all 29 languages):
- a silent default install puts mcp\qet_mcp.py in place and no Python;
  the same installer with the section ticked by default installs it, so
  the check tells the two apart;
- the installed Python runs the installed server: 15 tools listed,
  qet_project_info on an example answers as on Linux, and the server
  finds bin\QElectroTech.exe and elements\ by itself.
Not checked: a real Windows machine; the CI download step (fork run).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:00:30 +13:00
ispyisail ac952d3df6 Install the MCP server with QElectroTech
The MCP server (misc/qet-mcp) was only reachable from a source checkout.
It now ships with every package, next to the program, and finds that
QElectroTech and its element collection from where it sits:

- make install (Linux distributions, snap, flatpak):
  <prefix>/share/qelectrotech/mcp/qet_mcp.py, executable, with its README.
- Windows installer: a new "AI assistant (MCP)" component (on by default,
  ~200 KB), installed to <folder>\mcp. The workflow stages files/mcp, so
  the portable folder and the MSI, which packs all of files/, carry it too.

The server learns the Windows layout (<root>/mcp beside <root>/bin, whose
program is QElectroTech.exe, and <root>/elements), in addition to
<prefix>/share/qelectrotech/mcp. A copy saved anywhere else, even beside
some bin/ folder, is not taken for an installation.

The installer strings are given in all 29 installer languages: French
translated, the others in English until translated, which is what NSIS
would fall back to anyway but without its warning 6040 per language.

Checked: make install into a scratch prefix, then from the installed
script with nothing configured and no qelectrotech on PATH, an SVG export
and a qet_edit placing a common:// element both succeeded. makensis 3.10
compiles the installer with 0 warnings before and after (removing the
French description gives warning 6040), and the installer contains
mcp/qet_mcp.py. Suite 274/274 none skipped; each layout check was
removed in turn and a test failed. Snap/flatpak: installed by the same
CMake rule; launching their QElectroTech from outside is untested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:55:10 +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