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>
This commit is contained in:
ispyisail
2026-09-28 23:25:05 +13:00
parent af6c2f0dde
commit e7745afd18
3 changed files with 25 additions and 3 deletions
+6 -1
View File
@@ -296,7 +296,12 @@ Python, plus the hang guard on `addConductor` and the database refresh in
where two conductors meet at a terminal. It is turned at run time into an
end whose terminal carries only that conductor; where both of its ends
are shared the op fails and its `note` says why. Needs
`qet.conductorEnds()` in the build.
`qet.conductorEnds()` in the build. A uuid names one wire, but
`set_conductor` still changes the whole potential, the same as by
terminal. A project saved before conductors carried a uuid has none in
the file: QElectroTech makes a new one on every load and does not save
it, so `qet_conductors` reports `uuid` as empty and those conductors are
named by `element` + `terminal` until wire uuids last (#1103).
- **`qet_export` isolates its launch.** SingleApplication keys its socket
on `applicationFilePath()`, so a second launch of the same binary path
forwards its request to an already-running instance and returns *that*
+11 -2
View File
@@ -1419,6 +1419,7 @@ def _build_script(operations: list, output: str) -> str:
" if (ends && ends.length === 2) {",
" var lines = qet.conductors(folio);",
" for (var k = 0; k < 2; k++) {",
" if (ends[k] === '?') continue;",
" var n = 0;",
" for (var j = 0; j < lines.length; j++) {",
" var p = lines[j].split(' : ')[0].split(' -- ');",
@@ -1601,6 +1602,11 @@ def _build_script(operations: list, output: str) -> str:
if "element" in op or "terminal" in op:
raise ValueError(f"operation {i} ({name}): give either \"conductor\" "
f"(its uuid) or \"element\" + \"terminal\", not both")
if op["conductor"] == "":
raise ValueError(f"operation {i}: \"conductor\" is empty -- a conductor "
f"from a project saved before conductors carried a uuid "
f"has none in the file; name it by \"element\" + "
f"\"terminal\" instead")
if not isinstance(op["conductor"], str) or not _UUID_RE.fullmatch(op["conductor"]):
raise ValueError(f"operation {i}: \"conductor\" must be a conductor uuid, "
f"got {op['conductor']!r}")
@@ -2551,8 +2557,11 @@ TOOLS = [
"or give \"conductor\": its uuid from qet_conductors in place "
"of \"element\" + \"terminal\" (set_conductor, "
"move_conductor_segment and delete_conductor all accept it; it "
"needs one of the conductor's two terminals to carry only it); "
"its \"property\" is one of " + ", ".join(CONDUCTOR_PROPERTIES) +
"needs one of the conductor's two terminals to carry only it, "
"and a conductor qet_conductors lists with an empty uuid has "
"none to give). A uuid names one wire, but set_conductor still "
"changes its whole potential, as it does by terminal. "
"set_conductor's \"property\" is one of " + ", ".join(CONDUCTOR_PROPERTIES) +
". move_conductor_segment reroutes the drawn path itself rather "
"than a property of the potential -- addressed the same way (a "
"terminal carrying exactly one conductor), plus a segment index "
+8
View File
@@ -206,6 +206,12 @@ class EditValidation(unittest.TestCase):
s = self.build([{"op": "set_conductor", "folio": 0, "conductor": U,
"property": "num", "value": "W1"}])
self.assertIn('qet.setConductorProperty(0, e0.element, e0.terminal, "num", "W1")', s)
s = self.build([{"op": "move_conductor_segment", "folio": 2, "conductor": U,
"segment": 1, "dx": 10, "dy": 0}])
self.assertIn(f'var e0 = qetMcpConductorEnd(0, 2, "{U}");', s)
self.assertIn("(e0 ? qet.moveConductorSegment(2, e0.element, e0.terminal, 1, 10, 0) : false)", s)
# an end whose terminal or element is missing is never picked
self.assertIn("if (ends[k] === '?') continue;", s)
self.assertNotIn('"conductorEnds"', self.build(
[{"op": "delete_conductor", "folio": 0, "element": U, "terminal": 0}]))
with self.assertRaisesRegex(ValueError, "not both"):
@@ -213,6 +219,8 @@ class EditValidation(unittest.TestCase):
"element": U, "terminal": 0}])
with self.assertRaisesRegex(ValueError, "must be a conductor uuid"):
self.build([{"op": "delete_conductor", "folio": 0, "conductor": "W1"}])
with self.assertRaisesRegex(ValueError, "saved before conductors carried a uuid"):
self.build([{"op": "delete_conductor", "folio": 0, "conductor": ""}])
def test_every_op_generates_a_script(self):
# one minimal valid instance of every op