1 (edited by hairy_kiwi Yesterday 01:57:47)

Topic: Folio-level Mirror and Flip of Elements — ready for review

Hi Laurent, plc-user and everyone,


I've implemented folio-level Mirror and Flip of elements over the last week, as asked for in a few old threads, here and here, and also wanted by me for a long time (for creating avionics equipment interconnect diagrams). This complements plc-user's earlier work, which added rotate/mirror/flip of graphical primitives within the Element Editor. The key difference and benefit: mirror/flip now also works at folio level, live and non-destructively, on a placed element instance — so there's no longer a need to create and maintain otherwise-identical left/right duplicate .elmt files in the symbols collection. nomicons/smile

It's been tested very thoroughly at code level (qDebug probes through every save/reload cycle) and quite substantially while building the demo .qet and its PDF. I believe it's now ready for review, with a view to merging.

It's available on my fork:
github.com/hairykiwi/qelectrotech-sourc … irror-flip
(branch: folio-mirror-flip, built against: qt6_cmake_joshua)
I don't believe anything in it depends on Qt6, however I've only built it under Qt6.


Attachments
I'd normally attach these directly, but the forum's anti-bot still has me flagged nomicons/unsure
Here's a Dropbox link to the example .qet, its PDF output, and a technical brief (generated with Claude AI):
https://www.dropbox.com/scl/fo/yywangae … v&dl=0


Below is a mostly not-so-technical summary.


What works

Mirror and Flip on placed elements at folio level, in any combination with Rotate (90/180/etc).

Conductors stay correctly connected to their terminals through every transform — a conductor "reset" may be needed to tidy routing, but connections are preserved.

Dynamic element text (including rotated text, and grouped rotated text) survives mirror/flip and, importantly, round-trips correctly through save/reload.

One thing worth noting for backward compatibility: mirror/flip only sets a flag in the target <element> entry in the .qet. I believe older QET versions should simply ignore such flags — so files created in this branch still open in previous versions, and older files load fine here.

The attached demo project (.qet + PDF) steps through it: orientation tracking across all transform permutations, rotated and grouped-rotated dynamic text, conductors-stay-connected, non-dynamic text handling, and a basic industrial-schematic example built from the current QET symbols collection.


Two minor remaining niggles

1. After rotation, an element containing grouped rotated text will initially have that text rendered on screen as overlapping lines (see the screenshot in the Attachments). Following a save/reload cycle the layout self-corrects and stays correct. This is a separate issue from the fixed save-corruption bug (more on that below). I suspect grouped-rotated text inside an additionally rotated (and/or mirrored/flipped) element will arise so infrequently that few people will ever notice it. That said, it's on my list of tracked issues, saved for some rainy day.
2. The mirror/flip feature really wants any element text to be the dynamic text type. Static / unformatted simple text will mirror/flip as a graphic (i.e. it reverses), which is correct behaviour but usually not what you want for a label. Section 3 of the demo highlights this.


The big bug — fixed!

I doubt I could have created this feature without AI coding assistance, but the really substantial issue I leveraged Claude AI + Claude Code for was a save/reload corruption: grouped rotated dynamic text was being serialised through a "visual-preserving detach" — a routine meant for genuinely ungrouping text, which preserves its on-screen position as it detaches. On a mirrored element that routine baked the element's mirror into the child text coordinates, so on reload the mirror got applied twice and the text drifted by one group-width. That's fixed (in commit 05bcba506): now, each grouped child's geometry is serialised directly, in one frame, with no reflection baked in — the mirror stays a live flag, recomputed on load, never stored in coordinates. The mechanism and verification matrix are written up in the attached PDF for anyone who wants the detail.


A couple of related feature ideas

While testing mirror/flip (+ rotate), I found it very easy to lose track of an element's original orientation. That could matter a lot for industrial symbols that either should not, or must not, be mirrored or flipped. So I think these are worth implementing before too long, for convenience and preserving user sanity nomicons/wink

1. A non-destructive transform reset — clear all Rotate/Mirror/Flip on an element back to its original on-disk orientation while keeping any text the user has added.
2. Mirror/Flip during placement, the way Rotate already works while inserting. The non-destructive reset above should work during placement too.

One housekeeping note: my branch carries a small local main.cpp change (an app-name tweak for my own build) that should be ignored/reverted before merge — it's not part of the feature.


