From ea1f65107ee9059cfbcd468fbcf2976265c823bc Mon Sep 17 00:00:00 2001 From: ispyisail Date: Tue, 22 Sep 2026 14:48:17 +1200 Subject: [PATCH] Add manual conductor routing to the scripting API Conductor::moveSegment(index, dx, dy) is the same primitive handlerMouseMoveEvent()/handlerMouseReleaseEvent() apply on a drag -- move both axes on the target segment (each of ConductorSegment's moveX()/moveY() silently no-ops on the wrong axis or a static, terminal-anchored segment), recompute the path, and push one ChangeConductorCommand undo step via the existing saveProfile(). Caught while writing the first test for it: moveSegment() never set modified_path, so Conductor::toXml() skipped writing children and a manually rerouted conductor silently reverted to auto-routing on the very next save -- the change took effect in the running scene but never reached disk. Fixed by setting the flag, the same as every other path-modifying call site already does. qet.conductorSegments() lists a conductor's segments (endpoints in scene coordinates, orientation, static/movable) so a script can find the index it wants; qet.moveConductorSegment() applies the move and refuses a static segment or an out-of-range index. Co-Authored-By: Claude Sonnet 5 --- sources/qetgraphicsitem/conductor.cpp | 28 ++++++++++ sources/qetgraphicsitem/conductor.h | 1 + sources/scripting/qetscriptapi.cpp | 79 +++++++++++++++++++++++++++ sources/scripting/qetscriptapi.h | 8 +++ 4 files changed, 116 insertions(+) diff --git a/sources/qetgraphicsitem/conductor.cpp b/sources/qetgraphicsitem/conductor.cpp index 8b2d39666..f97fb97ec 100644 --- a/sources/qetgraphicsitem/conductor.cpp +++ b/sources/qetgraphicsitem/conductor.cpp @@ -1310,6 +1310,34 @@ const QList Conductor::segmentsList() const return(segments_vector); } +/** + @brief Conductor::moveSegment + Move one segment of this conductor by (dx, dy), the same primitive + handlerMouseMoveEvent()/handlerMouseReleaseEvent() apply on a manual + drag -- moveX()/moveY() each silently no-op on the wrong axis or a + static (terminal-anchored) segment, so both are always called and + whichever applies takes effect. Unlike a drag this commits the whole + move as a single undo step. + @param index a segmentsList() index + @param dx @param dy the movement, in the diagram's own coordinates + @return false if index is out of range +*/ +bool Conductor::moveSegment(int index, qreal dx, qreal dy) +{ + const QList segs = segmentsList(); + if (index < 0 || index >= segs.count()) return false; + + before_mov_text_pos_ = m_text_item->pos(); + ConductorSegment *seg = segs.at(index); + seg->moveX(dx); + seg->moveY(dy); + modified_path = true; + segmentsToPath(); + calculateTextItemPosition(); + saveProfile(); + return true; +} + /** @brief Conductor::length @return the length of this conductor diff --git a/sources/qetgraphicsitem/conductor.h b/sources/qetgraphicsitem/conductor.h index 63857e068..d1ecd6e43 100644 --- a/sources/qetgraphicsitem/conductor.h +++ b/sources/qetgraphicsitem/conductor.h @@ -114,6 +114,7 @@ class Conductor : public QGraphicsObject public: QVector handlerPoints() const; const QList segmentsList() const; + bool moveSegment(int index, qreal dx, qreal dy); void setPropertyToPotential( const ConductorProperties &property, diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp index a60aabdb1..074d1e83c 100644 --- a/sources/scripting/qetscriptapi.cpp +++ b/sources/scripting/qetscriptapi.cpp @@ -37,6 +37,7 @@ #include "../qetproject.h" #include "../qetresult.h" #include "../qetgraphicsitem/conductor.h" +#include "../conductorsegment.h" #include "../qetgraphicsitem/diagramimageitem.h" #include "../qetgraphicsitem/dynamicelementtextitem.h" #include "../qetgraphicsitem/independenttextitem.h" @@ -914,6 +915,84 @@ bool QetScriptApi::setConductorProperty(int folioIndex, const QString &elementUu return true; } +/** + @brief QetScriptApi::conductorSegments + List the drawn path of the conductor on a terminal, one line per + segment: "index: (x1,y1)-(x2,y2) horizontal|vertical static|movable". + static marks a segment anchored to a terminal (moveConductorSegment() + on it is a no-op, the same as dragging its handle would be -- there is + no handle on it in the GUI). Points are in scene coordinates, matching + element_geometry(). +*/ +QStringList QetScriptApi::conductorSegments(int folioIndex, const QString &elementUuid, + int terminalIndex) const +{ + // const_cast: findConductor logs, and log() writes to stderr, which is + // not a const operation on this object. The lookup itself changes + // nothing. + auto *self = const_cast(this); + Conductor *conductor = self->findConductor(folioIndex, elementUuid, terminalIndex, + QStringLiteral("conductorSegments")); + if (!conductor) return {}; + + QStringList result; + const QList segs = conductor->segmentsList(); + for (int i = 0; i < segs.count(); ++i) { + ConductorSegment *seg = segs.at(i); + const QPointF p1 = conductor->mapToScene(seg->firstPoint()); + const QPointF p2 = conductor->mapToScene(seg->secondPoint()); + result << QStringLiteral("%1: (%2,%3)-(%4,%5) %6 %7") + .arg(i) + .arg(p1.x()).arg(p1.y()).arg(p2.x()).arg(p2.y()) + .arg(seg->isHorizontal() ? QStringLiteral("horizontal") : QStringLiteral("vertical"), + seg->isStatic() ? QStringLiteral("static") : QStringLiteral("movable")); + } + return result; +} + +/** + @brief QetScriptApi::moveConductorSegment + Move one segment of the conductor on a terminal by (dx, dy) and push + one undo step for the whole move -- Conductor::moveSegment(), the same + primitive a manual handle drag applies. A segment only moves + perpendicular to its own direction, the same as dragging its handle: + dx moves a vertical segment, dy moves a horizontal one, and the other + of the pair is ignored (ConductorSegment::moveX()/moveY() each silently + no-op on the wrong axis) -- check conductorSegments() for which one + applies before calling this. dx/dy are in scene coordinates; a + translation-only item (every conductor) makes a scene-space delta equal + to a local one, so no conversion is needed. A static segment or an + out-of-range index is refused. +*/ +bool QetScriptApi::moveConductorSegment(int folioIndex, const QString &elementUuid, + int terminalIndex, int segmentIndex, + double dx, double dy) +{ + if (!m_project) return false; + if (m_project->isReadOnly()) { + log(QStringLiteral("qet.moveConductorSegment: project is read-only")); + return false; + } + Conductor *conductor = findConductor(folioIndex, elementUuid, terminalIndex, + QStringLiteral("moveConductorSegment")); + if (!conductor) return false; + + const QList segs = conductor->segmentsList(); + if (segmentIndex < 0 || segmentIndex >= segs.count()) { + log(QStringLiteral("qet.moveConductorSegment: terminal %1 of %2 has %3 " + "segment(s), no index %4") + .arg(terminalIndex).arg(elementUuid).arg(segs.count()).arg(segmentIndex)); + return false; + } + if (segs.at(segmentIndex)->isStatic()) { + log(QStringLiteral("qet.moveConductorSegment: segment %1 is anchored to a " + "terminal and cannot be moved").arg(segmentIndex)); + return false; + } + + return conductor->moveSegment(segmentIndex, dx, dy); +} + QString QetScriptApi::elementLinkType(int folioIndex, const QString &elementUuid) const { Element *element = findElement(folioIndex, elementUuid); diff --git a/sources/scripting/qetscriptapi.h b/sources/scripting/qetscriptapi.h index ae3303259..78ad1e5c7 100644 --- a/sources/scripting/qetscriptapi.h +++ b/sources/scripting/qetscriptapi.h @@ -402,6 +402,14 @@ class QetScriptApi : public QObject int terminalIndex, const QString &property, const QString &value); + // -- a conductor's own drawn path, not the whole potential's + // properties above -- one conductor only, addressed the same way -- + Q_INVOKABLE QStringList conductorSegments(int folioIndex, const QString &elementUuid, + int terminalIndex) const; + Q_INVOKABLE bool moveConductorSegment(int folioIndex, const QString &elementUuid, + int terminalIndex, int segmentIndex, + double dx, double dy); + // -- cross-references: master/slave and report links -- Q_INVOKABLE QString elementLinkType(int folioIndex, const QString &elementUuid) const; Q_INVOKABLE QStringList linkedElements(int folioIndex, const QString &elementUuid) const;