Clone
4
api_reference
ispyisail edited this page 2026-09-16 20:29:49 +12:00

Automating QElectroTech

How to drive QET from other programs, and what the XML files look like.

Correction, September 2026. Earlier versions of this page stated that QElectroTech supports Python scripting and a plugin system, and told readers to install plugins into ~/.local/share/QElectroTech/plugins/ and equivalents. None of that exists. There is no embedded interpreter, no plugin interface, and no plugin directory — QET never reads those paths. The page below describes what is actually there, which is a good deal, just not that.

What QET actually offers:

A headless command line 13 verbs that open a project and export, inspect or rewrite it without a GUI.
JavaScript scripting --run script.js project.qet, or "Run Script..." in the GUI — read the model, export, edit geometry with real undo. See JavaScript Scripting.
XML files .qet and .elmt are plain XML you can read and write with any tool.
An external companion program qet_tb_generator, launched from a menu entry.
C++ source For anyone building QET itself, or a fork.

What it still does not offer: a plugin API, a loadable-module directory, or an IPC/automation interface into an already-running instance. The JavaScript engine above is explicit, one-shot script execution, not a plugin system — nothing runs unless you tell it to.


1. The command line

This is the part most automation should use. A recognised export flag is detected before the GUI starts, so the process runs headless, does the work and exits — it does not open a window and does not hand off to an already-running instance.

qelectrotech --export-pdf myproject.qet out.pdf

Arguments are positional, not --flag=value. The shape is always qelectrotech <flag> <project.qet> <output>, with two exceptions noted below.

The verbs

Flag Output Notes
--export-pdf one PDF all folios, one page each
--export-png a directory one NN_Title.png per folio
--export-svg a directory one NN_Title.svg per folio
--export-bom CSV bill of materials, from the project database — same source as the GUI export
--export-wiring CSV from-to wiring list, one row per conductor — same as menu Project → Wiring list (database) / Export the wiring plan
--export-cables CSV the same logical list, built from the document XML instead
--export-wires CSV conductor numbers — same as menu Project → Export the list of conductor names
--export-nets CSV electrical nets — terminals grouped into potentials
--export-links CSV cross-references, flagging masters and slaves with no link
--info JSON structural dump: per-folio element and conductor counts, unconnected terminals. Writes to stdout if no output path is given
--resave .qet loads and writes the XML back out
--set-titleblock .qet stamps title-block fields, then saves
--check-elements report validates .elmt files — takes a file or directory, not a project

Two extra switches:

  • --show-terminals — paint terminal markers and names into PDF/PNG/SVG output. Off by default, which matches the GUI export dialog. Useful for visually debugging an unconnected pin.
  • --set-titleblock takes trailing key=value assignments after the output path: date=today or an ISO YYYY-MM-DD date, standard title-block keys, or any other key, which is stored as a custom field. Bad assignments fail before anything is written.

Exit codes

0 success · 1 the work failed (project would not open, nothing to export, file not writable) · 2 you called it wrong (missing argument, bad assignment). That makes it safe to use in a CI pipeline directly.

Worth knowing

  • --export-cables and --export-wiring are meant to agree. One is built from the document XML, the other from the project database. Running both and diffing them is a direct check that the database still describes the project — something otherwise only observable through the GUI.
  • Always pass a timeout in scripts. A project saved by an older QET raises a version warning on load; CLI mode answers message boxes instead of showing them, but an unexpected modal is the classic way to hang a headless run forever.
  • Crash-recovery backups are disabled in CLI mode on purpose — the background backup write races process exit.
  • QT_QPA_PLATFORM=offscreen is enough for these verbs. No Xvfb is required.
  • This is also QET's real answer to "print wire labels." There is no built-in wire-marker/ferrule label printer in QET (see the User Manual's Printing Diagrams section for what printing does cover). The documented forum workflow is to export this CSV and feed it into a label printer's own software — Brady, WAGO Smart-Printer and Phoenix Contact's tools all accept CSV directly. A known limitation, reported by a panel builder on the forum: the export is one row per conductor with no quantity consolidation, so N identical labels come out as N duplicate rows rather than one row with a quantity column — expect to de-duplicate in a spreadsheet before printing a large batch. Cembre-style printers that need a .FNR file or support-code column aren't produced by this export at all; that mapping has to be built by hand from the CSV.

Example: a revision pipeline

set -e
qelectrotech --set-titleblock in.qet stamped.qet indexrev=C date=today
qelectrotech --export-pdf stamped.qet "release/rev-C.pdf"
qelectrotech --export-bom stamped.qet "release/rev-C-bom.csv"

See also the CLI Reference.


2. Reading and writing the files directly

.qet and .elmt are XML. Anything that can parse XML can process them — Python's xml.etree, lxml, xmlstarlet, XSLT, whatever you like. This is what people mean when they say they "script QET": the script is an ordinary program of your own that reads and writes the files, run alongside QET rather than inside it.

A real .qet skeleton