I'm happy to walk through any of it, rebase as needed, and split into smaller commits if that helps review.


hairy_kiwi


EDIT: Update on Grouped-rotated text
Since posting, I've been testing the trickier combinations more closely and want to flag two rough edges, both confined to grouped rotated text and both to the live, unsaved on-screen render — the saved file is always correct, and once you save and reopen, the element displays and prints/exports correctly. Persisted data is never affected.

Grouped rotated text can momentarily overlap on screen while rotating an element (at odd 90° steps from the last-saved orientation). This looks like it may be independent of mirror/flip — i.e. possibly a pre-existing rotation rendering issue — and I'm investigating it separately.
Mirror/flip of grouped rotated text on an element that's also rotated (90°/270°) isn't yet transforming correctly in the live view. At 0° it's fine; the rotated combination needs more work, likely the same fix I applied to ungrouped rotated text extended to the grouped path.

Both are low-impact in practice (grouped rotated text is uncommon, the rotated+mirror/flip combination more so), and the feature is fully correct for the common cases. I'll post another update here as and when I have time. Details in the technical brief.

Post's attachments

Attachment icon QET Folio-level Mirror and Flip Technical Brief and Demo files.zip 1.66 mb, 4 downloads since 2026-06-13 

Attachment icon qet_folio_level_element_mirror_flip_demo01.pdf 77.19 kb, 6 downloads since 2026-06-13 

Attachment icon qet_folio_level_element_mirror_flip_technical_brief_02.pdf 32.45 kb, 6 downloads since 2026-06-13 

Re: Folio-level Mirror and Flip of Elements — ready for review

Hi Hamish,

thanks you a lot for your work, I 'm think if you put this feature on master, many guys who happy!

Best regards,
laurent

"Le jour où tu découvres le Libre, tu sais que tu ne pourras jamais plus revenir en arrière..."Questions regarding QET belong in this forum and will NOT be answered via PM! – Les questions concernant QET doivent être posées sur ce forum et ne seront pas traitées par MP !

3 (edited by hairy_kiwi Today 12:47:25)

Re: Folio-level Mirror and Flip of Elements — ready for review

Hi Laurent,

My pleasure nomicons/smile

I'll post the quick follow-up I promised later today, but there's a small issue to attend to first.

While setting up to work on a small fix, I rediscovered today why I'd previously commented out two lines in CMakeLists.txt (as a bit of a hack) to get QET building under Qt6 on macOS. The current qt6_cmake_joshua fails to configure on macOS (and likely Windows): CMakeLists.txt ~line 140 calls install(FILES … DESTINATION ${QET_APPDATA_PATH}) and the MIME-package line, but those path variables are only set in the if(UNIX AND NOT APPLE) block in paths_compilation_installation.cmake — so on macOS they're empty and configure aborts with "install FILES given no DESTINATION". It's gone unnoticed because almost nobody builds Qt6 on macOS. (Linux is unaffected — the variables are set there.)

Proposed fix, instead of my earlier comment-out hack: wrap those two Linux-only install lines in the same if(UNIX AND NOT APPLE) guard the paths already use — no change to Linux behaviour, just skips them where the paths don't exist. I'll send it as its own small PR.

Two things I'd appreciate your opinion on before I do:

1. Happy with the if(UNIX AND NOT APPLE) guard, or would you prefer it handled differently?
2. Should the org.qelectrotech…desktop install line go inside the same guard too (it's also Linux-only, though it doesn't currently error)?

This is what I propose — happy?

cmakeinstall(FILES LICENSE ELEMENTS.LICENSE CREDIT README ChangeLog DESTINATION share/doc/qelectrotech)
install(FILES misc/org.qelectrotech.qelectrotech.desktop DESTINATION share/applications)   # (desktop line — see Q2)
if(UNIX AND NOT APPLE)
  install(FILES misc/qelectrotech.xml DESTINATION ${QET_MIME_PACKAGE_PATH})
  install(FILES misc/qelectrotech.appdata.xml DESTINATION ${QET_APPDATA_PATH})
endif()

(I've also switched the MIME line to use ${QET_MIME_PACKAGE_PATH} for consistency — happy to keep it hardcoded if you'd prefer a minimal diff.)

