Skip to content

DMXRouter 1.11.2

Latest

Choose a tag to compare

@fiverecords fiverecords released this 25 May 18:49
f6eb146

The headline feature of v1.11.2 is MVR export from the Fixture Check pane — DMXRouter can now write a complete .mvr archive (GeneralSceneDescription.xml + bundled GDTFs) from any patch loaded in the Fixture Check tab. Combined with the RDM discovery and RDM → Fixture Check pipeline, this closes the loop for a commissioning workflow that lives entirely inside DMXRouter: walk on stage, scan the rig over RDM, send the discovered devices to Fixture Check, optionally override-and-test each fixture to confirm wiring and addresses, then export an MVR for import into a lighting console, a previz tool, or a 3D-visualisation pipeline. The other use case the export covers is the relay path — import an MVR from a design tool (Vectorworks, MA3, Capture), edit fixture addresses or modes inside DMXRouter, and export it back out for the next step in the workflow with all the original non-fixture scene content preserved: trusses, supports, scene objects, focus points, group containers, AUXData symbol/class definitions and any UserData from other providers all round-trip verbatim alongside the fixtures DMXRouter actually understands, with the original layer organisation intact. If MVR-xchange is enabled and peers are joined, the export dialog can also push the freshly-written file to those peers in the same gesture via a built-in checkbox — no separate hop through a USB stick or an MVR share dialog.

v1.11.2 also has two primary focus areas for daily-use workflows. The first is the node port configuration workflow — Apply now diffs each row against the last ArtPollReply and only sends the per-port settings that have actually changed, pending edits are visible at a glance with an indicator on the port-number cell, and editing one field no longer freezes the rest of the table from refreshing as new poll replies arrive. The three changes together aim to make targeted port edits during a live show predictable: the operator can tell what they've modified, what will be sent on the wire, and which other rows are still tracking the node's reported state. Reported on GitHub against v1.11.1.

The second focus is the GDTF assignment workflow — the "Assign GDTF manually" dialog gets a full overhaul into a two-pane catalogue/modes view with online-library access built into the bottom of the dialog, mode selection in the same gesture as file selection (so commissioning a fixture is one step instead of two), and correctness fixes that surface ambiguities and outright misses through dedicated status-bar indicators. The library matcher also gets smarter at picking between same-footprint modes by scoring against the RDM device's slot labels, and a new ESTA-ID fallback lets RDM devices resolve against the library even before their MANUFACTURER_LABEL has been fetched. Persistent decisions now cover any fixture identity, not just placeholders. Reported across multiple sessions during commissioning of touring rigs where two or three GDTFs of the same fixture (different uploaders, different revisions, different mode sets) live in the library and the operator needs to pick a specific one — the legacy single-column picker collapsed those into visually identical rows.

The rest of the release rounds out daily-use workflows and platform robustness. The RDM panel gains two finding-fixtures aids: a label search bar that filters the device tree by the RDM DEVICE_LABEL, and a visible highlight for fixtures newly discovered during the last Thorough Discovery run; the PID Browser tab is now a draggable splitter so its parameter list doesn't collapse to a single row on low-resolution screens. The PSN map adds a floor-plane toggle so trackers using the Z-up convention render the same way as PSN-spec Y-up trackers. The Fixture Patch tree gets a Change Fixture ID action on its right-click menu that renames one fixture or renumbers a whole selection consecutively from a starting value, useful for repairing MVRs that arrived with missing or wrong FIDs and for numbering fixtures added via RDM discovery or the manual-add dialogs. A long-standing correctness bug in the RDM-to-Fixture-Check pipeline is fixed: sACN-port-hosted devices now land at the operator's expected universe instead of one off, and the patch-linkage cross-reference matches the same fixture across both naming conventions. Network handling tightens with strict per-NIC isolation for sACN multicast subscriptions, removing fallback paths that quietly routed traffic onto cables the operator hadn't enabled, and the sACN sequence-error counter no longer false-positives once per second on universes coming from ETC consoles (per-address priority packets were consuming a sequence number on the wire that the stats tracker wasn't accounting for). The Stats & Log viewer fixes two GUI-freeze paths — bursty subsystem logging and filter changes used to block the main event loop for several seconds at a time. And the first interface refresh on Linux no longer blocks the UI on a synchronous nmcli query for friendly interface names.

