diff --git a/sources/qetgraphicsitem/conductor.cpp b/sources/qetgraphicsitem/conductor.cpp index aab4465d3..c51c4f8ce 100644 --- a/sources/qetgraphicsitem/conductor.cpp +++ b/sources/qetgraphicsitem/conductor.cpp @@ -2107,16 +2107,58 @@ QPainterPath Conductor::paintedPath() const } /** - @return la liste des positions des jonctions avec d'autres conducteurs + @return true if \a scene_point, where this conductor bends with + \a bend_type, lies on one of the segments of \a c and \a c does not + bend the same way at the same point. +*/ +bool Conductor::bendMakesJunction(const Conductor *c, const QPointF &scene_point, + Qt::Corner bend_type) const +{ + // exprime le point dans les coordonnees de l'autre conducteur + const QPointF conductor_point = c -> mapFromScene(scene_point); + bool on_conductor = false; + for (ConductorSegment *segment : c -> segmentsList()) + { + if (isContained(conductor_point, segment -> firstPoint(), segment -> secondPoint())) + { + on_conductor = true; + break; + } + } + if (!on_conductor) + return false; + + // ce point commun ne doit pas etre une bifurcation identique a celle-ci + for (const ConductorBend &cb : c -> bends()) + { + if (cb.first == conductor_point && cb.second == bend_type) + return false; + } + return true; +} + +/** + @return the positions, in this conductor's coordinates, of the junction + dots this conductor draws: each of its bends that lies on another + conductor of the same potential, unless that conductor bends the same + way at the same point. */ QList Conductor::junctions() const { QList junctions_list; - // pour qu'il y ait des jonctions, il doit y avoir d'autres conducteurs et des bifurcations - QList other_conductors = relatedConductors(this); QList bends_list = bends(); - if (other_conductors.isEmpty() || bends_list.isEmpty()) { + if (bends_list.isEmpty()) { + return(junctions_list); + } + + // Every conductor of the potential on this folio, not only those on + // this conductor's own terminals: a bend can lie on a conductor it + // shares no terminal with, e.g. when the horizontal parts of a chain + // of conductors are dragged over each other (issue #1280). + const QList other_conductors = + const_cast(this)->relatedPotentialConductors(false).values(); + if (other_conductors.isEmpty()) { return(junctions_list); } @@ -2139,37 +2181,18 @@ QList Conductor::junctions() const // si le point n'est pas une bifurcation, il ne peut etre une jonction (enfin pas au niveau de ce conducteur) if (!is_bend) continue; - bool is_junction = false; QPointF scene_point = mapToScene(point); - foreach(Conductor *c, other_conductors) + + bool is_junction = false; + for (Conductor *c : other_conductors) { - // exprime le point dans les coordonnees de l'autre conducteur - QPointF conductor_point = c -> mapFromScene(scene_point); - // recupere les segments de l'autre conducteur - QList c_segments = c -> segmentsList(); - if (c_segments.isEmpty()) - continue; - // parcoure les segments a la recherche d'un point commun - for (int j = 0 ; j < c_segments.count() ; ++ j) + if (bendMakesJunction(c, scene_point, current_bend_type)) { - ConductorSegment *segment = c_segments[j]; - // un point commun a ete trouve sur ce segment - if (isContained(conductor_point, segment -> firstPoint(), segment -> secondPoint())) - { - is_junction = true; - // ce point commun ne doit pas etre une bifurcation identique a celle-ci - QList other_conductor_bends = c -> bends(); - foreach(ConductorBend cb, other_conductor_bends) - { - if (cb.first == conductor_point && cb.second == current_bend_type) - { - is_junction = false; - } - } - } - if (is_junction) junctions_list << point; + is_junction = true; + break; } } + if (is_junction) junctions_list << point; } return(junctions_list); } diff --git a/sources/qetgraphicsitem/conductor.h b/sources/qetgraphicsitem/conductor.h index feff48004..387fc9d20 100644 --- a/sources/qetgraphicsitem/conductor.h +++ b/sources/qetgraphicsitem/conductor.h @@ -237,6 +237,7 @@ class Conductor : public QGraphicsObject uint segmentsCount(QET::ConductorSegmentType = QET::Both) const; QList segmentsToPoints() const; QList bends() const; + bool bendMakesJunction(const Conductor *, const QPointF &, Qt::Corner) const; void pointsToSegments(const QList&); Qt::Corner currentPathType() const; diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 20b257ca1..d845df4fd 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -441,6 +441,18 @@ target_link_libraries(tst_potentialtextcarrier PRIVATE Qt::Test) target_compile_definitions(tst_potentialtextcarrier PRIVATE "QET_TEST_BINARY_PATH=\"$\"") +# Junction dots: a wire's corner that lies on another wire of the same +# potential gets a dot, even when the two share no terminal (issue #1280). +# Exports fixtures/junction_dot_shared_potential.qet through the real binary. +add_executable( + tst_junctiondots + tst_junctiondots.cpp) +add_test(NAME tst_junctiondots COMMAND tst_junctiondots) +add_dependencies(tst_junctiondots qelectrotech) +target_link_libraries(tst_junctiondots PRIVATE Qt::Test) +target_compile_definitions(tst_junctiondots PRIVATE + "QET_TEST_BINARY_PATH=\"$\"") + # With SOURCE_DATE_EPOCH set, --export-pdf writes the same bytes for the # same project in every run. Exports examples/741.qet several times, in # separate processes. diff --git a/tests/qttest/fixtures/junction_dot_shared_potential.qet b/tests/qttest/fixtures/junction_dot_shared_potential.qet new file mode 100644 index 000000000..dbe55f8c8 --- /dev/null +++ b/tests/qttest/fixtures/junction_dot_shared_potential.qet @@ -0,0 +1,354 @@ + + + 10/4/26 + 04-10-2026 + 2026-10-04 + skeleton + /tmp/qet-mcp-new-jk9lz64u/skeleton.qet + 10:28 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 14 + + + 11 + + + + label + + + + + + + + + + + + + 14 + + + 11 + + + + label + + + + + + + + + + + + + 14 + + + 11 + + + + label + + + + + + + + + + + + + 14 + + + 11 + + + + label + + + + + + + + + + + + + + + + + + + + + Elements importats + Zavedené prvky + Importerede elementer + Importierte Elemente + Εισηγμένα στοιχεία + Imported elements + Elementos importados + Éléments importés + Uvezeni elementi + Elementi importati + インバートされた要素 + 전기 심볼 요소 + 전기 심볼 요소 + Elementen geïmporteerd + Elementen geïmporteerd + Elementy importowane + Elementos importados + Elementos importados + Elemente importate + Импортированные элементы + Uvoženi elementi + Importerade element + İthal öğeler + Імпортовані елементи + 导入元件 + + + + كهربائي + Elèctric + Elektrotechnika + Elektrisk + Elektrik + Ηλεκτρικά + Electric + Eléctrica + Sähkö + Electrique + Električno + Elektromos + Elettrica + 電気 + 전기 + Цахилгаан + Elektrisk + Elektrotechniek + Elektrisch + Elektrotechnika + Elétrico + Elétrica + Electric + Электротехника + Elektrické + Elektrika + Електрика + El + Elektrik + Електрика + 10_电气 + 电气工程 + + + + متعدد الأقطاب + Multifilar + Vícežilový + Flere ledere + Allpolig + Πολυγραμμικό + All-pole + Multifilar + Yleisnapa + Multifilaire + Univerzalni + Többvonalas szimbólumok + Multifilare + 複線 + 다-극 + Бүх туйл + Alle poler + Veel polig + All-pole + Schematy wieloliniowe + Multipolar + Multifilar + Bipolar + Многополюсные + Všepólové + Vsi poli + Свe-полни + Allpolig + Çok kutuplu + Багатополюсні + 10_全极 + + + + المرحّلات والموصّلات وجهات الاتصال + Relés, contactors i contactes + Relé, stykače a kontakty + Relæer, kontaktorer og kontakter + Relais, Schütze und Kontakte + Ηλεκτρονόμοι και επαφές + Relays, contactors and contacts + Relevadores, contactores y contactos + Releet, kontaktorit ja koskettimet + Relais, contacteurs et contacts + Releji, kontaktori i kontakti + Relék, kontaktorok és érintkezők + Relè, contattori e contatti + 継電器、接触器、接点 + 릴레이, 커넥터및 접점 + Реле, контактор болон контакт + Reléer, kontaktorere og kontakter + Relais, contactoren en contacten + Relais, contacteurs en contacten + Przekaźniki i styczniki + Relés, contatores e contactos + Relés, Contatores e contatos + Relee, contactoare și contacte + Реле, пускатели и контакты + Relé, stykače a kontakty + Releji, kontaktorji in kontakti + Релеји, контактори и контакти + Reläer, kontaktorer och kontakter + Röleler, kontaktörler ve kontaklar + Реле, контактори та контакти + 继电器、接触器与触点 + + + + جهات الاتصال + Contactes + Kontakty + Kontaktpersoner + Kontakte + Επαφές + Contacts + Contactos + Kosketimet + Contacts + Kontakti + Kapcsolatok + Contatti + 連絡先 + 접점 + Харилцагчид + Kontakter + Contacten + Contacten + Łączność + Contatos + Contatos + Contacte + Контакты + Kontakty + Stiki + Контакти + Kontakter + Kişiler + Контакти + 触点 + + + + + + عدّاد مفتوح عادة + Taulell normalment obert + Kontakt čítače (NO) + Normalt åben tæller + Öffner Zähler betätigt + Ανοικτή επαφή μετρητή + Normally open counter + Contador NA + Normaalisti avoin laskuri + Actionné par comptage NO + Brojač normalno otvoren + Alaphelyzetben nyitott számláló + Contatto azionato da un contatore (NO) + 常開カウンター + 평상시 열림 카운터 + Ихэвчлэн нээлттэй тоолуур + Normalt åpen teller + Teller contact (NO) + Normaal open teller + Zestyk zwierny licznika + Contador normalmente aberto + Contato NA acionado por contador + Contor normal deschis + Счётчик с нормально открытым контактом + Počítadlo s kontaktom NO + Števec normalno odprt + Бројач нормално отворен + Räknare med slutande kontakt + Normalde açık sayaç + Лічильник нормально розімкнений + 计数驱动常开触点 + + Author: The QElectroTech team +License: see http://qelectrotech.org/wiki/doc/elements_license + + + + + 14 + + + 11 + + + + label + + + + + + + + + + + + + + + + diff --git a/tests/qttest/tst_junctiondots.cpp b/tests/qttest/tst_junctiondots.cpp new file mode 100644 index 000000000..33000b781 --- /dev/null +++ b/tests/qttest/tst_junctiondots.cpp @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include + +#include +#include +#include +#include +#include +#include +#include + +// fixtures/junction_dot_shared_potential.qet: four contacts K1..K4 in a row, +// wired K1-K2, K1-K4 and K2-K3. All three wires run along one line above the +// contacts, so there is a T above K2 and another above K3. The corner above +// K3 belongs to K2-K3 and lies on K1-K4, which shares no terminal with it: +// that dot used to be missing (issue #1280). +class tst_junctiondots : public QObject +{ + Q_OBJECT + + QTemporaryDir m_dir; + + // Export the fixture to SVG and return the distinct positions of the + // junction dots drawn on it. + QSet dots() + { + const QString home = m_dir.filePath(QStringLiteral("home")); + const QString out = m_dir.filePath(QStringLiteral("svg")); + QDir().mkpath(home); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen")); + env.insert(QStringLiteral("HOME"), home); + env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config")); + env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data")); + env.insert(QStringLiteral("TMPDIR"), home); + QProcess proc; + proc.setProcessEnvironment(env); + proc.start(QStringLiteral(QET_TEST_BINARY_PATH), + {QStringLiteral("--export-svg"), + QFINDTESTDATA("fixtures/junction_dot_shared_potential.qet"), out}); + if (!proc.waitForFinished(60000) || proc.exitCode() != 0) + return {}; + + const QStringList svgs = QDir(out).entryList({QStringLiteral("*.svg")}); + if (svgs.size() != 1) + return {}; + QFile f(QDir(out).filePath(svgs.first())); + if (!f.open(QIODevice::ReadOnly)) + return {}; + const QString svg = QString::fromUtf8(f.readAll()); + + // A junction dot on a wire of the default width is a circle + // 3.0 across; the symbols in the fixture draw none that size. + static const QRegularExpression re( + QStringLiteral("")); + QSet found; + auto it = re.globalMatch(svg); + while (it.hasNext()) { + const auto m = it.next(); + found << m.captured(1) + QLatin1Char(',') + m.captured(2); + } + return found; + } + +private slots: + void initTestCase() + { + QVERIFY(m_dir.isValid()); + QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH))); + } + + void dotAtEveryT() + { + const QSet expected{QStringLiteral("200,260"), + QStringLiteral("300,260")}; + QCOMPARE(dots(), expected); + } +}; + +QTEST_GUILESS_MAIN(tst_junctiondots) +#include "tst_junctiondots.moc"