Merge pull request #1106 from ispyisail/feat/script-conductor-uuid

Add scripting calls to list wires by uuid and find their ends
This commit is contained in:
Laurent Trinques
2026-09-28 20:10:53 +02:00
committed by GitHub
4 changed files with 176 additions and 8 deletions
+54 -8
View File
@@ -836,6 +836,18 @@ bool QetScriptApi::addConductor(int folioIndex,
return t1->isLinkedTo(t2);
}
namespace {
/// "{element uuid} terminal N", the form conductors() prints an end in and
/// the conductor calls take as element uuid + terminal index.
QString describeEnd(Terminal *t)
{
if (!t || !t->parentElement()) return QStringLiteral("?");
return QStringLiteral("%1 terminal %2")
.arg(t->parentElement()->uuid().toString())
.arg(t->parentElement()->terminals().indexOf(t));
}
} // namespace
/**
@brief QetScriptApi::conductors
One line per conductor on the folio: which terminals it joins and its
@@ -851,23 +863,57 @@ QStringList QetScriptApi::conductors(int folioIndex) const
const QList<Diagram *> diagrams = m_project->diagrams();
if (folioIndex < 0 || folioIndex >= diagrams.count()) return list;
auto describe = [](Terminal *t) -> QString {
if (!t || !t->parentElement()) return QStringLiteral("?");
return QStringLiteral("%1 terminal %2")
.arg(t->parentElement()->uuid().toString())
.arg(t->parentElement()->terminals().indexOf(t));
};
DiagramContent content(diagrams.at(folioIndex), false);
const QList<Conductor *> all = content.conductors(DiagramContent::AnyConductor);
for (Conductor *c : all)
{
list << QStringLiteral("%1 -- %2 : num='%3'")
.arg(describe(c->terminal1), describe(c->terminal2), c->properties().text);
.arg(describeEnd(c->terminal1), describeEnd(c->terminal2), c->properties().text);
}
return list;
}
/**
@brief QetScriptApi::conductorUuids
The uuid of every conductor on the folio, in the order conductors()
lists them.
*/
QStringList QetScriptApi::conductorUuids(int folioIndex) const
{
QStringList list;
if (!m_project) return list;
const QList<Diagram *> diagrams = m_project->diagrams();
if (folioIndex < 0 || folioIndex >= diagrams.count()) return list;
DiagramContent content(diagrams.at(folioIndex), false);
for (Conductor *c : content.conductors(DiagramContent::AnyConductor))
list << c->uuid().toString();
return list;
}
/**
@brief QetScriptApi::conductorEnds
The two ends of the conductor carrying @p uuid, each as
"{element uuid} terminal N" -- the element uuid and terminal index the
conductor calls take -- or an empty list if the folio has no such
conductor. A uuid names one conductor even where two meet at a terminal,
which an element uuid + terminal index cannot.
*/
QStringList QetScriptApi::conductorEnds(int folioIndex, const QString &uuid) const
{
if (!m_project) return {};
const QList<Diagram *> diagrams = m_project->diagrams();
if (folioIndex < 0 || folioIndex >= diagrams.count()) return {};
const QUuid wanted(uuid);
if (wanted.isNull()) return {};
DiagramContent content(diagrams.at(folioIndex), false);
for (Conductor *c : content.conductors(DiagramContent::AnyConductor))
if (c->uuid() == wanted)
return {describeEnd(c->terminal1), describeEnd(c->terminal2)};
return {};
}
QString QetScriptApi::conductorProperty(int folioIndex, const QString &elementUuid,
int terminalIndex, const QString &property) const
{
+2
View File
@@ -396,6 +396,8 @@ class QetScriptApi : public QObject
// -- conductor properties, applied to the whole potential --
Q_INVOKABLE QStringList conductors(int folioIndex) const;
Q_INVOKABLE QStringList conductorUuids(int folioIndex) const;
Q_INVOKABLE QStringList conductorEnds(int folioIndex, const QString &uuid) const;
Q_INVOKABLE QString conductorProperty(int folioIndex, const QString &elementUuid,
int terminalIndex, const QString &property) const;
Q_INVOKABLE bool setConductorProperty(int folioIndex, const QString &elementUuid,
+16
View File
@@ -329,6 +329,22 @@ target_link_libraries(tst_conductorselfretrace PRIVATE Qt::Test)
target_compile_definitions(tst_conductorselfretrace PRIVATE
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
# qet.conductorUuids() / qet.conductorEnds(): a script lists a folio's
# conductors by uuid and finds each one's two ends. Runs a script through
# the real binary's --run on fixtures/qet_bug_repro_resaved.qet, so only
# where --run exists: a build without the Qt Qml module has no scripting
# (see QET_HAS_SCRIPTING in the top-level CMakeLists.txt).
if(QET_HAS_SCRIPTING)
add_executable(
tst_scriptconductoruuid
tst_scriptconductoruuid.cpp)
add_test(NAME tst_scriptconductoruuid COMMAND tst_scriptconductoruuid)
add_dependencies(tst_scriptconductoruuid qelectrotech)
target_link_libraries(tst_scriptconductoruuid PRIVATE Qt::Test)
target_compile_definitions(tst_scriptconductoruuid PRIVATE
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
endif()
# A symbol saved without a uuid gets the same one on every load, and
# inserting a folio does not change it. Runs the real binary's --resave on
# fixtures/qet_bug_repro_resaved.qet with the symbols' uuids stripped.
+104
View File
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QtTest>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QProcessEnvironment>
#include <QTemporaryDir>
#include <QUuid>
// qet.conductorUuids(folio) and qet.conductorEnds(folio, uuid): a script can
// list a folio's conductors by uuid and find where each one runs, in the
// "{element} terminal N" form conductors() prints and the conductor calls
// take. Runs a script through the real binary's --run.
class tst_scriptconductoruuid : public QObject
{
Q_OBJECT
QTemporaryDir m_dir;
// Run @p script on the fixture in a sandbox of its own and return the
// JSON object it logged.
QJsonObject run(const QString &script)
{
const QString path = m_dir.filePath(QStringLiteral("probe.js"));
const QString home = m_dir.filePath(QStringLiteral("home"));
QDir().mkpath(home);
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) return {};
f.write(script.toUtf8());
f.close();
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
env.insert(QStringLiteral("HOME"), home);
env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config"));
env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data"));
QProcess proc;
proc.setProcessEnvironment(env);
proc.start(QStringLiteral(QET_TEST_BINARY_PATH),
{QStringLiteral("--run"), path,
QFINDTESTDATA("fixtures/qet_bug_repro_resaved.qet")});
if (!proc.waitForFinished(60000)) return {};
const QString out = QString::fromUtf8(proc.readAllStandardOutput()
+ proc.readAllStandardError());
const QString mark = QStringLiteral("PROBE ");
for (const QString &line : out.split(QLatin1Char('\n'))) {
const int i = line.indexOf(mark);
if (i >= 0)
return QJsonDocument::fromJson(line.mid(i + mark.size()).toUtf8()).object();
}
return {};
}
private slots:
void initTestCase()
{
QVERIFY(m_dir.isValid());
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
}
void uuidsAndEndsMatchConductors()
{
const QJsonObject r = run(QStringLiteral(
"var uuids = qet.conductorUuids(0);\n"
"var ends = uuids.map(function (u) { return qet.conductorEnds(0, u); });\n"
"qet.log('PROBE ' + JSON.stringify({lines: qet.conductors(0), uuids: uuids, ends: ends,\n"
" unknown: qet.conductorEnds(0, '{00000000-0000-0000-0000-000000000001}'),\n"
" junk: qet.conductorEnds(0, 'not a uuid'),\n"
" badFolio: qet.conductorUuids(99)}));\n"));
QVERIFY2(!r.isEmpty(), "the script logged nothing");
const QJsonArray lines = r.value(QStringLiteral("lines")).toArray();
const QJsonArray uuids = r.value(QStringLiteral("uuids")).toArray();
const QJsonArray ends = r.value(QStringLiteral("ends")).toArray();
QCOMPARE(lines.size(), 7); // the fixture's conductors
QCOMPARE(uuids.size(), lines.size());
QSet<QString> distinct;
for (int i = 0; i < uuids.size(); ++i) {
const QString u = uuids.at(i).toString();
QVERIFY2(!QUuid(u).isNull(), qPrintable(u));
distinct.insert(u);
// same order as conductors(), and the same two ends it prints
const QJsonArray e = ends.at(i).toArray();
QCOMPARE(e.size(), 2);
const QString expected = e.at(0).toString() + QStringLiteral(" -- ")
+ e.at(1).toString() + QStringLiteral(" : ");
QVERIFY2(lines.at(i).toString().startsWith(expected),
qPrintable(lines.at(i).toString() + QStringLiteral(" | ") + expected));
}
QCOMPARE(distinct.size(), uuids.size());
QVERIFY(r.value(QStringLiteral("unknown")).toArray().isEmpty());
QVERIFY(r.value(QStringLiteral("junk")).toArray().isEmpty());
QVERIFY(r.value(QStringLiteral("badFolio")).toArray().isEmpty());
}
};
QTEST_APPLESS_MAIN(tst_scriptconductoruuid)
#include "tst_scriptconductoruuid.moc"