Merge pull request #1214 from ispyisail/feature/mcp-conductor-defaults

Add a qet_edit op for a folio's wire defaults (one number per potential)
This commit is contained in:
ispyisail
2026-10-02 15:09:56 +13:00
committed by GitHub
5 changed files with 141 additions and 0 deletions
+7
View File
@@ -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
+19
View File
@@ -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 -- "
+26
View File
@@ -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):
+84
View File
@@ -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<Diagram *> 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<Diagram *> 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 *> 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()
+5
View File
@@ -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);