From 2906a48aab339fab3d2d100917145c4264eaae5c Mon Sep 17 00:00:00 2001 From: ispyisail Date: Tue, 22 Sep 2026 16:28:04 +1200 Subject: [PATCH] Add electrical continuity checking to the scripting API qet.checkContinuity(folioIndex) runs two structural checks against the live Terminal/Conductor object graph -- Terminal::conductors() and Conductor::relatedPotentialConductors(), the same primitive setConductorProperty() already uses -- rather than a heuristic read of the saved XML: - unconnected_terminal (info): a terminal with no conductor at all. Deliberately low severity -- routine (a spare relay contact, an unused optional pin), not necessarily a mistake. - potential_mismatch (error): two conductors electrically on the same potential (following bridged terminal strips and linked report elements, matching setConductorProperty()'s own scope) disagreeing on num/conductor_color/conductor_section/function/bus/cable. QElectroTech's own edits always keep every member of a potential identical, so any divergence found here came from hand-edited XML, a legacy file, or an external tool -- verified with a test that patches a saved file's XML directly to introduce exactly that. Documented plainly what this does NOT check and why: pin electrical direction/power conflicts and No/Nc/Common contact shorts, since QElectroTech's terminal data model (Generic/Inner/Outer/No/Nc/Common -- contact role within one relay, not signal direction) does not carry the information either would need. This is continuity/consistency checking, not full ERC. Co-Authored-By: Claude Sonnet 5 --- sources/scripting/qetscriptapi.cpp | 120 +++++++++++++++++++++++++++++ sources/scripting/qetscriptapi.h | 5 ++ 2 files changed, 125 insertions(+) diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp index b08dd02e8..c7e659927 100644 --- a/sources/scripting/qetscriptapi.cpp +++ b/sources/scripting/qetscriptapi.cpp @@ -3837,6 +3837,126 @@ int QetScriptApi::searchAndReplace(const QString &kind, const QString &field, return changed; } +/** + @brief QetScriptApi::checkContinuity + Structural electrical checks against the live scene graph -- Terminal/ + Conductor/relatedPotentialConductors() -- rather than a heuristic read + of the saved XML the way qet_check's Python side works. Two checks: + + 1. unconnected_terminal (severity "info"): a terminal with no + conductor at all. Reported at low confidence deliberately -- an + unconnected terminal is routine (a spare relay contact, an unused + optional pin), not necessarily a mistake, so this is a prompt to + look, not a claim that something is wrong. + + 2. potential_mismatch (severity "error"): two conductors that + electrically belong to the same potential (connected transitively + through shared terminals, following bridged terminal strips and + linked report elements the same way setConductorProperty() does) + but disagree on num, conductor_color, conductor_section, function, + bus or cable. QElectroTech's own setConductorProperty() always + writes every member of a potential identically, so any divergence + found here did not come from this API or the GUI's equivalent + action -- it came from hand-edited XML, a legacy file, or an + external tool, and it is a real defect: two wire numbers on what + is electrically one node is exactly the kind of thing a human + reading the schematic would get wrong from. + + What this deliberately does NOT check, because QElectroTech's own + terminal data model does not carry the information a real check would + need: pin electrical direction/power conflicts (no terminal in this + model is marked input/output/power the way a KiCad pin is -- only + Generic/Inner/Outer/No/Nc/Common, which describe contact role within + one relay/switch, not signal direction), and short circuits between a + contact's No and Nc terminals sharing a Common (would need per- + contact-group semantics this does not attempt). Treat this as + continuity/consistency checking, not full ERC. + + @param folioIndex a single folio, or -1 for the whole project + @return a list of {kind, severity, folio, message, ...} objects; + kind-specific keys: unconnected_terminal has element/elementLabel/ + terminal/terminalName, potential_mismatch has property/values +*/ +QVariantList QetScriptApi::checkContinuity(int folioIndex) +{ + QVariantList findings; + if (!m_project) return findings; + const QList diagrams = m_project->diagrams(); + if (folioIndex >= diagrams.count()) { + log(QStringLiteral("qet.checkContinuity: folio %1 does not exist (%2 folio(s))") + .arg(folioIndex).arg(diagrams.count())); + return findings; + } + + for (int f = 0; f < diagrams.count(); ++f) { + if (folioIndex >= 0 && f != folioIndex) continue; + DiagramContent content(diagrams.at(f), false); + for (Element *elmt : std::as_const(content.m_elements)) { + const QList terminals = elmt->terminals(); + for (int ti = 0; ti < terminals.count(); ++ti) { + Terminal *t = terminals.at(ti); + if (!t->conductors().isEmpty()) continue; + QVariantMap finding; + finding.insert(QStringLiteral("kind"), QStringLiteral("unconnected_terminal")); + finding.insert(QStringLiteral("severity"), QStringLiteral("info")); + finding.insert(QStringLiteral("folio"), f); + finding.insert(QStringLiteral("element"), elmt->uuid().toString()); + finding.insert(QStringLiteral("elementLabel"), elmt->actualLabel()); + finding.insert(QStringLiteral("terminal"), ti); + finding.insert(QStringLiteral("terminalName"), t->name()); + finding.insert(QStringLiteral("message"), + QStringLiteral("terminal %1 ('%2') of %3 has no conductor") + .arg(ti).arg(t->name(), elmt->actualLabel())); + findings << finding; + } + } + } + + static const QStringList checked_properties = { + QStringLiteral("num"), QStringLiteral("conductor_color"), + QStringLiteral("conductor_section"), QStringLiteral("function"), + QStringLiteral("bus"), QStringLiteral("cable")}; + + QSet visited; + for (int f = 0; f < diagrams.count(); ++f) { + if (folioIndex >= 0 && f != folioIndex) continue; + DiagramContent content(diagrams.at(f), false); + const QList all = content.conductors(DiagramContent::AnyConductor); + for (Conductor *c : all) { + if (visited.contains(c)) continue; + QSet potential = c->relatedPotentialConductors(true); + potential << c; + visited += potential; + if (potential.count() < 2) continue; + + QHash> distinct_values; + for (Conductor *pc : std::as_const(potential)) { + for (const QString &prop : checked_properties) { + distinct_values[prop].insert(conductorPropertyValue(pc->properties(), prop)); + } + } + for (const QString &prop : checked_properties) { + if (distinct_values.value(prop).count() <= 1) continue; + QStringList values(distinct_values.value(prop).begin(), + distinct_values.value(prop).end()); + values.sort(); + QVariantMap finding; + finding.insert(QStringLiteral("kind"), QStringLiteral("potential_mismatch")); + finding.insert(QStringLiteral("severity"), QStringLiteral("error")); + finding.insert(QStringLiteral("folio"), f); + finding.insert(QStringLiteral("property"), prop); + finding.insert(QStringLiteral("values"), values); + finding.insert(QStringLiteral("message"), + QStringLiteral("conductors on the same electrical potential disagree " + "on %1: %2").arg(prop, values.join(QStringLiteral(", ")))); + findings << finding; + } + } + } + + return findings; +} + bool QetScriptApi::selectElement(const QString &elementUuid) { if (!m_project) return false; diff --git a/sources/scripting/qetscriptapi.h b/sources/scripting/qetscriptapi.h index 94b4d61d6..5426275db 100644 --- a/sources/scripting/qetscriptapi.h +++ b/sources/scripting/qetscriptapi.h @@ -547,6 +547,11 @@ class QetScriptApi : public QObject const QString &pattern, const QString &replacement, bool useRegex, bool caseSensitive); + // -- electrical continuity / ERC: read-only, structural checks + // against the live object graph rather than the XML -- see the + // .cpp doc comment for exactly what is and is not covered -- + Q_INVOKABLE QVariantList checkContinuity(int folioIndex); + // -- navigate and message -- Q_INVOKABLE bool selectElement(const QString &elementUuid); Q_INVOKABLE void deselectAll(int folioIndex);