diff --git a/misc/qet-mcp/test_qet_mcp.py b/misc/qet-mcp/test_qet_mcp.py index 3ada780c3..57af9cdf0 100644 --- a/misc/qet-mcp/test_qet_mcp.py +++ b/misc/qet-mcp/test_qet_mcp.py @@ -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): diff --git a/sources/utils/conductorcreator.cpp b/sources/utils/conductorcreator.cpp index 568b50542..6bfe717a7 100644 --- a/sources/utils/conductorcreator.cpp +++ b/sources/utils/conductorcreator.cpp @@ -128,7 +128,50 @@ bool ConductorCreator::needsPotentialChoice(const QList &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 ConductorCreator::distinctProperties(const QList &potentials) +{ + QList 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 cp_list = distinctProperties(potentials); + if (cp_list.size() >= 2) { - QList 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 */ QList ConductorCreator::existingPotential(const QList &terminals_list) { QList c_list; - QList t_exclude; - + QList> potentials; + for (Terminal *t : terminals_list) { - if (t_exclude.contains(t)) { + Conductor *seed = nullptr; + QList to_visit {t}; + QSet 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 &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 potential = seed->relatedPotentialConductors(true); + potential.insert(seed); + potentials.append(potential); + c_list.append(seed); } - + return c_list; } diff --git a/sources/utils/conductorcreator.h b/sources/utils/conductorcreator.h index a8c2c9a6a..3551d3eec 100644 --- a/sources/utils/conductorcreator.h +++ b/sources/utils/conductorcreator.h @@ -42,6 +42,8 @@ class ConductorCreator private: static QList existingPotential(const QList &terminals_list); + static ConductorProperties inheritedProperties(const Conductor *conductor); + static QList distinctProperties(const QList &potentials); bool setUpPropertieToUse(); Terminal *hubTerminal(); QList> terminalPairs(bool chain);