Saving sim results
This commit is contained in:
@@ -1,10 +1,21 @@
|
||||
from bedit.core.simulation.service import Simulation
|
||||
from bedit.core.simulation.openmodelica import OpenModelicaInterface
|
||||
from bedit.core.simulation.results import SimulationResults, SimulationTrace
|
||||
from bedit.core.simulation.results import (
|
||||
BerSimulationResultsSerializer,
|
||||
JsonSimulationResultsSerializer,
|
||||
SimulationExecutionResult,
|
||||
SimulationResults,
|
||||
SimulationResultsSerializer,
|
||||
SimulationTrace,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"OpenModelicaInterface",
|
||||
"Simulation",
|
||||
"SimulationResults",
|
||||
"SimulationExecutionResult",
|
||||
"SimulationResultsSerializer",
|
||||
"SimulationTrace",
|
||||
"BerSimulationResultsSerializer",
|
||||
"JsonSimulationResultsSerializer",
|
||||
]
|
||||
|
||||
@@ -14,6 +14,10 @@ from threading import Event, Lock, Thread, current_thread
|
||||
from typing import Any
|
||||
|
||||
from bedit.core.application_log import get_logger
|
||||
from bedit.core.simulation.results import (
|
||||
SimulationExecutionResult,
|
||||
load_openmodelica_csv,
|
||||
)
|
||||
|
||||
|
||||
log = get_logger(__name__)
|
||||
@@ -263,7 +267,7 @@ def _run_model_with_tcp(
|
||||
temp_dir: Path,
|
||||
progress_callback: Callable[[SimulationProgress], None] | None,
|
||||
message_callback: Callable[[SimulationMessage], None] | None,
|
||||
) -> int:
|
||||
) -> SimulationExecutionResult:
|
||||
executable_path = Path(executable)
|
||||
executable_command = executable
|
||||
if not executable_path.is_absolute() and executable_path.parent == Path("."):
|
||||
@@ -324,7 +328,20 @@ def _run_model_with_tcp(
|
||||
raise RuntimeError(
|
||||
f"Simulation process exited with status {return_code}{detail}"
|
||||
)
|
||||
return int(return_code)
|
||||
result_path = temp_dir / f"{Path(executable).stem}_res.csv"
|
||||
if not result_path.is_file():
|
||||
raise RuntimeError(
|
||||
f"OpenModelica did not create the expected result file {result_path.name!r}"
|
||||
)
|
||||
data = load_openmodelica_csv(result_path)
|
||||
log.info(
|
||||
"Loaded %d result columns from %s", len(data), result_path.name
|
||||
)
|
||||
return SimulationExecutionResult(
|
||||
return_code=int(return_code),
|
||||
result_file=str(result_path),
|
||||
data=data,
|
||||
)
|
||||
|
||||
|
||||
def _accept_simulation_connection(
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import csv
|
||||
import json
|
||||
import zlib
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import msgpack
|
||||
|
||||
|
||||
RESULTS_FORMAT = "bedit-simulation-results"
|
||||
RESULTS_VERSION = 1
|
||||
@@ -28,6 +32,7 @@ class SimulationResults:
|
||||
model_name: str = ""
|
||||
status: dict[str, Any] = field(default_factory=dict)
|
||||
messages: list[dict[str, str]] = field(default_factory=list)
|
||||
data: dict[str, list[float]] = field(default_factory=dict)
|
||||
traces: list[SimulationTrace] = field(default_factory=list)
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@@ -38,6 +43,7 @@ class SimulationResults:
|
||||
"modelName": self.model_name,
|
||||
"status": dict(self.status),
|
||||
"messages": [dict(message) for message in self.messages],
|
||||
"data": {name: list(values) for name, values in self.data.items()},
|
||||
"traces": [asdict(trace) for trace in self.traces],
|
||||
"metadata": dict(self.metadata),
|
||||
}
|
||||
@@ -54,6 +60,10 @@ class SimulationResults:
|
||||
model_name=str(data.get("modelName", "")),
|
||||
status=dict(data.get("status", {})),
|
||||
messages=[dict(message) for message in data.get("messages", [])],
|
||||
data={
|
||||
str(name): [float(value) for value in values]
|
||||
for name, values in dict(data.get("data", {})).items()
|
||||
},
|
||||
traces=traces,
|
||||
metadata=dict(data.get("metadata", {})),
|
||||
)
|
||||
@@ -61,18 +71,129 @@ class SimulationResults:
|
||||
raise ValueError("Malformed simulation-results data") from error
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SimulationExecutionResult:
|
||||
"""Completed process information delivered before its temp files disappear."""
|
||||
|
||||
return_code: int
|
||||
result_file: str
|
||||
data: dict[str, list[float]]
|
||||
|
||||
|
||||
def load_openmodelica_csv(path: str | Path) -> dict[str, list[float]]:
|
||||
"""Read an OpenModelica CSV result as one numeric array per column."""
|
||||
|
||||
source = Path(path)
|
||||
try:
|
||||
with source.open(newline="", encoding="utf-8") as file:
|
||||
reader = csv.reader(file)
|
||||
headers = next(reader)
|
||||
if not headers or any(not header for header in headers):
|
||||
raise ValueError("The result CSV has an invalid header")
|
||||
if len(set(headers)) != len(headers):
|
||||
raise ValueError("The result CSV contains duplicate column names")
|
||||
columns = {header: [] for header in headers}
|
||||
for row_number, row in enumerate(reader, start=2):
|
||||
if len(row) != len(headers):
|
||||
raise ValueError(
|
||||
f"Result CSV row {row_number} has {len(row)} values; "
|
||||
f"expected {len(headers)}"
|
||||
)
|
||||
for header, value in zip(headers, row, strict=True):
|
||||
columns[header].append(float(value))
|
||||
except OSError as error:
|
||||
raise ValueError(f"Could not read OpenModelica results: {error}") from error
|
||||
except StopIteration as error:
|
||||
raise ValueError("The OpenModelica result CSV is empty") from error
|
||||
except ValueError as error:
|
||||
if str(error).startswith(("The result CSV", "Result CSV")):
|
||||
raise
|
||||
raise ValueError(f"The OpenModelica result CSV is not numeric: {error}") from error
|
||||
return columns
|
||||
|
||||
|
||||
class JsonSimulationResultsSerializer:
|
||||
@staticmethod
|
||||
def load(path: Path) -> SimulationResults:
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise ValueError(f"Could not read simulation results: {error}") from error
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError("Simulation-results root must be an object")
|
||||
return SimulationResults.from_dict(data)
|
||||
|
||||
@staticmethod
|
||||
def save(results: SimulationResults, path: Path) -> None:
|
||||
temporary_path = path.with_suffix(path.suffix + ".tmp")
|
||||
temporary_path.write_text(
|
||||
json.dumps(results.to_dict(), indent=2, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
temporary_path.replace(path)
|
||||
|
||||
|
||||
class BerSimulationResultsSerializer:
|
||||
"""Compressed MessagePack serializer for binary simulation results."""
|
||||
|
||||
MAGIC = b"BER\x00"
|
||||
VERSION = 1
|
||||
|
||||
@classmethod
|
||||
def load(cls, path: Path) -> SimulationResults:
|
||||
try:
|
||||
payload = path.read_bytes()
|
||||
except OSError as error:
|
||||
raise ValueError(f"Could not read simulation results: {error}") from error
|
||||
header = cls.MAGIC + bytes([cls.VERSION])
|
||||
if not payload.startswith(header):
|
||||
raise ValueError("This is not a supported BEdit binary results file")
|
||||
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 results file is damaged") from error
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError("The BEdit binary results file has an invalid root value")
|
||||
return SimulationResults.from_dict(data)
|
||||
|
||||
@classmethod
|
||||
def save(cls, results: SimulationResults, path: Path) -> None:
|
||||
packed = msgpack.packb(results.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 SimulationResultsSerializer:
|
||||
"""Select JSON or compressed MessagePack based on the file extension."""
|
||||
|
||||
@staticmethod
|
||||
def load(path: str | Path) -> SimulationResults:
|
||||
target = Path(path)
|
||||
serializer = (
|
||||
BerSimulationResultsSerializer
|
||||
if target.suffix.lower() == ".ber"
|
||||
else JsonSimulationResultsSerializer
|
||||
)
|
||||
return serializer.load(target)
|
||||
|
||||
@staticmethod
|
||||
def save(results: SimulationResults, path: str | Path) -> None:
|
||||
target = Path(path)
|
||||
serializer = (
|
||||
BerSimulationResultsSerializer
|
||||
if target.suffix.lower() == ".ber"
|
||||
else JsonSimulationResultsSerializer
|
||||
)
|
||||
serializer.save(results, target)
|
||||
|
||||
|
||||
def save_simulation_results(path: str | Path, results: SimulationResults) -> None:
|
||||
Path(path).write_text(
|
||||
json.dumps(results.to_dict(), indent=2, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
SimulationResultsSerializer.save(results, path)
|
||||
|
||||
|
||||
def load_simulation_results(path: str | Path) -> SimulationResults:
|
||||
try:
|
||||
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise ValueError(f"Could not read simulation results: {error}") from error
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError("Simulation-results root must be an object")
|
||||
return SimulationResults.from_dict(data)
|
||||
return SimulationResultsSerializer.load(path)
|
||||
|
||||
Binary file not shown.
@@ -345,7 +345,7 @@ class PasteSelectionCommand(QUndoCommand):
|
||||
def __init__(
|
||||
self,
|
||||
controller,
|
||||
owner_id: str,
|
||||
owner_id: str | None,
|
||||
blocks: dict[str, Component],
|
||||
connections: dict[str, Connection],
|
||||
) -> None:
|
||||
|
||||
@@ -963,6 +963,68 @@ class DocumentController(QObject):
|
||||
self.undo_stack.push(PasteSelectionCommand(self, owner.id, blocks, connections))
|
||||
return list(blocks)
|
||||
|
||||
def paste_components_to(
|
||||
self,
|
||||
owner_id: str | None,
|
||||
source_components: list[Component],
|
||||
source_connections: list[Connection] | None = None,
|
||||
) -> list[str]:
|
||||
"""Clone components into the document root or a graph at origin."""
|
||||
|
||||
if self.document is None:
|
||||
raise ValueError("Open or create a document before pasting components")
|
||||
if owner_id is None:
|
||||
siblings = self.document.roots.values()
|
||||
else:
|
||||
owner = self.document.find_component(owner_id)
|
||||
if owner is None or owner.implementation_kind != "graph":
|
||||
raise ValueError("Components can only be pasted into a graph")
|
||||
siblings = owner.graph.blocks.values()
|
||||
|
||||
pairs = [(source, clone_component(source)) for source in source_components]
|
||||
if not pairs:
|
||||
return []
|
||||
id_map = {source.id: clone.id for source, clone in pairs}
|
||||
used = list(siblings)
|
||||
minimum_x = min(source.x for source, _clone in pairs)
|
||||
minimum_y = min(source.y for source, _clone in pairs)
|
||||
blocks: dict[str, Component] = {}
|
||||
for source, clone in pairs:
|
||||
clone.name = self._available_component_name(source.name, used, 0)
|
||||
if owner_id is not None:
|
||||
clone.x = source.x - minimum_x
|
||||
clone.y = source.y - minimum_y
|
||||
blocks[clone.id] = clone
|
||||
used.append(clone)
|
||||
|
||||
connections: dict[str, Connection] = {}
|
||||
if owner_id is not None:
|
||||
for source in source_connections or []:
|
||||
if source.source.block not in id_map or source.target.block not in id_map:
|
||||
continue
|
||||
properties = deepcopy(source.properties)
|
||||
for point in properties.get("waypoints", []):
|
||||
if isinstance(point, dict):
|
||||
point["x"] = float(point.get("x", 0)) - minimum_x
|
||||
point["y"] = float(point.get("y", 0)) - minimum_y
|
||||
connection = Connection(
|
||||
id=str(uuid4()),
|
||||
source=Endpoint(
|
||||
block=id_map[source.source.block], port=source.source.port
|
||||
),
|
||||
target=Endpoint(
|
||||
block=id_map[source.target.block], port=source.target.port
|
||||
),
|
||||
name=source.name,
|
||||
properties=properties,
|
||||
)
|
||||
connections[connection.id] = connection
|
||||
|
||||
self.undo_stack.push(
|
||||
PasteSelectionCommand(self, owner_id, blocks, connections)
|
||||
)
|
||||
return list(blocks)
|
||||
|
||||
def _graph_for(self, owner_id: str):
|
||||
if self.document is None:
|
||||
raise ValueError("There is no open document")
|
||||
|
||||
@@ -2326,6 +2326,63 @@ K\x80@\x89\x15\x8d\xc04\xd5\xb5^\xaf\x1bx\xfa\x19\
|
||||
\x84\xe7\x04\xcf\x88\xfd\xfd\xfd\xe4\xe8\xe8\xc8\x9dL&\x14\
|
||||
\xc7\xcc\x7f5p\xf2g\x94\xf7\x1f\xdf\x9a\xd2\x93\xfbC\
|
||||
\xb7\xa7\x00\x00\x00\x00IEND\xaeB`\x82\
|
||||
\x00\x00\x03m\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
|
||||
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
|
||||
\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
|
||||
\x00\x00\x09pHYs\x00\x00\x1b\xaf\x00\x00\x1b\xaf\x01\
|
||||
^\x1a\x91\x1c\x00\x00\x00\x07tIME\x07\xd9\x02\x10\
|
||||
\x17\x22\x16\x993\xa5<\x00\x00\x02\xedIDATx\
|
||||
\xda\xed\x97_H\xd3Q\x14\xc7\xcf\xd9\xef7\xcb\xca\x89\
|
||||
B \x91\x0f\xc9\xcczha\xf6\xa8\xb4\x06M\x90f\
|
||||
aiV$\x96V\x12\xf9P-S\x90d%Lh\
|
||||
V/B.\x8d\xc2\xb2\xc0\x1erS\xc1\x84\x1cdo\
|
||||
a\xa0\x0f>h\x0f\xfdy\xf0-\x9b\xa6\x0f\xb1{\xfb\
|
||||
n\xf4\x8b\x91\xe2\x5cn\xae\x07\x0f\xdc\x9dq/\xfc>\
|
||||
\xe7{\xce\xe1\xdc\xdf\x8f\xd6M\xb3\x86\x86\x86\xe2Dp\
|
||||
u\xbf\xe19pG\x82>!\x01\x04\xe1\x9aOd\x09\
|
||||
:\x13V\x02\xcd\xd6\x03H\x84\xa9\x04\xab\xaf\xaf\xef\x84\
|
||||
\xcb\x8f\x07\xe0z\xcf\x82AU\xc8\xa6\xeax\xfc\xf6\xd1\
|
||||
\x8dcKf\xa0\xa5\xa5\xa5\x0an$V\xd0\xea\xc7\xf3\
|
||||
\x86\x9a\xae\xf9\xd3\xb5\xdd\xf3\xaf\x84\xa4\xcfRR\xa9\x5c\
|
||||
&\x031\xb3\xf2\xf6\x1f\xb9P\xdb\x14\x90\xd2\xacH\xf6\
|
||||
\x01\xda\x13\x10T\xd1Z\x96\xec\xa7x\x07P\xd26\xc7\
|
||||
P\xdb!\x04\xbd\x10L\x15\xedg7-\x82\xc65\x00\
|
||||
\xc0m\x02?\x82\xd9\xf5\xa4j\xb3\x5c\xd3&,\xba\x1f\
|
||||
R\x8f\xd4\x93\xe3\xe5\xc5\x08\xf0X7\xa1\xa6> B\
|
||||
\xde\xbb&s\xc0\xb3\xf3x\x81'\xbbt\x1f\xc1,w\
|
||||
f\x19M\xd7$\xa0\xbe\xf7\xf2\x16\x19\xf5\x1c\x88\x0e|\
|
||||
L!\xc9\x8d$u\x97X\xa8\x0b\xde\xac\x0aS\xab$\
|
||||
3\x07\xd5\xb3\xd4\xd4\xc7'\x00\xa8\xce\x00\xfc)\xe0\xcc\
|
||||
\xa4\xdbK2\xe9\x96\x94\xaa\x0bu\xcfc\xa8\x1f\xbc\x92\
|
||||
\x22\xffq\x14GnB\xa4\xdbBRy\xcfRy\xcb\
|
||||
\xa4XmS\xcf\xa7I\xe8\xed,\xf4\x85\xbb&\xc7\xb4\
|
||||
\xda\xc7\xfe:\x06X\xf1d\x975y\x8c\xe5_\xbd\xc6\
|
||||
\x93\x96\xf0\xb3\x5c\x87\x9f++\xdf\x8d\xf6m\xbf\xfa\xa9\
|
||||
o[\x9d!\xe6\x97\x11\xc0\x198\x1e\x84\xea\x02\xa4|\
|
||||
?T\xbf\x09?\x97\x92\x8a\xc7\xb3\xf6H)\x92^#\
|
||||
\x1b\xae\xe5 \x03\x06\xb7e \xa5\xc3\xb4\xd2\x00\x00?\
|
||||
\x91\x87Z\x87R\x0e_h\x9b\xea\x9e\x0e?\xdf\xdd\xf8\
|
||||
\x9d\x03B\xde\x0cv>\x9a\xf1\x1aK\xbd\xb5\x7fk\xb3\
|
||||
u1\xb8\xdd0`x\xe8F\xf9\x1e\xe19\xea\x8a\x03\
|
||||
`\xa9\xfb\x09\xf8)\x80\x1d\xb6\x8f\xcf\x02\xda~\xa6}\
|
||||
\x86v\xdc\x981\x06\x045\x02NB\x90\xf7\xf0t\xb3\
|
||||
\x1f\x19\xb8\x80\xe5\xeeOo5\x84\xa9>\x040\x1aD\
|
||||
!\xc0MEs\xe7F\x97diM\xc8\xcc\xf9N\xa7\
|
||||
3'\xfc0\xb9\xe6\x1b\xe9\x98\x8c\xaaB\x07\x92\x142\
|
||||
\xebU6\xeb\xf1\xbc$\x85}\xd8\xbb;\xd1\x9c\xfa\xe1\
|
||||
\x0f0\xfd\x9e;\x04\x13\x1b\xecD\xec\x02\xdcJ\x92\xcf\
|
||||
\x03<\x14\xf5$\x04\xd8\x84\xd5\x85\xbf_P\xeba,\
|
||||
3\x96\x0f\x8a\x0fB}\xe6\xa43\xf5L\x08\x1enB\
|
||||
\xb5\x13J\x01\xb5\x13a\xaa\x87V3\x07|X\x0eI\
|
||||
4\xe5oK\x8b|\x1f\xcc\xd4\xfa\xd1h%\x00\xa7\x02\
|
||||
<\xbc\xaaA\xb4\xf0 m\x0c\x0e+:+\x9a\xad\x1e\
|
||||
\xfd\xaf\xdf\x091k\xea\xfe\xfe\xf8\xe1H\xdf\x05R\xca\
|
||||
|f\x1e\x89\xd1\x9ef\xbdhx\x0fE4mL\xaf\
|
||||
b/B\x06\x12o\xbf\x00\xa3\x17WYZq\xd9W\
|
||||
\x00\x00\x00\x00IEND\xaeB`\x82\
|
||||
\x00\x00\x04<\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
@@ -2396,6 +2453,35 @@ f\xa2\xd1\xa5\x5c\x22\xb4\x91SZ\xd5u\xd7\x0a\xd2]\
|
||||
|\x01\x85\x09\x800\x7fss\xd3{\xf6\x7fABG\
|
||||
Y\x01\x05l*\xfc\x00!\x00\x12\xf1%U\xb6\x0e\x00\
|
||||
\x00\x00\x00IEND\xaeB`\x82\
|
||||
\x00\x00\x01\xad\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
\x00\x00 \x00\x00\x00 \x08\x03\x00\x00\x00D\xa4\x8a\xc6\
|
||||
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
|
||||
\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01B(\
|
||||
\x9bx\x00\x00\x00\x07tIME\x07\xd9\x0c\x1c\x03\x1c\
|
||||
\x0e%S,b\x00\x00\x00uPLTE\x00\x00\x00\
|
||||
\x13\x13\x13\x0a\x0a\x0a\x0b\x0b\x0b\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6\xc6\xc8\xc7\xc7\
|
||||
\xc9\xc9\xc9\xcb\xcc\xcc\xce\xce\xce\xd0\xd0\xd0\xd2\xd4\xd4\xd6\
|
||||
\xd7\xd7\xd8\xd7\xd7\xd9\xd8\xd8\xda\xd9\xd9\xdb\xda\xda\xdb\xda\
|
||||
\xda\xdc\xdb\xdb\xdc\xdb\xdb\xdd\xdd\xdd\xdf\xde\xde\xdf\xe2\xe2\
|
||||
\xe3\xe3\xe3\xe5\xe3\xe3\xe6\xe4\xe4\xe6\xe6\xe6\xe7\xe7\xe7\xe9\
|
||||
\xe9\xe9\xea\xeb\xeb\xec\xed\xed\xee\xf0\xf0\xf1\xf3\xf3\xf4\xff\
|
||||
\xff\xff\xd3\x9b\xcc\x0e\x00\x00\x00\x0atRNS\x00\x09\
|
||||
\x15\x15\x1825678\xb5\xcc\xc0\x1e\x00\x00\x00\x01\
|
||||
bKGD&Z\x08\x98\xb5\x00\x00\x00\x9bIDA\
|
||||
Tx\xda\xd5\x93\xcb\x0e\x820\x10\x00\x8b\x0aEP|\
|
||||
u\xc1G)\x94\x02\xff\xff\x89.]\x0e\x18\xccr1\
|
||||
F\xe72\xd9d\xd26\x9bT\xfc\x02\xc1\x86%\x10a\
|
||||
\xcf\x12\x0a\xd9)\x86N\x0a\xd9^\x18Z\x0c\xdc\x91\xc1\
|
||||
a\xd0\x1c\x18\x1a\x0c\xec\x9e\xc1bP'\x9et\x97\xce\
|
||||
\x95\xd4\x18\x94=q#\xddIWR\x89\x81\x1e\x03E\
|
||||
\x82\x97IO\x02\x98\x8b\x82G\x01\x9e\xec\x8d\x8aj\xf1\
|
||||
\x84\xcf\xbc!W\x03\x90\x8d\x02oR^-^\xf1?\
|
||||
{\xe0\x83\xc8\x1a=`\xce\xc6\xebd\xa6\x93\x8b\xc4*\
|
||||
\xde2\xc4\xeb/|\xcd'\xec\xbfO\xbf\x90M\x1a\x0a\
|
||||
\x00\x00\x00\x00IEND\xaeB`\x82\
|
||||
\x00\x00\x02\x92\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
@@ -2632,10 +2718,20 @@ qt_resource_name = b"\
|
||||
\x00d\
|
||||
\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00s\x00a\x00v\x00e\x00.\x00p\x00n\x00g\
|
||||
\
|
||||
\x00\x15\
|
||||
\x02\xb4\x1f\x07\
|
||||
\x00o\
|
||||
\x00f\x00f\x00i\x00c\x00e\x00-\x00c\x00h\x00a\x00r\x00t\x00-\x00l\x00i\x00n\x00e\
|
||||
\x00.\x00p\x00n\x00g\
|
||||
\x00\x10\
|
||||
\x03\xe6\xd3g\
|
||||
\x00d\
|
||||
\x00r\x00a\x00w\x00-\x00e\x00l\x00l\x00i\x00p\x00s\x00e\x00.\x00p\x00n\x00g\
|
||||
\x00\x13\
|
||||
\x07\xd6O\x07\
|
||||
\x00v\
|
||||
\x00i\x00e\x00w\x00-\x00f\x00o\x00r\x00m\x00-\x00t\x00a\x00b\x00l\x00e\x00.\x00p\
|
||||
\x00n\x00g\
|
||||
\x00\x12\
|
||||
\x09\xb3>\xc7\
|
||||
\x00d\
|
||||
@@ -2653,7 +2749,7 @@ qt_resource_struct = b"\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1c\x00\x00\x00\x03\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1e\x00\x00\x00\x03\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x00N\xe8\
|
||||
\x00\x00\x01\x9f{C\xf1'\
|
||||
@@ -2661,11 +2757,13 @@ qt_resource_struct = b"\
|
||||
\x00\x00\x01\x9f\x7f\xa8\xa8)\
|
||||
\x00\x00\x02\xec\x00\x00\x00\x00\x00\x01\x00\x00tV\
|
||||
\x00\x00\x01\x9f{0\xc99\
|
||||
\x00\x00\x03\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x8d\xaa\
|
||||
\x00\x00\x01\x9f\x84\x8f\x00\xb9\
|
||||
\x00\x00\x01\xc4\x00\x00\x00\x00\x00\x01\x00\x00D2\
|
||||
\x00\x00\x01\x9f\x7f&\x83\xcd\
|
||||
\x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00<J\
|
||||
\x00\x00\x01\x9f{C\xf1\x18\
|
||||
\x00\x00\x03\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x8d\xaa\
|
||||
\x00\x00\x03\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x91\x1b\
|
||||
\x00\x00\x01\x9f\x7fY\xceg\
|
||||
\x00\x00\x02\xa2\x00\x00\x00\x00\x00\x01\x00\x00f\xc8\
|
||||
\x00\x00\x01\x9f\x7fV\xd5\xc0\
|
||||
@@ -2677,9 +2775,11 @@ qt_resource_struct = b"\
|
||||
\x00\x00\x01\x9f\x7fY\xce\x82\
|
||||
\x00\x00\x01\xe0\x00\x00\x00\x00\x00\x01\x00\x00Kh\
|
||||
\x00\x00\x01\x9f{C\xf1.\
|
||||
\x00\x00\x03\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x95[\
|
||||
\x00\x00\x01\x9f\x84\x8f\xa6\x10\
|
||||
\x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x006\x9c\
|
||||
\x00\x00\x01\x9f{{\xa5\xd5\
|
||||
\x00\x00\x03\xca\x00\x00\x00\x00\x00\x01\x00\x00\x91\xea\
|
||||
\x00\x00\x04&\x00\x00\x00\x00\x00\x01\x00\x00\x97\x0c\
|
||||
\x00\x00\x01\x9f\x7f&\x83\x10\
|
||||
\x00\x00\x00\xfe\x00\x00\x00\x00\x00\x01\x00\x00(e\
|
||||
\x00\x00\x01\x9f\x7f&\x83~\
|
||||
@@ -2703,7 +2803,7 @@ qt_resource_struct = b"\
|
||||
\x00\x00\x01\x9f{C\xf1=\
|
||||
\x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\
|
||||
\x00\x00\x01\x9f\x7f&\x83\x99\
|
||||
\x00\x00\x03\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x94\x80\
|
||||
\x00\x00\x04P\x00\x00\x00\x00\x00\x01\x00\x00\x99\xa2\
|
||||
\x00\x00\x01\x9f{{\xa5\xe3\
|
||||
\x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\
|
||||
\x00\x00\x01\x9f\x7f\xac\xf2\xc6\
|
||||
|
||||
@@ -39,7 +39,7 @@ class Ui_MainWindow(object):
|
||||
self.actionGraphParameters = QAction(MainWindow)
|
||||
self.actionGraphParameters.setObjectName(u"actionGraphParameters")
|
||||
icon1 = QIcon()
|
||||
icon1.addFile(u":/icons/icons/configure.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
icon1.addFile(u":/icons/icons/view-form-table.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionGraphParameters.setIcon(icon1)
|
||||
self.actionCompose = QAction(MainWindow)
|
||||
self.actionCompose.setObjectName(u"actionCompose")
|
||||
@@ -53,80 +53,82 @@ class Ui_MainWindow(object):
|
||||
self.actionRunSimulation.setIcon(icon3)
|
||||
self.actionSimulationWindow = QAction(MainWindow)
|
||||
self.actionSimulationWindow.setObjectName(u"actionSimulationWindow")
|
||||
self.actionSimulationWindow.setIcon(icon1)
|
||||
icon4 = QIcon()
|
||||
icon4.addFile(u":/icons/icons/office-chart-line.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSimulationWindow.setIcon(icon4)
|
||||
self.actionNew = QAction(MainWindow)
|
||||
self.actionNew.setObjectName(u"actionNew")
|
||||
icon4 = QIcon()
|
||||
icon4.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionNew.setIcon(icon4)
|
||||
icon5 = QIcon()
|
||||
icon5.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionNew.setIcon(icon5)
|
||||
self.actionRotateClockwise = QAction(MainWindow)
|
||||
self.actionRotateClockwise.setObjectName(u"actionRotateClockwise")
|
||||
icon5 = QIcon()
|
||||
icon5.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionRotateClockwise.setIcon(icon5)
|
||||
icon6 = QIcon()
|
||||
icon6.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionRotateClockwise.setIcon(icon6)
|
||||
self.actionZoomIn = QAction(MainWindow)
|
||||
self.actionZoomIn.setObjectName(u"actionZoomIn")
|
||||
icon6 = QIcon()
|
||||
icon6.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionZoomIn.setIcon(icon6)
|
||||
icon7 = QIcon()
|
||||
icon7.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionZoomIn.setIcon(icon7)
|
||||
self.actionZoomOut = QAction(MainWindow)
|
||||
self.actionZoomOut.setObjectName(u"actionZoomOut")
|
||||
icon7 = QIcon()
|
||||
icon7.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionZoomOut.setIcon(icon7)
|
||||
icon8 = QIcon()
|
||||
icon8.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionZoomOut.setIcon(icon8)
|
||||
self.actionCenterView = QAction(MainWindow)
|
||||
self.actionCenterView.setObjectName(u"actionCenterView")
|
||||
icon8 = QIcon()
|
||||
icon8.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionCenterView.setIcon(icon8)
|
||||
icon9 = QIcon()
|
||||
icon9.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionCenterView.setIcon(icon9)
|
||||
self.actionOpen = QAction(MainWindow)
|
||||
self.actionOpen.setObjectName(u"actionOpen")
|
||||
icon9 = QIcon()
|
||||
icon9.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionOpen.setIcon(icon9)
|
||||
icon10 = QIcon()
|
||||
icon10.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionOpen.setIcon(icon10)
|
||||
self.actionReloadLibraries = QAction(MainWindow)
|
||||
self.actionReloadLibraries.setObjectName(u"actionReloadLibraries")
|
||||
self.actionReloadSimulation = QAction(MainWindow)
|
||||
self.actionReloadSimulation.setObjectName(u"actionReloadSimulation")
|
||||
self.actionSave = QAction(MainWindow)
|
||||
self.actionSave.setObjectName(u"actionSave")
|
||||
icon10 = QIcon()
|
||||
icon10.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSave.setIcon(icon10)
|
||||
icon11 = QIcon()
|
||||
icon11.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSave.setIcon(icon11)
|
||||
self.actionSaveAs = QAction(MainWindow)
|
||||
self.actionSaveAs.setObjectName(u"actionSaveAs")
|
||||
icon11 = QIcon()
|
||||
icon11.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSaveAs.setIcon(icon11)
|
||||
icon12 = QIcon()
|
||||
icon12.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSaveAs.setIcon(icon12)
|
||||
self.actionExit = QAction(MainWindow)
|
||||
self.actionExit.setObjectName(u"actionExit")
|
||||
self.actionClose = QAction(MainWindow)
|
||||
self.actionClose.setObjectName(u"actionClose")
|
||||
self.actionUndo = QAction(MainWindow)
|
||||
self.actionUndo.setObjectName(u"actionUndo")
|
||||
icon12 = QIcon()
|
||||
icon12.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionUndo.setIcon(icon12)
|
||||
icon13 = QIcon()
|
||||
icon13.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionUndo.setIcon(icon13)
|
||||
self.actionRedo = QAction(MainWindow)
|
||||
self.actionRedo.setObjectName(u"actionRedo")
|
||||
icon13 = QIcon()
|
||||
icon13.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionRedo.setIcon(icon13)
|
||||
icon14 = QIcon()
|
||||
icon14.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionRedo.setIcon(icon14)
|
||||
self.actionCut = QAction(MainWindow)
|
||||
self.actionCut.setObjectName(u"actionCut")
|
||||
icon14 = QIcon()
|
||||
icon14.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionCut.setIcon(icon14)
|
||||
icon15 = QIcon()
|
||||
icon15.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionCut.setIcon(icon15)
|
||||
self.actionCopy = QAction(MainWindow)
|
||||
self.actionCopy.setObjectName(u"actionCopy")
|
||||
icon15 = QIcon()
|
||||
icon15.addFile(u":/icons/icons/edit-copy.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionCopy.setIcon(icon15)
|
||||
icon16 = QIcon()
|
||||
icon16.addFile(u":/icons/icons/edit-copy.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionCopy.setIcon(icon16)
|
||||
self.actionPaste = QAction(MainWindow)
|
||||
self.actionPaste.setObjectName(u"actionPaste")
|
||||
icon16 = QIcon()
|
||||
icon16.addFile(u":/icons/icons/edit-paste.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionPaste.setIcon(icon16)
|
||||
icon17 = QIcon()
|
||||
icon17.addFile(u":/icons/icons/edit-paste.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionPaste.setIcon(icon17)
|
||||
self.actionSelectAll = QAction(MainWindow)
|
||||
self.actionSelectAll.setObjectName(u"actionSelectAll")
|
||||
self.actionDelete = QAction(MainWindow)
|
||||
@@ -216,18 +218,18 @@ class Ui_MainWindow(object):
|
||||
self.workspaceHeaderLayout.setContentsMargins(6, 2, 6, 2)
|
||||
self.navigateUpButton = QToolButton(self.workspaceHeader)
|
||||
self.navigateUpButton.setObjectName(u"navigateUpButton")
|
||||
icon17 = QIcon()
|
||||
icon17.addFile(u":/icons/icons/arrow-up.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.navigateUpButton.setIcon(icon17)
|
||||
icon18 = QIcon()
|
||||
icon18.addFile(u":/icons/icons/arrow-up.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.navigateUpButton.setIcon(icon18)
|
||||
|
||||
self.workspaceHeaderLayout.addWidget(self.navigateUpButton)
|
||||
|
||||
self.navigateDownButton = QToolButton(self.workspaceHeader)
|
||||
self.navigateDownButton.setObjectName(u"navigateDownButton")
|
||||
self.navigateDownButton.setEnabled(False)
|
||||
icon18 = QIcon()
|
||||
icon18.addFile(u":/icons/icons/arrow-down.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.navigateDownButton.setIcon(icon18)
|
||||
icon19 = QIcon()
|
||||
icon19.addFile(u":/icons/icons/arrow-down.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.navigateDownButton.setIcon(icon19)
|
||||
|
||||
self.workspaceHeaderLayout.addWidget(self.navigateDownButton)
|
||||
|
||||
@@ -247,9 +249,9 @@ class Ui_MainWindow(object):
|
||||
|
||||
self.pointerToolButton = QToolButton(self.workspaceHeader)
|
||||
self.pointerToolButton.setObjectName(u"pointerToolButton")
|
||||
icon19 = QIcon()
|
||||
icon19.addFile(u":/icons/icons/edit-select.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.pointerToolButton.setIcon(icon19)
|
||||
icon20 = QIcon()
|
||||
icon20.addFile(u":/icons/icons/edit-select.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.pointerToolButton.setIcon(icon20)
|
||||
self.pointerToolButton.setCheckable(True)
|
||||
self.pointerToolButton.setChecked(True)
|
||||
|
||||
@@ -257,43 +259,43 @@ class Ui_MainWindow(object):
|
||||
|
||||
self.connectToolButton = QToolButton(self.workspaceHeader)
|
||||
self.connectToolButton.setObjectName(u"connectToolButton")
|
||||
icon20 = QIcon()
|
||||
icon20.addFile(u":/icons/icons/network-connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.connectToolButton.setIcon(icon20)
|
||||
icon21 = QIcon()
|
||||
icon21.addFile(u":/icons/icons/network-connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.connectToolButton.setIcon(icon21)
|
||||
self.connectToolButton.setCheckable(True)
|
||||
|
||||
self.workspaceHeaderLayout.addWidget(self.connectToolButton)
|
||||
|
||||
self.boxToolButton = QToolButton(self.workspaceHeader)
|
||||
self.boxToolButton.setObjectName(u"boxToolButton")
|
||||
icon21 = QIcon()
|
||||
icon21.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.boxToolButton.setIcon(icon21)
|
||||
icon22 = QIcon()
|
||||
icon22.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.boxToolButton.setIcon(icon22)
|
||||
self.boxToolButton.setCheckable(True)
|
||||
|
||||
self.workspaceHeaderLayout.addWidget(self.boxToolButton)
|
||||
|
||||
self.lineToolButton = QToolButton(self.workspaceHeader)
|
||||
self.lineToolButton.setObjectName(u"lineToolButton")
|
||||
icon22 = QIcon()
|
||||
icon22.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.lineToolButton.setIcon(icon22)
|
||||
icon23 = QIcon()
|
||||
icon23.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.lineToolButton.setIcon(icon23)
|
||||
self.lineToolButton.setCheckable(True)
|
||||
|
||||
self.workspaceHeaderLayout.addWidget(self.lineToolButton)
|
||||
|
||||
self.textToolButton = QToolButton(self.workspaceHeader)
|
||||
self.textToolButton.setObjectName(u"textToolButton")
|
||||
icon23 = QIcon()
|
||||
icon23.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.textToolButton.setIcon(icon23)
|
||||
icon24 = QIcon()
|
||||
icon24.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.textToolButton.setIcon(icon24)
|
||||
self.textToolButton.setCheckable(True)
|
||||
|
||||
self.workspaceHeaderLayout.addWidget(self.textToolButton)
|
||||
|
||||
self.rotateToolButton = QToolButton(self.workspaceHeader)
|
||||
self.rotateToolButton.setObjectName(u"rotateToolButton")
|
||||
self.rotateToolButton.setIcon(icon5)
|
||||
self.rotateToolButton.setIcon(icon6)
|
||||
|
||||
self.workspaceHeaderLayout.addWidget(self.rotateToolButton)
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
|
||||
QIcon, QImage, QKeySequence, QLinearGradient,
|
||||
QPainter, QPalette, QPixmap, QRadialGradient,
|
||||
QTransform)
|
||||
from PySide6.QtWidgets import (QApplication, QDockWidget, QLabel, QListWidget,
|
||||
QListWidgetItem, QMainWindow, QMenu, QMenuBar,
|
||||
QProgressBar, QSizePolicy, QToolBar, QVBoxLayout,
|
||||
QWidget)
|
||||
from PySide6.QtWidgets import (QApplication, QDockWidget, QHBoxLayout, QLabel,
|
||||
QListWidget, QListWidgetItem, QMainWindow, QMenu,
|
||||
QMenuBar, QProgressBar, QSizePolicy, QToolBar,
|
||||
QVBoxLayout, QWidget)
|
||||
from . import resources_rc
|
||||
|
||||
class Ui_SimulationWindow(object):
|
||||
@@ -27,33 +27,53 @@ class Ui_SimulationWindow(object):
|
||||
if not SimulationWindow.objectName():
|
||||
SimulationWindow.setObjectName(u"SimulationWindow")
|
||||
SimulationWindow.resize(900, 650)
|
||||
icon = QIcon()
|
||||
icon.addFile(u":/icons/icons/office-chart-line.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
SimulationWindow.setWindowIcon(icon)
|
||||
self.actionOpen = QAction(SimulationWindow)
|
||||
self.actionOpen.setObjectName(u"actionOpen")
|
||||
icon = QIcon()
|
||||
icon.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionOpen.setIcon(icon)
|
||||
icon1 = QIcon()
|
||||
icon1.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionOpen.setIcon(icon1)
|
||||
self.actionSave = QAction(SimulationWindow)
|
||||
self.actionSave.setObjectName(u"actionSave")
|
||||
icon1 = QIcon()
|
||||
icon1.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSave.setIcon(icon1)
|
||||
icon2 = QIcon()
|
||||
icon2.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSave.setIcon(icon2)
|
||||
self.actionClear = QAction(SimulationWindow)
|
||||
self.actionClear.setObjectName(u"actionClear")
|
||||
icon2 = QIcon()
|
||||
icon2.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionClear.setIcon(icon2)
|
||||
icon3 = QIcon()
|
||||
icon3.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionClear.setIcon(icon3)
|
||||
self.actionSaveAs = QAction(SimulationWindow)
|
||||
self.actionSaveAs.setObjectName(u"actionSaveAs")
|
||||
icon4 = QIcon()
|
||||
icon4.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSaveAs.setIcon(icon4)
|
||||
self.actionExit = QAction(SimulationWindow)
|
||||
self.actionExit.setObjectName(u"actionExit")
|
||||
self.actionAbout = QAction(SimulationWindow)
|
||||
self.actionAbout.setObjectName(u"actionAbout")
|
||||
self.actionAboutQt = QAction(SimulationWindow)
|
||||
self.actionAboutQt.setObjectName(u"actionAboutQt")
|
||||
self.actionToggleResults = QAction(SimulationWindow)
|
||||
self.actionToggleResults.setObjectName(u"actionToggleResults")
|
||||
self.actionToggleResults.setCheckable(True)
|
||||
self.actionToggleResults.setChecked(True)
|
||||
self.centralWidget = QWidget(SimulationWindow)
|
||||
self.centralWidget.setObjectName(u"centralWidget")
|
||||
self.centralWidget.setMaximumSize(QSize(0, 0))
|
||||
self.resultsLayout = QVBoxLayout(self.centralWidget)
|
||||
self.resultsLayout.setObjectName(u"resultsLayout")
|
||||
self.resultsPlaceholder = QLabel(self.centralWidget)
|
||||
self.resultsPlaceholder.setObjectName(u"resultsPlaceholder")
|
||||
self.resultsPlaceholder.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.resultsLayout.addWidget(self.resultsPlaceholder)
|
||||
|
||||
SimulationWindow.setCentralWidget(self.centralWidget)
|
||||
self.menuBar = QMenuBar(SimulationWindow)
|
||||
self.menuBar.setObjectName(u"menuBar")
|
||||
self.menuBar.setGeometry(QRect(0, 0, 900, 24))
|
||||
self.menuFile = QMenu(self.menuBar)
|
||||
self.menuFile.setObjectName(u"menuFile")
|
||||
self.menuView = QMenu(self.menuBar)
|
||||
@@ -69,47 +89,35 @@ class Ui_SimulationWindow(object):
|
||||
self.fileToolbar.setObjectName(u"fileToolbar")
|
||||
self.fileToolbar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
||||
SimulationWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.fileToolbar)
|
||||
self.resultsDock = QDockWidget(SimulationWindow)
|
||||
self.resultsDock.setObjectName(u"resultsDock")
|
||||
self.resultsDockContents = QWidget()
|
||||
self.resultsDockContents.setObjectName(u"resultsDockContents")
|
||||
self.resultsLayout = QVBoxLayout(self.resultsDockContents)
|
||||
self.resultsLayout.setObjectName(u"resultsLayout")
|
||||
self.resultsPlaceholder = QLabel(self.resultsDockContents)
|
||||
self.resultsPlaceholder.setObjectName(u"resultsPlaceholder")
|
||||
self.resultsPlaceholder.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.resultsLayout.addWidget(self.resultsPlaceholder)
|
||||
|
||||
self.resultsDock.setWidget(self.resultsDockContents)
|
||||
SimulationWindow.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.resultsDock)
|
||||
self.statusDock = QDockWidget(SimulationWindow)
|
||||
self.statusDock.setObjectName(u"statusDock")
|
||||
self.statusDockContents = QWidget()
|
||||
self.statusDockContents.setObjectName(u"statusDockContents")
|
||||
self.statusLayout = QVBoxLayout(self.statusDockContents)
|
||||
self.statusLayout.setObjectName(u"statusLayout")
|
||||
self.statusLabel = QLabel(self.statusDockContents)
|
||||
self.statusLabel.setObjectName(u"statusLabel")
|
||||
self.horizontalLayout = QHBoxLayout(self.statusDockContents)
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
self.timeLabel = QLabel(self.statusDockContents)
|
||||
self.timeLabel.setObjectName(u"timeLabel")
|
||||
|
||||
self.statusLayout.addWidget(self.statusLabel)
|
||||
self.horizontalLayout.addWidget(self.timeLabel)
|
||||
|
||||
self.progressBar = QProgressBar(self.statusDockContents)
|
||||
self.progressBar.setObjectName(u"progressBar")
|
||||
self.progressBar.setMaximum(10000)
|
||||
self.progressBar.setValue(0)
|
||||
|
||||
self.statusLayout.addWidget(self.progressBar)
|
||||
self.horizontalLayout.addWidget(self.progressBar)
|
||||
|
||||
self.timeLabel = QLabel(self.statusDockContents)
|
||||
self.timeLabel.setObjectName(u"timeLabel")
|
||||
self.statusLabel = QLabel(self.statusDockContents)
|
||||
self.statusLabel.setObjectName(u"statusLabel")
|
||||
|
||||
self.statusLayout.addWidget(self.timeLabel)
|
||||
self.horizontalLayout.addWidget(self.statusLabel)
|
||||
|
||||
self.statusDock.setWidget(self.statusDockContents)
|
||||
SimulationWindow.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.statusDock)
|
||||
self.logDock = QDockWidget(SimulationWindow)
|
||||
self.logDock.setObjectName(u"logDock")
|
||||
self.logDock.setFloating(False)
|
||||
self.logDock.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetFloatable|QDockWidget.DockWidgetFeature.DockWidgetMovable)
|
||||
self.logDockContents = QWidget()
|
||||
self.logDockContents.setObjectName(u"logDockContents")
|
||||
self.logLayout = QVBoxLayout(self.logDockContents)
|
||||
@@ -128,6 +136,7 @@ class Ui_SimulationWindow(object):
|
||||
self.menuBar.addAction(self.menuHelp.menuAction())
|
||||
self.menuFile.addAction(self.actionOpen)
|
||||
self.menuFile.addAction(self.actionSave)
|
||||
self.menuFile.addAction(self.actionSaveAs)
|
||||
self.menuFile.addSeparator()
|
||||
self.menuFile.addAction(self.actionClear)
|
||||
self.menuFile.addSeparator()
|
||||
@@ -138,6 +147,7 @@ class Ui_SimulationWindow(object):
|
||||
self.menuHelp.addAction(self.actionAboutQt)
|
||||
self.fileToolbar.addAction(self.actionOpen)
|
||||
self.fileToolbar.addAction(self.actionSave)
|
||||
self.fileToolbar.addAction(self.actionSaveAs)
|
||||
self.fileToolbar.addAction(self.actionClear)
|
||||
|
||||
self.retranslateUi(SimulationWindow)
|
||||
@@ -156,24 +166,28 @@ class Ui_SimulationWindow(object):
|
||||
self.actionSave.setShortcut(QCoreApplication.translate("SimulationWindow", u"Ctrl+S", None))
|
||||
#endif // QT_CONFIG(shortcut)
|
||||
self.actionClear.setText(QCoreApplication.translate("SimulationWindow", u"&Clear", None))
|
||||
self.actionSaveAs.setText(QCoreApplication.translate("SimulationWindow", u"Save &As\u2026", None))
|
||||
#if QT_CONFIG(shortcut)
|
||||
self.actionSaveAs.setShortcut(QCoreApplication.translate("SimulationWindow", u"Ctrl+Shift+S", None))
|
||||
#endif // QT_CONFIG(shortcut)
|
||||
self.actionExit.setText(QCoreApplication.translate("SimulationWindow", u"E&xit", None))
|
||||
#if QT_CONFIG(shortcut)
|
||||
self.actionExit.setShortcut(QCoreApplication.translate("SimulationWindow", u"Ctrl+W", None))
|
||||
#endif // QT_CONFIG(shortcut)
|
||||
self.actionAbout.setText(QCoreApplication.translate("SimulationWindow", u"&About Simulation Window", None))
|
||||
self.actionAboutQt.setText(QCoreApplication.translate("SimulationWindow", u"About &Qt", None))
|
||||
self.actionToggleResults.setText(QCoreApplication.translate("SimulationWindow", u"Results", None))
|
||||
self.resultsPlaceholder.setText(QCoreApplication.translate("SimulationWindow", u"Simulation graphs and result controls can be added here.", None))
|
||||
self.menuFile.setTitle(QCoreApplication.translate("SimulationWindow", u"&File", None))
|
||||
self.menuView.setTitle(QCoreApplication.translate("SimulationWindow", u"&View", None))
|
||||
self.menuPanels.setTitle(QCoreApplication.translate("SimulationWindow", u"&Panels", None))
|
||||
self.menuToolbars.setTitle(QCoreApplication.translate("SimulationWindow", u"&Toolbars", None))
|
||||
self.menuHelp.setTitle(QCoreApplication.translate("SimulationWindow", u"&Help", None))
|
||||
self.fileToolbar.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"File", None))
|
||||
self.resultsDock.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Results", None))
|
||||
self.resultsPlaceholder.setText(QCoreApplication.translate("SimulationWindow", u"Simulation graphs and result controls can be added here.", None))
|
||||
self.statusDock.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Status", None))
|
||||
self.statusLabel.setText(QCoreApplication.translate("SimulationWindow", u"No simulation has been run yet.", None))
|
||||
self.progressBar.setFormat(QCoreApplication.translate("SimulationWindow", u"%p%", None))
|
||||
self.timeLabel.setText(QCoreApplication.translate("SimulationWindow", u"Time: 0 s", None))
|
||||
self.progressBar.setFormat(QCoreApplication.translate("SimulationWindow", u"%p%", None))
|
||||
self.statusLabel.setText(QCoreApplication.translate("SimulationWindow", u"No simulation has been run yet.", None))
|
||||
self.logDock.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Log", None))
|
||||
# retranslateUi
|
||||
|
||||
|
||||
@@ -1892,6 +1892,22 @@ class GraphWorkspaceView(QGraphicsView):
|
||||
if self.controller is None:
|
||||
return
|
||||
mime_data = QApplication.clipboard().mimeData()
|
||||
if mime_data.hasFormat(COMPONENT_MIME_TYPE):
|
||||
try:
|
||||
payload = json.loads(
|
||||
bytes(mime_data.data(COMPONENT_MIME_TYPE)).decode("utf-8")
|
||||
)
|
||||
source = Component.from_dict(payload)
|
||||
except (KeyError, TypeError, ValueError, json.JSONDecodeError):
|
||||
return
|
||||
component_id = self.controller.add_component_copy(source, QPointF(0, 0))
|
||||
scene = self.scene()
|
||||
if isinstance(scene, GraphScene):
|
||||
scene.clearSelection()
|
||||
item = scene.component_items.get(component_id)
|
||||
if item is not None:
|
||||
item.setSelected(True)
|
||||
return
|
||||
if not mime_data.hasFormat(SELECTION_MIME_TYPE):
|
||||
return
|
||||
try:
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import Qt, Slot
|
||||
from PySide6.QtCore import QByteArray, QMimeData, Qt, Slot
|
||||
from PySide6.QtGui import QCloseEvent
|
||||
from PySide6.QtWidgets import (
|
||||
QButtonGroup,
|
||||
QApplication,
|
||||
QFileDialog,
|
||||
QMainWindow,
|
||||
QMenu,
|
||||
@@ -12,7 +14,7 @@ from PySide6.QtWidgets import (
|
||||
QTabWidget,
|
||||
)
|
||||
|
||||
from bedit.core.model import Component
|
||||
from bedit.core.model import Component, Connection
|
||||
from bedit.core.application_log import get_logger
|
||||
from bedit.core.serializer import DocumentSerializer
|
||||
from bedit.core.simulation import Simulation
|
||||
@@ -22,12 +24,15 @@ from bedit.gui.dialogs.graph_parameters import GraphParametersDialog
|
||||
from bedit.gui.dialogs.item_options import ItemOptionsDialog
|
||||
from bedit.gui.models.library_repository import LibraryRepository
|
||||
from bedit.gui.models.library_tree import (
|
||||
COMPONENT_MIME_TYPE,
|
||||
COMPONENT_ROLE,
|
||||
COMPONENT_ID_ROLE,
|
||||
COMPONENT_INSTANCE_ROLE,
|
||||
ITEM_KIND_ROLE,
|
||||
DocumentTreeModel,
|
||||
LibraryTreeModel,
|
||||
)
|
||||
from bedit.gui.graphics.workspace import SELECTION_MIME_TYPE
|
||||
from bedit.gui.dialogs.settings import SettingsDialog
|
||||
from bedit.gui.dialogs.port_options import PortOptionsDialog
|
||||
from bedit.gui.dialogs.parameter_options import ParameterOptionsDialog
|
||||
@@ -55,7 +60,9 @@ class MainWindow(QMainWindow):
|
||||
self.log.info("BEdit started")
|
||||
self.settings = application_settings()
|
||||
self._applying_text_definition = False
|
||||
self._simulation_window = SimulationWindow(self)
|
||||
# Keep a Python-owned top-level window. Giving it MainWindow as its Qt
|
||||
# parent makes some Linux window managers inherit the BEdit window icon.
|
||||
self._simulation_window = SimulationWindow()
|
||||
|
||||
self.libraries = LibraryRepository(self)
|
||||
self.simulation = Simulation(
|
||||
@@ -69,6 +76,7 @@ class MainWindow(QMainWindow):
|
||||
)
|
||||
self.document_tree_model = DocumentTreeModel(self.document_controller, self)
|
||||
self._configure_models()
|
||||
QApplication.clipboard().dataChanged.connect(self._update_edit_actions)
|
||||
self._connect_actions()
|
||||
self._populate_view_menu()
|
||||
self._restore_window_geometry()
|
||||
@@ -102,6 +110,10 @@ class MainWindow(QMainWindow):
|
||||
self.ui.documentTreeView.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||
self.ui.documentTreeView.customContextMenuRequested.connect(self.show_library_context_menu)
|
||||
self.ui.documentTreeView.doubleClicked.connect(self.activate_tree_component)
|
||||
self.ui.documentTreeView.clicked.connect(
|
||||
lambda _index: self._update_edit_actions()
|
||||
)
|
||||
self.ui.treeView.clicked.connect(lambda _index: self._update_edit_actions())
|
||||
self.document_tree_model.rebuilt.connect(self.ui.documentTreeView.expandAll)
|
||||
self.ui.graphView.set_model(self.document_controller)
|
||||
self.ui.graphView.componentOptionsRequested.connect(self.show_component_options)
|
||||
@@ -155,9 +167,9 @@ class MainWindow(QMainWindow):
|
||||
self.ui.actionUndo.triggered.connect(self.document_controller.undo_stack.undo)
|
||||
self.ui.actionRedo.triggered.connect(self.document_controller.undo_stack.redo)
|
||||
self.ui.actionDelete.triggered.connect(self.ui.graphView.delete_selected)
|
||||
self.ui.actionCopy.triggered.connect(self.ui.graphView.copy_selection)
|
||||
self.ui.actionCut.triggered.connect(self.ui.graphView.cut_selection)
|
||||
self.ui.actionPaste.triggered.connect(self.ui.graphView.paste_selection)
|
||||
self.ui.actionCopy.triggered.connect(self.copy_selection)
|
||||
self.ui.actionCut.triggered.connect(self.cut_selection)
|
||||
self.ui.actionPaste.triggered.connect(self.paste_selection)
|
||||
self.ui.actionSelectAll.triggered.connect(self.ui.graphView.select_all)
|
||||
self.ui.actionRotateClockwise.triggered.connect(self.ui.graphView.rotate_selected)
|
||||
self.addAction(self.ui.actionRotateClockwise)
|
||||
@@ -324,8 +336,24 @@ class MainWindow(QMainWindow):
|
||||
has_selection = bool(
|
||||
self.ui.graphView.scene() and self.ui.graphView.scene().selectedItems()
|
||||
)
|
||||
for action in (self.ui.actionCut, self.ui.actionCopy, self.ui.actionDelete):
|
||||
action.setEnabled(is_graph and has_selection)
|
||||
document_component_selected = bool(
|
||||
self.ui.documentTreeView.currentIndex().data(COMPONENT_ID_ROLE)
|
||||
)
|
||||
library_component_selected = isinstance(
|
||||
self.ui.treeView.currentIndex().data(COMPONENT_ROLE), dict
|
||||
)
|
||||
document_has_focus = self._view_has_focus(self.ui.documentTreeView)
|
||||
library_has_focus = self._view_has_focus(self.ui.treeView)
|
||||
self.ui.actionCopy.setEnabled(
|
||||
(is_graph and has_selection)
|
||||
or (document_has_focus and document_component_selected)
|
||||
or (library_has_focus and library_component_selected)
|
||||
)
|
||||
self.ui.actionCut.setEnabled(
|
||||
(is_graph and has_selection)
|
||||
or (document_has_focus and document_component_selected)
|
||||
)
|
||||
self.ui.actionDelete.setEnabled(is_graph and has_selection)
|
||||
self.ui.actionRotateClockwise.setEnabled(
|
||||
is_graph and self.ui.graphView.has_selected_components()
|
||||
)
|
||||
@@ -333,11 +361,114 @@ class MainWindow(QMainWindow):
|
||||
is_graph and self.ui.graphView.has_selected_components()
|
||||
)
|
||||
self.ui.actionSelectAll.setEnabled(is_graph)
|
||||
self.ui.actionPaste.setEnabled(is_graph)
|
||||
can_paste_component = QApplication.clipboard().mimeData().hasFormat(
|
||||
COMPONENT_MIME_TYPE
|
||||
) or QApplication.clipboard().mimeData().hasFormat(SELECTION_MIME_TYPE)
|
||||
self.ui.actionPaste.setEnabled(
|
||||
(is_graph and not document_has_focus and not library_has_focus)
|
||||
or (
|
||||
document_has_focus
|
||||
and self.document_controller.document is not None
|
||||
and can_paste_component
|
||||
)
|
||||
)
|
||||
self.ui.navigateDownButton.setEnabled(
|
||||
is_graph and self.ui.graphView.has_single_selected_component()
|
||||
)
|
||||
|
||||
def copy_selection(self) -> bool:
|
||||
if self._view_has_focus(self.ui.documentTreeView):
|
||||
return self._copy_tree_component(self.ui.documentTreeView)
|
||||
if self._view_has_focus(self.ui.treeView):
|
||||
return self._copy_tree_component(self.ui.treeView)
|
||||
return self.ui.graphView.copy_selection()
|
||||
|
||||
def cut_selection(self) -> None:
|
||||
if self._view_has_focus(self.ui.documentTreeView):
|
||||
index = self.ui.documentTreeView.currentIndex()
|
||||
component_id = index.data(COMPONENT_ID_ROLE)
|
||||
if component_id and self._copy_tree_component(self.ui.documentTreeView):
|
||||
self.document_controller.delete_component(component_id)
|
||||
return
|
||||
if self._view_has_focus(self.ui.treeView):
|
||||
self._copy_tree_component(self.ui.treeView)
|
||||
return
|
||||
self.ui.graphView.cut_selection()
|
||||
|
||||
def paste_selection(self) -> None:
|
||||
if self._view_has_focus(self.ui.documentTreeView):
|
||||
self._paste_into_document_tree()
|
||||
return
|
||||
self.ui.graphView.paste_selection()
|
||||
|
||||
def _copy_tree_component(self, tree) -> bool:
|
||||
component = tree.currentIndex().data(COMPONENT_ROLE)
|
||||
if not isinstance(component, dict):
|
||||
return False
|
||||
mime_data = QMimeData()
|
||||
mime_data.setData(
|
||||
COMPONENT_MIME_TYPE,
|
||||
QByteArray(json.dumps(component).encode("utf-8")),
|
||||
)
|
||||
QApplication.clipboard().setMimeData(mime_data)
|
||||
return True
|
||||
|
||||
def _paste_into_document_tree(self) -> None:
|
||||
index = self.ui.documentTreeView.currentIndex()
|
||||
kind = index.data(ITEM_KIND_ROLE)
|
||||
owner_id = None
|
||||
if kind == "current-component":
|
||||
component_id = index.data(COMPONENT_ID_ROLE)
|
||||
component = (
|
||||
self.document_controller.document.find_component(component_id)
|
||||
if self.document_controller.document
|
||||
else None
|
||||
)
|
||||
if component is None or component.implementation_kind != "graph":
|
||||
QMessageBox.warning(
|
||||
self, "Cannot Paste", "Select a graph component or the document root."
|
||||
)
|
||||
return
|
||||
owner_id = component.id
|
||||
elif kind != "current-document":
|
||||
QMessageBox.warning(
|
||||
self, "Cannot Paste", "Select a graph component or the document root."
|
||||
)
|
||||
return
|
||||
try:
|
||||
components, connections = self._clipboard_components()
|
||||
self.document_controller.paste_components_to(
|
||||
owner_id, components, connections
|
||||
)
|
||||
except ValueError as error:
|
||||
QMessageBox.warning(self, "Cannot Paste", str(error))
|
||||
|
||||
@staticmethod
|
||||
def _clipboard_components() -> tuple[list[Component], list]:
|
||||
mime_data = QApplication.clipboard().mimeData()
|
||||
try:
|
||||
if mime_data.hasFormat(SELECTION_MIME_TYPE):
|
||||
payload = json.loads(
|
||||
bytes(mime_data.data(SELECTION_MIME_TYPE)).decode("utf-8")
|
||||
)
|
||||
return (
|
||||
[Component.from_dict(item) for item in payload.get("components", [])],
|
||||
[Connection.from_dict(item) for item in payload.get("connections", [])],
|
||||
)
|
||||
if mime_data.hasFormat(COMPONENT_MIME_TYPE):
|
||||
payload = json.loads(
|
||||
bytes(mime_data.data(COMPONENT_MIME_TYPE)).decode("utf-8")
|
||||
)
|
||||
return [Component.from_dict(payload)], []
|
||||
except (KeyError, TypeError, ValueError, json.JSONDecodeError) as error:
|
||||
raise ValueError("The clipboard does not contain a valid component") from error
|
||||
raise ValueError("The clipboard does not contain a BEdit component")
|
||||
|
||||
@staticmethod
|
||||
def _view_has_focus(view) -> bool:
|
||||
focus = QApplication.focusWidget()
|
||||
return focus is view or (focus is not None and view.isAncestorOf(focus))
|
||||
|
||||
@Slot()
|
||||
def navigate_up(self) -> None:
|
||||
if self._resolve_source_edits():
|
||||
@@ -560,6 +691,8 @@ class MainWindow(QMainWindow):
|
||||
def show_library_context_menu(self, position) -> None:
|
||||
tree_view = self.ui.documentTreeView
|
||||
index = tree_view.indexAt(position)
|
||||
if index.isValid():
|
||||
tree_view.setCurrentIndex(index)
|
||||
kind = index.data(ITEM_KIND_ROLE)
|
||||
if kind == "current-component":
|
||||
component_id = index.data(COMPONENT_ID_ROLE)
|
||||
@@ -577,6 +710,11 @@ class MainWindow(QMainWindow):
|
||||
options_action = menu.addAction("Component Options…")
|
||||
ports_action = menu.addAction("Port Options…")
|
||||
parameters_action = menu.addAction("Parameter Options…")
|
||||
menu.addSeparator()
|
||||
copy_action = menu.addAction("Copy")
|
||||
cut_action = menu.addAction("Cut")
|
||||
paste_action = menu.addAction("Paste")
|
||||
paste_action.setEnabled(component.implementation_kind == "graph")
|
||||
delete_action = menu.addAction("Delete")
|
||||
selected = menu.exec(tree_view.viewport().mapToGlobal(position))
|
||||
if graph_action is not None and selected is graph_action:
|
||||
@@ -589,6 +727,13 @@ class MainWindow(QMainWindow):
|
||||
self.show_component_port_options(component_id)
|
||||
elif selected is parameters_action:
|
||||
self.show_component_parameter_options(component_id)
|
||||
elif selected is copy_action:
|
||||
self._copy_tree_component(tree_view)
|
||||
elif selected is cut_action:
|
||||
if self._copy_tree_component(tree_view):
|
||||
self.document_controller.delete_component(component_id)
|
||||
elif selected is paste_action:
|
||||
self._paste_into_document_tree()
|
||||
elif selected is delete_action:
|
||||
answer = QMessageBox.question(
|
||||
self,
|
||||
@@ -604,24 +749,34 @@ class MainWindow(QMainWindow):
|
||||
menu = QMenu(self)
|
||||
graph_action = menu.addAction("New Graph Block")
|
||||
text_action = menu.addAction("New Text Block")
|
||||
menu.addSeparator()
|
||||
paste_action = menu.addAction("Paste")
|
||||
selected = menu.exec(tree_view.viewport().mapToGlobal(position))
|
||||
if selected is graph_action:
|
||||
self.document_controller.add_root("graph")
|
||||
elif selected is text_action:
|
||||
self.document_controller.add_root("text")
|
||||
elif selected is paste_action:
|
||||
self._paste_into_document_tree()
|
||||
|
||||
@Slot(object)
|
||||
def show_external_library_context_menu(self, position) -> None:
|
||||
tree = self.ui.treeView
|
||||
index = tree.indexAt(position)
|
||||
if index.isValid():
|
||||
tree.setCurrentIndex(index)
|
||||
component = index.data(COMPONENT_INSTANCE_ROLE)
|
||||
if not isinstance(component, Component):
|
||||
return
|
||||
menu = QMenu(self)
|
||||
copy_action = menu.addAction("Copy")
|
||||
menu.addSeparator()
|
||||
ports_action = menu.addAction("Port Options…")
|
||||
parameters_action = menu.addAction("Parameter Options…")
|
||||
selected = menu.exec(tree.viewport().mapToGlobal(position))
|
||||
if selected is ports_action:
|
||||
if selected is copy_action:
|
||||
self._copy_tree_component(tree)
|
||||
elif selected is ports_action:
|
||||
dialog = PortOptionsDialog(component, self)
|
||||
if dialog.exec() == dialog.DialogCode.Accepted:
|
||||
old_inputs, old_outputs = deepcopy(component.inputs), deepcopy(component.outputs)
|
||||
@@ -773,6 +928,7 @@ class MainWindow(QMainWindow):
|
||||
event.ignore()
|
||||
return
|
||||
self.settings.setValue("window/geometry", self.saveGeometry())
|
||||
self._simulation_window.close()
|
||||
self.simulation.shutdown()
|
||||
self.log.info("BEdit closed")
|
||||
self.application_logger.removeHandler(self.log_handler)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import QFileDialog, QMainWindow, QMessageBox
|
||||
|
||||
from bedit.core.simulation.openmodelica import SimulationMessage, SimulationProgress
|
||||
from bedit.core.simulation.results import (
|
||||
SimulationExecutionResult,
|
||||
SimulationResults,
|
||||
load_simulation_results,
|
||||
save_simulation_results,
|
||||
@@ -31,6 +32,10 @@ class SimulationWindow(QMainWindow):
|
||||
self.results = SimulationResults()
|
||||
self._connect_actions()
|
||||
self._populate_view_menu()
|
||||
self.splitDockWidget(
|
||||
self.ui.statusDock, self.ui.logDock, Qt.Orientation.Vertical
|
||||
)
|
||||
self.ui.statusDock.setFixedHeight(self.ui.statusDock.sizeHint().height())
|
||||
self.progressReceived.connect(self._show_progress)
|
||||
self.messageReceived.connect(self._show_message)
|
||||
self.simulationFinished.connect(self._show_finished)
|
||||
@@ -39,15 +44,18 @@ class SimulationWindow(QMainWindow):
|
||||
def _connect_actions(self) -> None:
|
||||
self.ui.actionOpen.triggered.connect(self.open_results)
|
||||
self.ui.actionSave.triggered.connect(self.save_results)
|
||||
self.ui.actionSaveAs.triggered.connect(self.save_results_as)
|
||||
self.ui.actionClear.triggered.connect(self.clear)
|
||||
self.ui.actionExit.triggered.connect(self.close)
|
||||
self.ui.actionAbout.triggered.connect(self.show_about)
|
||||
self.ui.actionAboutQt.triggered.connect(
|
||||
lambda: QMessageBox.aboutQt(self, "About Qt Framework")
|
||||
)
|
||||
self.ui.actionToggleResults.toggled.connect(self.ui.centralWidget.setVisible)
|
||||
|
||||
def _populate_view_menu(self) -> None:
|
||||
for panel in (self.ui.statusDock, self.ui.logDock, self.ui.resultsDock):
|
||||
self.ui.menuPanels.addAction(self.ui.actionToggleResults)
|
||||
for panel in (self.ui.statusDock, self.ui.logDock):
|
||||
self.ui.menuPanels.addAction(panel.toggleViewAction())
|
||||
self.ui.menuToolbars.addAction(self.ui.fileToolbar.toggleViewAction())
|
||||
|
||||
@@ -97,15 +105,18 @@ class SimulationWindow(QMainWindow):
|
||||
)
|
||||
|
||||
def load_result_views(self) -> None:
|
||||
"""Populate custom plots from ``self.results.traces``.
|
||||
"""Populate custom plots from ``self.results.data`` and traces.
|
||||
|
||||
This is the intended integration point for a future plotting widget.
|
||||
"""
|
||||
|
||||
data_count = len(self.results.data)
|
||||
trace_count = len(self.results.traces)
|
||||
if trace_count:
|
||||
if data_count or trace_count:
|
||||
sample_count = len(next(iter(self.results.data.values()), []))
|
||||
self.ui.resultsPlaceholder.setText(
|
||||
f"{trace_count} trace(s) loaded; add graph rendering here."
|
||||
f"{data_count} data column(s), {sample_count} sample(s), and "
|
||||
f"{trace_count} configured trace(s) loaded; add graph rendering here."
|
||||
)
|
||||
|
||||
def open_results(self) -> None:
|
||||
@@ -113,7 +124,7 @@ class SimulationWindow(QMainWindow):
|
||||
self,
|
||||
"Open Simulation Results",
|
||||
"",
|
||||
"BEdit Simulation Results (*.json);;JSON Files (*.json)",
|
||||
"BEdit Binary Simulation Results (*.ber);;JSON Simulation Results (*.json)",
|
||||
)
|
||||
if not file_name:
|
||||
return
|
||||
@@ -131,18 +142,27 @@ class SimulationWindow(QMainWindow):
|
||||
def save_results(self) -> None:
|
||||
file_path = self._file_path
|
||||
if file_path is None:
|
||||
default_name = f"{_safe_file_stem(self.results.model_name)}-results.json"
|
||||
file_name, _selected_filter = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"Save Simulation Results",
|
||||
default_name,
|
||||
"BEdit Simulation Results (*.json);;JSON Files (*.json)",
|
||||
)
|
||||
if not file_name:
|
||||
return
|
||||
file_path = Path(file_name)
|
||||
if file_path.suffix.lower() != ".json":
|
||||
file_path = file_path.with_suffix(".json")
|
||||
self.save_results_as()
|
||||
return
|
||||
self._save_results_to(file_path)
|
||||
|
||||
def save_results_as(self) -> None:
|
||||
default_name = f"{_safe_file_stem(self.results.model_name)}-results.ber"
|
||||
file_name, selected_filter = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"Save Simulation Results As",
|
||||
default_name,
|
||||
"BEdit Binary Simulation Results (*.ber);;JSON Simulation Results (*.json)",
|
||||
)
|
||||
if not file_name:
|
||||
return
|
||||
file_path = Path(file_name)
|
||||
if file_path.suffix.lower() not in {".ber", ".json"}:
|
||||
suffix = ".json" if selected_filter.startswith("JSON") else ".ber"
|
||||
file_path = file_path.with_suffix(suffix)
|
||||
self._save_results_to(file_path)
|
||||
|
||||
def _save_results_to(self, file_path: Path) -> None:
|
||||
try:
|
||||
save_simulation_results(file_path, self.results)
|
||||
except OSError as error:
|
||||
@@ -209,7 +229,12 @@ class SimulationWindow(QMainWindow):
|
||||
def _show_finished(self, result) -> None:
|
||||
self._running = False
|
||||
self.results.status.update(phase="Simulation finished", progress=10000)
|
||||
self.results.metadata["processResult"] = result
|
||||
if isinstance(result, SimulationExecutionResult):
|
||||
self.results.data = result.data
|
||||
self.results.metadata["processReturnCode"] = result.return_code
|
||||
self.results.metadata["sourceResultFile"] = Path(result.result_file).name
|
||||
else:
|
||||
self.results.metadata["processResult"] = result
|
||||
self.ui.progressBar.setValue(10000)
|
||||
self.ui.statusLabel.setText("Simulation finished")
|
||||
self.load_result_views()
|
||||
|
||||
Reference in New Issue
Block a user