diff --git a/misc/qet-mcp/README.md b/misc/qet-mcp/README.md index 37a3a2cb8..6c15ead1f 100644 --- a/misc/qet-mcp/README.md +++ b/misc/qet-mcp/README.md @@ -487,6 +487,13 @@ Python, plus the hang guard on `addConductor` and the database refresh in at names none of them and is refused, so address a potential from one of its leaves. Property names are the file's own, so `qet_conductors` reads back exactly what was set. +- **`set_conductor_default` sets a folio's conductor defaults**, the + Conductors tab of Folio properties: `onetextperfolio` (`"true"` shows one + wire number per potential on the folio), or any `set_conductor` property, + which conductors drawn later on that folio start from. `"folio": -1` sets + the project's defaults instead, which each folio added afterwards copies; + it does not change existing folios. Like the dialogs, it is not on the + undo stack. - **`link_elements` takes a folio for each end**, because a master and its slave are normally on different folios. Whether a pair may be linked is decided by QElectroTech's own `isLinkable()`, so a script cannot make a diff --git a/misc/qet-mcp/qet_mcp.py b/misc/qet-mcp/qet_mcp.py index 20485964d..fa7f35360 100755 --- a/misc/qet-mcp/qet_mcp.py +++ b/misc/qet-mcp/qet_mcp.py @@ -1425,6 +1425,10 @@ OPS = { "set_project_title": ("setProjectTitle", [("title", "str")]), "set_folio_border": ("setFolioBorder", [("folio", "folio"), ("property", "str"), ("value", "str")]), + # A folio's conductor defaults, or with "folio": -1 the project's ones + # that each new folio copies. + "set_conductor_default": ("setConductorDefault", [("folio", "folio"), ("property", "str"), + ("value", "str")]), "embed_title_block_template": ("embedTitleBlockTemplate", [("name", "str")]), "duplicate_elements": ("duplicateElements", [("folio", "folio"), ("elements", "elmts"), ("to_folio", "folio"), ("x", "num"), ("y", "num")]), @@ -1499,6 +1503,10 @@ CONDUCTOR_PROPERTIES = ["num", "formula", "function", "bus", "cable", "color2", "bicolor", "style", "dash-size", "condsize", "numsize", "displaytext"] +# A folio's conductor defaults take the same names, plus the folio-wide +# "one text per potential" switch, which no single conductor carries. +CONDUCTOR_DEFAULT_PROPERTIES = ["onetextperfolio"] + CONDUCTOR_PROPERTIES + # Methods this tool needs that only exist in a build carrying the drawing # verbs. Probed in the script rather than assumed, because the failure mode # otherwise is a TypeError on line N of a generated file the caller never @@ -1752,6 +1760,11 @@ def _build_script(operations: list, output: str) -> str: if op.get("kind") == "element_info" and not op.get("field"): raise ValueError(f"operation {i}: element_info needs a non-empty " f"\"field\" (information key)") + if (name == "set_conductor_default" + and op.get("property") not in CONDUCTOR_DEFAULT_PROPERTIES): + raise ValueError(f"operation {i}: unknown conductor default " + f"{op.get('property')!r}; expected one of " + f"{', '.join(CONDUCTOR_DEFAULT_PROPERTIES)}") if name == "set_folio_border" and op.get("property") not in FOLIO_BORDER_PROPERTIES: raise ValueError(f"operation {i}: unknown folio border property " f"{op.get('property')!r}; expected one of " @@ -2840,6 +2853,12 @@ TOOLS = [ "set_folio_border sets one " "of the folio frame's " + ", ".join(FOLIO_BORDER_PROPERTIES) + " (counts 1-99, sizes 1-1000, display-* true/false). " + "set_conductor_default sets one of a folio's conductor defaults " + "(Folio properties > Conductors): onetextperfolio (true/false, one " + "wire number per potential on the folio) or any set_conductor " + "property, which new conductors on that folio start from; \"folio\": " + "-1 sets the project's defaults that each folio added later copies. " + "Not on the undo stack. " "embed_title_block_template copies a template into the project from " "the common/company/custom collection that has it (only reachable if " "the binary's compiled-in template path resolves to something real -- " diff --git a/misc/qet-mcp/test_qet_mcp.py b/misc/qet-mcp/test_qet_mcp.py index 422c4a085..3bdff5052 100644 --- a/misc/qet-mcp/test_qet_mcp.py +++ b/misc/qet-mcp/test_qet_mcp.py @@ -401,6 +401,8 @@ class EditValidation(unittest.TestCase): "regex": False, "case_sensitive": True}], "set_project_title": [{"op": "set_project_title", "title": "T"}], "set_folio_border": [f, {"op": "set_folio_border", "folio": "$f", "property": "columns", "value": "10"}], + "set_conductor_default": [f, {"op": "set_conductor_default", "folio": "$f", + "property": "onetextperfolio", "value": "true"}], "embed_title_block_template": [{"op": "embed_title_block_template", "name": "default"}], "duplicate_elements": el + [{"op": "duplicate_elements", "id": "d", "folio": "$f", "elements": ["$e"], "to_folio": "$f", "x": 50, "y": 50}], @@ -525,6 +527,8 @@ class EditValidation(unittest.TestCase): ("unknown kind", {"op": "search_and_replace", "kind": "folio", "field": "x", "pattern": "a", "replacement": "b", "regex": False, "case_sensitive": False}), + ("unknown conductor default", {"op": "set_conductor_default", "folio": 0, + "property": "label", "value": "x"}), ("unknown conductor field", {"op": "search_and_replace", "kind": "conductor", "field": "label", "pattern": "a", "replacement": "b", "regex": False, "case_sensitive": False}), @@ -3685,6 +3689,28 @@ class Integration(unittest.TestCase): with self.subTest(prop=prop, value=good): self.ok(self.sb.edit(base, [{"op": "set_folio_border", "folio": 0, "property": prop, "value": good}])) + def test_conductor_defaults(self): + """The folio's "one text per potential" switch (#1178), and the + project's defaults that a folio added later copies, as saved.""" + base = self.sb.new(folios=1) + r = self.ok(self.sb.edit(base, [ + {"op": "set_conductor_default", "folio": 0, "property": "onetextperfolio", "value": "true"}, + {"op": "set_conductor_default", "folio": 0, "property": "conductor_section", "value": "1.5"}, + {"op": "set_conductor_default", "folio": -1, "property": "onetextperfolio", "value": "true"}, + {"op": "add_folio"}])) + root = ET.parse(r["output"]).getroot() + folios = [d.find("defaultconductor") for d in root.iter("diagram")] + self.assertEqual([d.get("onetextperfolio") for d in folios], ["1", "1"], + "set on folio 0; copied from the project's defaults by the new folio") + self.assertEqual(folios[0].get("conductor_section"), "1.5") + self.assertEqual(root.find("newdiagrams/conductors").get("onetextperfolio"), "1") + for prop, bad in (("onetextperfolio", "yes"), ("numsize", "0"), ("color", "notacolour")): + with self.subTest(prop=prop, value=bad): + self.assertFalse(self.sb.edit(base, [{"op": "set_conductor_default", "folio": 0, + "property": prop, "value": bad}])["ok"]) + self.assertFalse(self.sb.edit(base, [{"op": "set_conductor_default", "folio": 5, + "property": "onetextperfolio", "value": "true"}])["ok"]) + # ---- duplicating ---- def test_duplicate_returns_copies_in_the_order_asked(self): diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp index 53f3a41aa..e312aaa39 100644 --- a/sources/scripting/qetscriptapi.cpp +++ b/sources/scripting/qetscriptapi.cpp @@ -40,6 +40,7 @@ #include "../qetproject.h" #include "../qetresult.h" #include "../qetgraphicsitem/conductor.h" +#include "../qetgraphicsitem/conductortextitem.h" #include "../conductorsegment.h" #include "../qetgraphicsitem/diagramimageitem.h" @@ -3701,6 +3702,89 @@ bool QetScriptApi::setFolioBorder(int folioIndex, const QString &property, const return true; } +/** + @brief QetScriptApi::conductorDefault + One property of the conductor defaults: a folio's (what its new + conductors start from, and where "one text per potential" lives), or + with folioIndex -1, the project's, which each new folio copies. + Empty if the folio or the property does not exist. +*/ +QString QetScriptApi::conductorDefault(int folioIndex, const QString &property) const +{ + if (!m_project) return QString(); + ConductorProperties p; + if (folioIndex == -1) { + p = m_project->defaultConductorProperties(); + } else { + const QList diagrams = m_project->diagrams(); + if (folioIndex < 0 || folioIndex >= diagrams.count()) return QString(); + p = diagrams.at(folioIndex)->defaultConductorProperties; + } + if (property == QLatin1String("onetextperfolio")) + return p.m_one_text_per_folio ? QStringLiteral("true") : QStringLiteral("false"); + return conductorPropertyValue(p, property); +} + +/** + @brief QetScriptApi::setConductorDefault + Set one property of the conductor defaults, under the names + setConductorProperty() takes plus "onetextperfolio" (true/false: one + number per potential on each folio). Like the Folio properties and + Project properties dialogs, this is not on the undo stack: neither + dialog has an undo command for it. +*/ +bool QetScriptApi::setConductorDefault(int folioIndex, const QString &property, const QString &value) +{ + if (!m_project) return false; + if (m_project->isReadOnly()) { + log(QStringLiteral("qet.setConductorDefault: project is read-only")); + return false; + } + Diagram *diagram = nullptr; + if (folioIndex != -1) { + const QList diagrams = m_project->diagrams(); + if (folioIndex < 0 || folioIndex >= diagrams.count()) return false; + diagram = diagrams.at(folioIndex); + } + const ConductorProperties old_p = diagram ? diagram->defaultConductorProperties + : m_project->defaultConductorProperties(); + ConductorProperties new_p = old_p; + if (property == QLatin1String("onetextperfolio")) { + const QString v = value.toLower(); + if (v != QLatin1String("true") && v != QLatin1String("false")) { + log(QStringLiteral("qet.setConductorDefault: onetextperfolio is true or false, not '%1'").arg(value)); + return false; + } + new_p.m_one_text_per_folio = (v == QLatin1String("true")); + } else if (!setConductorPropertyValue(new_p, property, value)) { + log(QStringLiteral("qet.setConductorDefault: cannot set '%1' to '%2'; properties are onetextperfolio, %3") + .arg(property, value, conductorPropertyNames().join(QStringLiteral(", ")))); + return false; + } + if (new_p == old_p) return true; + + if (!diagram) { + m_project->setDefaultConductorProperties(new_p); + return true; + } + diagram->defaultConductorProperties = new_p; + // Show or hide the conductor texts now, as the Folio properties dialog + // does (DiagramPropertiesDialog), or an export later in this run would + // draw them as they were. + if (new_p.m_one_text_per_folio != old_p.m_one_text_per_folio) + { + const QList conductor_list = diagram->conductors(); + for (Conductor *c : conductor_list) + { + const ConductorProperties cp = c->properties(); + c->textItem()->setVisible(cp.type == ConductorProperties::Multi && cp.m_show_text); + } + for (Conductor *c : conductor_list) + c->calculateTextItemPosition(); + } + return true; +} + /** @brief QetScriptApi::elementGeometry Where an element is: x and y are its origin (what setElementPosition() diff --git a/sources/scripting/qetscriptapi.h b/sources/scripting/qetscriptapi.h index dff3225e7..35ef9c5d5 100644 --- a/sources/scripting/qetscriptapi.h +++ b/sources/scripting/qetscriptapi.h @@ -538,6 +538,11 @@ class QetScriptApi : public QObject Q_INVOKABLE QString folioBorder(int folioIndex, const QString &property) const; Q_INVOKABLE bool setFolioBorder(int folioIndex, const QString &property, const QString &value); + // -- the conductor defaults of a folio (Folio properties > Conductors), + // or with folioIndex -1, the project's defaults for new folios -- + Q_INVOKABLE QString conductorDefault(int folioIndex, const QString &property) const; + Q_INVOKABLE bool setConductorDefault(int folioIndex, const QString &property, const QString &value); + // -- title block templates: which exist, embedding one into the project -- Q_INVOKABLE QStringList titleBlockTemplates() const; Q_INVOKABLE bool embedTitleBlockTemplate(const QString &name);