Skip to content

Commit

Permalink
Cleanup code by using dataclasses for storage
Browse files Browse the repository at this point in the history
  • Loading branch information
freand76 committed Jun 1, 2024
1 parent 8713bc9 commit 1bd4055
Show file tree
Hide file tree
Showing 13 changed files with 268 additions and 176 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies = [
"pyvcd>=0.3.0",
"pyside6>=6.6.0",
"pexpect==4.8.0",
"pydantic==2.7.1",
"qtawesome",
"yowasp-yosys==0.36.0.8.post620",
]
Expand Down
9 changes: 6 additions & 3 deletions src/digsim/app/gui_objects/_component_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from PySide6.QtGui import QFont, QFontMetrics, QPen
from PySide6.QtWidgets import QGraphicsItem, QGraphicsRectItem

from digsim.storage_model import GuiPositionDataClass

from ._component_context_menu import ComponentContextMenu
from ._component_port_item import PortGraphicsItem

Expand Down Expand Up @@ -365,6 +367,7 @@ def zlevel(self, level):
"""Set zlevel"""
self.setZValue(level)

def to_dict(self):
"""Return position as dict"""
return {"x": int(self._save_pos.x()), "y": int(self._save_pos.y()), "z": self.zlevel}
def to_gui_dataclass(self):
return GuiPositionDataClass(
x=int(self._save_pos.x()), y=int(self._save_pos.y()), z=int(self.zlevel)
)
27 changes: 13 additions & 14 deletions src/digsim/app/model/_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

"""An application model for a GUI simulated circuit"""

import json
import queue
import time
from pathlib import Path
Expand All @@ -12,6 +11,7 @@

from digsim.app.gui_objects import ComponentObject
from digsim.circuit.components.atoms import Component
from digsim.storage_model import AppFileDataClass

from ._model_objects import ModelObjects
from ._model_settings import ModelSettings
Expand Down Expand Up @@ -155,28 +155,27 @@ def run(self):
def save_circuit(self, path):
"""Save the circuit with GUI information"""
circuit_folder = str(Path(path).parent)
circuit_dict = self.objects.circuit_to_dict(circuit_folder)
shortcuts_dict = self.shortcuts.to_dict()
circuit_dict.update(shortcuts_dict)
settings_dict = self.settings.to_dict()
circuit_dict.update(settings_dict)
json_object = json.dumps(circuit_dict, indent=4)
with open(path, mode="w", encoding="utf-8") as json_file:
json_file.write(json_object)
model_dataclass = self.objects.circuit_to_model(circuit_folder)
appfile_dataclass = AppFileDataClass(
circuit=model_dataclass.circuit,
gui=model_dataclass.gui,
shortcuts=self.shortcuts.to_dict(),
settings=self.settings.get_all(),
)
appfile_dataclass.save(path)
self._changed = False
self.sig_control_notify.emit()

def load_circuit(self, path):
"""Load a circuit with GUI information"""
self._model_clear()
with open(path, mode="r", encoding="utf-8") as json_file:
circuit_dict = json.load(json_file)
app_file_dc = AppFileDataClass.load(path)
circuit_folder = str(Path(path).parent)
if len(circuit_folder) == 0:
circuit_folder = "."
exception_str_list = self.objects.dict_to_circuit(circuit_dict, circuit_folder)
self.shortcuts.from_dict(circuit_dict)
self.settings.from_dict(circuit_dict)
exception_str_list = self.objects.model_to_circuit(app_file_dc, circuit_folder)
self.shortcuts.from_dict(app_file_dc.shortcuts)
self.settings.from_dict(app_file_dc.settings)
self.model_init()
self._changed = False
self.objects.reset_undo_stack()
Expand Down
27 changes: 12 additions & 15 deletions src/digsim/app/model/_model_components.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) Fredrik Andersson, 2023
# Copyright (c) Fredrik Andersson, 2023-2024
# All rights reserved

"""Handle component objects in the model"""
Expand All @@ -7,6 +7,7 @@
from digsim.app.gui_objects import ComponentObject
from digsim.circuit.components import Buzzer
from digsim.circuit.components.atoms import CallbackComponent
from digsim.storage_model import GuiPositionDataClass


class ModelComponents:
Expand Down Expand Up @@ -149,20 +150,16 @@ def delete(self, component_object):
self._circuit.delete_component(component_object.component)
self._app_model.sig_delete_component.emit(component_object)

def create_from_dict(self, circuit_dict):
def add_gui_positions(self, gui_dc_dict):
"""Create model components from circuit_dict"""
for comp in self._circuit.get_toplevel_components():
gui_dict = circuit_dict.get("gui", {})
component_dict = gui_dict.get(comp.name(), {})
x = component_dict.get("x", 100)
y = component_dict.get("y", 100)
z = component_dict.get("z", 0)
component_object = self._add_object(comp, x, y)
component_object.zlevel = z

