mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-26 20:04:12 +02:00
remove-doxygen-lfs
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
46c35d2297 |
Let a script query the project database
Every structural question this API could answer, it answered by walking live objects. The project builds a SQLite database that already knows most of them, and nothing outside the application could reach it. tables() what is queryable, tables and views query(sql) rows, one object per row queryError() why the last one returned nothing This is not a new door. QElectroTech already ships a "Requête SQL personnalisée" box in the element-query dialog where a user types arbitrary SQL, guarded by projectDataBase::isReadOnlySelect(); query() goes through projectDataBase::newQuery(), which applies that same rule and returns the same rejection message. A script gets what a user already has, and neither can write: DELETE, UPDATE and a chained "SELECT 1; DROP TABLE" are all refused before reaching SQLite. An empty result and a failure are told apart. query() returns no rows for both, so queryError() carries the reason -- a refusal, or SQLite's own message for a bad column -- and is empty when the query simply matched nothing. Conflating those is how a silent typo in a column name becomes "there are no such elements". No updateDB() before querying, and that is a measured decision rather than an omission. A script that has just edited something is the expected caller, so a stale cache was the obvious hazard; but projectDataBase maintains itself incrementally through addElement(), elementInfoChanged(), addConductor() and the rest, which the undo commands behind every edit already call. Tested both ways on the cases most likely to go stale -- an element added and labelled, a conductor property changed -- each queried immediately afterwards through both the table and the view. Identical counts with the rebuild and without it, and updateDB() repopulates every table, so calling it per query would have been real cost for no benefit. The comment says so, so it is not added back on the assumption it must be needed. The views are the surface to depend on: element_nomenclature_view, project_summary_view and wiring_list_view exist to be queried. The tables are how the cache is arranged today and a column may move -- which is why tables() lists both and the header says which is which. Verified against examples/industrial.qet, the largest shipped project: 618 elements counted, the busiest wire numbers ranked (0VDC 93 times, 24V2 64), and duplicate element labels found by GROUP BY ... HAVING -- V6 seven times, V5 six -- which is a design-rule question no tool here could previously ask. Qt 6.10.2, ctest matches master. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2adc58c1c0 |
Let a script add the text and shapes a folio carries
The drawing furniture beside the circuit: a free-standing note, a line,
a rectangle, an ellipse, a polygon.
texts() addText() setTextContent() setTextColor()
setTextRotation() deleteText()
shapes() addShape() deleteShape()
Added with the same AddGraphicsObjectCommand the corresponding GUI tools
use, and changed through the plainText/color/rotation properties those
items already publish, so a script's note undoes like a hand-placed one.
These are addressed by index into a listing sorted by position, reading
order, because they have no better identity: unlike an element they carry
no uuid, and unlike a conductor they have no terminal to be named by.
Position is what they have and it persists, so the ordering survives a
save and reload -- verified by listing before and after, including a
rotated text whose bounding box moves. It does not survive adding or
deleting one: indexes after that point shift the way a list's do, which
is why texts() and shapes() exist rather than a caller keeping a handle.
The sort is on sceneBoundingRect(), not pos(). A QetShapeItem keeps its
geometry in its line/rect/polygon and leaves pos() at the origin, so
sorting on pos() put three shapes drawn in three different places all at
(0, 0) and made every shape index refer to whichever the set yielded
first -- which is what the first version of this did, and the test that
caught it was asking for three shapes and getting index 0 three times.
Path is deliberately not offered: it is built by successive clicks and
has no two-point form to give here.
Verified headlessly: three texts added bottom-up and listed in reading
order, edited, recoloured, rotated, one deleted; three shapes added,
listed with their real geometry, the middle one deleted and the right one
gone; unknown shape name, invalid colour and out-of-range index all
decline with a reason. Saved, reloaded, both listings identical.
Qt 6.10.2, build clean, ctest matches master, qet-lint clean on the
generated project, qet-coherence-check clean on the example corpus.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
82262c5980 |
Let a script number a conductor and link a cross-reference
Two gaps left over from the drawing verbs. A script could create a
conductor but not say what it was -- no number, colour, section or
formula -- and could not link a master to its slave, although
LinkElementCommand has been there all along and nothing bound it.
conductors() what is on this folio, and how to address it
conductorProperty()
setConductorProperty() num, formula, function, bus, cable,
tension_protocol, conductor_color,
conductor_section, color, text_color
elementLinkType() simple / master / slave / next_report / ...
linkedElements()
linkElements() two folio indices: a master and its slave are
normally on different folios
unlinkElement()
The property names are the ones the .qet file uses for the same fields,
so what a script sets is what a reader of the file sees rather than a
third spelling invented here.
A property is applied to every conductor of the same electrical
potential, not to the one conductor named. That is the rule the
application already follows -- SearchAndReplaceWorker pushes one
QPropertyUndoCommand per conductor of relatedPotentialConductors()
inside a macro -- because a wire number describes a potential, not one
drawn segment; setting it on one and leaving the rest of the potential
disagreeing would produce a file no GUI action could have produced.
Linking asks LinkElementCommand::isLinkable() rather than re-deriving
its rules, so a script cannot make a link the GUI would refuse: master
to master, a PLC master to a non-PLC slave, a next-report to another
next-report, or anything to an already-taken target.
A conductor is addressed as "the conductor on terminal i of element U".
It has no identity of its own to use instead: conductors carry no
persisted uuid, and the terminal1/terminal2 ids in the file are
folio-scoped integers QElectroTech renumbers on every save. Since the
change is potential-wide, any terminal of the potential names it equally
well, so in practice a potential is addressed from one of its leaves; a
terminal carrying several conductors names none of them and is refused
rather than guessed at.
Verified headlessly. Conductor: num, section and colour set from one end
of a potential and read back from the other, saved and reloaded, present
in the XML. Propagation shown to discriminate, which took two tries --
the first attempt wired A.0-B.0 and B.1-C.0 and saw no propagation,
correctly, because a coil's two terminals are opposite ends of the coil
and not one potential. Wiring a real hub at A.0 instead, a number set
via the B leaf appears on the C conductor too, in memory and in the
saved file. Cross-reference: a master on one folio linked to a slave on
another, linkedElements() agreeing from both ends, surviving save and
reload with link_uuid written on both folios; master-to-master,
self-link, unlink and relink all behave. Unknown property, invalid
colour, bare terminal and ambiguous terminal all decline with a reason.
Qt 6.10.2, build clean, ctest matches master, qet-coherence-check clean
on the example corpus, qet-lint clean on the generated projects.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
c740cdf1ac |
Let a script wire, label and rotate, not only place
The scripting API (bugtracker #162) could place an element and move it, and could count conductors but not make one. So a script could put a coil and a motor on a folio and had no way to connect them, which is most of what drawing is. This adds the missing verbs: addConductor() wire terminal i of one element to terminal j of another rotateElement() setElementInfo() any information key setElementLabel() the label key, by name, since it is the one people want addFolio() setFolioTitle() elementUuids() what is on this folio elementName() elementTerminals() which terminal index is which, before wiring it Each goes through the command the GUI already uses, so a script's edits undo like manual ones and reach the project database the same way: ConductorCreator (the drag-a-rectangle-over-terminals path, which is what makes a new conductor inherit an existing potential's properties and join auto-numbering), ChangeElementInformationCommand, QETProject::addNewDiagram(), ChangeTitleBlockCommand. rotateElement() pushes the same QPropertyUndoCommand on "rotation" that RotateSelectionCommand pushes for an Element, rather than RotateSelectionCommand itself, which works on the diagram's selection and would mean rewriting the user's selection to rotate one element. Terminals are addressed by index, not uuid. Terminal::uuid() is a property of the catalog .elmt definition: empty for most of the installed base, and where present, identical across every instance of that element -- two coils of the same type placed side by side have byte-identical terminal uuids, so a uuid cannot say which coil's A1 is meant. elementTerminals() exists so a script can see the indexing instead of guessing it. The one real hazard is that ConductorCreator asks the user which potential to inherit from when the two terminals sit on two different existing ones, and it asks with a plain modal QDialog that QET::QetMessageBox's non-interactive mode does not cover -- so under headless --run there is nobody to answer and the call never returns. Measured: with the check removed, that one call hangs until killed; with it, it declines in 0.4 s. addConductor() therefore refuses that case, the same way and for the same reason addElement() already refuses the import-conflict dialog. To make that check without duplicating the condition, existingPotential() becomes static over an explicit terminal list and ConductorCreator gains a public needsPotentialChoice() predicate. Behaviour of the GUI path is unchanged; setUpPropertieToUse() passes m_terminals_list to the same code it called before. Verified headlessly against a copy of examples/ArduinoLCD.qet: new folio titled, two coils placed, wired, labelled, an info key set and the element rotated; saved, reloaded, and the conductor, label, title and rotation (persisted as orientation="1") all read back. Re-saving the result is byte-identical. qet-lint clean on the generated project; qet-coherence-check clean on it and on the 24-project example corpus, and shown to report 9 findings on a deliberately broken copy of the same file, so the clean result discriminates. Qt 6.10.2, ctest identical to master (the 61 failures are the vendored KDE ECM suite, present on both). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4a83521306 |
Fix scripting review findings: import, save, backups, timeout
Addresses scorpio810's review of PR #891: - addElement() now imports the element into the project's own embedded collection first (QETProject::importElement()), same as the drag-from-collection-panel path -- otherwise the saved .qet referenced a definition outside the project, missing on another machine. Also found and fixed while wiring this up: ElementsLocation::setPath() forces any path to embed:// once a non-null project is passed, so the unconditional ElementsLocation(locationPath, m_project) this method used before silently broke every common://custom:// call. Refuses on an import-collision case that would otherwise reach QETProject::importElement()'s own modal ImportElementDialog, with nobody there to answer it in a script. - addElement()/setElementPosition()/moveElement()/deleteElement() refuse on a read-only project, matching their GUI equivalents. - save() goes through QETProject::write() when no path is given (read -only handling, saveddate/savedtime, QSaveFile via writeXmlFile()) and QET::writeXmlFile() directly for an explicit output path, instead of a plain QFile that skipped all of that. - Interactive "Run Script...": exports briefly disable project backups around the temporary QETProject CLIExport::run() opens on the same file, so it doesn't race the real open project's own KAutoSaveFile. Restored right after -- the headless --run path is untouched, since it depends on backups staying off for the whole run. - A watchdog thread now calls QJSEngine::setInterrupted() after 30s, so a runaway script (`while(true){}`) can't freeze the GUI or hang a CI job forever. First version used sleep_for() and blocked every run, fast ones included, for the full 30s on join() -- caught by testing a one-line script, fixed with wait_for() on a condition variable so a script that finishes early wakes the watchdog immediately. - Interactive script errors now also show a QetMessageBox, not just stderr (invisible on Windows). - Ran update_translations (lupdate) to pick up the 37 strings this feature had not yet added to the .ts files. Not applied: wrapping the whole script run in one undo macro. It would make the script's own qet.undo()/qet.redo() calls silent no-ops for the run's duration -- QUndoStack ignores undo()/redo() while a macro is open -- which would break that already-shipped, explicitly requested capability to get one convenience Ctrl+Z instead. Verified: Qt 6.10.2, builds clean, ctest 6/6. Ran each fix against a real project: addElement() on a common:// path now succeeds and the saved file embeds the definition (embed://import/...); read-only project refuses addElement(); save("") and save(otherpath) both produce a correctly embedded file; `while(true){}` under --run is interrupted at 30s where it previously hung forever, and a normal script now exits in ~0.3s instead of blocking for the full timeout budget. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
0920e82188 |
Add geometry editing, undo integration, and navigation to scripting
Extends the scripting surface from the previous commit with exactly the three things explicitly scoped out there, per follow-up direction: editing geometry, undo integration, and driving the GUI -- the last one narrowed to select/zoom/message after discussion, since "invoke any menu action by name" would let a script trigger a modal QDialog::exec() with nobody there to dismiss it, the same hang class investigated for bugtracker #882. ## New capabilities - addElement/setElementPosition/moveElement/deleteElement, through the same undo commands the GUI itself uses: AddGraphicsObjectCommand (the same one drag-from-collection-panel placement uses), QPropertyUndoCommand on the standard `pos` property, and DeleteQGraphicsItemCommand (refuses a non-deletable terminal, same as the Delete key). - undo/redo/canUndo/canRedo against the project's real QUndoStack -- the same one QETDiagramEditor's Ctrl+Z is wired to via undo_group.activeStack(), not a parallel mechanism. - selectElement/deselectAll (scene state, no view required -- works headless), zoomFit/zoomToContent/zoomReset (need the active DiagramView, so false headless where there is nothing to zoom), and showMessage (a modal QET::QetMessageBox::information -- safe headless because non-interactive mode is already on for the whole process before any script runs). ## Two real bugs caught by testing this, not assumed away 1. save() was still going through the same reopen-from-disk path as every export method: it opened a *second*, unmodified copy of the project from its file on disk and rewrote that. addElement() and friends operate on the live in-memory project, so nothing they did ever reached the saved file -- an element counted correctly in memory and then silently vanished from the output. Fixed by having save() write m_project->toXml() directly, the only method that touches the live instance rather than a fresh copy of the file. 2. A script calling setElementPosition() then moveElement() on the same element produced a saved position that didn't match either call, and undo/redo didn't step through them independently. Traced to QPropertyUndoCommand::mergeWith() (pre-existing, not new): consecutive commands on the same object+property merge when their text() also matches, and both calls build the identical "Déplacer %1" text for a given element -- exactly the same collapsing dragging an item repeatedly gets. Not a bug in the new code; a wrong assumption in the first test. Re-verified against the correct, merge-aware expectation: add -> merged move -> undo (back to first position) -> undo (element removed) -> redo (element back) -> redo (merged move reapplied) landed at the exact predicted final position, read back from the saved XML. ## Verified Qt6, build clean from a fresh reconfigure, ctest 6/6. - Headless: addElement returns a real uuid and the count updates; select/set-position/move all report correctly; the merge-aware undo/redo/save round trip above, confirmed against the saved file's actual XML, not just in-memory counters. - Corpus: the existing read-model smoke script re-run against all 24 shipped example projects on the fixed binary, 0 failures. - zoomFit correctly returns false headless (no view to act on), confirming the "narrowed GUI-driving" scope holds in code, not just in the doc comment. - GUI: running the add-only script via "Exécuter un script..." marked the project [modifié] in the title bar, the same change-tracking path a manual edit goes through -- consistent with the undo command actually being pushed onto the project's real stack rather than some side channel invisible to the rest of the application. Refs #162. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
eba258f6cd |
Add JavaScript scripting: --run and "Run Script..." (bugtracker #162)
Following up on my own comments there: a deliberately small, mostly read-only scripting surface, exposed to scripts as a single global `qet` object (QetScriptApi) built on QJSEngine rather than an embedded Python interpreter -- no new toolchain to package (QJSEngine ships in every Qt SDK QET already targets, via the Qml module), no GIL, no version pinning, automatic reflection of the QObject-derived core classes' own methods with no hand-written binding layer. ## What a script can do - Read the model: project title, file path, folio count/titles, element/conductor counts per folio. - Trigger the same operations the --export-* CLI flags already do (pdf/png/svg/cables/wires/bom/wiring/nets/links/info), plus set-titleblock and save -- thin wrappers around CLIExport::run(), reusing its already-tested logic rather than duplicating it. Deliberately NOT in this version: creating or editing diagram geometry, undo integration, driving the GUI. All explicitly out of scope per the discussion on #162. ## Two entry points, both built and tested - `qelectrotech --run script.js project.qet` -- headless/CI. - Projet > "Exécuter un script..." -- an interactive macro against the currently open project. Export/save calls act on the project's file on disk (see QetScriptApi's class comment for why), so unsaved GUI edits aren't visible to the script; save first if that matters. ## Optional dependency, not a hard requirement Qt::Qml is probed the same way QtPdf already is in this codebase: QUIET, non-fatal, behind a QET_HAS_SCRIPTING compile definition. A build without it compiles and links identically; the CLI flag and menu action are simply absent (main.cpp) or compile to a clear "not available" stderr message rather than silently disappearing (qetscripting.cpp), matching the existing QtPdf pattern rather than introducing a new one. One real bug caught building this, not assumed away: my first pass conditionally excluded the new source files from QET_SRC_FILES behind `if(QET_HAS_SCRIPTING)` inside qet_compilation_vars.cmake -- but that file is included before QET_HAS_SCRIPTING is set in the top-level CMakeLists.txt, so the variable didn't exist yet at that point and the files were silently never compiled, only caught by an undefined-symbol link error. Fixed by following the QtPdf file's own precedent: compile the files unconditionally, guard their Qt::Qml-dependent content internally instead. ## Verified Qt6, build clean, ctest 6/6. - Headless: a script reading project/folio/element/conductor counts, calling exportInfo() and exportPdf() against a real project -- correct JSON, a real single-page PDF confirmed with `file`. Error paths: a thrown script exception reports file:line:message and exit 1; missing script/project arguments exit 2 (matching CLIExport's own usage-error convention); a missing project file is reported and does not hang. - Corpus: the same read-model script run against all 24 shipped example projects, 0 failures. - GUI: "Exécuter un script..." opens a real file dialog filtered to *.js, running the picked script against the live open project produced the exact expected JSON export file, and the application was still fully responsive afterward. Refs #162. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |