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;