Merge pull request #1536 from DaTweety/fix/new-wire-inherits-potential

A new wire takes the potential it reaches through terminals and reports
This commit is contained in:
ispyisail
2026-10-19 16:21:10 +13:00
committed by GitHub
3 changed files with 145 additions and 41 deletions
+44
View File
@@ -4534,12 +4534,28 @@ class Integration(unittest.TestCase):
for i in range(4)],
{"op": "add_conductor", "folio": "$f", "from": "$e0", "from_terminal": 0, "to": "$e1", "to_terminal": 0},
{"op": "add_conductor", "folio": "$f", "from": "$e2", "from_terminal": 0, "to": "$e3", "to_terminal": 0},
{"op": "set_conductor", "folio": "$f", "element": "$e2", "terminal": 0, "property": "num", "value": "W2"},
{"op": "add_conductor", "folio": "$f", "from": "$e0", "from_terminal": 0, "to": "$e2", "to_terminal": 0},
], self.sb.p("conflict.qet"), elements_dir=ELEMENTS, timeout=60)
self.assertFalse(r.get("timed_out"), "the call hung on a modal dialog")
self.assertFalse(r["ok"])
self.assertEqual(r["operations"][-1]["result"], False)
def test_joining_two_agreeing_potentials_asks_nothing(self):
"""Two potentials with the same properties leave nothing to choose:
the wire joining them is drawn, headless as well."""
base = self.sb.new()
r = self.ok(self.sb.edit(base, [
{"op": "add_folio", "id": "f"},
*[{"op": "add_element", "id": f"e{i}", "folio": "$f", "path": COIL, "x": 100 + i * 200, "y": 100}
for i in range(4)],
{"op": "add_conductor", "folio": "$f", "from": "$e0", "from_terminal": 0, "to": "$e1", "to_terminal": 0},
{"op": "set_conductor", "folio": "$f", "element": "$e0", "terminal": 0, "property": "num", "value": "N"},
{"op": "add_conductor", "folio": "$f", "from": "$e2", "from_terminal": 0, "to": "$e3", "to_terminal": 0},
{"op": "set_conductor", "folio": "$f", "element": "$e2", "terminal": 0, "property": "num", "value": "N"},
{"op": "add_conductor", "folio": "$f", "from": "$e0", "from_terminal": 0, "to": "$e2", "to_terminal": 0}]))
self.assertEqual([c["num"] for c in m.tool_conductors(r["output"])["conductors"]], ["N"] * 3)
# ---- edit: conductor properties, potentials ----
def test_conductor_property_applies_to_the_whole_potential(self):
@@ -4555,6 +4571,34 @@ class Integration(unittest.TestCase):
rows = m.tool_conductors(r["output"])["conductors"]
self.assertEqual([c["num"] for c in rows], ["W7", "W7"])
def test_new_conductor_inherits_a_potential_through_terminal_and_report(self):
"""A wire drawn to the free side of a terminal element, or to a
report linked to a wired one, is on that potential already: it
takes the potential's number and line style, not the folio's
defaults (ConductorCreator::existingPotential)."""
base = self.sb.new()
ops = [{"op": "add_folio", "id": "f"}, {"op": "add_folio", "id": "g"},
{"op": "add_element", "id": "a", "folio": "$f", "path": COIL, "x": 100, "y": 100},
{"op": "add_element", "id": "t", "folio": "$f", "path": TERMINAL, "x": 300, "y": 300},
{"op": "add_element", "id": "b", "folio": "$f", "path": COIL, "x": 500, "y": 100},
{"op": "add_element", "id": "go", "folio": "$f", "path": NEXT_REPORT, "x": 700, "y": 100},
{"op": "add_element", "id": "in", "folio": "$g", "path": PREVIOUS_REPORT, "x": 100, "y": 100},
{"op": "add_element", "id": "c", "folio": "$g", "path": COIL, "x": 300, "y": 100},
{"op": "link_elements", "folio": "$f", "element": "$go", "to_folio": "$g", "to": "$in"},
{"op": "add_conductor", "folio": "$f", "from": "$a", "from_terminal": 0, "to": "$t", "to_terminal": 0},
{"op": "set_conductor", "folio": "$f", "element": "$a", "terminal": 0, "property": "num", "value": "W7"},
{"op": "set_conductor", "folio": "$f", "element": "$a", "terminal": 0, "property": "style", "value": "dashed"},
# the terminal's other side, then on through the report
{"op": "add_conductor", "folio": "$f", "from": "$t", "from_terminal": 1, "to": "$b", "to_terminal": 0},
{"op": "add_conductor", "folio": "$f", "from": "$b", "from_terminal": 0, "to": "$go", "to_terminal": 0},
{"op": "add_conductor", "folio": "$g", "from": "$in", "from_terminal": 0, "to": "$c", "to_terminal": 0}]
r = self.ok(self.sb.edit(base, ops))
rows = m.tool_conductors(r["output"])["conductors"]
self.assertEqual([c["num"] for c in rows], ["W7"] * 4)
styles = {c["value"] for c in m.tool_conductors(r["output"], attribute="style")["conductors"]}
self.assertEqual(len(styles), 1, styles)
self.assertIn("dash", styles.pop())
# ---- conductor manual routing ----
def test_move_conductor_segment_reroutes_the_path(self):
+99 -41
View File
@@ -128,7 +128,50 @@ bool ConductorCreator::needsPotentialChoice(const QList<Terminal *> &terminals_l
if (terminals_list.size() <= 1) {
return false;
}
return existingPotential(terminals_list).size() >= 2;
return distinctProperties(existingPotential(terminals_list)).size() >= 2;
}
/**
@brief ConductorCreator::inheritedProperties
The properties a new wire takes from @p conductor's potential. A
cable's core names the cable and the core in m_cable, m_cable_uuid
and m_cable_slot (written by CableManager): none of that belongs to a
new wire, which is in no cable yet. The text is the potential's wire
number and is kept, unless it only repeats the core label.
@param conductor : a conductor of the potential
@return the properties for a new conductor of that potential
*/
ConductorProperties ConductorCreator::inheritedProperties(const Conductor *conductor)
{
ConductorProperties properties = conductor->properties();
if (!properties.m_cable_uuid.isNull())
{
if (properties.text == properties.m_cable)
properties.text.clear();
properties.m_cable.clear();
properties.m_cable_uuid = QUuid();
properties.m_cable_slot = -1;
}
return properties;
}
/**
@brief ConductorCreator::distinctProperties
@param potentials : one conductor of each potential (existingPotential())
@return the properties a new wire could inherit from them, each once:
two potentials that agree leave nothing to choose
*/
QList<ConductorProperties> ConductorCreator::distinctProperties(const QList<Conductor *> &potentials)
{
QList<ConductorProperties> list;
for (const Conductor *c : potentials)
{
const ConductorProperties properties = inheritedProperties(c);
if (!list.contains(properties)) {
list.append(properties);
}
}
return list;
}
/**
@@ -146,28 +189,24 @@ bool ConductorCreator::setUpPropertieToUse()
//we get one of them
if (!potentials.isEmpty())
{
if (potentials.size() >= 2)
const QList<ConductorProperties> cp_list = distinctProperties(potentials);
if (cp_list.size() >= 2)
{
QList <ConductorProperties> cp_list;
for(Conductor *c : potentials) {
cp_list.append(c->properties());
}
bool cancelled = false;
m_properties = PotentialSelectorDialog::chosenProperties(cp_list, nullptr, &cancelled);
if (cancelled) {
return false;
}
for (Conductor *c : potentials) {
if (c->properties() == m_properties) {
m_sequential_number = c->sequenceNum();
}
}
}
else if (potentials.size() == 1)
else
{
m_properties = potentials.first()->properties();
m_sequential_number = potentials.first()->sequenceNum();
m_properties = cp_list.first();
}
for (Conductor *c : potentials) {
if (inheritedProperties(c) == m_properties) {
m_sequential_number = c->sequenceNum();
break;
}
}
return true;
}
@@ -179,46 +218,65 @@ bool ConductorCreator::setUpPropertieToUse()
/**
@brief ConductorCreator::existingPotential
Return the list of existing potential of
the terminal list
Return one conductor of each existing potential the terminals of
@p terminals_list are on.
A terminal without a wire of its own can still be on a potential: the
other side of a terminal element, a folio report linked to one that is
wired. Such a terminal is followed through relatedPotentialTerminal()
until a wired terminal is found -- the rule Conductor::
relatedPotentialConductors() applies, so that a wire drawn to the
free side of a terminal takes the properties (number, colour, line
style...) of the potential the terminal already carries instead of the
folio's defaults.
Two terminals on the same potential give that potential once.
@param terminals_list the terminals to inspect
@return c_list QList<Conductor *>
*/
QList<Conductor *> ConductorCreator::existingPotential(const QList<Terminal *> &terminals_list)
{
QList<Conductor *> c_list;
QList<Terminal *> t_exclude;
QList<QSet<Conductor *>> potentials;
for (Terminal *t : terminals_list)
{
if (t_exclude.contains(t)) {
Conductor *seed = nullptr;
QList<Terminal *> to_visit {t};
QSet<const Terminal *> visited;
while (!to_visit.isEmpty() && !seed)
{
Terminal *current = to_visit.takeFirst();
if (visited.contains(current)) {
continue;
}
visited.insert(current);
if (!current->conductors().isEmpty()) {
seed = current->conductors().first();
} else {
to_visit += relatedPotentialTerminal(current, true);
}
}
if (!seed) {
continue;
}
if (!t->conductors().isEmpty())
{
c_list.append(t->conductors().first());
//We must check if m_terminals_list contains a terminal
//in the same potential of c, and if true, exclude this terminal from the search.
for (Conductor *c : t->conductors().first()->relatedPotentialConductors(false))
{
if (terminals_list.contains(c->terminal1)) {
t_exclude.append(c->terminal1);
} else if (terminals_list.contains(c->terminal2)) {
t_exclude.append(c->terminal2);
}
bool known = false;
for (const QSet<Conductor *> &potential : std::as_const(potentials)) {
if (potential.contains(seed)) {
known = true;
break;
}
}
else if (t->parentElement()->linkType() & Element::AllReport && !t->parentElement()->isFree())
{
Element *linked_report = t->parentElement()->linkedElements().first();
if (!linked_report->conductors().isEmpty()) {
c_list.append(linked_report->conductors().first());
}
if (known) {
continue;
}
QSet<Conductor *> potential = seed->relatedPotentialConductors(true);
potential.insert(seed);
potentials.append(potential);
c_list.append(seed);
}
return c_list;
}
+2
View File
@@ -42,6 +42,8 @@ class ConductorCreator
private:
static QList<Conductor *> existingPotential(const QList<Terminal *> &terminals_list);
static ConductorProperties inheritedProperties(const Conductor *conductor);
static QList<ConductorProperties> distinctProperties(const QList<Conductor *> &potentials);
bool setUpPropertieToUse();
Terminal *hubTerminal();
QList<QPair<Terminal *, Terminal *>> terminalPairs(bool chain);