From ca42a2b7ff46dc018ad872ecdb91a882c9e1f4e9 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Sun, 2 Aug 2026 15:18:57 +0200 Subject: [PATCH 1/4] Auto-break conductor --- .../diagramevent/diagrameventaddelement.cpp | 176 +++++++++++++++++- sources/qetdiagrameditor.cpp | 21 +++ sources/qetdiagrameditor.h | 1 + sources/qetproject.cpp | 28 +++ sources/qetproject.h | 4 + 5 files changed, 222 insertions(+), 8 deletions(-) diff --git a/sources/diagramevent/diagrameventaddelement.cpp b/sources/diagramevent/diagrameventaddelement.cpp index 203a0d953..5410551ca 100644 --- a/sources/diagramevent/diagrameventaddelement.cpp +++ b/sources/diagramevent/diagrameventaddelement.cpp @@ -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 +#include /** @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 broken_endpoints; + + if (m_diagram->project()->autoBreakConductor()) { - QPair pair = element -> AlignedFreeTerminals().takeFirst(); + //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()) + { + 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::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> aligned_pairs; + if (m_diagram->project()->autoConductor()) + aligned_pairs = element->AlignedFreeTerminals(); + + for (const QPair &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); diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index 5b2ba6779..dd6e0a572 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -369,6 +369,21 @@ void QETDiagramEditor::setUpActions() pv->project()->setAutoConductor(ac); }); + //AutoBreakConductor + m_auto_break_conductor = new QAction (QET::Icons::Conductor, tr("Coupure automatique de conducteur(s)","Tool tip of auto break conductor"), this); + m_auto_break_conductor->setStatusTip (tr("Couper automatiquement les conducteurs existants lors du placement d'un élément", "Status tip of auto break conductor")); + m_auto_break_conductor->setCheckable (true); + { + QSettings settings; + m_auto_break_conductor->setChecked(settings.value("diagrameditor/auto_break_conductor", false).toBool()); + } + connect(m_auto_break_conductor, &QAction::triggered, [this](bool abc) { + QSettings settings; + settings.setValue("diagrameditor/auto_break_conductor", abc); + if (ProjectView *pv = currentProjectView()) + pv->project()->setAutoBreakConductor(abc); + }); + //Switch background color m_grey_background = new QAction (QET::Icons::DiagramBg, tr("Couleur de fond blanc/gris","Tool tip of white/grey background button"), this); m_grey_background -> setStatusTip (tr("Affiche la couleur de fond du folio en blanc ou en gris", "Status tip of white/grey background button")); @@ -808,6 +823,7 @@ void QETDiagramEditor::setUpToolBar() diagram_tool_bar -> addAction (m_edit_diagram_properties); diagram_tool_bar -> addAction (m_conductor_reset); diagram_tool_bar -> addAction (m_auto_conductor); + diagram_tool_bar -> addAction (m_auto_break_conductor); m_add_item_tool_bar = new QToolBar(tr("Ajouter"), this); m_add_item_tool_bar->setObjectName("adding"); @@ -1892,9 +1908,14 @@ void QETDiagramEditor::slot_updateModeActions() { m_auto_conductor -> setEnabled (true); m_auto_conductor -> setChecked (pv -> project() -> autoConductor()); + m_auto_break_conductor -> setEnabled (true); + m_auto_break_conductor -> setChecked (pv -> project() -> autoBreakConductor()); } else + { m_auto_conductor -> setDisabled(true); + m_auto_break_conductor -> setDisabled(true); + } } /** diff --git a/sources/qetdiagrameditor.h b/sources/qetdiagrameditor.h index 0114a186e..fdd3c12bb 100644 --- a/sources/qetdiagrameditor.h +++ b/sources/qetdiagrameditor.h @@ -191,6 +191,7 @@ class QETDiagramEditor : public QETMainWindow *redo, ///< Redo the latest cancelled operation *m_paste, ///< Paste clipboard content on the current diagram *m_auto_conductor, ///< Enable/Disable the use of auto conductor + *m_auto_break_conductor, ///< Enable/Disable the use of auto break conductor *conductor_default, ///< Show a dialog to edit default conductor properties *m_grey_background, ///< Switch the background color in white or grey *m_draw_grid, ///< Switch the background grid display or not diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index 3d205edfa..6d248e925 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -69,6 +69,10 @@ m_project_properties_handler{this} init(); QSettings settings; + + //Read auto break conductor default from global settings + m_auto_break_conductor = settings.value(QStringLiteral("diagrameditor/auto_break_conductor"), false).toBool(); + int size = settings.beginReadArray(QStringLiteral("diagrameditor/defaultguides")); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); @@ -924,6 +928,28 @@ void QETProject::setAutoConductor(bool ac) m_auto_conductor = ac; } +/** + @brief QETProject::autoBreakConductor + @return true if use of auto break conductor is authorized. + See also Q_PROPERTY autoBreakConductor +*/ +bool QETProject::autoBreakConductor() const +{ + return m_auto_break_conductor; +} + +/** + @brief QETProject::setAutoBreakConductor + @param abc + Enable the use of auto break conductor if true + See also Q_PROPERTY autoBreakConductor +*/ +void QETProject::setAutoBreakConductor(bool abc) +{ + if (abc != m_auto_break_conductor) + m_auto_break_conductor = abc; +} + /** @brief QETProject::autoFolioNumberingNewFolios emit Signal to add new Diagram with autonum @@ -1732,6 +1758,7 @@ void QETProject::readDefaultPropertiesXml(QDomDocument &xml_project) { m_current_conductor_autonum = conds_autonums.attribute(QStringLiteral("current_autonum")); m_freeze_new_conductors = conds_autonums.attribute(QStringLiteral("freeze_new_conductors")) == QLatin1String("true"); + m_auto_break_conductor = conds_autonums.attribute(QStringLiteral("auto_break_conductors")) == QLatin1String("true"); for (auto elmt : QET::findInDomElement(conds_autonums, QStringLiteral("conductor_autonum"))) { NumerotationContext nc; @@ -1858,6 +1885,7 @@ void QETProject::writeDefaultPropertiesXml(QDomElement &xml_element) QDomElement conductor_autonums = xml_document.createElement("conductors_autonums"); conductor_autonums.setAttribute("current_autonum", m_current_conductor_autonum); conductor_autonums.setAttribute("freeze_new_conductors", m_freeze_new_conductors ? "true" : "false"); + conductor_autonums.setAttribute("auto_break_conductors", m_auto_break_conductor ? "true" : "false"); foreach (QString key, conductorAutoNum().keys()) { QDomElement conductor_autonum = conductorAutoNum(key).toXml(xml_document, "conductor_autonum"); if (key != "" && conductorAutoNumFormula(key) != "") { diff --git a/sources/qetproject.h b/sources/qetproject.h index dfd5ab8ea..891512c43 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -90,6 +90,7 @@ class QETProject : public QObject }; Q_PROPERTY(bool autoConductor READ autoConductor WRITE setAutoConductor) + Q_PROPERTY(bool autoBreakConductor READ autoBreakConductor WRITE setAutoBreakConductor) // constructors, destructor public: @@ -181,9 +182,11 @@ class QETProject : public QObject void setFreezeNewConductors(bool); bool autoConductor () const; + bool autoBreakConductor () const; bool autoElement () const; bool autoFolio () const; void setAutoConductor (bool ac); + void setAutoBreakConductor (bool abc); void setAutoElement (bool ae); void autoFolioNumberingNewFolios (); void autoFolioNumberingSelectedFolios(int, int, const QString&); @@ -312,6 +315,7 @@ class QETProject : public QObject QHash m_element_autonum; //Title and NumContext hash QString m_current_element_autonum; bool m_auto_conductor = true; + bool m_auto_break_conductor = false; XmlElementCollection *m_elements_collection = nullptr; bool m_freeze_new_elements = false; bool m_freeze_new_conductors = false; From d850241f91725b1ce2ecee06fcf7a143c7a3446c Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Mon, 3 Aug 2026 19:17:38 +0200 Subject: [PATCH 2/4] fix --- .../diagramevent/diagrameventaddelement.cpp | 102 ++++++++++++------ 1 file changed, 71 insertions(+), 31 deletions(-) diff --git a/sources/diagramevent/diagrameventaddelement.cpp b/sources/diagramevent/diagrameventaddelement.cpp index 5410551ca..9e8648f7d 100644 --- a/sources/diagramevent/diagrameventaddelement.cpp +++ b/sources/diagramevent/diagrameventaddelement.cpp @@ -275,8 +275,20 @@ void DiagramEventAddElement::addElement() 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 @@ -286,8 +298,6 @@ void DiagramEventAddElement::addElement() 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(); @@ -311,23 +321,11 @@ void DiagramEventAddElement::addElement() 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; @@ -350,7 +348,6 @@ void DiagramEventAddElement::addElement() break; } - //Fallback: use nearest endpoint if (!connect_to) { qreal d1 = QLineF(t_dock, c1_dock).length(); qreal d2 = QLineF(t_dock, c2_dock).length(); @@ -358,27 +355,58 @@ void DiagramEventAddElement::addElement() else { connect_to = c2; other = c1; } } - //Delete the old conductor + all_matches.append({c, connect_to, other}); + } + + if (all_matches.isEmpty()) + continue; + + //Find the best match (closest connect_to endpoint). + const ConductorMatch &best = *std::min_element(all_matches.constBegin(), all_matches.constEnd(), + [&](const ConductorMatch &a, const ConductorMatch &b) { + return QLineF(t_dock, a.connect_to->dockConductor()).length() + < QLineF(t_dock, b.connect_to->dockConductor()).length(); + }); + + //Only break conductors that share the same "other" endpoint as the best match. + //This prevents bridging independent nets: if two unrelated conductors + //cross at a dock point, only the one belonging to the same circuit + //(same other endpoint) gets broken. The other is left untouched. + QList matches; + for (const auto &m : all_matches) { + if (m.other == best.other) + matches.append(m); + } + + //Mark terminal as used + used_terminals.insert(t); + + //Delete all matched conductors in one batch DiagramContent content; - content.m_other_conductors.append(c); + 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 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); + //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(connect_to); - used_terminals.insert(t); + broken_endpoints.insert(m.connect_to); + } - //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(); + //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()) @@ -386,7 +414,20 @@ void DiagramEventAddElement::addElement() if (used_terminals.contains(ot)) continue; - qreal dist = QLineF(ot->dockConductor(), other_dock).length(); + //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; @@ -394,17 +435,16 @@ void DiagramEventAddElement::addElement() } if (other_terminal) { - Conductor *new_c2 = new Conductor(other, 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(other); + broken_endpoints.insert(best.other); used_terminals.insert(other_terminal); } - } } } From 5ec49eeddae2cf868be35655b611f82b89bd58a4 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Mon, 3 Aug 2026 21:48:34 +0200 Subject: [PATCH 3/4] fix bug 2 --- .../diagramevent/diagrameventaddelement.cpp | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/sources/diagramevent/diagrameventaddelement.cpp b/sources/diagramevent/diagrameventaddelement.cpp index 9e8648f7d..e8f0f8635 100644 --- a/sources/diagramevent/diagrameventaddelement.cpp +++ b/sources/diagramevent/diagrameventaddelement.cpp @@ -361,22 +361,27 @@ void DiagramEventAddElement::addElement() if (all_matches.isEmpty()) continue; - //Find the best match (closest connect_to endpoint). - const ConductorMatch &best = *std::min_element(all_matches.constBegin(), all_matches.constEnd(), - [&](const ConductorMatch &a, const ConductorMatch &b) { - return QLineF(t_dock, a.connect_to->dockConductor()).length() - < QLineF(t_dock, b.connect_to->dockConductor()).length(); - }); - - //Only break conductors that share the same "other" endpoint as the best match. - //This prevents bridging independent nets: if two unrelated conductors - //cross at a dock point, only the one belonging to the same circuit - //(same other endpoint) gets broken. The other is left untouched. - QList matches; + //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) { - if (m.other == best.other) - matches.append(m); + 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); @@ -406,7 +411,7 @@ void DiagramEventAddElement::addElement() //Connect the shared "other" endpoint to the nearest free terminal //with matching orientation. - QPointF other_dock = best.other->dockConductor(); + QPointF other_dock = best_other->dockConductor(); Terminal *other_terminal = nullptr; qreal best_dist = std::numeric_limits::max(); foreach (Terminal *ot, element->terminals()) @@ -435,14 +440,14 @@ void DiagramEventAddElement::addElement() } if (other_terminal) { - Conductor *new_c2 = new Conductor(best.other, 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); + broken_endpoints.insert(best_other); used_terminals.insert(other_terminal); } } From 06d1d70142a8d206487a8b94ecb80f09f0463d8d Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Tue, 4 Aug 2026 16:48:52 +0200 Subject: [PATCH 4/4] bug 2 --- .../diagramevent/diagrameventaddelement.cpp | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/sources/diagramevent/diagrameventaddelement.cpp b/sources/diagramevent/diagrameventaddelement.cpp index e8f0f8635..2f018e28b 100644 --- a/sources/diagramevent/diagrameventaddelement.cpp +++ b/sources/diagramevent/diagrameventaddelement.cpp @@ -31,6 +31,34 @@ #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 @@ -297,7 +325,9 @@ void DiagramEventAddElement::addElement() c->terminal2->parentElement() == element) continue; - //Check if dock point lies on the conductor path. + //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(); @@ -306,15 +336,10 @@ void DiagramEventAddElement::addElement() 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)) + if (distanceToSegment(local_dock, segment) < 5.0) { - qreal dist = QLineF(local_dock, projection).length(); - if (dist < 5.0) - { - point_on_conductor = true; - break; - } + point_on_conductor = true; + break; } }