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
@@ -170,6 +170,12 @@ void ProjectMainConfigPage::applyProjectConf()
modified_project = true;
}
const auto wire_hops = WireHops::fromString(wire_hops_cb_ -> currentData().toString());
if (m_project -> wireHops() != wire_hops) {
m_project -> setWireHops(wire_hops);
modified_project = true;
}
if (modified_project) {
m_project -> setModified(true);
}
@@ -206,6 +212,19 @@ void ProjectMainConfigPage::initWidgets()
usage_enabled_cb_ = new QCheckBox(tr("Suivre le temps passé sur ce projet (uniquement enregistré localement dans ce fichier)", "checkbox label"));
usage_reset_pb_ = new QPushButton(tr("Réinitialiser", "button label"));
connect(usage_reset_pb_, &QPushButton::clicked, this, &ProjectMainConfigPage::resetUsageTracker);
//Hops where two conductors cross without being connected (issue #436)
wire_hops_label_ = new QLabel(tr("Croisements de conducteurs :", "label when configuring"));
wire_hops_cb_ = new QComboBox();
wire_hops_cb_ -> addItem(tr("Sans saut", "wire crossings"),
WireHops::toString(WireHops::Mode::None));
wire_hops_cb_ -> addItem(tr("Saut sur les conducteurs horizontaux", "wire crossings"),
WireHops::toString(WireHops::Mode::Horizontal));
wire_hops_cb_ -> addItem(tr("Saut sur les conducteurs verticaux", "wire crossings"),
WireHops::toString(WireHops::Mode::Vertical));
wire_hops_cb_ -> setToolTip(tr("Dessine un petit arc là où deux conducteurs se croisent sans être reliés. "
"Seul le dessin change : aucun élément n'est ajouté et aucun conducteur n'est coupé.",
"tooltip"));
}
/**
@@ -231,6 +250,13 @@ void ProjectMainConfigPage::initLayout()
usage_layout0 -> addWidget(usage_reset_pb_);
main_layout0 -> addLayout(usage_layout0);
main_layout0 -> addWidget(usage_enabled_cb_);
main_layout0 -> addSpacing(10);
QHBoxLayout *wire_hops_layout0 = new QHBoxLayout();
wire_hops_layout0 -> addWidget(wire_hops_label_);
wire_hops_layout0 -> addWidget(wire_hops_cb_);
wire_hops_layout0 -> addStretch();
main_layout0 -> addLayout(wire_hops_layout0);
setLayout(main_layout0);
this -> setMinimumWidth(680);
@@ -251,6 +277,9 @@ void ProjectMainConfigPage::readValuesFromProject()
.arg(total_seconds / 3600)
.arg((total_seconds % 3600) / 60));
usage_enabled_cb_ -> setChecked(usage_tracker.isEnabled());
const int wire_hops_index = wire_hops_cb_ -> findData(WireHops::toString(m_project -> wireHops()));
wire_hops_cb_ -> setCurrentIndex(qMax(0, wire_hops_index));
}
/**
@@ -277,6 +306,7 @@ void ProjectMainConfigPage::adjustReadOnly()
title_value_ -> setReadOnly(is_read_only);
usage_enabled_cb_ -> setDisabled(is_read_only);
usage_reset_pb_ -> setDisabled(is_read_only);
wire_hops_cb_ -> setDisabled(is_read_only);
}
//######################################################################################//