Addresses scorpio810's review of PR #890 (bugtracker #734):
- The four cas "3"/"4" bridge-skip guards now compare qRound()ed
coordinates, matching how the bridge coordinate itself is computed --
an exact != would miss a pair already grid-equal after rounding but
off by a sub-pixel remainder, and still route a degenerate bridge for
it. Verified no behavior change on the shipped corpus: per-file
self-retrace counts are identical before/after (every coordinate
there already lands exactly on-grid).
- Added tests/qttest/tst_conductorselfretrace.cpp, fixture
qet_bug_repro_resaved.qet (the report's own canonical reproduction):
exports it via the built binary's --export-svg and asserts no
conductor path is self-retracing. Confirmed it actually catches the
regression, not just passes vacuously -- reverted conductor.cpp to
master and reran: fails, 1 self-retracing path found.
Not changed in code: the cas "4" descending-branch dead-code note (kept
for symmetry, as already agreed) and the schema_unifilaire_voltaique2.qet
trade-off (flagged for the reviewer's own visual check).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
generateConductorPath()'s cas "3"/"4" branches each insert a two-point
bridge along one axis, at a coordinate on the other axis computed as
the midpoint of depart/arrivee and then snapped to the routing grid by
a loop that walks it strictly downward until it divides evenly.
When depart and arrivee already agree on the axis the bridge would run
along, no bridge is needed at all -- the midpoint starts on the
correct, already-shared value. But the snap can still walk it off that
value, or, when it happens to already sit on the grid, the two bridge
points just duplicate depart and arrivee outright. Either way the
conductor renders with an unnecessary out-and-back excursion or a
small looping detour at a join that needed neither.
Fix: skip the bridge in exactly that degenerate case, per branch, on
the axis that branch actually bridges on. descendant and montant are
not mirror images of each other in which axis each cas guards on --
each site says so.
Verified against the report's own canonical reproduction
(qet_bug_repro_resaved.qet from the linked gist): 1 self-retracing
path -> 0. Full corpus of 24 shipped example projects (stored
segments, as shipped, not regenerated): 74 -> 65 self-retracing
conductor paths, zero regressions -- only two files changed, both
improved.
One disclosed trade-off, found while checking for regressions: in
schema_unifilaire_voltaique2.qet, 8 terminal pairs closer together
than twice the extension length (docked stubs already cross before
any bridge is considered) go from an existing small rectangular-loop
artifact to a straight out-and-back retrace covering the full gap --
same count of defective paths (8 -> 8), a different shape, still no
connectivity change either way. Not chased further; a real fix for
that narrower "crossed stubs" case is a separate piece of work.
The shipped affuteuse_250h.qet's own 12 self-retracing paths (verified
by stripping all 185 stored <segment> elements and forcing full
regeneration) are unchanged by this fix -- they are a structurally
different point-count signature (a 4-point back-and-forth reachable
from cas "1"/"2", not the cas "3"/"4" grid-snap bridge this fixes),
consistent with what the issue thread already flagged as a separate,
undiagnosed mechanism.
Refs #734 (own root-cause comment, 2026-08-13).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The F2 color editor recolors one conductor, but the next one drawn
falls straight back to defaultConductorProperties -- the choice made
via F2 is lost the moment you place another wire, and lost again on
restart. LastUsedStyle already solves the same problem for shapes
(pen/brush) and free text (font), session-scoped and deliberately not
QSettings-backed; this extends it with a conductor color, following
the identical has/get/set shape.
F2's handler records the color after pushing its undo command.
Conductor's constructor -- the one place a new conductor's properties
are set from defaultConductorProperties -- overrides just the color
field when a session color has been recorded, leaving every other
default (style, thickness, text) alone.
Verified: build clean, ctest 6/6. Could not get a reliable headless
GUI trace of "F2 one wire, draw a new one, see it inherit the color"
-- drag-and-drop element placement under Xvfb was unreliable in this
environment (one attempt did nothing, another drew an unintended long
conductor undo didn't fully clear). The code path is otherwise
identical to the already-shipped shape/text mechanism this mirrors.
Refs #461.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Conductor::paint() drew every junction as a fixed 3.0-unit ellipse,
regardless of how wide the conductor carrying it is. The conductor width is
user-settable from 0.4 to 20.0, so at anything above about 3.0 the dot is
narrower than the line it sits on and disappears entirely -- exactly when a
junction most needs to be legible.
The dot now scales with m_properties.cond_size, floored at the historic 3.0
so nothing changes at or below the default width of 1.0. Only the wide
conductors the report is about are affected.
cond_size is used rather than the pen width because the pen is inflated by 4
while the mouse is over the conductor; the junction should not grow on
hover.
Measured with a temporary trace over examples/741.qet: at the default width
the diameter stays 3.00, and with condsize="5" it becomes 15.00. Visually,
a PNG export of that widened project shows two junctions that were invisible
under the line rendering as clear dots. ctest 4/4, Qt 5.15.18.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Saving an unmodified project produced a different byte stream on
every run: QGraphicsScene::items() returns items in stacking order,
and ties between same-Z items follow the scene's internal index --
not any content-derived order -- so it isn't reproducible across
process runs. The legacy terminal-id table inherits the same
instability, since ids are assigned sequentially in element order.
Sort list_elements and list_conductors into a deterministic order
before serializing, using a key built from data that's actually
stable across loads (position), not Element::uuid()/Conductor::uuid():
for an item with no persisted uuid attribute, fromXml() invents a
fresh random one on every load, so sorting by uuid would still be
non-deterministic across process runs for any legacy file -- which
this corpus has plenty of.
Also fixes a second, related source of byte-level non-determinism
found while verifying the above: Conductor::toXml() unconditionally
wrote m_uuid back out, including the synthetic value fromXml() just
invented for a conductor with no uuid attribute in the file. Every
conductor in every example project checked has no persisted uuid at
all, so this alone meant no project with conductors could ever
resave identically, regardless of ordering. Conductor gets a
m_persist_uuid flag, false only when the uuid it's holding was
synthesized rather than loaded, so toXml() stops writing a value
that was never meant to be permanent.
Deliberately NOT applying the same uuid-persistence fix to Element:
element uuids are cross-referenced by other elements' <links_uuids>
blocks for master/slave/report linking (element.cpp, tmp_uuids_link,
matched by elmt->uuid() == stored uuid on load). Making an element's
own uuid non-persistent would silently break that match for any
linked element without one already -- a real regression, not a
theoretical one. Left as a smaller, separate residual: 1-6 elements
per project across the corpus (a few tenths of a percent) still get
a fresh uuid on each load, same class of bug, needs the link-aware
version of this fix instead of this one.
Verified against 8 example projects (the ones with conductors, plus
the two zero-conductor control cases from FINDINGS.md F002), 5
resaves each in isolated HOME/XDG environments:
- Element and conductor ORDER: 0 churning sections across the whole
corpus (previously the majority of diagrams in industrial.qet,
m_000.qet and tremie_vibrante.qet churned on every run).
- Conductor uuid VALUES: 0 churn (previously every conductor in
every project, since none have a persisted uuid).
- 6 of 8 projects are now byte-for-byte identical (md5) across all 5
runs. The remaining 2 (industrial.qet, m_000.qet) differ only in
the handful of element uuids covered by the known Element residual
above -- confirmed by checking those uuids specifically, not
inferred.
- Element/conductor counts before and after resave match exactly on
every project (no data loss from the sort).
Fixes#754.
ElementsPanelWidget::duplicateDiagram() round-trips the folio through XML
and then gives the copied *elements* fresh uuids, because element.uuid is
the primary key of the project database and a duplicate silently fails to
insert. Conductors now have the same problem and needed the same loop:
conductor.uuid is likewise a primary key, its insert is a plain INSERT
rather than INSERT OR IGNORE, and a failure only reaches qDebug(). Without
this, every wire on a duplicated folio is missing from the wiring list and
from the per-element wire count, with nothing shown to the user.
Verified against the real schema: inserting the same conductor uuid for a
second folio fails with "UNIQUE constraint failed: conductor.uuid", leaving
one row where two were expected.
Also harden the uuid read in Conductor::fromXml(). The default argument of
QDomElement::attribute() is evaluated whether or not the attribute exists,
so a uuid was minted for every conductor on every load and thrown away; and
the default only applies when the attribute is *absent*, so a present but
empty or malformed uuid="" parsed to a null QUuid rather than a fresh one --
and null uuids collide with each other exactly as duplicates do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Slice 1 of discussion #503 (from-to wiring list built on projectDataBase).
Conductor is the one item type on a diagram without a stable identity
of its own -- Element and Diagram both have a uuid, Conductor didn't.
This is the prerequisite the wiring-list tables need: a conductor
table keyed by uuid, the same way the existing element table is keyed
by Element::uuid().
- Conductor gets a QUuid m_uuid, generated in the constructor, with
uuid()/newUuid() accessors mirroring Element's exact pattern.
- toXml()/fromXml() read/write a "uuid" attribute the same way
Element already does, including the same generate-on-missing
fallback (QUuid(e.attribute("uuid", QUuid::createUuid().toString())))
for projects saved before this change.
- PasteDiagramCommand::redo() calls newUuid() on every pasted
conductor (content.conductors(), all three categories), mirroring
the existing per-element newUuid() call right above it -- otherwise
copy-paste would duplicate a conductor's uuid.
Backward compatibility: Conductor::valideXml() doesn't require the
"uuid" attribute, so old files parse unchanged. Verified by opening a
genuinely pre-uuid project (examples/industrial.qet, 150 folios, 671
conductors, legacy integer terminal1/terminal2 references with no
uuid attribute at all) -- loads and renders correctly, gets uuids
assigned on load, and those uuids are stable across a second
load/save cycle (byte-identical uuid values). Verified paste
separately: copying a selection with conductors and pasting produces
distinct new uuids for every pasted conductor, none colliding with
the originals or each other.
clazy is a compiler plugin which allows clang to understand Qt
semantics. You get more than 50 Qt related compiler warnings, ranging
from unneeded memory allocations to misusage of API, including fix-its
for automatic refactoring.
https://invent.kde.org/sdk/clazy
Add a combo box in the tool bar of diagram editor
to quickly change the size of the graphics handler item.
The sarto commit :D
NOTE
only available for diagram editor, element editor will
come later.