Skip to content
Snippets Groups Projects
Commit 247155a9 authored by Jacob Wahlman's avatar Jacob Wahlman :ok_hand: Committed by Adam Jakobsson
Browse files

Resolve "Simulate SFG in GUI"

parent 70977725
No related branches found
No related tags found
2 merge requests!67WIP: B-ASIC version 1.0.0 hotfix,!65B-ASIC version 1.0.0
......@@ -5,7 +5,7 @@ QGraphicsLineItem, QGraphicsWidget
from PySide2.QtCore import Qt, QSize, QLineF, QPoint, QRectF
from PySide2.QtGui import QIcon, QFont, QPainter, QPen
from b_asic import Signal
from b_asic import Signal
class Arrow(QGraphicsLineItem):
......@@ -25,6 +25,8 @@ class Arrow(QGraphicsLineItem):
menu.exec_(self.cursor().pos())
def remove(self):
self.signal.remove_destination()
self.signal.remove_source()
self._window.scene.removeItem(self)
self._window.signalList.remove(self)
......
......@@ -20,24 +20,31 @@ class DragButton(QPushButton):
moved = Signal()
def __init__(self, name, operation, operation_path_name, is_show_name, window, parent = None):
self.name = name
self.ports = []
self.is_show_name = is_show_name
self._window = window
self.operation = operation
self.operation_path_name = operation_path_name
self.clicked = 0
self.pressed = False
self._m_press = False
self._m_drag = False
self._mouse_press_pos = None
self._mouse_move_pos = None
super(DragButton, self).__init__(self._window)
super(DragButton, self).__init__(parent)
def contextMenuEvent(self, event):
menu = QMenu()
properties = QAction("Properties")
menu.addAction(properties)
properties.triggered.connect(self.show_properties_window)
delete = QAction("Delete")
menu.addAction(delete)
delete.triggered.connect(self.remove)
menu.exec_(self.cursor().pos())
def show_properties_window(self, event):
def show_properties_window(self):
self.properties_window = PropertiesWindow(self, self._window)
self.properties_window.show()
......@@ -45,55 +52,86 @@ class DragButton(QPushButton):
self.label = label
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self._m_press = True
self._mouse_press_pos = event.pos()
self._mouse_move_pos = event.pos()
for signal in self._window.signalList:
signal.update()
self.clicked += 1
if self.clicked == 1:
self.pressed = True
self.setStyleSheet("background-color: grey; border-style: solid;\
border-color: black; border-width: 2px; border-radius: 10px")
self.setStyleSheet(""" QToolTip { background-color: white;
color: black }""")
path_to_image = os.path.join('operation_icons', self.operation_path_name + '_grey.png')
self.setIcon(QIcon(path_to_image))
self.setIconSize(QSize(55, 55))
self._window.pressed_operations.append(self)
elif self.clicked == 2:
self.clicked = 0
self.pressed = False
self.setStyleSheet("background-color: white; border-style: solid;\
border-color: black; border-width: 2px; border-radius: 10px")
self.setStyleSheet(""" QToolTip { background-color: white;
color: black}""")
path_to_image = os.path.join('operation_icons', self.operation_path_name + '.png')
self.setIcon(QIcon(path_to_image))
self.setIconSize(QSize(55, 55))
self._window.pressed_operations.remove(self)
super(DragButton, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() == Qt.LeftButton:
if event.buttons() == Qt.LeftButton and self._m_press:
self._m_drag = True
self.move(self.mapToParent(event.pos() - self._mouse_press_pos))
if self in self._window.pressed_operations:
for button in self._window.pressed_operations:
if button is self:
continue
button.move(button.mapToParent(event.pos() - self._mouse_press_pos))
self._window.update()
super(DragButton, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if self._mouse_press_pos is not None:
moved = event.pos() - self._mouse_press_pos
if moved.manhattanLength() > 3:
event.ignore()
return
self._m_press = False
if self._m_drag:
if self._mouse_press_pos is not None:
moved = event.pos() - self._mouse_press_pos
if moved.manhattanLength() > 3:
event.ignore()
self._m_drag = False
else:
self.select_button(event.modifiers())
super(DragButton, self).mouseReleaseEvent(event)
def _toggle_button(self, pressed=False):
self.pressed = not pressed
self.setStyleSheet(f"background-color: {'white' if not self.pressed else 'grey'}; border-style: solid;\
border-color: black; border-width: 2px")
path_to_image = os.path.join('operation_icons', f"{self.operation_path_name}{'_grey.png' if self.pressed else '.png'}")
self.setIcon(QIcon(path_to_image))
self.setIconSize(QSize(55, 55))
def select_button(self, modifiers=None):
if modifiers != Qt.ControlModifier:
for button in self._window.pressed_operations:
button._toggle_button(button.pressed)
self._toggle_button(self.pressed)
self._window.pressed_operations = [self]
else:
self._toggle_button(self.pressed)
if self in self._window.pressed_operations:
self._window.pressed_operations.remove(self)
else:
self._window.pressed_operations.append(self)
for signal in self._window.signalList:
signal.update()
def remove(self):
self.deleteLater()
\ No newline at end of file
self._window.scene.removeItem(self._window.operationDict[self])
_signals = []
for signal, ports in self._window.signalPortDict.items():
if any([port in self._window.portDict[self] for port in ports]):
signal.remove()
_signals.append(signal)
for signal in _signals:
del self._window.signalPortDict[signal]
for port in self._window.portDict[self]:
if port in self._window.pressed_ports:
self._window.pressed_ports.remove(port)
if self in self._window.pressed_operations:
self._window.pressed_operations.remove(self)
if self in self._window.operationDict.keys():
del self._window.operationDict[self]
\ No newline at end of file
......@@ -223,6 +223,8 @@ class Ui_main_window(object):
self.edit_menu.setObjectName("edit_menu")
self.view_menu = QtWidgets.QMenu(self.menu_bar)
self.view_menu.setObjectName("view_menu")
self.run_menu = QtWidgets.QMenu(self.menu_bar)
self.run_menu.setObjectName("run_menu")
main_window.setMenuBar(self.menu_bar)
self.status_bar = QtWidgets.QStatusBar(main_window)
self.status_bar.setObjectName("status_bar")
......@@ -235,6 +237,8 @@ class Ui_main_window(object):
self.actionUndo.setObjectName("actionUndo")
self.actionRedo = QtWidgets.QAction(main_window)
self.actionRedo.setObjectName("actionRedo")
self.actionSimulateSFG = QtWidgets.QAction(main_window)
self.actionSimulateSFG.setObjectName("actionSimulateSFG")
self.actionToolbar = QtWidgets.QAction(main_window)
self.actionToolbar.setCheckable(True)
self.actionToolbar.setObjectName("actionToolbar")
......@@ -244,9 +248,11 @@ class Ui_main_window(object):
self.edit_menu.addAction(self.actionUndo)
self.edit_menu.addAction(self.actionRedo)
self.view_menu.addAction(self.actionToolbar)
self.run_menu.addAction(self.actionSimulateSFG)
self.menu_bar.addAction(self.file_menu.menuAction())
self.menu_bar.addAction(self.edit_menu.menuAction())
self.menu_bar.addAction(self.view_menu.menuAction())
self.menu_bar.addAction(self.run_menu.menuAction())
self.retranslateUi(main_window)
self.operation_list.setCurrentIndex(1)
......@@ -269,6 +275,8 @@ class Ui_main_window(object):
self.file_menu.setTitle(_translate("main_window", "File"))
self.edit_menu.setTitle(_translate("main_window", "Edit"))
self.view_menu.setTitle(_translate("main_window", "View"))
self.run_menu.setTitle(_translate("main_window", "Run"))
self.actionSimulateSFG.setText(_translate("main_window", "Simulate SFG"))
self.save_menu.setText(_translate("main_window", "Save"))
self.exit_menu.setText(_translate("main_window", "Exit"))
self.exit_menu.setShortcut(_translate("main_window", "Ctrl+Q"))
......
......@@ -3,6 +3,7 @@ B-ASIC GUI Module.
This python file is the main window of the GUI for B-ASIC.
"""
from pprint import pprint
from os import getcwd, path
import sys
......@@ -11,12 +12,12 @@ from gui_interface import Ui_main_window
from arrow import Arrow
from port_button import PortButton
from b_asic import Operation
from b_asic import Operation, SFG, InputPort, OutputPort
from b_asic.simulation import Simulation
import b_asic.core_operations as c_oper
import b_asic.special_operations as s_oper
from utils import decorate_class, handle_error
from b_asic import SFG
from b_asic import InputPort, OutputPort
from simulate_sfg_window import SimulateSFGWindow, Plot
from numpy import linspace
......@@ -24,11 +25,10 @@ from PySide2.QtWidgets import QApplication, QWidget, QMainWindow, QLabel, QActio
QStatusBar, QMenuBar, QLineEdit, QPushButton, QSlider, QScrollArea, QVBoxLayout,\
QHBoxLayout, QDockWidget, QToolBar, QMenu, QLayout, QSizePolicy, QListWidget,\
QListWidgetItem, QGraphicsView, QGraphicsScene, QShortcut, QGraphicsTextItem,\
QGraphicsProxyWidget
QGraphicsProxyWidget, QInputDialog
from PySide2.QtCore import Qt, QSize
from PySide2.QtGui import QIcon, QFont, QPainter, QPen, QBrush, QKeySequence
MIN_WIDTH_SCENE = 600
MIN_HEIGHT_SCENE = 520
......@@ -44,21 +44,25 @@ class MainWindow(QMainWindow):
self._operations_from_name = dict()
self.zoom = 1
self.sfg_name_i = 0
self.operationList = []
self.operationDict = dict()
self.operationItemSceneList = []
self.signalList = []
self.pressed_operations = []
self.portList = []
self.portDict = dict()
self.signalPortDict = dict()
self.pressed_ports = []
self.sfg_list = []
self.source = None
self._window = self
self.init_ui()
self.add_operations_from_namespace(c_oper, self.ui.core_operations_list)
self.add_operations_from_namespace(s_oper, self.ui.special_operations_list)
self.shortcut_core = QShortcut(QKeySequence("Ctrl+R"), self.ui.operation_box)
self.shortcut_core.activated.connect(self._refresh_operations_list_from_namespace)
self.shortcut_select_all = QShortcut(QKeySequence("Ctrl+A"), self.graphic_view)
self.shortcut_select_all.activated.connect(self._select_all_operations)
self.scene.selectionChanged.connect(self._select_operations)
self.move_button_index = 0
self.is_show_names = True
......@@ -69,6 +73,8 @@ class MainWindow(QMainWindow):
self.check_show_names.setChecked(1)
self.ui.view_menu.addAction(self.check_show_names)
self.ui.actionSimulateSFG.triggered.connect(self.simulate_sfg)
def init_ui(self):
self.ui.core_operations_list.itemClicked.connect(self.on_list_widget_item_clicked)
self.ui.special_operations_list.itemClicked.connect(self.on_list_widget_item_clicked)
......@@ -81,7 +87,7 @@ class MainWindow(QMainWindow):
self.graphic_view = QGraphicsView(self.scene, self)
self.graphic_view.setRenderHint(QPainter.Antialiasing)
self.graphic_view.setGeometry(self.ui.operation_box.width(), 0, self.width(), self.height())
self.graphic_view.setDragMode(QGraphicsView.ScrollHandDrag)
self.graphic_view.setDragMode(QGraphicsView.RubberBandDrag)
def create_toolbar_view(self):
self.toolbar = self.addToolBar("Toolbar")
......@@ -99,16 +105,16 @@ class MainWindow(QMainWindow):
self.graphic_view.scale(self.zoom, self.zoom)
self.zoom = old_zoom
def view_operation_names(self, event):
def view_operation_names(self):
if self.check_show_names.isChecked():
self.is_show_names = True
else:
self.is_show_names = False
for operation in self.operationList:
for operation in self.operationDict.keys():
operation.label.setOpacity(self.is_show_names)
operation.is_show_name = self.is_show_names
def exit_app(self, checked):
def exit_app(self):
QApplication.quit()
def create_SFG_from_toolbar(self):
......@@ -120,8 +126,9 @@ class MainWindow(QMainWindow):
elif isinstance(op.operation, s_oper.Output):
outputs.append(op.operation)
self.sfg_name_i += 1
sfg = SFG(inputs=inputs, outputs=outputs, name="sfg" + str(self.sfg_name_i))
name = QInputDialog.getText(self, "Create SFG", "Name: ", QLineEdit.Normal)
sfg = SFG(inputs=inputs, outputs=outputs, name=name[0])
for op in self.pressed_operations:
op.setToolTip(sfg.name)
self.sfg_list.append(sfg)
......@@ -132,24 +139,22 @@ class MainWindow(QMainWindow):
"""
return [length / 2] if ports == 1 else linspace(0, length, ports)
def _create_port(self, operation, port, output_port=True):
text = ">" if output_port else "<"
button = PortButton(text, operation, port, self)
button.setStyleSheet("background-color: white")
button.connectionRequested.connect(self.connectButton)
return button
def add_ports(self, operation):
_output_ports_dist = self._determine_port_distance(55 - 17, operation.operation.output_count)
_input_ports_dist = self._determine_port_distance(55 - 17, operation.operation.input_count)
self.portDict[operation] = list()
for i, dist in enumerate(_input_ports_dist):
port = self._create_port(operation, operation.operation.input(i))
port = PortButton(">", operation, operation.operation.input(i), self)
self.portDict[operation].append(port)
operation.ports.append(port)
port.move(0, dist)
port.show()
for i, dist in enumerate(_output_ports_dist):
port = self._create_port(operation, operation.operation.output(i))
port = PortButton(">", operation, operation.operation.output(i), self)
self.portDict[operation].append(port)
operation.ports.append(port)
port.move(55 - 12, dist)
port.show()
......@@ -188,6 +193,7 @@ class MainWindow(QMainWindow):
attr_button.setParent(None)
attr_button_scene = self.scene.addWidget(attr_button)
attr_button_scene.moveBy(self.move_button_index * 100, 0)
attr_button_scene.setFlag(attr_button_scene.ItemIsSelectable, True)
self.move_button_index += 1
operation_label = QGraphicsTextItem(attr_oper.type_name(), attr_button_scene)
if not self.is_show_names:
......@@ -195,7 +201,7 @@ class MainWindow(QMainWindow):
operation_label.setTransformOriginPoint(operation_label.boundingRect().center())
operation_label.moveBy(10, -20)
attr_button.add_label(operation_label)
self.operationList.append(attr_button)
self.operationDict[attr_button] = attr_button_scene
except Exception as e:
print("Unexpected error occured: ", e)
......@@ -210,13 +216,8 @@ class MainWindow(QMainWindow):
self._create_operation(item)
def keyPressEvent(self, event):
pressed_operations = []
for op in self.operationList:
if op.pressed:
pressed_operations.append(op)
if event.key() == Qt.Key_Delete:
for pressed_op in pressed_operations:
self.operationList.remove(pressed_op)
for pressed_op in self.pressed_operations:
pressed_op.remove()
self.move_button_index -= 1
super().keyPressEvent(event)
......@@ -228,15 +229,62 @@ class MainWindow(QMainWindow):
if isinstance(self.pressed_ports[i].port, OutputPort) and \
isinstance(self.pressed_ports[i+1].port, InputPort):
line = Arrow(self.pressed_ports[i], self.pressed_ports[i + 1], self)
self.signalPortDict[line] = [self.pressed_ports[i], self.pressed_ports[i + 1]]
self.scene.addItem(line)
self.signalList.append(line)
for port in self.pressed_ports:
port.select_port()
self.update()
def paintEvent(self, event):
for signal in self.signalList:
for signal in self.signalPortDict.keys():
signal.moveLine()
def _select_all_operations(self):
self.pressed_operations.clear()
for button in self.operationDict.keys():
button._toggle_button(pressed=False)
self.pressed_operations.append(button)
def _select_operations(self):
selected = [button.widget() for button in self.scene.selectedItems()]
for button in selected:
button._toggle_button(pressed=False)
for button in self.pressed_operations:
if button not in selected:
button._toggle_button(pressed=True)
self.pressed_operations = selected
def _simulate_sfg(self):
for sfg, properties in self.dialog.properties.items():
simulation = Simulation(sfg, input_providers=properties["input_values"], save_results=properties["all_results"])
l_result = simulation.run_for(properties["iteration_count"])
if properties["all_results"]:
print(f"{'=' * 10} {sfg.name} {'=' * 10}")
pprint(simulation.results)
print(f"{'=' * 10} /{sfg.name} {'=' * 10}")
if properties["show_plot"]:
self.plot = Plot(simulation, sfg)
self.plot.show()
def simulate_sfg(self):
self.dialog = SimulateSFGWindow(self)
for sfg in self.sfg_list:
self.dialog.add_sfg_to_dialog(sfg)
self.dialog.show()
# Wait for input to dialog. Kinda buggy because of the separate window in the same thread.
self.dialog.simulate.connect(self._simulate_sfg)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
......
......@@ -8,12 +8,17 @@ class PortButton(QPushButton):
connectionRequested = Signal(QPushButton)
moved = Signal()
def __init__(self, name, operation, port, window, parent=None):
super(PortButton, self).__init__(name, operation, parent)
self.pressed = False
self.window = window
self._window = window
self.port = port
self.operation = operation
self.clicked = 0
super(PortButton, self).__init__(name, operation)
self._m_drag = False
self._m_press = False
self.setStyleSheet("background-color: white")
self.connectionRequested.connect(self._window.connectButton)
def contextMenuEvent(self, event):
menu = QMenu()
......@@ -22,18 +27,32 @@ class PortButton(QPushButton):
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.clicked += 1
if self.clicked == 1:
self.setStyleSheet("background-color: grey")
self.pressed = True
self.window.pressed_ports.append(self)
elif self.clicked == 2:
self.setStyleSheet("background-color: white")
self.pressed = False
self.clicked = 0
self.window.pressed_ports.remove(self)
self.select_port(event.modifiers())
super(PortButton, self).mousePressEvent(event)
def mouseReleaseEvent(self, event):
super(PortButton, self).mouseReleaseEvent(event)
def _toggle_port(self, pressed=False):
self.pressed = not pressed
self.setStyleSheet(f"background-color: {'white' if not self.pressed else 'grey'}")
def select_port(self, modifiers=None):
if modifiers != Qt.ControlModifier:
for port in self._window.pressed_ports:
port._toggle_port(port.pressed)
self._toggle_port(self.pressed)
self._window.pressed_ports = [self]
else:
self._toggle_port(self.pressed)
if self in self._window.pressed_ports:
self._window.pressed_ports.remove(self)
else:
self._window.pressed_ports.append(self)
for signal in self._window.signalList:
signal.update()
from PySide2.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout,\
QLabel, QCheckBox, QSpinBox, QGroupBox, QFrame, QFormLayout, QGridLayout, QSizePolicy
from PySide2.QtCore import Qt, Signal
from PySide2.QtGui import QIntValidator
from matplotlib.backends import qt_compat
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class SimulateSFGWindow(QDialog):
simulate = Signal()
def __init__(self, window):
super(SimulateSFGWindow, self).__init__()
self._window = window
self.properties = dict()
self.sfg_to_layout = dict()
self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
self.setWindowTitle("Simulate SFG")
self.dialog_layout = QVBoxLayout()
self.simulate_btn = QPushButton("Simulate")
self.simulate_btn.clicked.connect(self.save_properties)
self.dialog_layout.addWidget(self.simulate_btn)
self.setLayout(self.dialog_layout)
def add_sfg_to_dialog(self, sfg):
sfg_layout = QVBoxLayout()
options_layout = QFormLayout()
name_label = QLabel(f"{sfg.name}")
sfg_layout.addWidget(name_label)
spin_box = QSpinBox()
spin_box.setRange(0, 2147483647)
options_layout.addRow("Iteration Count: ", spin_box)
check_box = QCheckBox()
options_layout.addRow("Plot Results: ", check_box)
check_box = QCheckBox()
options_layout.addRow("Get All Results: ", check_box)
input_layout = QVBoxLayout()
input_label = QLabel("Input Values: ")
input_layout.addWidget(input_label)
input_grid = QGridLayout()
x, y = 0, 0
for i in range(sfg.input_count):
if i % 2 == 0 and i > 0:
x += 1
y = 0
_input_value = QLineEdit()
_input_value.setValidator(QIntValidator())
_input_value.setFixedWidth(50)
input_grid.addWidget(_input_value, x, y)
y += 1
input_layout.addLayout(input_grid)
sfg_layout.addLayout(options_layout)
sfg_layout.addLayout(input_layout)
frame = QFrame()
frame.setFrameShape(QFrame.HLine)
frame.setFrameShadow(QFrame.Sunken)
self.dialog_layout.addWidget(frame)
self.sfg_to_layout[sfg] = sfg_layout
self.dialog_layout.addLayout(sfg_layout)
def save_properties(self):
for sfg, sfg_row in self.sfg_to_layout.items():
_option_values = sfg_row.children()[0]
_input_values = sfg_row.children()[1].children()[0]
_, spin_box, _, check_box_plot, _, check_box_all = map(lambda x: _option_values.itemAt(x).widget(), range(_option_values.count()))
input_values = map(lambda x: _input_values.itemAt(x).widget(), range(_input_values.count()))
input_values = [int(widget.text()) if widget.text() else 0 for widget in input_values]
self.properties[sfg] = {
"iteration_count": spin_box.value(),
"show_plot": check_box_plot.isChecked(),
"all_results": check_box_all.isChecked(),
"input_values": input_values
}
# If we plot we should also print the entire data, since you can't really interact with the graph.
if check_box_plot.isChecked():
self.properties[sfg]["all_results"] = True
self.accept()
self.simulate.emit()
class Plot(FigureCanvas):
def __init__(self, simulation, sfg, parent=None, width=5, height=4, dpi=100):
self.simulation = simulation
self.sfg = sfg
fig = Figure(figsize=(width, height), dpi=dpi)
fig.suptitle(sfg.name, fontsize=20)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self._plot_values_sfg()
def _plot_values_sfg(self):
x_axis = list(range(len(self.simulation.results.keys())))
for _output in range(self.sfg.output_count):
y_axis = list()
for _iter in range(len(self.simulation.results.keys())):
y_axis.append(self.simulation.results[_iter][str(_output)])
self.axes.plot(x_axis, y_axis)
......@@ -249,8 +249,8 @@ class MAD(AbstractOperation):
def __init__(self, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, src2: Optional[SignalSourceProvider] = None, name: Name = ""):
super().__init__(input_count = 3, output_count = 1, name = name, input_sources = [src0, src1, src2])
@property
def type_name(self) -> TypeName:
@classmethod
def type_name(cls) -> TypeName:
return "mad"
def evaluate(self, a, b, c):
......
......@@ -72,10 +72,11 @@ setuptools.setup(
"pybind11>=2.3.0",
"numpy",
"pyside2",
"graphviz"
"graphviz",
"matplotlib"
],
packages = ["b_asic"],
ext_modules = [CMakeExtension("b_asic")],
cmdclass = {"build_ext": CMakeBuild},
zip_safe = False
)
\ No newline at end of file
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment