First Commit
Sedona sax editor AI yardimi ile yaziliyor
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
# Sedona SAX Tree View Viewer & Program Editor
|
||||
|
||||
A Tkinter desktop editor for Sedona Framework application files (`.sax`). It shows the
|
||||
component hierarchy as a navigation tree, paints the selected folder's children as a
|
||||
wiresheet of draggable component boxes, draws the links between them, and writes box
|
||||
positions back into each component's `meta` property on save.
|
||||
|
||||
Current version: **0.0.0.024** (see [Versioning](#versioning)).
|
||||
|
||||
## Requirements
|
||||
|
||||
Python **3.13 from Homebrew**, which brings Tk 9:
|
||||
|
||||
```bash
|
||||
brew install python-tk@3.13
|
||||
```
|
||||
|
||||
Do **not** run this with `/usr/bin/python3`. Apple's system Python ships Tk 8.5.9, a
|
||||
2010 build that is broken on current macOS: windows open at the right size with the
|
||||
right titles, but no widget contents ever paint. A blank white panel is the symptom,
|
||||
and it looks exactly like a layout bug in this code. It isn't.
|
||||
|
||||
Verify which Tk an interpreter has:
|
||||
|
||||
```bash
|
||||
/opt/homebrew/bin/python3.13 -c "import tkinter; r=tkinter.Tk(); print(r.tk.call('info','patchlevel'))"
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
cd sedona_editor
|
||||
/opt/homebrew/bin/python3.13 run.py
|
||||
```
|
||||
|
||||
On first launch, set **Sedona home folder** in the Preferences menu — the directory
|
||||
containing `manifests/` and `kits/`. Nothing will load until it is set. Preferences are
|
||||
stored in `editor.properties`, read from the current working directory.
|
||||
|
||||
On macOS the menu bar (File / App / Preferences) appears at the **top of the screen**,
|
||||
not inside the window. Tk always hands it to the system menu bar.
|
||||
|
||||
## Module layout
|
||||
|
||||
| Module | Responsibility | Needs a display |
|
||||
|---|---|---|
|
||||
| `app_state.py` | Preferences, runtime state, widget handles, `mark_dirty`/`mark_clean` | no |
|
||||
| `sax_dom.py` | `meta` bit-packing codec, component path resolution | no |
|
||||
| `schema.py` | Kit manifest loading, slot inheritance, slot visibility rules | no |
|
||||
| `wiresheet.py` | Grid, component boxes, link routing and painting | yes |
|
||||
| `sax_file.py` | Open/save, navigation tree, cascading delete | yes |
|
||||
| `main.py` | Widgets, menus, event handlers, dialogs, main loop | yes |
|
||||
| `run.py` | Entry point | yes |
|
||||
|
||||
Imports form a DAG — `sax_dom`/`schema` depend on nothing UI-shaped, `wiresheet` and
|
||||
`sax_file` build on them, `main` wires it together. Event handlers live in `main`,
|
||||
which is what keeps the graph acyclic.
|
||||
|
||||
### Shared state
|
||||
|
||||
Modules reach shared state as **`app_state.<name>`**, always qualified. A bare
|
||||
`from app_state import is_dirty` would bind a copy, so a later rebinding would be
|
||||
invisible to every other module. Every mutation of "the document changed" goes through
|
||||
`app_state.mark_dirty()`, which also applies the unsaved-state pink canvas tint.
|
||||
|
||||
### Testing without a window
|
||||
|
||||
`app_state`, `sax_dom`, `schema` and `wiresheet` import with no display, so the rules
|
||||
can be exercised directly:
|
||||
|
||||
```python
|
||||
import xml.etree.ElementTree as ET
|
||||
import app_state, schema, wiresheet
|
||||
|
||||
app_state.SEDONA_HOME = "../sedona"
|
||||
app_state.xml_root_element = ET.parse("../DDC_8-ATP_PION_17.sax").getroot()
|
||||
schema.load_schema_kits_and_manifests()
|
||||
|
||||
idx = wiresheet.build_linked_slot_index()
|
||||
print(schema.get_slots_for_type("ontrolControl::HvacControl",
|
||||
idx.get("/AHU20_1/TmpCont/HvacCon", ())))
|
||||
```
|
||||
|
||||
## How it reads a Sedona app
|
||||
|
||||
### Manifests
|
||||
|
||||
Each `<kit name="..."/>` in the file's `<schema>` block is resolved to
|
||||
`<sedona home>/manifests/<kit>/<kit>-<checksum>.xml`, falling back to the
|
||||
highest-sorting file in that directory when the checksum is absent. Slots are
|
||||
flattened along the `base` chain, so inherited slots keep their declaration order.
|
||||
|
||||
A kit with no manifest directory is currently **fatal** — the app reports it and calls
|
||||
`sys.exit(1)`. See [Known gaps](#known-gaps).
|
||||
|
||||
### Slot visibility
|
||||
|
||||
A component type can declare far more slots than are worth seeing; `HvacControl` has
|
||||
47. Rows are filtered by these rules, in precedence order:
|
||||
|
||||
| Precedence | Condition | Result |
|
||||
|---|---|---|
|
||||
| 1 | Slot appears in `<links>` (either end) | **Show** — overrides everything below |
|
||||
| 2 | `<bool name="summary" val="false"/>` facet | Hide |
|
||||
| 3 | Runtime slot (no `c` in `flags`) | **Show** |
|
||||
| 4 | Config slot (`c`), unlinked | Hide |
|
||||
| 5 | `o` flag (operator) | Hide |
|
||||
|
||||
Rule 1 outranks rule 2 deliberately: a linked slot with `@summary false` still needs a
|
||||
row for its wire to terminate on, otherwise the wire points at the box's centre.
|
||||
|
||||
Row numbers are the manifest slot ids, so hidden rows leave **gaps** in the numbering
|
||||
(`01, 02, 03, 04, 05, 08, 11...`) rather than renumbering. The numbers stay meaningful
|
||||
against the manifest.
|
||||
|
||||
Consequence worth knowing: `meta` is a config slot that nothing links to, so it is
|
||||
hidden from every box. It holds the packed position, not process data.
|
||||
|
||||
### Why outputs read `null`
|
||||
|
||||
Config slots carry `flags="c"` and are persisted in the `.sax`. Runtime slots carry no
|
||||
flag and are **never** persisted — they only exist on a live device. So `out` and `in`
|
||||
have no `<prop>` in the file and the renderer substitutes the string `null`. That is
|
||||
correct for an offline app dump, not a fault.
|
||||
|
||||
### The `meta` property
|
||||
|
||||
A packed 32-bit integer, decoded in `sax_dom.py`:
|
||||
|
||||
```
|
||||
bits 31-24 x position, in grid units
|
||||
bits 23-16 y position, in grid units
|
||||
bits 15-8 reserved (preserved verbatim on write)
|
||||
bits 7-0 user group bits 1-4 in the low nibble, reserved high nibble
|
||||
```
|
||||
|
||||
Writes re-encode only x and y and preserve every other bit, so dragging a box never
|
||||
disturbs the rest of the value.
|
||||
|
||||
### Links
|
||||
|
||||
`<links>` sits at the document root, with `from`/`to` references of the form
|
||||
`/path/to/Component.slotName`.
|
||||
|
||||
- Both endpoints on the current sheet → an orthogonal wire is routed **around** the
|
||||
boxes. Routing tries a clear vertical channel between the two boxes first, then a
|
||||
lane above or below everything (which is what feedback links right-to-left need),
|
||||
and only falls back to a direct dog-leg if nothing is clear.
|
||||
- One endpoint off-sheet → a **knob** is drawn at the slot's edge, marking a connection
|
||||
that leaves this sheet. In the sample app, 137 of 217 links are same-sheet, so knobs
|
||||
are not a rare case.
|
||||
|
||||
Wires and knobs are painted behind the component boxes and re-routed on every drag
|
||||
step, since they are not part of the dragged canvas group.
|
||||
|
||||
## Versioning
|
||||
|
||||
`VERSION` lives in `app_state.py` and is shown in the window title.
|
||||
|
||||
- **Feature** → bump the version.
|
||||
- **Bug fix** → no bump.
|
||||
- **Refactor with no behaviour change** → no bump.
|
||||
|
||||
## Known gaps
|
||||
|
||||
- A kit missing from `<sedona home>/manifests/` exits the whole app rather than warning
|
||||
and declining to open that file.
|
||||
- The yellow square in each box header is drawn but wired to nothing; it is the natural
|
||||
place for a per-box collapse/expand toggle.
|
||||
- No undo, and no keyboard shortcuts for save.
|
||||
- `on_canvas_release` snaps using the rounded-rectangle polygon's first coordinate,
|
||||
which is `x1 + radius` rather than `x1`, so snapping is offset by the corner radius.
|
||||
- Slot values are display-only; there is no editing of a slot from the wiresheet.
|
||||
Reference in New Issue
Block a user