mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-20 07:14:13 +02:00
Document JavaScript scripting (--run, Run Script...)
New page: scripting.md -- the two entry points, every qet.* method (read the model, export/save, edit geometry with real undo, navigate/ message), three worked examples, exit codes, and the same limitations the source's own class doc comment states, kept in sync deliberately. Updates api_reference.md and cli_reference.md, both of which currently say plainly that QET has no embedded scripting language -- true until now. api_reference.md in particular carries a September 2026 correction notice from when this page wrongly claimed Python scripting and a plugin system existed; updated to state precisely what changed (a real JavaScript engine) and what still has not (no plugin system, nothing runs automatically, no project file can carry or trigger a script). Status: describes PR #891, not yet merged into master. Every new/ changed section says so. Pushing now for discoverability rather than waiting for merge, per explicit direction; drop the pending-PR notices once #891 lands. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
+2
@@ -121,6 +121,8 @@
|
||||
|
||||
**[CLI Reference](cli_reference)** — command line usage
|
||||
|
||||
**[JavaScript Scripting](scripting)** — `--run`, geometry editing, undo
|
||||
|
||||
**[Development Roadmap](development_roadmap)**
|
||||
|
||||
**[Vision](vision)** — _proposal, under discussion_
|
||||
|
||||
+16
-12
@@ -14,14 +14,16 @@ How to drive QET from other programs, and what the XML files look like.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **A headless command line** | 13 verbs that open a project and export, inspect or rewrite it without a GUI. The real automation surface. |
|
||||
| **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](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 does not offer:** embedded scripting of any language, a plugin API, a
|
||||
loadable-module directory, or an IPC/automation interface into a running
|
||||
instance.
|
||||
**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.
|
||||
|
||||
---
|
||||
|
||||
@@ -282,12 +284,14 @@ Stated plainly, because this page previously claimed otherwise:
|
||||
|
||||
| Claim | Reality |
|
||||
|---|---|
|
||||
| Embedded Python scripting | No interpreter is linked or loaded. Every Python reference in the source is the `qet_tb_generator` launcher. |
|
||||
| A plugin system / plugin interface | No `QPluginLoader`, no plugin ABI, nothing that loads external code. |
|
||||
| `~/.local/share/QElectroTech/plugins/` and platform equivalents | QET never reads these paths. Creating them does nothing. |
|
||||
| An automation API into a running instance | None. SingleApplication forwards *file arguments* to a running instance; that is all. |
|
||||
| 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. |
|
||||
|
||||
Whether QET should gain a scripting interface is an open question — see the
|
||||
[Vision](vision) page and the
|
||||
[Development Roadmap](development_roadmap). If it does, this page will say so
|
||||
when it is real, and not before.
|
||||
**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](scripting)** for what
|
||||
it actually does.
|
||||
|
||||
+10
-7
@@ -196,6 +196,7 @@ Arguments are **positional**, not `--flag=value`. The shape is always
|
||||
| `--resave` | rewritten `.qet` |
|
||||
| `--set-titleblock` | `.qet` with title-block fields stamped |
|
||||
| `--check-elements` | validates `.elmt` files or a directory of them |
|
||||
| `--run` | run a JavaScript macro against a project — see **[JavaScript Scripting](scripting)** |
|
||||
|
||||
Plus `--show-terminals`, which paints terminal markers into PDF/PNG/SVG output.
|
||||
|
||||
@@ -289,12 +290,15 @@ wait
|
||||
For complex automation:
|
||||
1. Use the export flags above — they cover PDF, images, BOM, wiring lists,
|
||||
nets, cross-references and title-block stamping
|
||||
2. For anything they do not cover, write a program of your own that reads and
|
||||
writes the `.qet` / `.elmt` XML directly
|
||||
3. See **[Automating QElectroTech](api_reference)** for both
|
||||
2. For loops, conditionals, or reading the model to decide what to export,
|
||||
use JavaScript scripting: `--run script.js project.qet`, or "Run
|
||||
Script..." in the GUI. See **[JavaScript Scripting](scripting)**.
|
||||
3. For anything neither covers, write a program of your own that reads and
|
||||
writes the `.qet` / `.elmt` XML directly — see
|
||||
**[Automating QElectroTech](api_reference)**
|
||||
|
||||
QET has no embedded scripting language and no plugin system; automation is the
|
||||
command line plus your own tools.
|
||||
QET still has no plugin system: nothing runs automatically, and no project
|
||||
file can carry or trigger a script. Every script run is explicit.
|
||||
|
||||
---
|
||||
|
||||
@@ -462,10 +466,9 @@ wait # Wait for all to finish
|
||||
|
||||
| Limitation | Workaround |
|
||||
|-----------|-----------|
|
||||
| No CLI option to create or edit a diagram | Edit the `.qet` XML directly — see **[Automating QElectroTech](api_reference)** |
|
||||
| No CLI option to create or edit a diagram *from a flag* | Use JavaScript scripting (`--run`) for edits with real undo, or the `.qet` XML directly for anything scripting doesn't cover — see **[JavaScript Scripting](scripting)** and **[Automating QElectroTech](api_reference)** |
|
||||
| One project per invocation | Loop in the shell; each run is independent |
|
||||
| No DXF export from the CLI | Use GUI export (File → Export) |
|
||||
| No embedded scripting language | Write an external program against the XML |
|
||||
|
||||
> **Corrected September 2026.** This section previously claimed QET had no
|
||||
> headless export and no CLI PDF export, and pointed readers at Python
|
||||
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
# JavaScript Scripting
|
||||
|
||||
> **Status: pending.** Everything on this page describes
|
||||
> [PR #891](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/891),
|
||||
> not yet merged. Nothing here works until that lands — check the PR before
|
||||
> trying any of this against your own build. This page will drop this notice
|
||||
> once it does.
|
||||
|
||||
Read a project's model, export it, and edit its geometry from a script —
|
||||
headless for CI, or interactively against the diagram you have open.
|
||||
|
||||
---
|
||||
|
||||
## Why this exists, and why JavaScript
|
||||
|
||||
[Issue #162](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/162)
|
||||
asked for scripted automation: export derived files (PDF, BOM, cable lists)
|
||||
after every revision, and drive that from CI. The headless export flags (see
|
||||
**[CLI Reference](cli_reference)**) already covered the fixed, one-shot cases.
|
||||
This covers the rest: loops, conditionals, reading the model to decide what to
|
||||
do next, and — new in this PR — editing the diagram itself.
|
||||
|
||||
The engine is Qt's own **QJSEngine**, not an embedded Python interpreter.
|
||||
That was a deliberate choice, not the obvious one:
|
||||
|
||||
- **No new toolchain to package.** QJSEngine ships in the `Qml` module of
|
||||
every Qt SDK QET already targets. Nothing new to install on Windows/macOS
|
||||
packaging, no interpreter version to pin, no GIL.
|
||||
- **The core classes reflect into scripts almost for free**, since they are
|
||||
already `QObject`s — no hand-written binding layer to maintain.
|
||||
- **It is optional at build time**, probed the same non-fatal way `QtPdf`
|
||||
already is. A QET build without the `Qml` module compiles identically;
|
||||
scripting is simply absent.
|
||||
|
||||
---
|
||||
|
||||
## Two ways to run a script
|
||||
|
||||
### Headless — `--run`
|
||||
|
||||
```bash
|
||||
qelectrotech --run script.js project.qet
|
||||
```
|
||||
|
||||
Opens the project, runs the script, exits. Exit codes match the rest of the
|
||||
CLI: `0` success, `1` the script threw or the project failed to open, `2`
|
||||
called wrongly (missing arguments, file not found).
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
qelectrotech --run export_after_revision.js "$1"
|
||||
```
|
||||
|
||||
### Interactive — "Run Script..."
|
||||
|
||||
**Projet → Exécuter un script...** opens a file picker (filtered to `*.js`)
|
||||
and runs the chosen script against the *currently open* project. Useful for
|
||||
one-off macros you don't want to wire into CI.
|
||||
|
||||
---
|
||||
|
||||
## What a script can do
|
||||
|
||||
Every script sees one global, `qet`. Four groups of capability, each with a
|
||||
different scope — read the boundary of each before assuming more:
|
||||
|
||||
### Read the model
|
||||
|
||||
```js
|
||||
qet.projectTitle() // -> "My Panel"
|
||||
qet.filePath() // -> "/path/to/project.qet"
|
||||
qet.folioCount() // -> 3
|
||||
qet.folioTitle(0) // -> "Power"
|
||||
qet.elementCount(folioIndex) // -> 42
|
||||
qet.conductorCount(folioIndex) // -> 58
|
||||
```
|
||||
|
||||
Same numbers `--info` reports, available programmatically instead of parsed
|
||||
from JSON.
|
||||
|
||||
### Export and save
|
||||
|
||||
Thin wrappers around the same `--export-*` machinery documented in
|
||||
**[CLI Reference](cli_reference)** — same logic, same output, called from a
|
||||
script instead of a flag:
|
||||
|
||||
```js
|
||||
qet.exportPdf(output, showTerminals = false)
|
||||
qet.exportPng(outDir, showTerminals = false)
|
||||
qet.exportSvg(outDir, showTerminals = false)
|
||||
qet.exportCables(output)
|
||||
qet.exportWires(output)
|
||||
qet.exportBom(output)
|
||||
qet.exportWiring(output)
|
||||
qet.exportNets(output)
|
||||
qet.exportLinks(output)
|
||||
qet.exportInfo(output) // output may be "" for stdout
|
||||
qet.setTitleBlock(output, ["revision=B", "date=today"])
|
||||
qet.save(output) // output may be "" to save in place
|
||||
```
|
||||
|
||||
All return `true`/`false`.
|
||||
|
||||
**Important, and worth reading twice:** every export method above re-opens
|
||||
the project fresh from its file on disk. **They never see edits a script made
|
||||
with the methods in the next section** — only `save()` writes the project's
|
||||
live, in-memory state. Editing then exporting means calling `save()` first:
|
||||
|
||||
```js
|
||||
qet.addElement(0, "embed://…/relay.elmt", 100, 100);
|
||||
qet.save(""); // write the edit to disk
|
||||
qet.exportPdf("out.pdf"); // now this sees it
|
||||
```
|
||||
|
||||
### Edit geometry, with real undo
|
||||
|
||||
```js
|
||||
qet.addElement(folioIndex, locationPath, x, y) // -> uuid string, or "" on failure
|
||||
qet.setElementPosition(folioIndex, elementUuid, x, y)
|
||||
qet.moveElement(folioIndex, elementUuid, dx, dy)
|
||||
qet.deleteElement(folioIndex, elementUuid)
|
||||
|
||||
qet.undo() // -> false if the stack is empty
|
||||
qet.redo()
|
||||
qet.canUndo()
|
||||
qet.canRedo()
|
||||
```
|
||||
|
||||
These go through the exact same undo commands the GUI itself uses
|
||||
(`AddGraphicsObjectCommand`, `QPropertyUndoCommand`, `DeleteQGraphicsItemCommand`).
|
||||
**Ctrl+Z in the editor undoes a script's edits exactly as it would the
|
||||
equivalent manual ones** — they are, mechanically, the same commands on the
|
||||
same stack, not a side channel.
|
||||
|
||||
One consequence worth knowing before it surprises you: Qt's undo stack merges
|
||||
consecutive commands on the same object and property when their label also
|
||||
matches. `setElementPosition()` and `moveElement()` on the same element
|
||||
produce the same label, so calling them back-to-back **collapses into one
|
||||
undo step** — the same way dragging an item repeatedly does, not one step per
|
||||
call.
|
||||
|
||||
`locationPath` is an element collection path — `embed://…` for something
|
||||
already embedded in this project, `common://…` / `custom://…` for the shared
|
||||
collections. Same paths you see in a `.qet` file's `<element type="…">`
|
||||
attribute.
|
||||
|
||||
`deleteElement()` refuses to remove an element with a non-deletable terminal
|
||||
(a linked master/slave, for instance) — same rule the Delete key follows.
|
||||
|
||||
### Navigate and message
|
||||
|
||||
```js
|
||||
qet.selectElement(elementUuid) // scene state; works headless, no view needed
|
||||
qet.deselectAll(folioIndex)
|
||||
qet.zoomFit() // -> false headless (no view to act on)
|
||||
qet.zoomToContent()
|
||||
qet.zoomReset()
|
||||
qet.showMessage("Done.") // a modal info box; safe headless (auto-dismissed)
|
||||
```
|
||||
|
||||
**Deliberately not here:** triggering an arbitrary menu action by name. A
|
||||
script that could invoke any `QAction` could just as easily open a modal
|
||||
dialog with nobody there to dismiss it — a real, previously-hit hang class in
|
||||
this codebase (see the discussion on
|
||||
[#882](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/882)).
|
||||
Every method above is either non-blocking by construction, or — for
|
||||
`showMessage` — safe under QET's existing non-interactive mode, which is
|
||||
already on for the whole process before any headless script runs.
|
||||
|
||||
### Logging
|
||||
|
||||
```js
|
||||
qet.log("anything you want in stderr")
|
||||
```
|
||||
|
||||
A script has no console of its own; this is how you see output, including
|
||||
under `--run`.
|
||||
|
||||
---
|
||||
|
||||
## Worked examples
|
||||
|
||||
### Export everything after a revision (the issue's original ask)
|
||||
|
||||
```js
|
||||
// bump_and_export.js
|
||||
qet.setTitleBlock("", ["revision=" + qet.projectTitle(), "date=today"]);
|
||||
qet.save("");
|
||||
qet.exportPdf(qet.filePath().replace(".qet", ".pdf"));
|
||||
qet.exportBom(qet.filePath().replace(".qet", "_bom.csv"));
|
||||
qet.log("Exported revision for " + qet.projectTitle());
|
||||
```
|
||||
|
||||
```bash
|
||||
qelectrotech --run bump_and_export.js panel.qet
|
||||
```
|
||||
|
||||
### CI: fail the build if a project won't open or export
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
for f in projects/*.qet; do
|
||||
qelectrotech --run ci_check.js "$f"
|
||||
done
|
||||
```
|
||||
|
||||
```js
|
||||
// ci_check.js
|
||||
if (qet.folioCount() === 0) {
|
||||
qet.log("ERROR: no folios in " + qet.filePath());
|
||||
throw new Error("empty project");
|
||||
}
|
||||
var ok = qet.exportPdf("/tmp/check.pdf");
|
||||
if (!ok) throw new Error("PDF export failed");
|
||||
```
|
||||
|
||||
A thrown error exits `1` and CI fails the step — no separate exit-code
|
||||
plumbing needed.
|
||||
|
||||
### Place several elements from a list, then save
|
||||
|
||||
```js
|
||||
var placements = [
|
||||
["embed://…/relay.elmt", 100, 100],
|
||||
["embed://…/contactor.elmt", 200, 100],
|
||||
["embed://…/breaker.elmt", 300, 100],
|
||||
];
|
||||
for (var i = 0; i < placements.length; i++) {
|
||||
var p = placements[i];
|
||||
var uuid = qet.addElement(0, p[0], p[1], p[2]);
|
||||
if (!uuid) qet.log("failed to place: " + p[0]);
|
||||
}
|
||||
qet.save("");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Errors and exit codes (headless)
|
||||
|
||||
| Situation | Exit code |
|
||||
|---|---|
|
||||
| Script ran to completion | `0` |
|
||||
| Script threw an uncaught exception | `1` |
|
||||
| Project failed to open | `1` |
|
||||
| Missing `script.js` or `project.qet` argument | `2` |
|
||||
| Script or project file not found | `2` |
|
||||
|
||||
An uncaught exception is reported as `Script error: <path>:<line>: <message>`
|
||||
on stderr.
|
||||
|
||||
---
|
||||
|
||||
## Limitations, on purpose
|
||||
|
||||
Same three lines as the class-level scope in the source, kept here so a
|
||||
change of mind shows up in one obvious place:
|
||||
|
||||
- **No arbitrary GUI actions.** See the "Navigate and message" section above
|
||||
for exactly why.
|
||||
- **Export methods don't see unsaved edits** without an explicit `save()`
|
||||
first — see the callout above.
|
||||
- **This is not a plugin system.** No script runs automatically, on open or
|
||||
otherwise; every run is explicit, either a `--run` invocation or a menu
|
||||
click. There is still no loadable-module directory and no way for a
|
||||
project file to carry or trigger a script — see
|
||||
**[Automating QElectroTech](api_reference)** for the file-format
|
||||
boundary this respects.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- **[CLI Reference](cli_reference)** — the export flags this feature wraps
|
||||
- **[Automating QElectroTech](api_reference)** — the file-format and headless
|
||||
export ground this builds on
|
||||
- [PR #891](https://github.com/qelectrotech/qelectrotech-source-mirror/pull/891) — implementation, with the exact tests run against it
|
||||
- [Issue #162](https://github.com/qelectrotech/qelectrotech-source-mirror/issues/162) — the original request and design discussion
|
||||
Reference in New Issue
Block a user