diff --git a/misc/qet-mcp/test_qet_mcp.py b/misc/qet-mcp/test_qet_mcp.py index 722588ae9..d36d58b74 100644 --- a/misc/qet-mcp/test_qet_mcp.py +++ b/misc/qet-mcp/test_qet_mcp.py @@ -3242,6 +3242,76 @@ class PlcIntegration(unittest.TestCase): self.assertTrue(r["operations"][-1]["succeeded"]) +@needs_examples +class UuidIndexLookups(unittest.TestCase): + """qet.tableIndex() and qet.elementTextIndex(): a uuid in, the index the + other calls take out, as qet.textIndex() does for free texts. The index is + what shifts when an earlier item is deleted; the uuid is what holds.""" + + def run_script(self, project, body): + r = m._run_qet(BINARY, [str(project)], elements_dir=ELEMENTS or None, + script=body + '\nqet.log("OUT " + JSON.stringify(out));', tail=200_000) + line = next((ln for ln in (r["stdout"] + "\n" + r["stderr"]).splitlines() if "OUT " in ln), None) + self.assertIsNotNone(line, r.get("stderr", "")[-500:]) + return json.loads(line.split("OUT ", 1)[1]) + + def test_table_index_follows_the_table_across_a_deletion(self): + root = ET.parse(Path(EXAMPLES) / "industrial.qet").getroot() + folio, table = next((i, d.find("tables/graphics_table")) for i, d in enumerate(root.iter("diagram")) + if d.find("tables/graphics_table") is not None) + twin = ET.fromstring(ET.tostring(table)) + twin.set("uuid", "{11111111-2222-4333-8444-555555555555}") + twin.set("x", str(float(table.get("x")) + 900)) + list(root.iter("diagram"))[folio].find("tables").append(twin) + with tempfile.TemporaryDirectory() as tmp: + p = Path(tmp) / "two_tables.qet" + ET.ElementTree(root).write(p, encoding="utf-8") + first, second = table.get("uuid"), twin.get("uuid") + out = self.run_script(p, f""" + var out = {{}}; + out.first = qet.tableIndex({folio}, "{first}"); + out.second = qet.tableIndex({folio}, "{second}"); + out.bogus = qet.tableIndex({folio}, "{{00000000-0000-4000-8000-000000000000}}"); + out.not_uuid = qet.tableIndex({folio}, "table"); + out.list = qet.tables({folio}); + qet.deleteTable({folio}, out.first); + out.second_after = qet.tableIndex({folio}, "{second}"); + out.first_after = qet.tableIndex({folio}, "{first}");""") + self.assertEqual(sorted([out["first"], out["second"]]), [0, 1]) + # each index names the right table: the twin sits 900 further right + at = lambda i: float(re.search(r"at \(([-\d.]+),", out["list"][i]).group(1)) + self.assertEqual(at(out["second"]) - at(out["first"]), 900) + self.assertEqual((out["bogus"], out["not_uuid"]), (-1, -1)) + self.assertEqual((out["second_after"], out["first_after"]), (0, -1)) + + def test_element_text_index_needs_the_element_as_well(self): + """Copies of a symbol share their text fields' uuids (2612_ats_singlephase.qet): + the same field uuid resolves on each copy to that copy's own field.""" + root = ET.parse(Path(EXAMPLES) / "2612_ats_singlephase.qet").getroot() + owners = {} + for i, d in enumerate(root.iter("diagram")): + for el in d.iter("element"): + for t in el.iter("dynamic_elmt_text"): + if t.get("uuid"): + owners.setdefault((i, t.get("uuid")), []).append((el.get("uuid"), t.findtext("text"))) + (folio, field), copies = next((k, v) for k, v in owners.items() + if len(v) >= 2 and len({s for _, s in v}) >= 2) + (a, shows_a), (b, shows_b) = copies[:2] + out = self.run_script(Path(EXAMPLES) / "2612_ats_singlephase.qet", f""" + var out = {{}}; + out.ia = qet.elementTextIndex({folio}, "{a}", "{field}"); + out.ib = qet.elementTextIndex({folio}, "{b}", "{field}"); + out.la = qet.elementTexts({folio}, "{a}"); + out.lb = qet.elementTexts({folio}, "{b}"); + out.bogus = qet.elementTextIndex({folio}, "{a}", "{{00000000-0000-4000-8000-000000000000}}"); + out.no_element = qet.elementTextIndex({folio}, "{{00000000-0000-4000-8000-000000000001}}", "{field}");""") + self.assertGreaterEqual(out["ia"], 0) + self.assertGreaterEqual(out["ib"], 0) + self.assertIn(f"shows='{shows_a}'", out["la"][out["ia"]]) + self.assertIn(f"shows='{shows_b}'", out["lb"][out["ib"]]) + self.assertEqual((out["bogus"], out["no_element"]), (-1, -1)) + + @needs_examples class CorpusIntegration(unittest.TestCase): def test_folio_counts_match_what_qelectrotech_itself_holds(self): diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp index 04a696d68..b3d083a3f 100644 --- a/sources/scripting/qetscriptapi.cpp +++ b/sources/scripting/qetscriptapi.cpp @@ -2891,6 +2891,27 @@ int QetScriptApi::imageIndex(int folioIndex, const QString &uuid) const return indexOfUuid(sortedImages(folioIndex), uuid); } +/// @brief QetScriptApi::tableIndex as textIndex(), for tables(folioIndex). +int QetScriptApi::tableIndex(int folioIndex, const QString &uuid) const +{ + return indexOfUuid(sortedTables(folioIndex), uuid); +} + +/** + @brief QetScriptApi::elementTextIndex + The current index in elementTexts() of the text field carrying + @p textUuid, on the element @p elementUuid, or -1. + A field's uuid is unique only within its element: copying an element + keeps its fields' uuids, so the element is part of the address. +*/ +int QetScriptApi::elementTextIndex(int folioIndex, const QString &elementUuid, + const QString &textUuid) const +{ + Element *element = findElement(folioIndex, elementUuid); + if (!element) return -1; + return indexOfUuid(element->dynamicTextItems(), textUuid); +} + /** @brief QetScriptApi::addImage Place a picture from a file, as the "add image" tool does after its file diff --git a/sources/scripting/qetscriptapi.h b/sources/scripting/qetscriptapi.h index 14f1b4459..533b70830 100644 --- a/sources/scripting/qetscriptapi.h +++ b/sources/scripting/qetscriptapi.h @@ -483,6 +483,7 @@ class QetScriptApi : public QObject // -- a BOM/nomenclature or summary table placed on a folio -- Q_INVOKABLE QStringList tables(int folioIndex) const; + Q_INVOKABLE int tableIndex(int folioIndex, const QString &uuid) const; Q_INVOKABLE int addTable(int folioIndex, const QString &kind, const QString &name, const QString &query); Q_INVOKABLE bool deleteTable(int folioIndex, int tableIndex); @@ -508,6 +509,8 @@ class QetScriptApi : public QObject // -- the text fields shown on a symbol (label, terminal names, ...) -- Q_INVOKABLE QStringList elementTexts(int folioIndex, const QString &elementUuid) const; + Q_INVOKABLE int elementTextIndex(int folioIndex, const QString &elementUuid, + const QString &textUuid) const; Q_INVOKABLE int addElementText(int folioIndex, const QString &elementUuid, const QString &source, const QString &value, double x, double y);