def get_circuit_dict(self):
"""Create model components dict"""
model_components_dict = {"gui": {}}
gui_dc = gui_dc_dict.get(comp.name(), GuiPositionDataClass())
component_object = self._add_object(comp, gui_dc.x, gui_dc.y)
component_object.zlevel = gui_dc.z

def get_gui_dict(self):
"""Return gui dict from component objects"""
gui_dict = {}
for comp, comp_object in self.get_dict().items():
model_components_dict["gui"][comp.name()] = comp_object.to_dict()
return model_components_dict
gui_dict[comp.name()] = comp_object.to_gui_dataclass()
return gui_dict
52 changes: 29 additions & 23 deletions src/digsim/app/model/_model_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from digsim.circuit import Circuit
from digsim.circuit.components.atoms import DigsimException
from digsim.storage_model import AppFileDataClass, ModelDataClass

from ._model_components import ModelComponents
from ._model_new_wire import NewWire
Expand Down Expand Up @@ -72,32 +73,37 @@ def delete_selected(self):
self._app_model.model_changed()
self._app_model.sig_delete_wires.emit()

def circuit_to_dict(self, circuit_folder):
"""Convert circuit and objects to dict"""
circuit_dict = self.circuit.to_dict(circuit_folder)
model_components_dict = self.components.get_circuit_dict()
circuit_dict.update(model_components_dict)
return circuit_dict
def model_to_circuit(self, model_dc, circuit_folder):
if isinstance(model_dc, AppFileDataClass):
# Loaded model
dc = ModelDataClass.from_app_file_dc(model_dc)
else:
dc = model_dc

def dict_to_circuit(self, circuit_dict, circuit_folder):
"""Convert dict to circuit and objects"""
exception_str_list = []
try:
exception_str_list = self.circuit.from_dict(
circuit_dict, circuit_folder, component_exceptions=False, connect_exceptions=False
# Create circuit
exception_str_list = self.circuit.from_dataclass(
dc.circuit,
circuit_folder,
component_exceptions=False,
connect_exceptions=False,
)
# Add component positions
self.components.add_gui_positions(dc.gui)
except DigsimException as exc:
self.sig_error.emit(f"Circuit error: {str(exc)}")
return exception_str_list

# Create GUI components
self.components.create_from_dict(circuit_dict)

return exception_str_list

def _restore_state(self, state):
def circuit_to_model(self, circuit_folder):
model_dc = ModelDataClass(
circuit=self.circuit.to_dataclass(circuit_folder), gui=self.components.get_gui_dict()
)
return model_dc

def _restore_state(self, model_dc):
self.clear()
exception_str_list = self.dict_to_circuit(state, "/")
exception_str_list = self.model_to_circuit(model_dc, None)
self._app_model.model_init()
self._app_model.model_changed()
if len(exception_str_list) > 0:
Expand All @@ -111,7 +117,7 @@ def reset_undo_stack(self):

def push_undo_state(self, clear_redo_stack=True):
"""Push undo state to stack"""
self._undo_stack.append(self.circuit_to_dict("/"))
self._undo_stack.append(self.circuit_to_model("/"))
if clear_redo_stack:
self._redo_stack = []
self._app_model.sig_control_notify.emit()
Expand All @@ -124,23 +130,23 @@ def drop_undo_state(self):

def push_redo_state(self):
"""Push redo state to stack"""
self._redo_stack.append(self.circuit_to_dict("/"))
self._redo_stack.append(self.circuit_to_model("/"))

def undo(self):
"""Undo to last saved state"""
if len(self._undo_stack) > 0:
self.push_redo_state()
state = self._undo_stack.pop()
self._restore_state(state)
model_dc = self._undo_stack.pop()
self._restore_state(model_dc)
self._app_model.sig_control_notify.emit()
self._app_model.sig_synchronize_gui.emit()

def redo(self):
"""Undo to last saved state"""
if len(self._redo_stack) > 0:
self.push_undo_state(clear_redo_stack=False)
state = self._redo_stack.pop()
self._restore_state(state)
model_dc = self._redo_stack.pop()
self._restore_state(model_dc)
self._app_model.sig_control_notify.emit()
self._app_model.sig_synchronize_gui.emit()

Expand Down
6 changes: 1 addition & 5 deletions src/digsim/app/model/_model_settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) Fredrik Andersson, 2023
# Copyright (c) Fredrik Andersson, 2023-2024
# All rights reserved

"""Handle settings in the model"""
Expand All @@ -25,10 +25,6 @@ def get_all(self):
"""Return settings dict"""
return self._settings

def to_dict(self):
"""Return settings dict to save"""
return {"settings": self._settings}

