Added binary file format
This commit is contained in:
@@ -162,6 +162,9 @@ runtime. A tiny generic prompt with one field and OK/Cancel may remain code-only
|
|||||||
## Document and library files
|
## Document and library files
|
||||||
|
|
||||||
- Documents use the `bedit-document` JSON format.
|
- Documents use the `bedit-document` JSON format.
|
||||||
|
- Documents can be stored as human-readable `.bedit.json`/`.json` through
|
||||||
|
`JsonDocumentSerializer`, or as compressed MessagePack `.beb` through
|
||||||
|
`BebDocumentSerializer`. UI document I/O dispatches via `DocumentSerializer`.
|
||||||
- `test.bedit.json` is a useful manually created example during development.
|
- `test.bedit.json` is a useful manually created example during development.
|
||||||
- Library documents use the same recursive document model.
|
- Library documents use the same recursive document model.
|
||||||
- Library parsing belongs in `core/libraries.py`; Qt change notifications belong
|
- Library parsing belongs in `core/libraries.py`; Qt change notifications belong
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ BEdit document used as a copy source. The built-in example defines A, B, and C.
|
|||||||
- File → Close Document removes the active document and returns to an empty
|
- File → Close Document removes the active document and returns to an empty
|
||||||
workspace. An open graph uses a light gray grid whose visible and snapping
|
workspace. An open graph uses a light gray grid whose visible and snapping
|
||||||
spacing are configured separately.
|
spacing are configured separately.
|
||||||
- Edit → Settings → Libraries accepts document files or folders of JSON files.
|
- Edit → Settings → Libraries accepts JSON or `.beb` document files and folders.
|
||||||
|
|
||||||
Every component owns its ports, declarative icon, properties, and child graph:
|
Every component owns its ports, declarative icon, properties, and child graph:
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ description = "A starter Qt desktop application"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"msgpack>=1.0,<2",
|
||||||
"PySide6>=6.7,<7",
|
"PySide6>=6.7,<7",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ bedit = "bedit.gui.app:main"
|
|||||||
where = ["src"]
|
where = ["src"]
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
bedit = ["data/libraries/*.json"]
|
bedit = ["data/libraries/*.json", "data/libraries/*.beb"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import json
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from bedit.core.model import GraphDocument
|
from bedit.core.model import GraphDocument
|
||||||
|
from bedit.core.serializer import DocumentSerializer
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -13,7 +13,7 @@ class LibraryDocument:
|
|||||||
|
|
||||||
|
|
||||||
def bundled_library_path() -> Path:
|
def bundled_library_path() -> Path:
|
||||||
return Path(__file__).resolve().parents[1] / "data" / "libraries" / "default.json"
|
return Path(__file__).resolve().parents[1] / "data" / "libraries" / "default.beb"
|
||||||
|
|
||||||
|
|
||||||
def default_library_paths() -> list[str]:
|
def default_library_paths() -> list[str]:
|
||||||
@@ -21,12 +21,12 @@ def default_library_paths() -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def load_library_file(path: Path) -> LibraryDocument:
|
def load_library_file(path: Path) -> LibraryDocument:
|
||||||
with path.open(encoding="utf-8") as file:
|
document = DocumentSerializer.load(path)
|
||||||
data = json.load(file)
|
|
||||||
document = GraphDocument.from_dict(data)
|
|
||||||
name = str(document.metadata.get("name") or path.stem)
|
name = str(document.metadata.get("name") or path.stem)
|
||||||
return LibraryDocument(name, document, str(path))
|
return LibraryDocument(name, document, str(path))
|
||||||
|
|
||||||
|
|
||||||
def library_candidates(path: Path) -> list[Path]:
|
def library_candidates(path: Path) -> list[Path]:
|
||||||
return sorted(path.glob("*.json")) if path.is_dir() else [path]
|
if not path.is_dir():
|
||||||
|
return [path]
|
||||||
|
return sorted((*path.glob("*.json"), *path.glob("*.beb")))
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
|
import zlib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import msgpack
|
||||||
|
|
||||||
from bedit.core.model import GraphDocument
|
from bedit.core.model import GraphDocument
|
||||||
|
|
||||||
|
|
||||||
@@ -20,3 +23,54 @@ class JsonDocumentSerializer:
|
|||||||
json.dump(document.to_dict(), file, indent=2)
|
json.dump(document.to_dict(), file, indent=2)
|
||||||
file.write("\n")
|
file.write("\n")
|
||||||
temporary_path.replace(path)
|
temporary_path.replace(path)
|
||||||
|
|
||||||
|
|
||||||
|
class BebDocumentSerializer:
|
||||||
|
"""Compressed MessagePack serializer for the binary .beb format."""
|
||||||
|
|
||||||
|
MAGIC = b"BEB\x00"
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, path: Path) -> GraphDocument:
|
||||||
|
payload = path.read_bytes()
|
||||||
|
header = cls.MAGIC + bytes([cls.VERSION])
|
||||||
|
if not payload.startswith(header):
|
||||||
|
raise ValueError("This is not a supported BEdit binary document")
|
||||||
|
try:
|
||||||
|
data = msgpack.unpackb(zlib.decompress(payload[len(header) :]), raw=False)
|
||||||
|
except (ValueError, zlib.error, msgpack.exceptions.MsgpackException) as error:
|
||||||
|
raise ValueError("The BEdit binary document is damaged") from error
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise ValueError("The BEdit binary document has an invalid root value")
|
||||||
|
return GraphDocument.from_dict(data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def save(cls, document: GraphDocument, path: Path) -> None:
|
||||||
|
packed = msgpack.packb(document.to_dict(), use_bin_type=True)
|
||||||
|
payload = cls.MAGIC + bytes([cls.VERSION]) + zlib.compress(packed, level=9)
|
||||||
|
temporary_path = path.with_suffix(path.suffix + ".tmp")
|
||||||
|
temporary_path.write_bytes(payload)
|
||||||
|
temporary_path.replace(path)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentSerializer:
|
||||||
|
"""Select the document serializer from its file extension."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(path: Path) -> GraphDocument:
|
||||||
|
serializer = (
|
||||||
|
BebDocumentSerializer
|
||||||
|
if path.suffix.lower() == ".beb"
|
||||||
|
else JsonDocumentSerializer
|
||||||
|
)
|
||||||
|
return serializer.load(path)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save(document: GraphDocument, path: Path) -> None:
|
||||||
|
serializer = (
|
||||||
|
BebDocumentSerializer
|
||||||
|
if path.suffix.lower() == ".beb"
|
||||||
|
else JsonDocumentSerializer
|
||||||
|
)
|
||||||
|
serializer.save(document, path)
|
||||||
|
|||||||
BIN
BEdit/src/bedit/data/libraries/default.beb
Normal file
BIN
BEdit/src/bedit/data/libraries/default.beb
Normal file
Binary file not shown.
@@ -1,435 +0,0 @@
|
|||||||
{
|
|
||||||
"format": "bedit-document",
|
|
||||||
"version": 1,
|
|
||||||
"metadata": {
|
|
||||||
"name": "Untitled"
|
|
||||||
},
|
|
||||||
"roots": [
|
|
||||||
{
|
|
||||||
"id": "7ad651ef-6f76-4064-81d3-3fd6dd4d2918",
|
|
||||||
"name": "gain",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"rotation": 0.0,
|
|
||||||
"interface": {
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "port-df34ce84",
|
|
||||||
"name": "in",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"iconPosition": {
|
|
||||||
"x": 64.0,
|
|
||||||
"y": 64.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "signal",
|
|
||||||
"multipleConnections": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"id": "port-fe9e6486",
|
|
||||||
"name": "out",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"iconPosition": {
|
|
||||||
"x": 88.0,
|
|
||||||
"y": 40.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "signal",
|
|
||||||
"multipleConnections": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"shape": "rectangle",
|
|
||||||
"fill": "#f4f4f4",
|
|
||||||
"border": "#303030",
|
|
||||||
"text": "Text",
|
|
||||||
"size": {
|
|
||||||
"width": 128.0,
|
|
||||||
"height": 128.0
|
|
||||||
},
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"type": "rectangle",
|
|
||||||
"x": 32.0,
|
|
||||||
"y": 32.0,
|
|
||||||
"width": 64.0,
|
|
||||||
"height": 64.0,
|
|
||||||
"fill": "#f4f4f4",
|
|
||||||
"stroke": "#303030",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"cornerRadius": 5.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"x": 40.0,
|
|
||||||
"y": 40.0,
|
|
||||||
"width": 48.0,
|
|
||||||
"height": 48.0,
|
|
||||||
"text": "K",
|
|
||||||
"color": "#00007f",
|
|
||||||
"fontSize": 24.0,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"stroke": "#00007f",
|
|
||||||
"fill": "#ffffff"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"showName": false
|
|
||||||
},
|
|
||||||
"library": {
|
|
||||||
"showSubtree": true
|
|
||||||
},
|
|
||||||
"implementation": {
|
|
||||||
"kind": "text",
|
|
||||||
"source": {
|
|
||||||
"equations": "out = k*in;",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"id": "parameter-61a43a86",
|
|
||||||
"name": "k",
|
|
||||||
"type": "real",
|
|
||||||
"value": "1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "12b21b11-8828-4a90-a561-19491dc9632e",
|
|
||||||
"name": "differentiate",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"rotation": 0.0,
|
|
||||||
"interface": {
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "port-1cbabc8f",
|
|
||||||
"name": "in",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"iconPosition": {
|
|
||||||
"x": 64.0,
|
|
||||||
"y": 64.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "signal",
|
|
||||||
"multipleConnections": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"id": "port-4eeea4e7",
|
|
||||||
"name": "out",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"iconPosition": {
|
|
||||||
"x": 88.0,
|
|
||||||
"y": 40.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "signal",
|
|
||||||
"multipleConnections": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"shape": "rectangle",
|
|
||||||
"fill": "#f4f4f4",
|
|
||||||
"border": "#303030",
|
|
||||||
"text": "Text",
|
|
||||||
"size": {
|
|
||||||
"width": 128.0,
|
|
||||||
"height": 128.0
|
|
||||||
},
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"type": "rectangle",
|
|
||||||
"x": 32.0,
|
|
||||||
"y": 32.0,
|
|
||||||
"width": 64.0,
|
|
||||||
"height": 64.0,
|
|
||||||
"fill": "#f4f4f4",
|
|
||||||
"stroke": "#303030",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"cornerRadius": 5.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"x": 40.0,
|
|
||||||
"y": 40.0,
|
|
||||||
"width": 48.0,
|
|
||||||
"height": 48.0,
|
|
||||||
"text": "d/dt",
|
|
||||||
"color": "#00007f",
|
|
||||||
"fontSize": 18.0,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"stroke": "#00007f",
|
|
||||||
"fill": "#ffffff"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"showName": false
|
|
||||||
},
|
|
||||||
"library": {
|
|
||||||
"showSubtree": true
|
|
||||||
},
|
|
||||||
"implementation": {
|
|
||||||
"kind": "text",
|
|
||||||
"source": {
|
|
||||||
"equations": "initial out = initial;\nout = der(in);",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"id": "parameter-331e38be",
|
|
||||||
"name": "initial",
|
|
||||||
"type": "real",
|
|
||||||
"value": "0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "afdc7696-622b-47e4-b2c3-28ec60786bf2",
|
|
||||||
"name": "integrate",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"rotation": 0.0,
|
|
||||||
"interface": {
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "port-12903207",
|
|
||||||
"name": "in",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"iconPosition": {
|
|
||||||
"x": 64.0,
|
|
||||||
"y": 64.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "signal",
|
|
||||||
"multipleConnections": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"id": "port-b3370b1a",
|
|
||||||
"name": "out",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"iconPosition": {
|
|
||||||
"x": 88.0,
|
|
||||||
"y": 40.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "signal",
|
|
||||||
"multipleConnections": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"shape": "rectangle",
|
|
||||||
"fill": "#f4f4f4",
|
|
||||||
"border": "#303030",
|
|
||||||
"text": "Text",
|
|
||||||
"size": {
|
|
||||||
"width": 128.0,
|
|
||||||
"height": 128.0
|
|
||||||
},
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"type": "rectangle",
|
|
||||||
"x": 32.0,
|
|
||||||
"y": 32.0,
|
|
||||||
"width": 64.0,
|
|
||||||
"height": 64.0,
|
|
||||||
"fill": "#f4f4f4",
|
|
||||||
"stroke": "#303030",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"cornerRadius": 5.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"x": 64.0,
|
|
||||||
"y": 40.0,
|
|
||||||
"width": 28.0,
|
|
||||||
"height": 48.0,
|
|
||||||
"text": "dt",
|
|
||||||
"color": "#00007f",
|
|
||||||
"fontSize": 18.0,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"stroke": "#00007f",
|
|
||||||
"fill": "#ffffff"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"x": 40.0,
|
|
||||||
"y": 32.0,
|
|
||||||
"width": 28.0,
|
|
||||||
"height": 54.0,
|
|
||||||
"fill": "none",
|
|
||||||
"stroke": "#00007f",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"lineStyle": "none",
|
|
||||||
"text": "\u222b",
|
|
||||||
"fontSize": 32.0,
|
|
||||||
"color": "#00007f"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"showName": false
|
|
||||||
},
|
|
||||||
"library": {
|
|
||||||
"showSubtree": true
|
|
||||||
},
|
|
||||||
"implementation": {
|
|
||||||
"kind": "text",
|
|
||||||
"source": {
|
|
||||||
"equations": "initial out = initial;\nder(out) = in;",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"id": "parameter-6bdc1c76",
|
|
||||||
"name": "initial",
|
|
||||||
"type": "real",
|
|
||||||
"value": "0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "f6b8e5c9-4d13-4f7b-b574-e00ca96e42c3",
|
|
||||||
"name": "add",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"rotation": 0.0,
|
|
||||||
"interface": {
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "port-c995845c",
|
|
||||||
"name": "in",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"iconPosition": {
|
|
||||||
"x": 64.0,
|
|
||||||
"y": 64.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "signal",
|
|
||||||
"multipleConnections": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"id": "port-a2f0dea6",
|
|
||||||
"name": "out",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"iconPosition": {
|
|
||||||
"x": 80.0,
|
|
||||||
"y": 48.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "signal",
|
|
||||||
"multipleConnections": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"shape": "rectangle",
|
|
||||||
"fill": "#f4f4f4",
|
|
||||||
"border": "#303030",
|
|
||||||
"text": "Text",
|
|
||||||
"size": {
|
|
||||||
"width": 128.0,
|
|
||||||
"height": 128.0
|
|
||||||
},
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"type": "circle",
|
|
||||||
"x": 40.0,
|
|
||||||
"y": 40.0,
|
|
||||||
"width": 48.0,
|
|
||||||
"height": 48.0,
|
|
||||||
"fill": "#f4f4f4",
|
|
||||||
"stroke": "#303030",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"lineStyle": "solid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"x": 48.0,
|
|
||||||
"y": 48.0,
|
|
||||||
"width": 32.0,
|
|
||||||
"height": 32.0,
|
|
||||||
"fill": "none",
|
|
||||||
"stroke": "#00007f",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"lineStyle": "none",
|
|
||||||
"text": "+",
|
|
||||||
"fontSize": 18.0,
|
|
||||||
"color": "#00007f"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"showName": false
|
|
||||||
},
|
|
||||||
"library": {
|
|
||||||
"showSubtree": true
|
|
||||||
},
|
|
||||||
"implementation": {
|
|
||||||
"kind": "text",
|
|
||||||
"source": {
|
|
||||||
"equations": "out = sum(in[i] for i in 1:in.N);",
|
|
||||||
"parameters": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -40,7 +40,7 @@ from bedit.core.model import (
|
|||||||
)
|
)
|
||||||
from bedit.core.simulation import Simulation
|
from bedit.core.simulation import Simulation
|
||||||
from bedit.core.port_types import PortTypeRegistry
|
from bedit.core.port_types import PortTypeRegistry
|
||||||
from bedit.core.serializer import JsonDocumentSerializer
|
from bedit.core.serializer import DocumentSerializer
|
||||||
|
|
||||||
|
|
||||||
class DocumentController(QObject):
|
class DocumentController(QObject):
|
||||||
@@ -108,7 +108,7 @@ class DocumentController(QObject):
|
|||||||
self.filePathChanged.emit(None)
|
self.filePathChanged.emit(None)
|
||||||
|
|
||||||
def load(self, path: Path) -> None:
|
def load(self, path: Path) -> None:
|
||||||
self.document = JsonDocumentSerializer.load(path)
|
self.document = DocumentSerializer.load(path)
|
||||||
self.active_component_id = next(iter(self.document.roots), None)
|
self.active_component_id = next(iter(self.document.roots), None)
|
||||||
self.file_path = path
|
self.file_path = path
|
||||||
self.undo_stack.clear()
|
self.undo_stack.clear()
|
||||||
@@ -124,7 +124,7 @@ class DocumentController(QObject):
|
|||||||
target = path or self.file_path
|
target = path or self.file_path
|
||||||
if target is None:
|
if target is None:
|
||||||
raise ValueError("No file path has been selected")
|
raise ValueError("No file path has been selected")
|
||||||
JsonDocumentSerializer.save(self.document, target)
|
DocumentSerializer.save(self.document, target)
|
||||||
self.file_path = target
|
self.file_path = target
|
||||||
self.undo_stack.setClean()
|
self.undo_stack.setClean()
|
||||||
self.filePathChanged.emit(target)
|
self.filePathChanged.emit(target)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from pathlib import Path
|
|||||||
from PySide6.QtCore import QSettings, Signal
|
from PySide6.QtCore import QSettings, Signal
|
||||||
from PySide6.QtWidgets import QDialog, QFileDialog
|
from PySide6.QtWidgets import QDialog, QFileDialog
|
||||||
|
|
||||||
from bedit.core.libraries import default_library_paths
|
from bedit.core.libraries import bundled_library_path, default_library_paths
|
||||||
from bedit.gui.preferences import application_settings
|
from bedit.gui.preferences import application_settings
|
||||||
from bedit.gui.generated.ui_settings_dialog import Ui_SettingsDialog
|
from bedit.gui.generated.ui_settings_dialog import Ui_SettingsDialog
|
||||||
|
|
||||||
@@ -56,8 +56,22 @@ class SettingsDialog(QDialog):
|
|||||||
settings = settings if settings is not None else application_settings()
|
settings = settings if settings is not None else application_settings()
|
||||||
value = settings.value("libraries/paths", default_library_paths())
|
value = settings.value("libraries/paths", default_library_paths())
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return [value]
|
paths = [value]
|
||||||
return [str(path) for path in value]
|
else:
|
||||||
|
paths = [str(path) for path in value]
|
||||||
|
bundled = bundled_library_path()
|
||||||
|
migrated = [
|
||||||
|
str(bundled)
|
||||||
|
if not Path(path).exists()
|
||||||
|
and Path(path).parent == bundled.parent
|
||||||
|
and Path(path).suffix.lower() == ".json"
|
||||||
|
else path
|
||||||
|
for path in paths
|
||||||
|
]
|
||||||
|
if migrated != paths:
|
||||||
|
settings.setValue("libraries/paths", migrated)
|
||||||
|
settings.sync()
|
||||||
|
return migrated
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def graph_grid_size(settings: QSettings | None = None) -> int:
|
def graph_grid_size(settings: QSettings | None = None) -> int:
|
||||||
@@ -79,7 +93,7 @@ class SettingsDialog(QDialog):
|
|||||||
self,
|
self,
|
||||||
"Add library",
|
"Add library",
|
||||||
"",
|
"",
|
||||||
"BEdit libraries (*.json);;All files (*)",
|
"BEdit libraries (*.beb *.bedit.json *.json);;All files (*)",
|
||||||
)
|
)
|
||||||
if path:
|
if path:
|
||||||
self._append_unique_path(path)
|
self._append_unique_path(path)
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ class Ui_SettingsDialog(object):
|
|||||||
self.iconGridLabel.setText(QCoreApplication.translate("SettingsDialog", u"Icon grid size:", None))
|
self.iconGridLabel.setText(QCoreApplication.translate("SettingsDialog", u"Icon grid size:", None))
|
||||||
self.iconGridSpinBox.setSuffix(QCoreApplication.translate("SettingsDialog", u" units", None))
|
self.iconGridSpinBox.setSuffix(QCoreApplication.translate("SettingsDialog", u" units", None))
|
||||||
self.settingsTabs.setTabText(self.settingsTabs.indexOf(self.generalTab), QCoreApplication.translate("SettingsDialog", u"General", None))
|
self.settingsTabs.setTabText(self.settingsTabs.indexOf(self.generalTab), QCoreApplication.translate("SettingsDialog", u"General", None))
|
||||||
self.libraryPathsLabel.setText(QCoreApplication.translate("SettingsDialog", u"Load library JSON files from these files or folders at startup:", None))
|
self.libraryPathsLabel.setText(QCoreApplication.translate("SettingsDialog", u"Load library JSON or BEdit Binary files from these files or folders at startup:", None))
|
||||||
self.addLibraryFileButton.setText(QCoreApplication.translate("SettingsDialog", u"Add File\u2026", None))
|
self.addLibraryFileButton.setText(QCoreApplication.translate("SettingsDialog", u"Add File\u2026", None))
|
||||||
self.addLibraryFolderButton.setText(QCoreApplication.translate("SettingsDialog", u"Add Folder\u2026", None))
|
self.addLibraryFolderButton.setText(QCoreApplication.translate("SettingsDialog", u"Add Folder\u2026", None))
|
||||||
self.removeLibraryPathButton.setText(QCoreApplication.translate("SettingsDialog", u"Remove", None))
|
self.removeLibraryPathButton.setText(QCoreApplication.translate("SettingsDialog", u"Remove", None))
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from PySide6.QtWidgets import (
|
|||||||
|
|
||||||
from bedit.core.model import Component, Parameter
|
from bedit.core.model import Component, Parameter
|
||||||
from bedit.core.application_log import get_logger
|
from bedit.core.application_log import get_logger
|
||||||
from bedit.core.serializer import JsonDocumentSerializer
|
from bedit.core.serializer import DocumentSerializer
|
||||||
from bedit.core.simulation import Simulation
|
from bedit.core.simulation import Simulation
|
||||||
from bedit.gui.controllers.document import DocumentController
|
from bedit.gui.controllers.document import DocumentController
|
||||||
from bedit.gui.dialogs.component_options import ComponentOptionsDialog
|
from bedit.gui.dialogs.component_options import ComponentOptionsDialog
|
||||||
@@ -407,7 +407,11 @@ class MainWindow(QMainWindow):
|
|||||||
if not self._resolve_source_edits() or not self._maybe_save():
|
if not self._resolve_source_edits() or not self._maybe_save():
|
||||||
return
|
return
|
||||||
filename, _ = QFileDialog.getOpenFileName(
|
filename, _ = QFileDialog.getOpenFileName(
|
||||||
self, "Open graph", "", "BEdit graphs (*.bedit.json *.json);;All files (*)"
|
self,
|
||||||
|
"Open graph",
|
||||||
|
"",
|
||||||
|
"BEdit documents (*.bedit.json *.json *.beb);;"
|
||||||
|
"BEdit JSON (*.bedit.json *.json);;BEdit Binary (*.beb);;All files (*)",
|
||||||
)
|
)
|
||||||
if not filename:
|
if not filename:
|
||||||
return
|
return
|
||||||
@@ -437,16 +441,21 @@ class MainWindow(QMainWindow):
|
|||||||
def save_document_as(self) -> bool:
|
def save_document_as(self) -> bool:
|
||||||
if self.document_controller.document is None:
|
if self.document_controller.document is None:
|
||||||
return False
|
return False
|
||||||
filename, _ = QFileDialog.getSaveFileName(
|
filename, selected_filter = QFileDialog.getSaveFileName(
|
||||||
self,
|
self,
|
||||||
"Save graph",
|
"Save graph",
|
||||||
"untitled.bedit.json",
|
"untitled.beb",
|
||||||
"BEdit graphs (*.bedit.json);;JSON files (*.json);;All files (*)",
|
"BEdit Binary (*.beb);;BEdit JSON (*.bedit.json *.json);;All files (*)",
|
||||||
)
|
)
|
||||||
if not filename:
|
if not filename:
|
||||||
return False
|
return False
|
||||||
|
path = Path(filename)
|
||||||
|
if path.suffix.lower() not in {".json", ".beb"}:
|
||||||
|
path = path.with_suffix(
|
||||||
|
".beb" if "Binary" in selected_filter else ".bedit.json"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
path = self.document_controller.save(Path(filename))
|
path = self.document_controller.save(path)
|
||||||
self.log.info("Saved document %s", path)
|
self.log.info("Saved document %s", path)
|
||||||
except OSError as error:
|
except OSError as error:
|
||||||
self.log.error("Could not save document %s: %s", filename, error)
|
self.log.error("Could not save document %s: %s", filename, error)
|
||||||
@@ -557,7 +566,7 @@ class MainWindow(QMainWindow):
|
|||||||
try:
|
try:
|
||||||
if library is not None:
|
if library is not None:
|
||||||
library.document.validate()
|
library.document.validate()
|
||||||
JsonDocumentSerializer.save(library.document, Path(library.source_path))
|
DocumentSerializer.save(library.document, Path(library.source_path))
|
||||||
except (OSError, ValueError) as error:
|
except (OSError, ValueError) as error:
|
||||||
component.inputs, component.outputs = old_inputs, old_outputs
|
component.inputs, component.outputs = old_inputs, old_outputs
|
||||||
self.log.error("Could not change library ports: %s", error)
|
self.log.error("Could not change library ports: %s", error)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import json
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from PySide6.QtCore import QObject, Signal
|
from PySide6.QtCore import QObject, Signal
|
||||||
@@ -23,7 +22,7 @@ class LibraryRepository(QObject):
|
|||||||
for candidate in library_candidates(path):
|
for candidate in library_candidates(path):
|
||||||
try:
|
try:
|
||||||
libraries.append(load_library_file(candidate))
|
libraries.append(load_library_file(candidate))
|
||||||
except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError) as error:
|
except (OSError, ValueError, KeyError, TypeError) as error:
|
||||||
warnings.append(f"{candidate}: {error}")
|
warnings.append(f"{candidate}: {error}")
|
||||||
self.libraries = libraries
|
self.libraries = libraries
|
||||||
self.load_warnings = warnings
|
self.load_warnings = warnings
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="libraryPathsLabel">
|
<widget class="QLabel" name="libraryPathsLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Load library JSON files from these files or folders at startup:</string>
|
<string>Load library JSON or BEdit Binary files from these files or folders at startup:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
|||||||
Reference in New Issue
Block a user