<project title="ArduinoLCD" version="0.80">
    <properties>
        <property show="1" name="saveddate">17/04/2021</property>
    </properties>
    <newdiagrams>
        <border rows="8" cols="17" rowsize="80" colsize="60" .../>
        <inset folio="%id/%total" author="" title="" .../>
        <conductors type="multi" .../>
        <report label="%f-%l%c"/>
        <xrefs>
            <xref type="coil" master_label="%f-%l%c" slave_label="(%f-%l%c)" .../>
        </xrefs>
    </newdiagrams>
    <diagram title="LCD 4 DATA" order="1" folio="%id/%total" cols="11" rows="7" ...>
        <elements>
            <element x="390" y="570" z="10" orientation="0"
                     type="embed://import/oznaczenia/tekst_08.elmt"
                     uuid="{52d4b9e8-05c3-49a2-8455-a42ff651200a}"
                     prefix="" freezeLabel="false">
                ...
            </element>
        </elements>
        <conductors> ... </conductors>
    </diagram>
    <collection> ... </collection>
</project>

Points that trip people up:

  • There is no <diagrams> wrapper. <diagram> elements are direct children of <project>, one per folio, ordered by their order attribute.
  • <element> has no id. It is identified by uuid, and its type attribute is a location, usually embed://… for an element copied into the project's own collection.
  • <newdiagrams> holds the defaults for new folios, not the folios themselves.
  • Project-wide element definitions live under <collection>, so a project is usually self-contained.

Full reference: Project XML.

A real .elmt skeleton

<definition version="0.90" type="element" link_type="master"
            width="200" height="70" hotspot_x="100" hotspot_y="35">
    <uuid uuid="{2cbd2b72-1d04-f03d-758f-ce3a14dbb3c5}"/>
    <names>
        <name lang="en">UPS</name>
        <name lang="fr">UPS</name>
    </names>
    <kindInformations>
        <kindInformation name="type">coil</kindInformation>
    </kindInformations>
    <informations>Author: RDS for QElectroTech</informations>
    <description>
        <rect x="0" y="0" width="460" height="590" style="..."/>
        <terminal uuid="{73279019-…}" name="" x="-90" y="10" orientation="w" type="Generic"/>
    </description>
</definition>

Points that trip people up:

  • width, height, hotspot_x, hotspot_y are required attributes of <definition>, and width/height must be multiples of 10 — QET rounds them up otherwise.
  • The uuid is an attribute (<uuid uuid="{…}"/>), not element text.
  • <name> entries are per-language and need a lang attribute.
  • link_type is what makes an element a master, slave or terminal — see Linking elements.

Full reference: Elements XML.

Validate what you generate

Nothing checks a hand-written file until QET opens it, but the CLI will check element files for you:

qelectrotech --check-elements my_elements/

It reports each file as OK, WARN (loads but suspicious — for instance zero terminals) or FAIL (unparseable, wrong root tag, missing bounding box), and exits non-zero on any failure. Put it in CI if you generate .elmt files.

Rules of thumb

  • Close the project in QET before rewriting its file. QET holds its own in-memory model and will overwrite you on save.
  • --resave is a cheap normaliser: load and write back, then diff, to see what QET silently rewrites before you build a tool on an assumption about the markup.
  • Element and conductor identity is by UUID. Copying an element node without giving it a fresh uuid produces two elements that QET considers the same one.

3. qet_tb_generator — what "plugin" means in QET

There is one menu entry, Launch the terminal block creation plugin, and one program behind it. qet_tb_generator is a separate third-party program, distributed on PyPI, that reads and writes .qet files from the outside to build terminal strips.

python -m pip install --upgrade qet_tb_generator

QET launches it by searching a fixed list of locations — QETApp::dataDir() + "/binary/", the current directory, ~/.qet/, and then whatever PATH resolves — and starting it as an ordinary child process. That is the entire integration: no shared memory, no API, no callbacks. If the executable is not on one of those paths, the menu entry cannot find it.

Community support lives on the forum: Scripts · Code/Programming

Writing "a plugin" therefore means writing a standalone program that manipulates the files, exactly as §2 describes. There is no interface to implement and no directory to install into.


4. The C++ source

For changing QET itself, or building a fork:

Orientation points, all in sources/:

Class File Role
QETProject qetproject.cpp a project: folios, collection, load and save
Diagram diagram.cpp one folio (a QGraphicsScene)
Element qetgraphicsitem/element.cpp a placed symbol
Conductor qetgraphicsitem/conductor.cpp a wire
Terminal qetgraphicsitem/terminal.cpp a connection point
projectDataBase dataBase/projectdatabase.cpp the query cache — see The project database

Note the vocabulary gap: the UI says wire, page and symbol; the code says conductor, diagram and element. Grepping for the UI word is the usual way to conclude wrongly that something is not implemented.


5. Things that do not exist

Stated plainly, because this page previously claimed otherwise:

Claim Reality
Embedded Python scripting Still no Python interpreter. Every Python reference in the source is the qet_tb_generator launcher.
A plugin system / plugin interface Still no QPluginLoader, no plugin ABI, nothing that loads external code, and no directory a project or a third party can drop code into.
An automation API into a running instance Still none. SingleApplication forwards file arguments to a running instance; that is all.

One thing that changed: QET now has an embedded JavaScript engine (QJSEngine, not Python) for explicit, one-shot script runs — --run script.js project.qet, or "Run Script..." in the GUI. Not a plugin system: nothing runs automatically, no project file can carry or trigger a script, and it only reads/exports/edits through a narrow, named API, not arbitrary code against QET's internals. See JavaScript Scripting for what it actually does.