From 8a2d31d83502cfb4b9ebd671ab5e28f1d4eb2737 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Tue, 22 Sep 2026 11:14:59 +1200 Subject: [PATCH] Let a script group, bridge and sort a terminal strip's real terminals stripRealTerminals(strip) index, owning element, current physical position, neighbours groupTerminals(strip, indices) merge onto one physical position bridgeTerminals(strip, indices) wire together without merging sortTerminalStrip(strip) canonical physical order Each goes through the same command the terminal strip editor's own group/bridge/sort buttons push (GroupTerminalsCommand, BridgeTerminalsCommand, SortTerminalStripCommand), so a script's changes undo like the editor's. groupTerminals() replicates the editor's own receiver-selection heuristic line for line rather than picking the first terminal named: the physical position that already carries the most real terminals receives the others, not necessarily the one at index 0. Verified with a case built to distinguish the two: three terminals grouped first (one position, three real terminals), then a fourth, previously-alone terminal grouped with one of those three, named first in the call -- the alone terminal moved onto the three-terminal position, ending at four, not the other way around. bridgeTerminals() refuses through TerminalStrip::isBridgeable() itself, the same check the editor's bridge button applies, rather than re-deriving what "the same level" means. Real terminals are addressed by index into stripRealTerminals(), the strip's own order; grouping shifts later physical-position indices down, so the header says to re-list after a change that adds or removes one, the same rule already documented for texts, shapes and images. Verified end to end on four placed terminal elements: added to a strip, grouped two, refused a group of one and an out-of-range index, bridged the remaining two, sorted, undo restoring order without disturbing the grouping (sort doesn't touch it, so it shouldn't), and a bad strip index refused on all three operations. Qt 6.10.2, build clean, ctest 12/12, coherence gate clean. Co-Authored-By: Claude Sonnet 5 --- sources/scripting/qetscriptapi.cpp | 175 +++++++++++++++++++++++++++++ sources/scripting/qetscriptapi.h | 23 ++++ 2 files changed, 198 insertions(+) diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp index 8e692c8e2..a0a71eba7 100644 --- a/sources/scripting/qetscriptapi.cpp +++ b/sources/scripting/qetscriptapi.cpp @@ -38,6 +38,10 @@ #include "../qetgraphicsitem/qetshapeitem.h" #include "../TerminalStrip/UndoCommand/addterminalstripcommand.h" #include "../TerminalStrip/UndoCommand/addterminaltostripcommand.h" +#include "../TerminalStrip/UndoCommand/bridgeterminalscommand.h" +#include "../TerminalStrip/UndoCommand/groupterminalscommand.h" +#include "../TerminalStrip/UndoCommand/sortterminalstripcommand.h" +#include "../TerminalStrip/physicalterminal.h" #include "../TerminalStrip/realterminal.h" #include "../TerminalStrip/terminalstrip.h" #include "../autoNum/assignvariables.h" @@ -1668,6 +1672,177 @@ bool QetScriptApi::addTerminalToStrip(int stripIndex, int folioIndex, const QStr return real->parentStrip() == strips.at(stripIndex); } +/** + @brief QetScriptApi::stripRealTerminals + The strip's real terminals -- the wire-ends added by addTerminalToStrip() + -- one line per index: the owning element's uuid, the terminal's own + name, and which physical position (clamp) it currently sits on, by that + position's own index (so several real terminals reporting the same + physical index are already grouped together). +*/ +QStringList QetScriptApi::stripRealTerminals(int stripIndex) const +{ + QStringList list; + if (!m_project) return list; + const QVector strips = m_project->terminalStrip(); + if (stripIndex < 0 || stripIndex >= strips.count()) return list; + TerminalStrip *strip = strips.at(stripIndex); + + const QVector> physical = strip->physicalTerminal(); + const QVector> real = strip->realTerminals(); + for (int i = 0 ; i < real.count() ; ++i) + { + QSharedPointer rt = real.at(i); + QSharedPointer pt = rt->physicalTerminal(); + const int physical_index = pt ? physical.indexOf(pt) : -1; + list << QStringLiteral("%1: %2 terminal '%3', physical position %4 (%5 terminal(s) there)") + .arg(i) + .arg(rt->element() ? rt->element()->uuid().toString() : QStringLiteral("?")) + .arg(rt->label()) + .arg(physical_index) + .arg(pt ? pt->realTerminalCount() : 0); + } + return list; +} + +namespace { +/** + Resolve a list of indices into stripRealTerminals() to the RealTerminal + objects groupTerminals()/bridgeTerminals() need, or an empty (and + therefore refusable) list if any index is out of range or the list has + fewer than the two terminals either operation requires. +*/ +QVector> resolveRealTerminals( + TerminalStrip *strip, const QVariantList &indices, const QString &caller, + QetScriptApi *api) +{ + QVector> out; + const QVector> all = strip->realTerminals(); + if (indices.count() < 2) { + api->log(QStringLiteral("qet.%1: at least two real terminals are required").arg(caller)); + return {}; + } + for (const QVariant &v : indices) { + bool ok = false; + const int i = v.toInt(&ok); + if (!ok || i < 0 || i >= all.count()) { + api->log(QStringLiteral("qet.%1: %2 is not a valid real terminal index (strip has %3)") + .arg(caller, v.toString()).arg(all.count())); + return {}; + } + out << all.at(i); + } + return out; +} +} // namespace + +/** + @brief QetScriptApi::groupTerminals + Merge several real terminals onto one physical position, through + GroupTerminalsCommand exactly as the terminal strip editor's "group" + button does -- including which position receives the others: the one + among those named that already carries the most real terminals, the + same heuristic the editor applies, not necessarily the first one given. +*/ +bool QetScriptApi::groupTerminals(int stripIndex, const QVariantList &realTerminalIndices) +{ + if (!m_project) return false; + const QString caller = QStringLiteral("groupTerminals"); + if (m_project->isReadOnly()) { + log(QStringLiteral("qet.%1: project is read-only").arg(caller)); + return false; + } + const QVector strips = m_project->terminalStrip(); + if (stripIndex < 0 || stripIndex >= strips.count()) { + log(QStringLiteral("qet.%1: no strip at index %2").arg(caller).arg(stripIndex)); + return false; + } + TerminalStrip *strip = strips.at(stripIndex); + const QVector> chosen = resolveRealTerminals(strip, realTerminalIndices, caller, this); + if (chosen.isEmpty()) return false; + + QSharedPointer receiver = chosen.first()->physicalTerminal(); + int best_count = 0; + for (const QSharedPointer &rt : chosen) { + QSharedPointer pt = rt->physicalTerminal(); + const int count = pt ? pt->realTerminalCount() : 0; + if (count > 1 && count > best_count) { + best_count = count; + receiver = pt; + } + } + if (!receiver) { + log(QStringLiteral("qet.%1: no physical position to receive the group").arg(caller)); + return false; + } + + QVector> to_group = chosen; + for (const QSharedPointer &rt : receiver->realTerminals()) { + to_group.removeOne(rt); + } + if (to_group.isEmpty()) { + log(QStringLiteral("qet.%1: every named terminal is already on the receiving position").arg(caller)); + return true; + } + const int before = strip->physicalTerminalCount(); + m_project->undoStack()->push(new GroupTerminalsCommand(strip, receiver, to_group)); + return strip->physicalTerminalCount() < before; +} + +/** + @brief QetScriptApi::bridgeTerminals + Wire several real terminals together electrically, through + BridgeTerminalsCommand as the editor's "bridge" button does. Refused, + via TerminalStrip::isBridgeable(), when they are not all at the same + level -- the editor's own check, not a rule reimplemented here. +*/ +bool QetScriptApi::bridgeTerminals(int stripIndex, const QVariantList &realTerminalIndices) +{ + if (!m_project) return false; + const QString caller = QStringLiteral("bridgeTerminals"); + if (m_project->isReadOnly()) { + log(QStringLiteral("qet.%1: project is read-only").arg(caller)); + return false; + } + const QVector strips = m_project->terminalStrip(); + if (stripIndex < 0 || stripIndex >= strips.count()) { + log(QStringLiteral("qet.%1: no strip at index %2").arg(caller).arg(stripIndex)); + return false; + } + TerminalStrip *strip = strips.at(stripIndex); + const QVector> chosen = resolveRealTerminals(strip, realTerminalIndices, caller, this); + if (chosen.isEmpty()) return false; + + if (!strip->isBridgeable(chosen)) { + log(QStringLiteral("qet.%1: these terminals cannot be bridged -- they are not all at the same level") + .arg(caller)); + return false; + } + m_project->undoStack()->push(new BridgeTerminalsCommand(strip, chosen)); + return true; +} + +/** + @brief QetScriptApi::sortTerminalStrip + Reorder a strip's physical positions into the canonical order the + editor's own "sort" button computes, through SortTerminalStripCommand. +*/ +bool QetScriptApi::sortTerminalStrip(int stripIndex) +{ + if (!m_project) return false; + if (m_project->isReadOnly()) { + log(QStringLiteral("qet.sortTerminalStrip: project is read-only")); + return false; + } + const QVector strips = m_project->terminalStrip(); + if (stripIndex < 0 || stripIndex >= strips.count()) { + log(QStringLiteral("qet.sortTerminalStrip: no strip at index %1").arg(stripIndex)); + return false; + } + m_project->undoStack()->push(new SortTerminalStripCommand(strips.at(stripIndex))); + return true; +} + namespace { QHash autoNumTable(QETProject *project, const QString &kind, bool *ok) { diff --git a/sources/scripting/qetscriptapi.h b/sources/scripting/qetscriptapi.h index 695bbde35..d582a72a6 100644 --- a/sources/scripting/qetscriptapi.h +++ b/sources/scripting/qetscriptapi.h @@ -168,6 +168,25 @@ class DynamicElementTextItem; re-sorted) and does shift when one is removed. Only elements whose link type is "terminal" can be added, the same restriction the editor enforces by construction. + + stripRealTerminals() lists the strip's real terminals -- the actual + wire-ends added by addTerminalToStrip(), one per index -- which + physical position (clamp) each currently sits on and how many + neighbours share it, since that is what groupTerminals() and + bridgeTerminals() address by index into. + + groupTerminals() merges several real terminals onto one physical + position, choosing the receiving position the same way the terminal + strip editor's own "group" button does: the position among the ones + named that already carries the most real terminals, not necessarily + the first one given -- a script asking to group indices [0, 1] is not + guaranteed index 0's position is where they end up. bridgeTerminals() + wires several real terminals together electrically without merging + their positions, refused (TerminalStrip::isBridgeable()) when they + are not all at the same level -- the same check the editor's bridge + button applies, not a rule reimplemented here. sortTerminalStrip() + reorders the strip's physical positions into the canonical order the + editor's own sort button computes. - @b Auto-numbering: define a named numbering context of kind "conductor", "element" or "folio", built from parts written "type[:value[:increase]]" -- types are the ones the auto-numbering @@ -402,6 +421,10 @@ class QetScriptApi : public QObject Q_INVOKABLE bool removeTerminalStrip(int stripIndex); Q_INVOKABLE bool addTerminalToStrip(int stripIndex, int folioIndex, const QString &elementUuid); + Q_INVOKABLE QStringList stripRealTerminals(int stripIndex) const; + Q_INVOKABLE bool groupTerminals(int stripIndex, const QVariantList &realTerminalIndices); + Q_INVOKABLE bool bridgeTerminals(int stripIndex, const QVariantList &realTerminalIndices); + Q_INVOKABLE bool sortTerminalStrip(int stripIndex); // -- auto-numbering contexts (conductor, element, folio) -- Q_INVOKABLE QStringList autoNums(const QString &kind) const;