def from_dict(self, circuit_dict):
"""Get settings from circuit dict"""
for key, data in circuit_dict.get("settings", {}).items():
Expand Down
6 changes: 3 additions & 3 deletions src/digsim/app/model/_model_shortcuts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) Fredrik Andersson, 2023
# Copyright (c) Fredrik Andersson, 2023-2024
# All rights reserved

"""Handle shortcuts in the model"""
Expand Down Expand Up @@ -72,9 +72,9 @@ def release(self, qtkey):

def to_dict(self):
"""Generate dict from shortcuts"""
shortcuts_dict = {"shortcuts": {}}
shortcuts_dict = {}
for key, component in self._shortcut_component.items():
shortcuts_dict["shortcuts"][key] = component.name()
shortcuts_dict[key] = component.name()
return shortcuts_dict

def from_dict(self, model_dict):
Expand Down
80 changes: 20 additions & 60 deletions src/digsim/circuit/_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
Module that handles the circuit simulation of components
"""

import json
import os

from digsim.storage_model import CircuitDataClass, CircuitFileDataClass

from ._waves_writer import WavesWriter
from .components.atoms import Component, DigsimException
from .components.atoms import DigsimException


class CircuitError(DigsimException):
Expand Down Expand Up @@ -70,6 +71,11 @@ def __init__(self, name=None, vcd=None):
else:
self._vcd = None

@property
def name(self):
"""Get the circuit name"""
return self._name

@property
def time_ns(self):
"""Get the current simulation time (ns)"""
Expand Down Expand Up @@ -243,65 +249,24 @@ def get_component(self, component_name):
return comp
raise CircuitError(f"Component '{component_name}' not found")

def _connect_from_dict(self, source, dest):
source_component_name = source.split(".")[0]
source_port_name = source.split(".")[1]
dest_component_name = dest.split(".")[0]
dest_port_name = dest.split(".")[1]

source_component = self.get_component(source_component_name)
dest_componment = self.get_component(dest_component_name)
source_component.port(source_port_name).wire = dest_componment.port(dest_port_name)

def to_dict(self, folder=None):
def to_dataclass(self, folder=None):
"""Generate dict from circuit, used when storing circuit"""
if self._name is None:
raise CircuitError("Circuit must have a name")

self._folder = folder
components_list = []
for comp in self.get_toplevel_components():
components_list.append(comp.to_dict())
return CircuitDataClass.from_circuit(self)

connection_list = []
for comp in self.get_toplevel_components():
for port in comp.ports:
port_conn_list = port.to_dict_list()
connection_list.extend(port_conn_list)

circuit_dict = {
"circuit": {
"name": self._name,
"components": components_list,
"wires": connection_list,
}
}

return circuit_dict

def from_dict(
self, circuit_dict, folder=None, component_exceptions=True, connect_exceptions=True
def from_dataclass(
self, circuit_dc, folder=None, component_exceptions=True, connect_exceptions=True
):
"""Clear circuit and add components from dict"""
self._folder = folder
self.clear()
if "circuit" not in circuit_dict:
raise CircuitError("No 'circuit' in JSON")
json_circuit = circuit_dict["circuit"]
if "name" not in json_circuit:
raise CircuitError("No 'circuit/name' in JSON")
self._name = json_circuit["name"]
if "components" not in json_circuit:
raise CircuitError("No 'circuit/components' in JSON")
json_components = json_circuit["components"]
if "wires" not in json_circuit:
raise CircuitError("No 'circuit/connections' in JSON")
json_connections = json_circuit["wires"]

exception_str_list = []
for json_component in json_components:
for component in circuit_dc.components:
try:
Component.from_dict(self, json_component)
component.create(self)
except DigsimException as exc:
if component_exceptions:
raise exc
Expand All @@ -310,9 +275,9 @@ def from_dict(
if component_exceptions:
raise exc
exception_str_list.append(f"{str(exc.__class__.__name__)}:{str(exc)}")
for json_connection in json_connections:
for wire in circuit_dc.wires:
try:
self._connect_from_dict(json_connection["src"], json_connection["dst"])
wire.connect(self)
except DigsimException as exc:
if connect_exceptions:
raise exc
Expand All @@ -322,15 +287,10 @@ def from_dict(

def to_json_file(self, filename):
"""Store circuit in json file"""
circuit_dict = self.to_dict()
json_object = json.dumps(circuit_dict, indent=4)

with open(filename, mode="w", encoding="utf-8") as json_file:
json_file.write(json_object)
circuitfile_dc = CircuitFileDataClass(circuit=self.to_dataclass())
circuitfile_dc.save(filename)

def from_json_file(self, filename, folder=None):
"""Load circuit from json file"""
self._folder = folder
with open(filename, mode="r", encoding="utf-8") as json_file:
circuit_dict = json.load(json_file)
self.from_dict(circuit_dict, folder)
file_dc = CircuitFileDataClass.load(filename)
self.from_dataclass(file_dc.circuit, folder)
Loading

0 comments on commit 1bd4055

Please sign in to comment.