MVR export from Fixture Check

  • A new "Export MVR…" button on the Fixture Check toolbar writes a complete .mvr archive of the currently-loaded patch. Click opens an Export MVR dialog with a destination-file picker (defaults to Documents with a timestamped filename, remembers the last directory the operator chose across sessions), a layer-name field (defaults to "DMXRouter Export" — the single layer the exported fixtures will live in inside the archive), and an optional system-note field that lands in the MVR's UserData section as a human-readable tag for the operator on the receiving side. A live preview at the bottom of the dialog shows fixture count, unique GDTFs that will be bundled, estimated archive size, and any warnings (fixtures skipped for not having a GDTF assigned, GDTFs the library couldn't locate). Clicking Export builds the archive, writes it to disk, optionally pushes via MVR-xchange (see below), and reports a summary. The archive is MVR 1.6 conformant (DIN SPEC 15801) — readable by every major lighting console, previz suite, and CAD tool that supports MVR import.

  • An optional "Also send via MVR-xchange after writing the file" checkbox in the Export dialog publishes the freshly-built archive to MVR-xchange peers in the same gesture. The checkbox is visible only when the MVR-xchange service is bound (the normal case); it's pre-checked when the service is enabled and peers are joined, pre-checked with an explanatory tooltip when the service is enabled but no peers are currently joined (the MVR is still committed locally so peers can request it when they connect later), and shown disabled with an explanatory tooltip when the service is turned off. Behind the scenes, the export hands the archive bytes to the same publishing path Phase 2 of MVR-xchange used in v1.11.1 — fresh FileUUID, MVR_COMMIT broadcast to joined peers, automatic cold push to peers that haven't joined the inbound listener yet. The dialog's summary message reports both the file write and the MVR-xchange action so the operator gets a single confirmation for the whole workflow.

  • The export bundles each fixture's original GDTF unchanged inside the archive. When a fixture in the patch was matched to a real GDTF in the local library (the normal case after MVR import, after the RDM → Fixture Check resolver picks a library entry, or after a manual GDTF assignment from the patch tree), that GDTF file is read from disk and added to the archive verbatim — no re-parsing, no re-serialisation, so DataVersion / FixtureTypeID / mode tables / wheel definitions / 3D model references all round-trip exactly. Multi-cell parent containers (LED bars, pixel matrices) are emitted at the top level; their cells are not duplicated as standalone fixtures because the console expands them itself from the GeometryReference in the parent's GDTF.

  • Fixtures without a GDTF assignment (typically RDM discoveries where the library matcher returned no hit, or rare MA2-library imports without a GDTF counterpart) are flagged in a pre-flight prompt before the Export dialog opens. The prompt lists each affected fixture with its operator-assigned name and manufacturer/model so the operator can locate them, and offers three actions: "Resolve in Patch tree…" jumps the focus to the patch tree so the operator can right-click each fixture and use the new Assign GDTF Manually picker (with online-library Browse for one-step download); "Continue and skip" proceeds with the export omitting the unresolved fixtures entirely (no <Fixture> node is emitted for them, and they don't contribute to the archive's bundled GDTFs — the receiving console therefore doesn't see them at all, rather than seeing them with a missing-GDTF reference); "Cancel" backs out without exporting. This replaces the previous silent-skip behaviour where the only signal of a dropped fixture was a small warning line in the export preview.

  • MVRs imported from another tool round-trip through DMXRouter without losing their non-fixture content. Trusses, supports, scene objects, video screens, projectors, focus points, group containers, AUXData symbol/class definitions, and any UserData from other providers are all captured verbatim from the source archive at import time (along with the geometry, texture, and 3D-model files they reference) and re-emitted into the exported MVR alongside the fixtures DMXRouter actually understands. The original layer organisation is preserved too: each fixture lands back in its source layer with its UUID, name, and matrix intact; the receiving console sees the same scene structure the operator originally imported, plus whatever fixture-level edits (address changes, mode swaps, GDTF resolutions) were made along the way. Fixtures added during the DMXRouter session that didn't come from the source MVR (RDM discoveries, manual additions) are collected into a separate "DMXRouter Export" layer appended at the end so they stay distinguishable from the imported set. Note that the pass-through content is in-session only — it's not persisted in the patch's autosave, so an import → close-app → reopen → export sequence falls back to single-layer fixture-only output; for full-fidelity round-trip the export should happen in the same DMXRouter session as the import. The Export dialog's summary and the post-export message both report the pass-through node and aux-file counts so it's clear what got carried across.

  • Importing a second MVR over an existing patch now surfaces the non-fixture trade-off in the Replace / Merge prompt. The prompt that has always asked whether the new MVR should discard the current patch or merge into it gains an extra paragraph and detailed tooltips whenever the existing patch carries pass-through content from an earlier import: Replace discards the existing scene content along with the fixtures; Merge combines both sides — existing AUXData and UserData are kept (first-wins on cross-MVR symbol-table conflicts, with a warning), layer-level non-fixture children from the incoming MVR that don't UUID-collide with the existing ones are appended, and aux files are added without overwriting same-named entries. Each fixture in the merged patch retains its source-layer mapping (the step-2 UUID remap that handles fixture-UUID collisions across the two MVRs is also applied to the layer map so the right fixture still maps to the right layer in the merged patch), so a re-export after a merge puts trusses and scene objects from both MVRs back in their respective original layers, with the fixtures correctly nested alongside.

  • The exported MVR contains the minimum spec-required structure to re-create the patch on the receiving side, no more (when there's no source MVR to pass through — see above). That is: a GeneralSceneDescription root with provider="DMXRouter" and providerVersion="1.11.2", a single Layer named per the dialog field, and one <Fixture> node per fixture carrying its UUID, name, <Matrix> (mm, Z-up, right-handed — identity for RDM-discovered fixtures whose position isn't yet known, preserved verbatim for fixtures imported from a previous MVR), <GDTFSpec> + <GDTFMode>, one <Address break="N">absolute</Address> per patched break (absolute integer DMX address — the form every reference exporter uses and the one third-party visualisers actually parse), plus the mandatory <FixtureID> / <FixtureIDNumeric> / <UnitNumber> triplet. Optional sections that don't apply to a commissioning workflow (Color, Function, DMXInvert flags, custom commands, overwrites, mappings, gobos, the per-channel Protocols block, alignments, connections) are omitted — consoles fill their own defaults for those on import. The XML is locale-neutral (floats always use '.' as the decimal separator regardless of OS locale) and UTF-8 encoded, so an export written on a German Windows machine imports identically into an English-locale console.

Fixture ID editing

  • A new "Change Fixture ID…" action on the patch tree's right-click menu lets the operator rename a single fixture or renumber a whole selection consecutively in one gesture. A small dialog asks for a starting Fixture ID, prefilled with the anchor fixture's current numeric ID if it has one. For a single-fixture selection the dialog acts as a plain rename; for a multi-selection the first fixture in click order gets the entered number and the rest follow consecutively (N, N+1, N+2 …). A live preview at the bottom of the dialog shows the assignment as the operator changes the spin box value — for selections of six or fewer fixtures the full list is shown ("100, 101, 102, 103"), for larger selections the first three and the last are shown with an ellipsis ("100, 101, 102, …, 132") — so the operator can verify the chain before committing. Useful for two workflows that were awkward before: repairing MVRs that arrived with missing or wrong Fixture IDs (most older WYSIWYG exports come through with all FIDs at zero, for example), and giving fresh IDs to fixtures added via RDM discovery or the Add Fixture Manually / Add MA2 Fixture dialogs, which start unnumbered and would otherwise have to be edited externally before export. Numeric IDs are stored in both the MVR FixtureID (text) and FixtureIDNumeric (integer) fields in lockstep so consoles that read either form pick the right value on MVR export.

  • Cells inside a multi-selection are handled correctly by the renumber. Multi-cell containers (LED bars, pixel matrices) appear in the tree with their cells nested underneath, and the cells inherit their Fixture ID from the parent container at MVR export time — assigning an FID to a cell directly would produce an inconsistency the exporter would either silently flatten or surface as a duplicate-FID warning. The renumber dialog skips cells in the input and the preview shows a note explaining the resulting gap in the chain ("(N cell(s) in the selection will inherit from their container — they don't consume a Fixture ID slot.)"), so a selection of [container, cell, container] with starting FID 100 lands the second container at 102 (not 101), and the gap at 101 is documented rather than mysterious.

  • Fixture IDs shared across two or more fixtures are flagged in the patch tree. Mirroring the long-standing DMX address overlap check, the FID column tints red and gets a hover tooltip listing the other fixtures using that ID whenever two or more fixtures end up sharing a Fixture ID. The match follows the wire-format equality the MVR writer uses — numeric form when fixtureIdNumeric is set, case-insensitive trimmed text otherwise — so a console importing the export would see the same conflicts the patch tree flags. Unassigned fixtures (empty FID, numeric zero) are not flagged because the MVR writer's auto-renumber pre-pass gives them fresh unique values at export time, so they can't actually collide on the wire and flagging them would be permanent red noise across every RDM-discovered or manually-added fixture that hasn't been numbered yet. The detection runs as part of the same tree-rebuild pass that catches DMX overlaps, so the warning surfaces immediately after a Change Fixture ID action, an MVR import, or any other operation that affects the patch.

Node port configuration

  • The Port Config tab's Apply button now diffs each row against the last ArtPollReply and emits ArtAddress packets only for the per-port fields that have actually changed, instead of re-writing every port's entire address block on every click. Previously a 4-port node received roughly 21 ArtAddress packets per Apply — one bind-wide net/subnet/swOut update plus five per-port commands (Merge, Direction, Style, Protocol, RDM) for each port — regardless of whether the operator had touched any of those ports. For a show with port 2 mis-addressed mid-set and a single Universe fix to make, the other three ports were being re-asserted at their current values too, which is correct under normal conditions but collides unpleasantly with an upstream tool that just changed one of them. Apply now reads each row's current widget state, compares it against a snapshot of what the node reported on the last poll, and emits one combined ArtAddress for the bind only when net / subnet / per-port universe has actually moved, plus per-port command packets only for the specific fields that changed. A status line below the Apply button reads back the result — Sent 1 packet (1 port touched) for the single-field case, No changes to apply when nothing differs from the last poll, Sent 8 packets (4 ports touched) for a wider edit — so the operator gets immediate confirmation of what hit the wire. The snapshot updates after each Apply to match what was just sent, so clicking Apply twice in a row without further edits is correctly a no-op rather than a double-send. If a row's baseline is somehow missing at click time (it shouldn't be — populate writes it for every row), the row falls back to the previous "send everything" behaviour rather than silently skipping, so there's no regression path where a pending edit could fail to commit.

  • A port row now turns amber the moment any of its fields differs from what the node last reported, so pending edits are visible at a glance before the operator clicks Apply. The port-number cell on the left of each row gets an amber background and a suffix on its number as soon as Direction, Net, Subnet, Universe, Absolute, Merge, Style, Protocol or RDM diverges from the baseline; the indicator clears automatically the instant the operator types the original value back, and also clears once Apply has committed the changes (since the baseline updates to what was just sent). The trigger watches every editable cell in the row, so it lights up whether the change came from typing into a spinbox, switching a combo, editing the Absolute address (which the existing logic decomposes into Net / Sub / Uni transparently), or toggling Protocol — anything the operator could click into the table, the indicator catches.

  • Editing a field in one port row no longer freezes the rest of the table from refreshing as new ArtPollReplies arrive. Previously the entire port table suppressed its 2.5-second refresh whenever any of its cell widgets had focus — necessary to keep an in-progress edit from being clobbered by a poll reply arriving mid-keystroke, but overly aggressive in that it left every other row stale too. If port 2 was mis-addressed and the operator was typing the fix while port 3 was simultaneously being reconfigured by an upstream tool, port 3's row in DMXRouter would lag behind the node's actual state until the operator finished and clicked away from port 2. The refresh now decides row-by-row: only the row whose widget has focus is preserved with its current edit, every other row updates on the normal cadence. If the node's port structure itself changes — bind count, port count per bind — the whole table rebuilds anyway, since the focused-widget pointer might be sitting in a row that's about to disappear.

RDM panel

  • The device tree has a new label search bar that filters the list by RDM DEVICE_LABEL (case-insensitive substring match). On a rig with one or two fixtures the existing tree is easy to scan; on shows with a hundred or several hundred RDM devices spread across multiple gateways and ports, finding "the front truss key 3" used to mean expanding every gateway and squinting at column 0. The bar sits between the tree toolbar and the tree itself with its own full-width input row, and a "N matches" / "no matches" status label to the right so the operator can tell the difference between "rig has no devices" and "filter active, nothing matched the typed string". Hierarchical view hides gateways and ports that have no matching children, and automatically expands the ones that do — so typing into the field surfaces the matching fixtures directly without further clicking. Flat view filters the device list straight. The clear button (the ✕ on the right of the field) drops the filter and restores the full tree. The filter re-applies automatically after each tree refresh, so devices arriving via discovery while the bar is active are filtered immediately — no need to retype. Matches are against the raw operator-assigned label only (E1.20 PID 0x0082, the same value the export-by-port CSV writes); devices that haven't returned a label yet, or simply have none set, will not match, which is the expected behaviour for a label-specific search.

  • Devices found during a Thorough Discovery run are now highlighted in the device tree until the next discovery is triggered. After clicking Thorough Discovery and watching three (or however many configured) passes accumulate fixtures, the result used to be a single count in the status label — "found 7 new device(s) — 42 total" — with no way to tell which seven in the tree were the new ones. The Name column for each newly-discovered device now renders in accent cyan + bold, with a tooltip explaining the highlight and how it clears. The highlight survives tree refreshes, view-mode toggles (flat / hierarchical), and label-search filter changes, so the operator can interact with the tree freely without losing the visual cue. Clicking a highlighted device dismisses its highlight individually — standard "I've seen this one" acknowledgement behaviour familiar from other applications; the remaining new devices stay highlighted until either clicked or until any discovery button is pressed (Discover, Force Full Discovery, or a fresh Thorough), which clears all remaining highlights at once. Cancelling an in-flight Thorough run still highlights whatever devices were already found before the cancel, since the operator may have cancelled because they had enough information and the partial result is still worth surfacing.

  • The PID Browser tab no longer squeezes the Supported Parameters list down to a single row on shorter screens. The tab stacks a parameter list above a Send-RDM-Command form and a Response text area; on a 1080p monitor the layout worked, but the form's Param Data box and the Response box had fixed maximum heights of 50 and 100 pixels respectively, sitting in a flat vertical layout that — combined with section labels, form rows, and the GET / SET button row — added up to roughly 275 pixels of near-fixed content below the list. On a netbook, a half-height window, or any rig running DMXRouter on a 720p laptop screen, that fixed total ate so much of the tab's vertical space that the list (which is supposed to take the remainder) collapsed to one or two visible rows and the tab became effectively unusable for browsing manufacturer-specific PIDs. The tab is now a draggable vertical splitter between the list (top) and the command + response panel (bottom). The list gets ~60 % of the space by default; the operator can drag the divider to reallocate, and the position persists across launches via the same QSettings mechanism that already remembers the device tree's column widths and tab order. The Param Data and Response text edits keep small minimum heights (40 and 60 pixels) so they stay legible at small splitter positions without being able to collapse to nothing.

  • The Self-Tests dropdown on the Sensors tab no longer flickers and closes mid-pick when fresh RDM data arrives for the selected device. Selecting a self-test from the dropdown after clicking Fetch Tests used to be a moving target: every ~400 ms of debounced RDM responses (status messages, sensor updates, anything that triggered the tab-debounce timer) caused the whole tab to re-render, including a clear() + repopulate of the self-test combo. The previous logic correctly saved and restored the selected test number across the rebuild, but the rebuild itself collapsed any open dropdown — so an operator scrolling through a long test list saw the menu vanish every few seconds, forcing a re-click. The combo now checks whether its current item set already matches the device's self-test list (count + per-row test number) and short-circuits the rebuild entirely when nothing has changed. Only the status label below ("N tests available" / "Test #N running") still ticks per refresh so test-execution state stays current. When the test list does genuinely change (new device selected, or a refresh of the descriptions), the rebuild still happens with the existing save / restore-by-data path, now with signal blocking around the transient -1/0 currentIndex flickers.

PSN map

  • The PSN map can now project the X-Y plane as the stage floor for trackers that use the Z-up coordinate convention, in addition to the PSN-spec X-Z (Y-up) default. PSN spec declares +X right, +Y up, +Z depth — so the floor plane is X-Z and Y is height. Some tracking systems (certain BlackTrax setups, CAD-derived motion-capture rigs, anything grown out of a 3D-modelling pipeline) emit data in a Z-up convention instead, where the floor plane is X-Y and Z is height. Feeding those into the default projection puts trackers on a vertical-versus-front-back axis the operator wasn't expecting, with positions that look wrong even though the underlying data is fine. A new "Floor:" combo in the PSN viewer toolbar selects which interpretation the map applies — "X-Z (Y up)" for PSN-spec trackers, "X-Y (Z up)" for the alt convention. The compass in the bottom-right of the map relabels its vertical arm to match (+Z in Y-up mode, +Y in Z-up mode) so the orientation reference stays accurate to what's actually being projected. The selection persists across launches, so a venue's regular convention stays selected without having to re-toggle. Switching forces an auto-fit because the secondary axis just swapped and the operator's old manual centre point no longer maps to the same on-screen position.

sACN reception

  • sACN multicast subscriptions are now strictly bound to the network interfaces the operator selected in the interface manager — with no fallback path of any kind. Previously, the receiver had two rescue paths: when the selected NIC failed the IGMP join, and when no interface was enabled at all, it would silently fall back to the OS default-route NIC. Both paths used to look like a courtesy ("at least the universe reaches the receiver"), but in practice were worse than failing cleanly: the receive-side strict-drop policy added in v1.10.1 enforces IP_PKTINFO interface isolation and discards any packet arriving on an unconfigured NIC anyway, so the universe ended up silent in DMXRouter while the IGMP membership opened by the fallback caused the upstream switch to keep forwarding sACN multicast onto a cable the operator never asked it to. Bandwidth burned for nothing. When network-level isolation is the goal — and that's the entire reason the per-NIC selection mechanism exists — "silently route around the operator's choice" is the wrong default. Failure modes are now surfaced via the console log: an aggregate warning when no interfaces are enabled ("N universe(s) will stay silent until at least one interface is enabled") and a per-universe warning when joins fail on every configured NIC. Both cases self-heal — the join is retried automatically the next time the interface configuration changes.

  • The sACN sequence-error counter in the Stats panel no longer climbs by roughly one per second on universes received from ETC consoles (Eos family, ETCnomad, Element, Ion, and any other ETC source emitting per-address priority). ETC's per-address-priority extension interleaves a 0xDD start-code packet into the regular 0x00 DMX stream every 800–1000 ms — the cadence is set in ETC's own published per-address-priority specification. Per E1.31-2018 §6.7 ("the sequence number is incremented in each successive packet sent"), that 0xDD packet consumes a sequence number on the wire just like a data packet would, so a clean ETC stream looks like 0x00 seq=85, 0x00 seq=86, …, 0xDD seq=130, 0x00 seq=131. DMXRouter already recognised the 0xDD packet as priority data rather than DMX levels — its values were never being applied to outputs — but the stats tracker was returning early for 0xDD without recording the packet's sequence number against the per-source counter. The next regular 0x00 packet was then diffed against the 0x00 from before the 0xDD, looking like a +2 forward gap; the sequence checker treats any forward gap of more than one as a missed packet and incremented the universe's sequence-error counter. Result: a perfectly clean ETC sACN universe reported one false-positive sequence error per second, which is exactly the rate at which ETC emits 0xDD. The tracker now records the 0xDD packet's sequence number without counting the diff as an error, so the next 0x00 diffs cleanly against it and the counter stays at zero on a healthy wire. Non-ETC sources that don't emit 0xDD are unaffected by the change. Note that this fix was developed without an ETC console on hand to reproduce against — if you have one in your rig, the verification is to watch the sequence-error column for a known-clean ETC universe and confirm it stays at zero instead of climbing slowly.

RDM → Fixture Check / linkage

  • Sending an RDM device to the Fixture Check pane, and matching RDM devices against existing patch entries, now respect the gateway port's actual protocol — sACN ports map to the operator's universe directly, Art-Net ports keep the +1 wire-to-patch offset. The RDM tree already showed the correct universe per protocol since v1.11.0 (sACN ports labelled "sACN Universe N" where N equals the wire Port-Address per the Luminex / MA convention; Art-Net ports labelled with the conventional N+1 patch number). The "Send to Fixture Check" pipeline and the patch-linkage cross-reference both hardcoded the Art-Net +1 conversion regardless of protocol, which on an sACN node meant: discovered fixture at sACN universe 5 (PA 5) was injected into the patch at universe 6; a fixture the operator had manually patched at universe 5 was never detected as already-present and a duplicate entry got created; and the linkage panel showed the original manual entry as "Missing" plus the duplicate as an "Extra device" for the same physical fixture. The conversion now routes through a single helper that consults the gateway's current ArtPollReply to determine each port's mode (bind.goodOutput[p] & GO_OUTPUT_SACN), falls back to "any sACN port on the same node" when the exact port-address can't be matched yet (the case right after a fresh port re-address), and defaults to Art-Net when the gateway has timed out of discovery entirely — preserving the codebase's pre-v1.11.2 behaviour for that edge case so nothing regresses. The same helper powers both the factory write path and the linkage match path, so the two can't drift apart again.

Persistent GDTF assignments

  • The "Change assigned GDTF…" decision now persists across re-imports for fixtures the auto-matcher had already resolved to a real GDTF, not only for placeholder fixtures. When importing an MVR or sending an RDM device to Fixture Check, the auto-matcher picks a GDTF from the local library by manufacturer + model name. The operator can override that pick from the patch tree's right-click menu (the action reads "Assign GDTF manually…" for placeholders and "Change assigned GDTF…" for already-resolved fixtures). The override has always been remembered for the current show, and across re-imports for fixtures that came in as unresolvable placeholders. But for fixtures the auto-matcher had resolved — the common case where, say, the matcher picks "MAC Aura XB" and the operator wants "MAC Aura XIP" — the persistent half of the dictionary was silently being skipped: the override worked in the current session, then on the next save-and-reopen the auto-match re-asserted itself and the operator had to redo the correction. The persistent layer now records corrections on any fixture identity, so re-importing the same MVR or rediscovering the same RDM device picks up the operator's preferred GDTF automatically.

  • RDM "Send to Fixture Check" now consults the persistent GDTF dictionary before the library's score-based matcher runs. Previously the dictionary was only read during MVR import; an operator who had pinned a specific GDTF for "Robe Lighting / MegaPointe" via an MVR re-import had to re-pin it again the first time the same fixture surfaced via RDM discovery. The two resolution paths now share one source of truth — a correction made on either side applies to both, in either direction, with no per-source bookkeeping. If the dictionary points at a GDTF that's no longer on disk (operator deleted the file from the library directory), the resolution falls through cleanly to the library and synth fallbacks rather than failing.

Assign GDTF manually — picker overhaul

  • The Assign GDTF Manually dialog now shows the library in a four-column table (Manufacturer / Fixture / Revision / File) with a modes-and-channels preview pane on the right. Previously the picker was a single-column list rendering each entry as "Manufacturer / Model" with the filename hidden in a hover tooltip. Two distinct GDTF files for the same fixture (different uploaders, different revisions, sometimes different mode sets) appeared as two visually identical rows — the operator couldn't tell them apart without hovering each one, and even then had to remember the filename through the click and OK. The new layout breaks the row into discrete columns so the revision suffix (r3010, v2, …) and the filename are visible at a glance, and clicking a row populates the right pane with that file's mode list and channel counts so the operator can confirm the modes line up with what they expect before committing. Sort order is stable across rescans (Manufacturer → Fixture → Filename so duplicates stay grouped consecutively), the filter box searches across all four columns (typing "robe pointe r3010" narrows to the exact revision in one go), and the dialog opens at 920×560 — wider than the legacy single-column popup, sized to fit the catalogue + preview comfortably without truncating either.

  • Selecting a mode in the preview pane pins it as part of the assignment, so picking a GDTF and choosing its correct mode is one gesture instead of two. Previously the picker only let the operator choose the GDTF file; the resolver then picked a mode by auto-matching (exact name → footprint → keyword tiers), and if it picked the wrong one the operator had to dismiss the picker, return to the patch tree, right-click the fixture again, and run Change Mode separately. Clicking a row in the right pane now saves that mode alongside the file pin, the resolver applies it directly (skipping the auto-match tiers), and the mode persists across show save/reload along with the file pin. Leaving the right pane untouched keeps the legacy auto-match behaviour — the mode pin is opt-in. Double-clicking a mode is the fastest path from "open picker" to "done": pick GDTF on left, double-click mode on right, that's it. The currently-effective mode is pre-selected (and bolded) in the right pane when the dialog opens, so the operator sees what's live right now and can re-pin by clicking a different row.

  • Two GDTFs of the same fixture sharing a FixtureTypeID UUID are now distinguishable in the assignment. The library's UUID index used to silently keep only one entry per UUID, so two files with the same ID — manufacturer revisions that intentionally preserve the FixtureTypeID across versions, or re-uploads to gdtf-share.com that copy the original — appeared as a single row in the picker. Even when both rows did render (UUIDs differed but mfr+name matched), the assignment was stored by UUID alone: picking either row in the dialog produced the same resolved file, because the resolver looked up by UUID and the UUID index only knew about the last-indexed file. The picker now stores both the UUID and the file path alongside the override, the resolver opens the specific file the operator selected, and the parse cache is keyed per-file so two files with the same UUID can be parsed and held in cache independently without conflating their modes/channels. Forward-compatible JSON: the new file-path hint is written as an optional field per override, older patches without it keep loading and continue to use the previous UUID-only path; a deleted file (operator removed it from the library directory after pinning) falls back gracefully to UUID lookup so the override doesn't die silently.

  • A "Browse online library…" button at the bottom of the picker opens gdtf-share.com pre-targeted to the placeholder's manufacturer + model, and any GDTF downloaded appears in the picker's catalogue automatically without closing and reopening the dialog. Common workflow: right-click a placeholder fixture, choose Assign GDTF manually, realise the local library doesn't have what's needed, click Browse online library, search and download, watch the new file appear in the picker's left pane with the operator's previous selection preserved, pick it, set the mode, click Assign. The browse dialog is parented to the picker so it stays on top and closes with the picker if dismissed. The library rescan triggered by the download is what drives the catalogue refresh — same path as any other library import, so file integrity / mode parsing / persistent-dictionary lookups all behave consistently with manually-imported GDTFs.

  • The patch tree no longer scrolls back to the top after changing a fixture's GDTF, mode, or any other operation that triggers a tree rebuild. Selection has been preserved across rebuilds since v1.9.7 (UUID-based restore), but viewport scroll wasn't — every patch change slammed the tree back to the top, so the operator who right-clicked fixture #143 in a 200-fixture rig and changed its GDTF assignment then had to scroll back down to keep working with it. Vertical and horizontal scroll positions are now snapshotted alongside the selection and restored after the rebuild completes (deferred to the next event-loop tick so the new scrollbar range has finished computing — a synchronous restore would clamp against the cleared scrollbar range and land at 0 anyway). Applies to every code path that rebuilds the tree: GDTF assignment changes, Change Mode, RDM linkage refresh, sort-header clicks, manual add fixture, and the bulk operations on the Fixture Check toolbar.

GDTF library diagnostics

  • Two new status-bar indicators surface library issues at a glance. An orange "N GDTF ambiguities" button appears when one or more library lookups returned multiple candidate files for the same (manufacturer, model) query during the current session; clicking opens a review dialog where the operator picks the preferred file per ambiguity, and the choice is saved to the cross-show GDTF dictionary so future imports and RDM resolves use the same file automatically. A red "N missing GDTFs" button appears when one or more library lookups returned no match at all; clicking opens GDTF Share Browse pre-targeted at those fixtures so the missing GDTFs can be downloaded in one batch. Both buttons are hidden when their list is empty. The missing-fixtures list resets automatically after a library rescan (typically after a Share download lands); the ambiguities list is intentionally NOT cleared by rescan since adding files to the library can only increase the ambiguity count, never reduce it — the operator's picks invalidate entries individually as they are resolved in the review dialog.

  • A new "View → GDTF resolution log…" debug view shows recent library match attempts and their outcomes. Each line is timestamped and categorised: [exact] for a clean single-file match, [exact-ambiguous] when multiple files matched the same query, [fuzzy score=N] when a suffix-stripped or partial match found a hit, or [miss] when nothing resolved. Useful for diagnosing why a particular fixture ended up with the GDTF it did — especially after importing an MVR with hundreds of fixtures where one or two seem to have picked the wrong file. Capped at 200 recent attempts (oldest dropped when full) and cleared automatically when the library is rescanned, since the reasoning over the old index is no longer valid once the catalogue changes.

RDM device resolution

  • The resolver picks between modes of the same channel count by matching RDM slot labels against the GDTF's channel attributes. When a GDTF declares two modes with the same total channel count — for example "16-bit movement" and "8-bit movement + 8-bit speed" both at 18 channels, or two arrangements of the same effects engine with identical footprint — the previous mode picker took the first by index order, which depending on how the GDTF authoring tool ordered the modes could be either one. The pick now scores each candidate against the RDM device's SLOT_INFO PID response: for each slot the device reports, look up its GDTF Attribute (Pan / Tilt / Dimmer / etc.) and check whether the candidate mode has that attribute at the corresponding offset. The mode with the most matches wins. Falls back to the original first-match behaviour for GDTFs without slot info, or when all candidates tie. Most visible in MA Lighting and ETC fixtures that ship multiple same-footprint modes for protocol-compatibility variants — the right one now lands without the operator having to Change Mode after the resolve.

  • RDM devices that haven't reported their manufacturer label yet can now resolve against the GDTF library using the ESTA-assigned manufacturer ID from the device UID. Previously, sending a device to Fixture Check before its MANUFACTURER_LABEL PID (E1.20 PID 0x0081) had been fetched — slow responders, devices that don't implement the PID at all, or just a click on the device while discovery was still working through its PID auto-probe sequence — meant the library lookup ran with an empty manufacturer string and failed by definition, dropping the device through to the synthetic-dimmer fallback. The resolver now consults a manufacturer-ID lookup table when the device-reported string is missing or too short to match (<3 characters), mapping the 16-bit ESTA ID in the UID prefix (the high 16 bits of every RDM UID, registered with ESTA per E1.20 §5.1) to the canonical vendor name and using that for the library query. The table is loaded from an optional JSON file at %APPDATA%\DMXRouter\DMXRouter\data\esta_manufacturers.json on Windows (~/.local/share/DMXRouter/DMXRouter/data/esta_manufacturers.json on Linux/macOS) — drop a snapshot of the official ESTA registry there to enable the fallback. A template with the format and source URL is shipped alongside the application in resources/esta_manufacturers_template.json. With no file present the fallback is silently a no-op and the resolver behaves identically to v1.11.1, so installations that don't want this stay unaffected.

Stats & Log dashboard

  • The log viewer no longer freezes the entire application when subsystems emit log lines in bursts or when the operator changes the log filter. Two scenarios surfaced during a demo: filtering the log by the Qt category (or any other category, when the buffer holds a couple of thousand entries) used to lock the UI for several seconds while the viewer re-rendered every cached entry one by one; and a Thorough RDM Discovery run would lock the UI for the duration of all three discovery passes, because every "RDM: New device …", "RDM: Probing device …", etc. log line synchronously inserted a small HTML fragment into the QTextEdit in the same thread the discovery was running in. Both shared a root cause — the log viewer rendered each entry the instant it arrived, with no batching and no yielding back to the event loop, so a flood of qInfo lines or a single filter rebuild blocked Qt's main loop until the work was done. Log rendering is now coalesced behind a 50 ms timer: incoming entries are queued, then batched up to 200 at a time into a single insertHtml call, with the event loop running freely between batches. A 2000-entry filter rebuild now spreads across ~10 frame-paced ticks (≈500 ms total) instead of one multi-second freeze; bursty subsystem logging stays in the queue and drains in the background without ever blocking the foreground work that produced it. The user-visible cap on log history (2000 entries, dropping the oldest 25% when full) is unchanged.

Startup (Linux)

  • The first interface refresh on Linux no longer blocks the UI thread on nmcli. Linux builds query NetworkManager to map kernel device names (eth0, wlan0, enp3s0…) to the friendly connection names the operator chose ("Office Wired", "Show LAN", etc.). The very first refresh used to run nmcli connection show --active synchronously with a 2-second timeout, which on a healthy host returns in tens of milliseconds — but on Raspberry Pi targets, on hosts where NetworkManager and systemd are still warming up at boot, or on slow VMs, the call could take its full 2 seconds and freeze the entire app while it ran. The first refresh now kicks off the same query asynchronously (the second-and-later-refresh code path already used the async helper) and continues with whatever the cache currently holds — empty on the very first pass, so interfaces appear briefly with their kernel names. When the async query returns, it updates the cache and re-runs the interface refresh so the friendly names appear with a perceived latency of tens of milliseconds rather than a hard UI freeze. Windows and macOS were not affected and are unchanged.

Keyboard navigation

  • Arrow-key navigation through the universe list in the Universe Monitor and the node tree in Node Config now refreshes the rest of the pane, instead of leaving it stuck on whichever item the operator last clicked with the mouse. Both lists were wired to fire their selection-change handler only on mouse clicks; selecting an item with the keyboard moved the highlight visually but kept the grid (or the right-hand config tabs, in Node Config's case) showing the previously-clicked item's data. The handlers now also trigger on keyboard-driven selection changes, so the operator can step through universes or nodes with the arrow keys and watch each one's contents follow.