Add optional hops where wires cross (#436)

Where two wires cross without being connected, a project can now draw a
small arc (a hop) on one of them, instead of the user inserting a jump
symbol and splitting the wire. It is a project setting, off by default:
Project properties > Général > "Croisements de conducteurs", with no
hops, hops on horizontal wires, or hops on vertical wires. Choosing the
orientation rather than following drawing order keeps a project
consistent.

Only the drawing changes: no element is added and no wire is split. A
wire that ends or bends on another one is a junction and never hops,
and a crossing too close to the end of a segment for the arc to fit is
drawn plainly. PDF, SVG and image export and printing show the hops,
since they paint through Conductor::paint(); DXF export, which writes
the segments itself, does not.

The setting is saved as <wire_crossings hop="..."/> under the project
root, next to <usage>, and only when hops are on, so a project that
never used them saves exactly as before.

The geometry is in wirehops.cpp, free of any graphics item, and tested
on its own. Conductor::paintedPath() feeds it the conductors of the
folio from a snapshot of their scene points, rebuilt only when a
conductor changes shape, moves, or enters or leaves a folio; each hop
path is cached the same way. Asking the scene for the conductors in a
rect instead strokes every candidate's shape, which made a 366-wire
folio render 0.6 s slower; with the snapshot the difference is within
measurement noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-01 21:18:12 +13:00
parent d777b8d44d
commit 79a8637251
11 changed files with 700 additions and 3 deletions
+59
View File
@@ -1232,6 +1232,31 @@ void QETProject::setAutoConductor(bool ac)
m_auto_conductor = ac;
}
/**
@brief QETProject::wireHops
@return which wire of a crossing draws a hop (issue #436),
WireHops::Mode::None when crossings are drawn as plain lines.
*/
WireHops::Mode QETProject::wireHops() const {
return m_wire_hops;
}
/**
@brief QETProject::setWireHops
Set which wire of a crossing draws a hop, and redraw every folio.
@param mode
*/
void QETProject::setWireHops(WireHops::Mode mode)
{
if (mode == m_wire_hops) {
return;
}
m_wire_hops = mode;
for (Diagram *diagram : diagrams()) {
diagram->update();
}
}
/**
@brief QETProject::autoBreakConductor
@return true if use of auto break conductor is authorized.
@@ -1341,6 +1366,7 @@ QDomDocument QETProject::toXml()
// local, non-transmitted usage tracking (time spent on this project)
writeUsageXml(project_root);
writeWireHopsXml(project_root);
// Properties for news diagrams
QDomElement new_diagrams_properties = xml_doc.createElement("newdiagrams");
@@ -1850,6 +1876,7 @@ void QETProject::readProjectXml(QDomDocument &xml_project)
//Load the local, non-transmitted usage tracking
readUsageXml(xml_project);
readWireHopsXml(xml_project);
//Load the default properties for the new diagrams
readDefaultPropertiesXml(xml_project);
@@ -2028,6 +2055,21 @@ void QETProject::readUsageXml(QDomDocument &xml_project)
m_project_properties_handler.usageTracker().fromXml(xml_project.documentElement());
}
/**
@brief QETProject::readWireHopsXml
Read the <wire_crossings> element of the project, if any.
A project without it draws no hop.
@param xml_project : the xml description of the project
*/
void QETProject::readWireHopsXml(QDomDocument &xml_project)
{
const QDomElement crossings = xml_project.documentElement()
.firstChildElement(QStringLiteral("wire_crossings"));
m_wire_hops = crossings.isNull()
? WireHops::Mode::None
: WireHops::fromString(crossings.attribute(QStringLiteral("hop")));
}
/**
@brief QETProject::readDefaultPropertiesXml
load default properties for new diagram, found in the xml of this project
@@ -2173,6 +2215,23 @@ void QETProject::writeUsageXml(QDomElement &xml_element) {
m_project_properties_handler.usageTracker().toXml(xml_element);
}
/**
@brief QETProject::writeWireHopsXml
Export which wire of a crossing hops as a <wire_crossings> child of
\a xml_element. Written only when hops are on, so a project that never
used them saves exactly as before.
*/
void QETProject::writeWireHopsXml(QDomElement &xml_element)
{
if (m_wire_hops == WireHops::Mode::None) {
return;
}
QDomElement crossings = xml_element.ownerDocument()
.createElement(QStringLiteral("wire_crossings"));
crossings.setAttribute(QStringLiteral("hop"), WireHops::toString(m_wire_hops));
xml_element.appendChild(crossings);
}
/**
@brief QETProject::writeDefaultPropertiesXml
Export all defaults properties used by a new diagram and his content