Auto-break conductor

This commit is contained in:
Kellermorph
2026-08-02 15:18:57 +02:00
parent c1694f2b4f
commit ca42a2b7ff
5 changed files with 222 additions and 8 deletions
+168 -8
View File
@@ -20,11 +20,16 @@
#include "../conductorautonumerotation.h"
#include "../diagram.h"
#include "../undocommand/addgraphicsobjectcommand.h"
#include "../undocommand/deleteqgraphicsitemcommand.h"
#include "../factory/elementfactory.h"
#include "../qetapp.h"
#include "../qetdiagrameditor.h"
#include "../qetgraphicsitem/element.h"
#include "../qetgraphicsitem/conductor.h"
#include "../qetgraphicsitem/terminal.h"
#include "../qet.h"
#include <QPainterPath>
#include <limits>
/**
@brief DiagramEventAddElement::DiagramEventAddElement
@@ -248,16 +253,171 @@ void DiagramEventAddElement::addElement()
QUndoCommand *undo_object = new QUndoCommand(tr("Ajouter %1").arg(element->name()));
new AddGraphicsObjectCommand(element, m_diagram, m_element -> pos(), undo_object);
//When we search for free aligned terminal we temporally remove m_element to
//avoid any interaction with the function Element::AlignedFreeTerminals
//This is useful when an element has two (or more) terminals on opposite sides,
//because m_element is exactly at the same pos of the new element
//added to the scene so new conductor are created between terminal of the new element
//and the opposite terminal of m_element.
//When we search for free aligned terminal we temporally remove m_element to
//avoid any interaction with the function Element::AlignedFreeTerminals
//This is useful when an element has two (or more) terminals on opposite sides,
//because m_element is exactly at the same pos of the new element
//added to the scene so new conductor are created between terminal of the new element
//and the opposite terminal of m_element.
m_diagram->removeItem(m_element);
while (!element -> AlignedFreeTerminals().isEmpty() && m_diagram -> project() -> autoConductor())
//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<Terminal *> broken_endpoints;
if (m_diagram->project()->autoBreakConductor())
{
QPair <Terminal *, Terminal *> pair = element -> AlignedFreeTerminals().takeFirst();
//Track which conductors we already handled for this element
QList<Conductor *> conductors_handled;
//Track which terminals of the new element are already used (by break or other-connect)
QSet<Terminal *> used_terminals;
foreach (Terminal *t, element->terminals())
{
QPointF t_dock = t->dockConductor();
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.
//Convert dock_point to the conductor's local coordinate system,
//because c->path() is in local coordinates (generated via mapFromScene).
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));
QPointF projection;
if (QET::orthogonalProjection(local_dock, segment, &projection))
{
qreal dist = QLineF(local_dock, projection).length();
if (dist < 5.0)
{
point_on_conductor = true;
break;
}
}
}
if (!point_on_conductor)
continue;
//The terminal lies on this conductor.
//Break it: delete old conductor, create one new conductor from the
//aligned endpoint to the new terminal. The other endpoint is left
//for auto-connect to handle (e.g. connecting to the opposite terminal
//of the new element).
Terminal *c1 = c->terminal1;
Terminal *c2 = c->terminal2;
conductors_handled.append(c);
//Get scene positions for endpoint selection
QPointF c1_dock = c1->dockConductor();
QPointF c2_dock = c2->dockConductor();
//Determine which endpoint to connect based on terminal orientation.
//Terminal::orientation() already accounts for element rotation,
//so a North terminal rotated 90° returns East, etc.
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;
}
//Fallback: use nearest endpoint
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; }
}
//Delete the old conductor
DiagramContent content;
content.m_other_conductors.append(c);
new DeleteQGraphicsItemCommand(m_diagram, content, undo_object);
//Create new conductor from the aligned endpoint to the new terminal
Conductor *new_c = new Conductor(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(connect_to);
used_terminals.insert(t);
//Also connect the 'other' endpoint to preserve bent conductor segments.
//For L-shaped conductors (e.g. horizontal + vertical), the 'other' endpoint
//may be far from the new element. Find the nearest free terminal of the new
//element and create a conductor to it.
QPointF other_dock = other->dockConductor();
Terminal *other_terminal = nullptr;
qreal best_dist = std::numeric_limits<qreal>::max();
foreach (Terminal *ot, element->terminals())
{
if (used_terminals.contains(ot))
continue;
qreal dist = QLineF(ot->dockConductor(), other_dock).length();
if (dist < best_dist) {
best_dist = dist;
other_terminal = ot;
}
}
if (other_terminal) {
Conductor *new_c2 = new Conductor(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(other);
used_terminals.insert(other_terminal);
}
}
}
}
//Auto-connect: collect all aligned pairs first, then filter and process.
QList<QPair<Terminal *, Terminal *>> aligned_pairs;
if (m_diagram->project()->autoConductor())
aligned_pairs = element->AlignedFreeTerminals();
for (const QPair<Terminal *, Terminal *> &pair : aligned_pairs)
{
//Skip if the other terminal was an endpoint of a broken conductor
if (broken_endpoints.contains(pair.second))
continue;
Conductor *conductor = new Conductor(pair.first, pair.second);
new AddGraphicsObjectCommand(conductor, m_diagram, QPointF(), undo_object);