From 39b1e836bf0f6554cccf499a847e85926ed66277 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Sat, 8 Aug 2026 22:10:47 +0200 Subject: [PATCH 1/2] Follow up Auto-break conductors --- cmake/qet_compilation_vars.cmake | 3 + sources/autobreakconductor.cpp | 238 ++++++++++++++++++ sources/autobreakconductor.h | 48 ++++ sources/diagramcommands.cpp | 29 +++ sources/diagramcommands.h | 2 + .../diagramevent/diagrameventaddelement.cpp | 215 +--------------- sources/elementsmover.cpp | 11 + 7 files changed, 333 insertions(+), 213 deletions(-) create mode 100644 sources/autobreakconductor.cpp create mode 100644 sources/autobreakconductor.h diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index 9670794e3..c0daa3aca 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -298,6 +298,9 @@ set(QET_SRC_FILES ${QET_DIR}/sources/dataBase/ui/summaryquerywidget.cpp ${QET_DIR}/sources/dataBase/ui/summaryquerywidget.h + ${QET_DIR}/sources/autobreakconductor.cpp + ${QET_DIR}/sources/autobreakconductor.h + ${QET_DIR}/sources/diagramevent/diagrameventaddelement.cpp ${QET_DIR}/sources/diagramevent/diagrameventaddelement.h ${QET_DIR}/sources/diagramevent/diagrameventaddimage.cpp diff --git a/sources/autobreakconductor.cpp b/sources/autobreakconductor.cpp new file mode 100644 index 000000000..2e2966f73 --- /dev/null +++ b/sources/autobreakconductor.cpp @@ -0,0 +1,238 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#include "autobreakconductor.h" + +#include "diagram.h" +#include "qetproject.h" +#include "conductorautonumerotation.h" +#include "qetgraphicsitem/element.h" +#include "qetgraphicsitem/terminal.h" +#include "qetgraphicsitem/conductor.h" +#include "undocommand/addgraphicsobjectcommand.h" +#include "undocommand/deleteqgraphicsitemcommand.h" + +#include +#include +#include + +namespace { + +/** + @brief distanceToSegment + @param point : point to measure from + @param segment : the finite segment (not the infinite line through it) + @return the distance from @a point to the closest point actually on + @a segment. Unlike QET::orthogonalProjection(), which reports a hit + for any point on the segment's infinite extension, this clamps the + projection to the segment itself: a point collinear with a wire but + past its actual drawn end is correctly reported as far away, not "on" + the wire. +*/ +qreal distanceToSegment(const QPointF &point, const QLineF &segment) +{ + const QPointF a = segment.p1(); + const QPointF b = segment.p2(); + const QPointF ab = b - a; + const qreal len2 = QPointF::dotProduct(ab, ab); + + if (len2 <= 0.0) + return QLineF(point, a).length(); + + qreal t = QPointF::dotProduct(point - a, ab) / len2; + t = qBound(0.0, t, 1.0); + return QLineF(point, a + t * ab).length(); +} + +} // anonymous namespace + +/** + @brief autoBreakConductors + For each terminal of @a element, check if its dock point lies on an + existing conductor. If so, break the conductor and reconnect through + the element's terminal. Broken conductors from the same circuit + (sharing the same far-end endpoint) are broken together; independent + crossing conductors are left untouched. +*/ +QSet autoBreakConductors( + Diagram *diagram, + Element *element, + QUndoCommand *parent) +{ + QSet broken_endpoints; + + if (!diagram->project()->autoBreakConductor()) + return broken_endpoints; + + QList conductors_handled; + QSet used_terminals; + + foreach (Terminal *t, element->terminals()) + { + if (used_terminals.contains(t)) + continue; + + QPointF t_dock = t->dockConductor(); + + struct ConductorMatch { + Conductor *conductor; + Terminal *connect_to; + Terminal *other; + }; + QList all_matches; + + foreach (Conductor *c, diagram->conductors()) + { + if (conductors_handled.contains(c) || + c->terminal1->parentElement() == element || + c->terminal2->parentElement() == element) + continue; + + QPointF local_dock = c->mapFromScene(t_dock); + bool point_on_conductor = false; + QPainterPath path = c->path(); + for (int i = 0; i < path.elementCount() - 1; ++i) + { + const QPainterPath::Element &e1 = path.elementAt(i); + const QPainterPath::Element &e2 = path.elementAt(i + 1); + QLineF segment(QPointF(e1.x, e1.y), QPointF(e2.x, e2.y)); + if (distanceToSegment(local_dock, segment) < 5.0) + { + point_on_conductor = true; + break; + } + } + + if (!point_on_conductor) + continue; + + Terminal *c1 = c->terminal1; + Terminal *c2 = c->terminal2; + QPointF c1_dock = c1->dockConductor(); + QPointF c2_dock = c2->dockConductor(); + + Terminal *connect_to = nullptr; + Terminal *other = nullptr; + + switch (t->orientation()) { + case Qet::North: + if (c1_dock.y() < t_dock.y()) { connect_to = c1; other = c2; } + else if (c2_dock.y() < t_dock.y()) { connect_to = c2; other = c1; } + break; + case Qet::South: + if (c1_dock.y() > t_dock.y()) { connect_to = c1; other = c2; } + else if (c2_dock.y() > t_dock.y()) { connect_to = c2; other = c1; } + break; + case Qet::East: + if (c1_dock.x() > t_dock.x()) { connect_to = c1; other = c2; } + else if (c2_dock.x() > t_dock.x()) { connect_to = c2; other = c1; } + break; + case Qet::West: + if (c1_dock.x() < t_dock.x()) { connect_to = c1; other = c2; } + else if (c2_dock.x() < t_dock.x()) { connect_to = c2; other = c1; } + break; + } + + if (!connect_to) { + qreal d1 = QLineF(t_dock, c1_dock).length(); + qreal d2 = QLineF(t_dock, c2_dock).length(); + if (d1 <= d2) { connect_to = c1; other = c2; } + else { connect_to = c2; other = c1; } + } + + all_matches.append({c, connect_to, other}); + } + + if (all_matches.isEmpty()) + continue; + + QMap> groups; + for (const auto &m : all_matches) { + groups[m.other].append(m); + } + Terminal *best_other = nullptr; + int best_count = 0; + for (auto it = groups.constBegin(); it != groups.constEnd(); ++it) { + if (it.value().size() > best_count) { + best_count = it.value().size(); + best_other = it.key(); + } + } + + const QList &matches = groups[best_other]; + + used_terminals.insert(t); + + DiagramContent content; + for (const auto &m : matches) { + content.m_other_conductors.append(m.conductor); + conductors_handled.append(m.conductor); + } + new DeleteQGraphicsItemCommand(diagram, content, parent); + + for (const auto &m : matches) { + Conductor *new_c = new Conductor(m.connect_to, t); + new AddGraphicsObjectCommand(new_c, diagram, QPointF(), parent); + ConductorAutoNumerotation can(new_c, diagram, parent); + can.numerate(); + if (diagram->freezeNewConductors() || diagram->project()->isFreezeNewConductors()) + new_c->setFreezeLabel(true); + + broken_endpoints.insert(m.connect_to); + } + + QPointF other_dock = best_other->dockConductor(); + Terminal *other_terminal = nullptr; + qreal best_dist = std::numeric_limits::max(); + foreach (Terminal *ot, element->terminals()) + { + if (used_terminals.contains(ot)) + continue; + + QPointF ot_dock = ot->dockConductor(); + bool orientation_ok = false; + switch (ot->orientation()) { + case Qet::North: orientation_ok = other_dock.y() < ot_dock.y(); break; + case Qet::South: orientation_ok = other_dock.y() > ot_dock.y(); break; + case Qet::East: orientation_ok = other_dock.x() > ot_dock.x(); break; + case Qet::West: orientation_ok = other_dock.x() < ot_dock.x(); break; + } + if (!orientation_ok) + continue; + + qreal dist = QLineF(ot_dock, other_dock).length(); + if (dist < best_dist) { + best_dist = dist; + other_terminal = ot; + } + } + + if (other_terminal) { + Conductor *new_c2 = new Conductor(best_other, other_terminal); + new AddGraphicsObjectCommand(new_c2, diagram, QPointF(), parent); + ConductorAutoNumerotation can2(new_c2, diagram, parent); + can2.numerate(); + if (diagram->freezeNewConductors() || diagram->project()->isFreezeNewConductors()) + new_c2->setFreezeLabel(true); + + broken_endpoints.insert(best_other); + used_terminals.insert(other_terminal); + } + } + + return broken_endpoints; +} diff --git a/sources/autobreakconductor.h b/sources/autobreakconductor.h new file mode 100644 index 000000000..532eecd05 --- /dev/null +++ b/sources/autobreakconductor.h @@ -0,0 +1,48 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#ifndef AUTOBREAKCONDUCTOR_H +#define AUTOBREAKCONDUCTOR_H + +#include + +class Diagram; +class Element; +class Terminal; +class QUndoCommand; + +/** + @brief autoBreakConductors + For each terminal of @a element, check if its dock point lies on an + existing conductor. If so, break the conductor and reconnect through the + element's terminal. Broken conductors from the same circuit (sharing the + same far-end endpoint) are broken together; independent crossing + conductors are left untouched. + @param diagram the diagram containing the conductors + @param element the element whose terminals trigger breaks + @param parent undo command under which break/reconnect sub-commands + are created (may be nullptr, in which case no undo is + recorded) + @return set of terminals that were connected to a broken conductor's + far-end (useful for preventing duplicate auto-connect) +*/ +QSet autoBreakConductors( + Diagram *diagram, + Element *element, + QUndoCommand *parent); + +#endif // AUTOBREAKCONDUCTOR_H diff --git a/sources/diagramcommands.cpp b/sources/diagramcommands.cpp index 2922c32b1..467524fd3 100644 --- a/sources/diagramcommands.cpp +++ b/sources/diagramcommands.cpp @@ -17,7 +17,9 @@ */ #include "diagramcommands.h" +#include "autobreakconductor.h" #include "diagram.h" +#include "qetproject.h" #include "qetgraphicsitem/conductortextitem.h" #include "qetgraphicsitem/element.h" #include "qetgraphicsitem/elementtextitemgroup.h" @@ -48,6 +50,7 @@ PasteDiagramCommand::PasteDiagramCommand( Diagram *dia, const DiagramContent &c, PasteDiagramCommand::~PasteDiagramCommand() { diagram -> qgiManager().release(content.items(filter)); + delete m_break_cmd; } /** @@ -58,6 +61,10 @@ void PasteDiagramCommand::undo() { diagram -> showMe(); + //Undo auto-break before removing items, so terminals are still on scene + if (m_break_cmd) + m_break_cmd->undo(); + foreach(QGraphicsItem *item, content.items(filter)) diagram->removeItem(item); } @@ -102,6 +109,24 @@ void PasteDiagramCommand::redo() } } } + + //Auto-break conductors on first paste. Items are already on the + //scene at this point (added before this command was created). + if (diagram->project()->autoBreakConductor()) + { + m_break_cmd = new QUndoCommand(); + for (Element *e : content.m_elements) { + autoBreakConductors(diagram, e, m_break_cmd); + } + if (m_break_cmd->childCount() == 0) { + delete m_break_cmd; + m_break_cmd = nullptr; + } + else + { + m_break_cmd->redo(); + } + } } else { @@ -109,6 +134,10 @@ void PasteDiagramCommand::redo() for (QGraphicsItem *item : qgis_list) { diagram->addItem(item); } + + //Re-execute the stored break commands (items are back on scene) + if (m_break_cmd) + m_break_cmd->redo(); } const QList qgis_list = content.items(); diff --git a/sources/diagramcommands.h b/sources/diagramcommands.h index 070214b85..5c05e151b 100644 --- a/sources/diagramcommands.h +++ b/sources/diagramcommands.h @@ -53,6 +53,8 @@ class PasteDiagramCommand : public QUndoCommand { int filter; /// prevent the first call to redo() bool first_redo; + /// auto-break conductor sub-commands (created on first redo) + QUndoCommand *m_break_cmd = nullptr; }; /** diff --git a/sources/diagramevent/diagrameventaddelement.cpp b/sources/diagramevent/diagrameventaddelement.cpp index 9949c54d8..14b7cd5ed 100644 --- a/sources/diagramevent/diagrameventaddelement.cpp +++ b/sources/diagramevent/diagrameventaddelement.cpp @@ -28,37 +28,10 @@ #include "../qetgraphicsitem/conductor.h" #include "../qetgraphicsitem/terminal.h" #include "../qet.h" +#include "../autobreakconductor.h" #include #include -namespace { - /** - @brief distanceToSegment - @param point : point to measure from - @param segment : the finite segment (not the infinite line through it) - @return the distance from @a point to the closest point actually on - @a segment. Unlike QET::orthogonalProjection(), which reports a hit - for any point on the segment's infinite extension, this clamps the - projection to the segment itself: a point collinear with a wire but - past its actual drawn end is correctly reported as far away, not "on" - the wire. - */ - qreal distanceToSegment(const QPointF &point, const QLineF &segment) - { - const QPointF a = segment.p1(); - const QPointF b = segment.p2(); - const QPointF ab = b - a; - const qreal len2 = QPointF::dotProduct(ab, ab); - - if (len2 <= 0.0) - return QLineF(point, a).length(); - - qreal t = QPointF::dotProduct(point - a, ab) / len2; - t = qBound(0.0, t, 1.0); - return QLineF(point, a + t * ab).length(); - } -} - /** @brief DiagramEventAddElement::DiagramEventAddElement Defaut constructor @@ -292,191 +265,7 @@ void DiagramEventAddElement::addElement() //Auto break conductor: if a terminal of the new element lies on an existing //conductor, break the conductor and reconnect through the new element's terminal. //Track the endpoints of broken conductors so auto-connect doesn't create duplicates. - QSet broken_endpoints; - - if (m_diagram->project()->autoBreakConductor()) - { - //Track which conductors we already handled for this element - QList conductors_handled; - //Track which terminals of the new element are already used (by break or other-connect) - QSet used_terminals; - - foreach (Terminal *t, element->terminals()) - { - //Skip terminals already used by a previous break+reconnect (other_terminal) - if (used_terminals.contains(t)) - continue; - - QPointF t_dock = t->dockConductor(); - - //Collect all conductors that pass through this dock point. - struct ConductorMatch { - Conductor *conductor; - Terminal *connect_to; //endpoint "before" the dock point (based on orientation) - Terminal *other; //endpoint "after" the dock point - }; - QList all_matches; - - foreach (Conductor *c, m_diagram->conductors()) - { - //Skip conductors we already handled or connected to the new element - if (conductors_handled.contains(c) || - c->terminal1->parentElement() == element || - c->terminal2->parentElement() == element) - continue; - - //Check if dock point lies on the conductor path. Distance is - //measured to the segment itself (clamped), not the infinite - //line through it -- see distanceToSegment(). - QPointF local_dock = c->mapFromScene(t_dock); - bool point_on_conductor = false; - QPainterPath path = c->path(); - for (int i = 0; i < path.elementCount() - 1; ++i) - { - const QPainterPath::Element &e1 = path.elementAt(i); - const QPainterPath::Element &e2 = path.elementAt(i + 1); - QLineF segment(QPointF(e1.x, e1.y), QPointF(e2.x, e2.y)); - if (distanceToSegment(local_dock, segment) < 5.0) - { - point_on_conductor = true; - break; - } - } - - if (!point_on_conductor) - continue; - - Terminal *c1 = c->terminal1; - Terminal *c2 = c->terminal2; - QPointF c1_dock = c1->dockConductor(); - QPointF c2_dock = c2->dockConductor(); - - Terminal *connect_to = nullptr; - Terminal *other = nullptr; - - switch (t->orientation()) { - case Qet::North: - if (c1_dock.y() < t_dock.y()) { connect_to = c1; other = c2; } - else if (c2_dock.y() < t_dock.y()) { connect_to = c2; other = c1; } - break; - case Qet::South: - if (c1_dock.y() > t_dock.y()) { connect_to = c1; other = c2; } - else if (c2_dock.y() > t_dock.y()) { connect_to = c2; other = c1; } - break; - case Qet::East: - if (c1_dock.x() > t_dock.x()) { connect_to = c1; other = c2; } - else if (c2_dock.x() > t_dock.x()) { connect_to = c2; other = c1; } - break; - case Qet::West: - if (c1_dock.x() < t_dock.x()) { connect_to = c1; other = c2; } - else if (c2_dock.x() < t_dock.x()) { connect_to = c2; other = c1; } - break; - } - - if (!connect_to) { - qreal d1 = QLineF(t_dock, c1_dock).length(); - qreal d2 = QLineF(t_dock, c2_dock).length(); - if (d1 <= d2) { connect_to = c1; other = c2; } - else { connect_to = c2; other = c1; } - } - - all_matches.append({c, connect_to, other}); - } - - if (all_matches.isEmpty()) - continue; - - //Group matches by their "other" endpoint and find the largest group. - //This ensures that when multiple independent circuits cross at the - //same dock point, the group with the most conductors gets priority, - //not whichever happens to have the nearest connect_to endpoint. - QMap> groups; - for (const auto &m : all_matches) { - groups[m.other].append(m); - } - Terminal *best_other = nullptr; - int best_count = 0; - for (auto it = groups.constBegin(); it != groups.constEnd(); ++it) { - if (it.value().size() > best_count) { - best_count = it.value().size(); - best_other = it.key(); - } - } - - //Only break conductors in the largest group (same "other" endpoint). - //This prevents bridging independent nets: if two unrelated conductors - //cross at a dock point, only the larger group gets broken. - const QList &matches = groups[best_other]; - - //Mark terminal as used - used_terminals.insert(t); - - //Delete all matched conductors in one batch - DiagramContent content; - for (const auto &m : matches) { - content.m_other_conductors.append(m.conductor); - conductors_handled.append(m.conductor); - } - new DeleteQGraphicsItemCommand(m_diagram, content, undo_object); - - //Create new conductors from each connect_to endpoint to this terminal. - //This creates junctions at the terminal when multiple conductors - //from the same circuit pass through the dock point (e.g. left and - //right conductors going to the same terminal strip terminal). - for (const auto &m : matches) { - Conductor *new_c = new Conductor(m.connect_to, t); - new AddGraphicsObjectCommand(new_c, m_diagram, QPointF(), undo_object); - ConductorAutoNumerotation can(new_c, m_diagram, undo_object); - can.numerate(); - if (m_diagram->freezeNewConductors() || m_diagram->project()->isFreezeNewConductors()) - new_c->setFreezeLabel(true); - - broken_endpoints.insert(m.connect_to); - } - - //Connect the shared "other" endpoint to the nearest free terminal - //with matching orientation. - QPointF other_dock = best_other->dockConductor(); - Terminal *other_terminal = nullptr; - qreal best_dist = std::numeric_limits::max(); - foreach (Terminal *ot, element->terminals()) - { - if (used_terminals.contains(ot)) - continue; - - //Check that the other_dock approaches from the correct direction - //relative to the candidate terminal's orientation. - QPointF ot_dock = ot->dockConductor(); - bool orientation_ok = false; - switch (ot->orientation()) { - case Qet::North: orientation_ok = other_dock.y() < ot_dock.y(); break; - case Qet::South: orientation_ok = other_dock.y() > ot_dock.y(); break; - case Qet::East: orientation_ok = other_dock.x() > ot_dock.x(); break; - case Qet::West: orientation_ok = other_dock.x() < ot_dock.x(); break; - } - if (!orientation_ok) - continue; - - qreal dist = QLineF(ot_dock, other_dock).length(); - if (dist < best_dist) { - best_dist = dist; - other_terminal = ot; - } - } - - if (other_terminal) { - Conductor *new_c2 = new Conductor(best_other, other_terminal); - new AddGraphicsObjectCommand(new_c2, m_diagram, QPointF(), undo_object); - ConductorAutoNumerotation can2(new_c2, m_diagram, undo_object); - can2.numerate(); - if (m_diagram->freezeNewConductors() || m_diagram->project()->isFreezeNewConductors()) - new_c2->setFreezeLabel(true); - - broken_endpoints.insert(best_other); - used_terminals.insert(other_terminal); - } - } - } + QSet broken_endpoints = autoBreakConductors(m_diagram, element, undo_object); //Auto-connect: collect all aligned pairs first, then filter and process. QList> aligned_pairs; diff --git a/sources/elementsmover.cpp b/sources/elementsmover.cpp index 59233c623..f07427349 100644 --- a/sources/elementsmover.cpp +++ b/sources/elementsmover.cpp @@ -17,6 +17,7 @@ */ #include "elementsmover.h" #include "qetproject.h" +#include "autobreakconductor.h" #include "conductorautonumerotation.h" #include "diagram.h" #include "qetgraphicsitem/conductor.h" @@ -178,6 +179,16 @@ void ElementsMover::endMovement() undo_object->setText(quc->text()); } + //Auto-break conductors: for each moved element, break any conductor + //whose path passes through a terminal dock point, and reconnect through + //the element. The element is already at its final position on screen. + if (m_diagram->project()->autoBreakConductor()) + { + for (Element *e : m_moved_content.m_elements) { + autoBreakConductors(m_diagram, e, undo_object); + } + } + //There is only one element moved, and project authorize auto conductor, //we try auto connection of conductor; typedef DiagramContent dc; From d95e744494bc410cd049cf490e91a026ace5e410 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Sun, 9 Aug 2026 09:11:40 +0200 Subject: [PATCH 2/2] autoBreakConductors: share conductors_handled/used_terminals across batch When multiple elements are pasted or moved in one batch, each call to autoBreakConductors() now receives the shared state from the previous call. This prevents two elements in the same batch from independently claiming the same conductor, which would result in a double-delete on redo(). Requested by ispyisail in PR review. --- sources/autobreakconductor.cpp | 7 +++---- sources/autobreakconductor.h | 21 +++++++++++++------ sources/diagramcommands.cpp | 5 ++++- .../diagramevent/diagrameventaddelement.cpp | 5 ++++- sources/elementsmover.cpp | 5 ++++- 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/sources/autobreakconductor.cpp b/sources/autobreakconductor.cpp index 2e2966f73..944f106d8 100644 --- a/sources/autobreakconductor.cpp +++ b/sources/autobreakconductor.cpp @@ -71,16 +71,15 @@ qreal distanceToSegment(const QPointF &point, const QLineF &segment) QSet autoBreakConductors( Diagram *diagram, Element *element, - QUndoCommand *parent) + QUndoCommand *parent, + QList &conductors_handled, + QSet &used_terminals) { QSet broken_endpoints; if (!diagram->project()->autoBreakConductor()) return broken_endpoints; - QList conductors_handled; - QSet used_terminals; - foreach (Terminal *t, element->terminals()) { if (used_terminals.contains(t)) diff --git a/sources/autobreakconductor.h b/sources/autobreakconductor.h index 532eecd05..3818e6c5b 100644 --- a/sources/autobreakconductor.h +++ b/sources/autobreakconductor.h @@ -19,10 +19,12 @@ #define AUTOBREAKCONDUCTOR_H #include +#include class Diagram; class Element; class Terminal; +class Conductor; class QUndoCommand; /** @@ -32,17 +34,24 @@ class QUndoCommand; element's terminal. Broken conductors from the same circuit (sharing the same far-end endpoint) are broken together; independent crossing conductors are left untouched. - @param diagram the diagram containing the conductors - @param element the element whose terminals trigger breaks - @param parent undo command under which break/reconnect sub-commands - are created (may be nullptr, in which case no undo is - recorded) + @param diagram the diagram containing the conductors + @param element the element whose terminals trigger breaks + @param parent undo command under which break/reconnect + sub-commands are created + @param conductors_handled shared list of conductors already claimed by + a previous call in the same batch (prevents + double-processing when multiple elements are + broken in one pass) + @param used_terminals shared set of terminals already used as + connect_to or other_terminal in the same batch @return set of terminals that were connected to a broken conductor's far-end (useful for preventing duplicate auto-connect) */ QSet autoBreakConductors( Diagram *diagram, Element *element, - QUndoCommand *parent); + QUndoCommand *parent, + QList &conductors_handled, + QSet &used_terminals); #endif // AUTOBREAKCONDUCTOR_H diff --git a/sources/diagramcommands.cpp b/sources/diagramcommands.cpp index 467524fd3..ab3a6fdeb 100644 --- a/sources/diagramcommands.cpp +++ b/sources/diagramcommands.cpp @@ -115,8 +115,11 @@ void PasteDiagramCommand::redo() if (diagram->project()->autoBreakConductor()) { m_break_cmd = new QUndoCommand(); + QList conductors_handled; + QSet used_terminals; for (Element *e : content.m_elements) { - autoBreakConductors(diagram, e, m_break_cmd); + autoBreakConductors(diagram, e, m_break_cmd, + conductors_handled, used_terminals); } if (m_break_cmd->childCount() == 0) { delete m_break_cmd; diff --git a/sources/diagramevent/diagrameventaddelement.cpp b/sources/diagramevent/diagrameventaddelement.cpp index 14b7cd5ed..94a84d7dc 100644 --- a/sources/diagramevent/diagrameventaddelement.cpp +++ b/sources/diagramevent/diagrameventaddelement.cpp @@ -265,7 +265,10 @@ void DiagramEventAddElement::addElement() //Auto break conductor: if a terminal of the new element lies on an existing //conductor, break the conductor and reconnect through the new element's terminal. //Track the endpoints of broken conductors so auto-connect doesn't create duplicates. - QSet broken_endpoints = autoBreakConductors(m_diagram, element, undo_object); + QList conductors_handled; + QSet used_terminals; + QSet broken_endpoints = autoBreakConductors(m_diagram, element, undo_object, + conductors_handled, used_terminals); //Auto-connect: collect all aligned pairs first, then filter and process. QList> aligned_pairs; diff --git a/sources/elementsmover.cpp b/sources/elementsmover.cpp index f07427349..28c8c344d 100644 --- a/sources/elementsmover.cpp +++ b/sources/elementsmover.cpp @@ -184,8 +184,11 @@ void ElementsMover::endMovement() //the element. The element is already at its final position on screen. if (m_diagram->project()->autoBreakConductor()) { + QList conductors_handled; + QSet used_terminals; for (Element *e : m_moved_content.m_elements) { - autoBreakConductors(m_diagram, e, undo_object); + autoBreakConductors(m_diagram, e, undo_object, + conductors_handled, used_terminals); } }