From e7745afd18991ce949d04cae6fad08f981e22cc5 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Mon, 28 Sep 2026 23:25:05 +1300 Subject: [PATCH] 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 --- misc/qet-mcp/README.md | 7 ++++++- misc/qet-mcp/qet_mcp.py | 13 +++++++++++-- misc/qet-mcp/test_qet_mcp.py | 8 ++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/misc/qet-mcp/README.md b/misc/qet-mcp/README.md index 08317a00c..486b85379 100644 --- a/misc/qet-mcp/README.md +++ b/misc/qet-mcp/README.md @@ -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* diff --git a/misc/qet-mcp/qet_mcp.py b/misc/qet-mcp/qet_mcp.py index 0bf06a875..402cb8be1 100755 --- a/misc/qet-mcp/qet_mcp.py +++ b/misc/qet-mcp/qet_mcp.py @@ -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 " diff --git a/misc/qet-mcp/test_qet_mcp.py b/misc/qet-mcp/test_qet_mcp.py index 592dbd9b7..a7a44d76e 100644 --- a/misc/qet-mcp/test_qet_mcp.py +++ b/misc/qet-mcp/test_qet_mcp.py @@ -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