No rush — I'll keep working in the meantime. Thanks!

---
Version française :
Bonjour Laurent,

Avec plaisir nomicons/smile

Je publierai le suivi rapide que j'ai promis plus tard dans la journée, mais il y a d'abord un petit souci à régler.

En préparant le terrain pour un petit correctif, j'ai redécouvert aujourd'hui pourquoi j'avais précédemment commenté deux lignes dans CMakeLists.txt (un peu un bricolage) afin de pouvoir compiler QET sous Qt6 sur macOS. Le qt6_cmake_joshua actuel échoue à la configuration sur macOS (et probablement Windows) : CMakeLists.txt ~ligne 140 appelle install(FILES … DESTINATION ${QET_APPDATA_PATH}) ainsi que la ligne du paquet MIME, mais ces variables de chemin ne sont définies que dans le bloc if(UNIX AND NOT APPLE) de paths_compilation_installation.cmake — donc sur macOS elles sont vides et la configuration s'interrompt avec « install FILES given no DESTINATION ». Cela est passé inaperçu parce que presque personne ne compile Qt6 sur macOS. (Linux n'est pas affecté — les variables y sont définies.)

Correctif proposé, au lieu de mon bricolage précédent (les lignes commentées) : envelopper ces deux lignes install propres à Linux dans le même guard if(UNIX AND NOT APPLE) que celui déjà utilisé pour les chemins — aucun changement de comportement sous Linux, on saute simplement ces lignes là où les chemins n'existent pas. Je l'enverrai sous forme d'une petite PR distincte.

Deux points sur lesquels j'aimerais ton avis avant de le faire :

1. Es-tu d'accord avec le guard if(UNIX AND NOT APPLE), ou préférerais-tu une autre approche ?
2. La ligne d'installation de org.qelectrotech…desktop devrait-elle aussi être placée dans le même guard (elle est également propre à Linux, bien qu'elle ne provoque pas d'erreur actuellement) ?

Pas d'urgence — je continue à travailler en attendant. Merci !

Re: Folio-level Mirror and Flip of Elements — ready for review

Hi Hamish,

I don't know, I use Qmake to build the macOS packages; I'm not a big fan of CMake...
qmake -spec macx-clang
https://github.com/qelectrotech/qelectr … 64.sh#L127

"Le jour où tu découvres le Libre, tu sais que tu ne pourras jamais plus revenir en arrière..."Questions regarding QET belong in this forum and will NOT be answered via PM! – Les questions concernant QET doivent être posées sur ce forum et ne seront pas traitées par MP !

Re: Folio-level Mirror and Flip of Elements — ready for review

Hi Hamish,

Q1 — yes, if(UNIX AND NOT APPLE) is the right guard. It mirrors exactly what paths_compilation_installation.cmake already uses for QET_APPDATA_PATH and QET_MIME_PACKAGE_PATH, so the logic stays consistent throughout.

Q2 — yes, put the .desktop install line inside the same guard too. A .desktop file is a freedesktop.org standard and has no meaning on macOS or Windows, so skipping it there is both correct and cleaner.

Using ${QET_MIME_PACKAGE_PATH} for the MIME line instead of a hardcoded path is also the right call — consistency with the rest of the file.

Your proposed block looks good to me:

    install(FILES LICENSE ELEMENTS.LICENSE CREDIT README ChangeLog
            DESTINATION share/doc/qelectrotech)
    if(UNIX AND NOT APPLE)
      install(FILES misc/org.qelectrotech.qelectrotech.desktop
              DESTINATION share/applications)
      install(FILES misc/qelectrotech.xml
              DESTINATION ${QET_MIME_PACKAGE_PATH})
      install(FILES misc/qelectrotech.appdata.xml
              DESTINATION ${QET_APPDATA_PATH})
    endif()

Please send it as a separate PR as you planned — easier to review and cherry-pick independently of the mirror/flip feature.

Best regards,
Laurent


Edit: I've just sent the patch; I'm going to run a few builds to test it on Windows CI/CD.

"Le jour où tu découvres le Libre, tu sais que tu ne pourras jamais plus revenir en arrière..."Questions regarding QET belong in this forum and will NOT be answered via PM! – Les questions concernant QET doivent être posées sur ce forum et ne seront pas traitées par MP !