diff --git a/b_asic/GUI/arrow.py b/b_asic/GUI/arrow.py index aea8fec3b488d7fbfe49766f3a23b3fa2346a920..badb1f0b80e11d86b73a26757d9554d3c01c4b54 100644 --- a/b_asic/GUI/arrow.py +++ b/b_asic/GUI/arrow.py @@ -9,12 +9,10 @@ from b_asic import Signal class Arrow(QGraphicsLineItem): - def __init__(self, source, destination, window, create_signal=True, parent=None): + def __init__(self, source, destination, window, signal=None, parent=None): super(Arrow, self).__init__(parent) self.source = source - # if an signal does not exist create one - if create_signal: - self.signal = Signal(source.port, destination.port) + self.signal = Signal(source.port, destination.port) if signal is None else signal self.destination = destination self._window = window self.moveLine() @@ -30,7 +28,18 @@ class Arrow(QGraphicsLineItem): self.signal.remove_destination() self.signal.remove_source() self._window.scene.removeItem(self) - self._window.signalList.remove(self) + if self in self._window.signalList: + self._window.signalList.remove(self) + + if self in self._window.signalPortDict: + for port1, port2 in self._window.signalPortDict[self]: + for operation, operation_ports in self._window.portDict.items(): + if (port1 in operation_ports or port2 in operation_ports) and operation in self._window.opToSFG: + self._window.logger.info(f"Operation detected in existing sfg, removing sfg with name: {self._window.opToSFG[operation].name}.") + del self._window.sfg_dict[self._window.opToSFG[operation].name] + self._window.opToSFG = {op: self._window.opToSFG[op] for op in self._window.opToSFG if self._window.opToSFG[op] is not self._window.opToSFG[operation]} + + del self._window.signalPortDict[self] def moveLine(self): self.setPen(QPen(Qt.black, 3)) diff --git a/b_asic/GUI/drag_button.py b/b_asic/GUI/drag_button.py index fe78f46323400555fce5687cc2459009e61c0a4a..6d5c47c39adb5c06147abf505bea17e04808b4a9 100644 --- a/b_asic/GUI/drag_button.py +++ b/b_asic/GUI/drag_button.py @@ -123,16 +123,15 @@ class DragButton(QPushButton): for signal, ports in self._window.signalPortDict.items(): if any(map(lambda port: set(port).intersection(set(self._window.portDict[self])), ports)): self._window.logger.info(f"Removed signal with name: {signal.signal.name} to/from operation: {self.operation.name}.") - signal.remove() _signals.append(signal) + for signal in _signals: + signal.remove() + if self in self._window.opToSFG: self._window.logger.info(f"Operation detected in existing sfg, removing sfg with name: {self._window.opToSFG[self].name}.") - self._window.opToSFG = {op: self._window.opToSFG[op] for op in self._window.opToSFG if self._window.opToSFG[op] is self._window.opToSFG[self]} del self._window.sfg_dict[self._window.opToSFG[self].name] - - for signal in _signals: - del self._window.signalPortDict[signal] + self._window.opToSFG = {op: self._window.opToSFG[op] for op in self._window.opToSFG if self._window.opToSFG[op] is not self._window.opToSFG[self]} for port in self._window.portDict[self]: if port in self._window.pressed_ports: diff --git a/b_asic/GUI/main_window.py b/b_asic/GUI/main_window.py index 0607100a2f45b618f179b8a2bffcc98d52eb9173..d8067bcbea1b6e4399bd9d486473cb394377812d 100644 --- a/b_asic/GUI/main_window.py +++ b/b_asic/GUI/main_window.py @@ -101,6 +101,8 @@ class MainWindow(QMainWindow): self.shortcut_save.activated.connect(self.save_work) self.shortcut_help = QShortcut(QKeySequence("Ctrl+?"), self) self.shortcut_help.activated.connect(self.display_faq_page) + self.shortcut_signal = QShortcut(QKeySequence(Qt.Key_Space), self) + self.shortcut_signal.activated.connect(self._connect_button) self.logger.info("Finished setting up GUI") self.logger.info("For questions please refer to 'Ctrl+?', or visit the 'Help' section on the toolbar.") @@ -212,6 +214,7 @@ class MainWindow(QMainWindow): for op in sfg.split(): self.operationDragDict[op].setToolTip(sfg.name) + self.opToSFG[self.operationDragDict[op]] = sfg self.sfg_dict[sfg.name] = sfg self.logger.info(f"Loaded sfg from path: {module}.") @@ -257,11 +260,55 @@ class MainWindow(QMainWindow): sfg = SFG(inputs=inputs, outputs=outputs, name=name) self.logger.info(f"Created SFG with name: {name} from selected operations.") - sfg_operations = sfg.operations.copy() - for op in self.pressed_operations: - operation = [op_ for op_ in sfg_operations if op_.type_name() == op.operation.type_name()][0] - op.operation = operation - sfg_operations.remove(operation) + def check_equality(signal, signal_2): + if not (signal.source.operation.type_name() == signal_2.source.operation.type_name() \ + and signal.destination.operation.type_name() == signal_2.destination.operation.type_name()): + return False + + if hasattr(signal.source.operation, "value") and hasattr(signal_2.source.operation, "value") \ + and hasattr(signal.destination.operation, "value") and hasattr(signal_2.destination.operation, "value"): + if not (signal.source.operation.value == signal_2.source.operation.value \ + and signal.destination.operation.value == signal_2.destination.operation.value): + return False + + if hasattr(signal.source.operation, "name") and hasattr(signal_2.source.operation, "name") \ + and hasattr(signal.destination.operation, "name") and hasattr(signal_2.destination.operation, "name"): + if not (signal.source.operation.name == signal_2.source.operation.name \ + and signal.destination.operation.name == signal_2.destination.operation.name): + return False + + try: + _signal_source_index = [signal.source.operation.outputs.index(port) for port in signal.source.operation.outputs if signal in port.signals] + _signal_2_source_index = [signal_2.source.operation.outputs.index(port) for port in signal_2.source.operation.outputs if signal_2 in port.signals] + except ValueError: + return False # Signal output connections not matching + + try: + _signal_destination_index = [signal.destination.operation.inputs.index(port) for port in signal.destination.operation.inputs if signal in port.signals] + _signal_2_destination_index = [signal_2.destination.operation.inputs.index(port) for port in signal_2.destination.operation.inputs if signal_2 in port.signals] + except ValueError: + return False # Signal input connections not matching + + if not (_signal_source_index == _signal_2_source_index and _signal_destination_index == _signal_2_destination_index): + return False + + return True + + for pressed_op in self.pressed_operations: + for operation in sfg.operations: + for input_ in operation.inputs: + for signal in input_.signals: + for line in self.signalPortDict: + if check_equality(line.signal, signal): + line.source.operation.operation = signal.source.operation + line.destination.operation.operation = signal.destination.operation + + for output_ in operation.outputs: + for signal in output_.signals: + for line in self.signalPortDict: + if check_equality(line.signal, signal): + line.source.operation.operation = signal.source.operation + line.destination.operation.operation = signal.destination.operation for op in self.pressed_operations: op.setToolTip(sfg.name) @@ -328,7 +375,6 @@ class MainWindow(QMainWindow): self.add_operations_from_namespace(namespace, self.ui.custom_operations_list) def create_operation(self, op, position=None): - self.logger.info(f"Creating operation of type: {op.type_name()}.") try: attr_button = DragButton(op.graph_id, op, op.type_name().lower(), True, window = self) if position is None: @@ -352,10 +398,9 @@ class MainWindow(QMainWindow): attr_button.setParent(None) attr_button_scene = self.scene.addWidget(attr_button) if position is None: - attr_button_scene.moveBy(self.move_button_index * 100, 0) + attr_button_scene.moveBy(int(self.scene.width() / 2), int(self.scene.height() / 2)) attr_button_scene.setFlag(attr_button_scene.ItemIsSelectable, True) - self.move_button_index += 1 - operation_label = QGraphicsTextItem(op.type_name(), attr_button_scene) + operation_label = QGraphicsTextItem(op.name, attr_button_scene) if not self.is_show_names: operation_label.setOpacity(0) operation_label.setTransformOriginPoint(operation_label.boundingRect().center()) @@ -394,7 +439,7 @@ class MainWindow(QMainWindow): self.pressed_operations.clear() super().keyPressEvent(event) - def _connect_button(self, event): + def _connect_button(self, *event): if len(self.pressed_ports) < 2: self.logger.warning("Can't connect less than two ports. Please select more.") return @@ -403,6 +448,11 @@ class MainWindow(QMainWindow): source = self.pressed_ports[i] if isinstance(self.pressed_ports[i].port, OutputPort) else self.pressed_ports[i + 1] destination = self.pressed_ports[i + 1] if source is not self.pressed_ports[i + 1] else self.pressed_ports[i] if source.port.operation is destination.port.operation: + self.logger.warning("Can't connect to the same port") + continue + + if type(source.port) == type(destination.port): + self.logger.warning(f"Can't connect port of type: {type(source.port).__name__} to port of type: {type(destination.port).__name__}.") continue self.connect_button(source, destination) @@ -411,9 +461,13 @@ class MainWindow(QMainWindow): port.select_port() def connect_button(self, source, destination): - signal_exists = any([signal.destination is destination.port for signal in source.port.signals]) + signal_exists = (signal for signal in source.port.signals if signal.destination is destination.port) self.logger.info(f"Connecting: {source.operation.operation.type_name()} -> {destination.operation.operation.type_name()}.") - line = Arrow(source, destination, self, create_signal=not signal_exists) + try: + line = Arrow(source, destination, self, signal=next(signal_exists)) + except StopIteration: + line = Arrow(source, destination, self) + if line not in self.signalPortDict: self.signalPortDict[line] = [] diff --git a/b_asic/GUI/operation_icons/reg.png b/b_asic/GUI/operation_icons/t.png similarity index 100% rename from b_asic/GUI/operation_icons/reg.png rename to b_asic/GUI/operation_icons/t.png diff --git a/b_asic/GUI/operation_icons/reg_grey.png b/b_asic/GUI/operation_icons/t_grey.png similarity index 100% rename from b_asic/GUI/operation_icons/reg_grey.png rename to b_asic/GUI/operation_icons/t_grey.png diff --git a/b_asic/GUI/properties_window.py b/b_asic/GUI/properties_window.py index 04d899fd317dfbaa6a5b65934edd62a9fc479b68..580ce409bbe30ec2ec74ddcda542168dc4d7985c 100644 --- a/b_asic/GUI/properties_window.py +++ b/b_asic/GUI/properties_window.py @@ -1,7 +1,7 @@ from PySide2.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout,\ QLabel, QCheckBox, QGridLayout from PySide2.QtCore import Qt -from PySide2.QtGui import QIntValidator +from PySide2.QtGui import QDoubleValidator class PropertiesWindow(QDialog): def __init__(self, operation, main_window): @@ -22,13 +22,17 @@ class PropertiesWindow(QDialog): self.vertical_layout = QVBoxLayout() self.vertical_layout.addLayout(self.name_layout) - if self.operation.operation_path_name == "c": + if hasattr(self.operation.operation, "value") or hasattr(self.operation.operation, "initial_value"): self.constant_layout = QHBoxLayout() self.constant_layout.setSpacing(50) - self.constant_value = QLabel("Constant:") - self.edit_constant = QLineEdit(str(self.operation.operation.value)) - self.only_accept_int = QIntValidator() - self.edit_constant.setValidator(self.only_accept_int) + self.constant_value = QLabel("Value:") + if hasattr(self.operation.operation, "value"): + self.edit_constant = QLineEdit(str(self.operation.operation.value)) + else: + self.edit_constant = QLineEdit(str(self.operation.operation.initial_value)) + + self.only_accept_float = QDoubleValidator() + self.edit_constant.setValidator(self.only_accept_float) self.constant_layout.addWidget(self.constant_value) self.constant_layout.addWidget(self.edit_constant) self.vertical_layout.addLayout(self.constant_layout) @@ -66,7 +70,7 @@ class PropertiesWindow(QDialog): input_value.setPlaceholderText(str(self.operation.operation.latency)) except ValueError: input_value.setPlaceholderText("-1") - int_valid = QIntValidator() + int_valid = QDoubleValidator() int_valid.setBottom(-1) input_value.setValidator(int_valid) input_value.setFixedWidth(50) @@ -101,7 +105,7 @@ class PropertiesWindow(QDialog): input_value.setPlaceholderText(str(self.operation.operation.latency)) except ValueError: input_value.setPlaceholderText("-1") - int_valid = QIntValidator() + int_valid = QDoubleValidator() int_valid.setBottom(-1) input_value.setValidator(int_valid) input_value.setFixedWidth(50) @@ -122,9 +126,13 @@ class PropertiesWindow(QDialog): def save_properties(self): self._window.logger.info(f"Saving properties of operation: {self.operation.name}.") self.operation.name = self.edit_name.text() + self.operation.operation.name = self.edit_name.text() self.operation.label.setPlainText(self.operation.name) - if self.operation.operation_path_name == "c": - self.operation.operation.value = int(self.edit_constant.text()) + if hasattr(self.operation.operation, "value"): + self.operation.operation.value = float(self.edit_constant.text().replace(",", ".")) + elif hasattr(self.operation.operation, "initial_value"): + self.operation.operation.initial_value = float(self.edit_constant.text().replace(",", ".")) + if self.check_show_name.isChecked(): self.operation.label.setOpacity(1) self.operation.is_show_name = True @@ -132,6 +140,6 @@ class PropertiesWindow(QDialog): self.operation.label.setOpacity(0) self.operation.is_show_name = False - self.operation.operation.set_latency_offsets({port: int(self.latency_fields[port].text()) if self.latency_fields[port].text() and int(self.latency_fields[port].text()) > 0 else None for port in self.latency_fields}) + self.operation.operation.set_latency_offsets({port: float(self.latency_fields[port].text().replace(",", ".")) if self.latency_fields[port].text() and float(self.latency_fields[port].text().replace(",", ".")) > 0 else None for port in self.latency_fields}) self.reject() \ No newline at end of file diff --git a/b_asic/GUI/simulate_sfg_window.py b/b_asic/GUI/simulate_sfg_window.py index 9e60e3dbeb1f8036e55e0992eb65feb985aa6c31..f787e1ce25c0881ffdc5fa0c238ead0a971c3d61 100644 --- a/b_asic/GUI/simulate_sfg_window.py +++ b/b_asic/GUI/simulate_sfg_window.py @@ -1,7 +1,7 @@ from PySide2.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout,\ QLabel, QCheckBox, QSpinBox, QGroupBox, QFrame, QFormLayout, QGridLayout, QSizePolicy, QFileDialog, QShortcut from PySide2.QtCore import Qt, Signal -from PySide2.QtGui import QIntValidator, QKeySequence +from PySide2.QtGui import QDoubleValidator, QKeySequence from matplotlib.backends import qt_compat from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas @@ -69,9 +69,8 @@ class SimulateSFGWindow(QDialog): input_label = QLabel("in" + str(i)) input_layout.addWidget(input_label) input_value = QLineEdit() - input_value.setPlaceholderText("0") - input_value.setValidator(QIntValidator()) - input_value.setFixedWidth(50) + input_value.setPlaceholderText("e.g 0, 0, 0") + input_value.setFixedWidth(100) input_layout.addWidget(input_value) input_layout.addStretch() input_layout.setSpacing(10) @@ -90,19 +89,47 @@ class SimulateSFGWindow(QDialog): self.sfg_to_layout[sfg] = sfg_layout self.dialog_layout.addLayout(sfg_layout) - def save_properties(self): - for sfg, properties in self.input_fields.items(): + def parse_input_values(self, input_values): + _input_values = [] + for _list in list(input_values): + _list_values = [] + for val in _list: + val = val.strip() + try: + if not val: + val = 0 + + _list_values.append(complex(val)) + except ValueError: + self._window.logger.warning(f"Skipping value: {val}, not a digit.") + continue - self.properties[sfg] = { - "iteration_count": self.input_fields[sfg]["iteration_count"].value(), - "show_plot": self.input_fields[sfg]["show_plot"].isChecked(), - "all_results": self.input_fields[sfg]["all_results"].isChecked(), - "input_values": [int(widget.text()) if widget.text() else 0 for widget in self.input_fields[sfg]["input_values"]] - } + _input_values.append(_list_values) - # If we plot we should also print the entire data, since you can't really interact with the graph. - if self.properties[sfg]["show_plot"]: - self.properties[sfg]["all_results"] = True + return _input_values + + def save_properties(self): + for sfg, properties in self.input_fields.items(): + input_values = self.parse_input_values(widget.text().split(",") if widget.text() else [0] for widget in self.input_fields[sfg]["input_values"]) + if max(len(list_) for list_ in input_values) != min(len(list_) for list_ in input_values): + self._window.logger.error(f"Minimum length of input lists are not equal to maximum length of input lists: {max(len(list_) for list_ in input_values)} != {min(len(list_) for list_ in input_values)}.") + elif self.input_fields[sfg]["iteration_count"].value() > min(len(list_) for list_ in input_values): + self._window.logger.error(f"Minimum length of input lists are less than the iteration count: {self.input_fields[sfg]['iteration_count'].value()} > {min(len(list_) for list_ in input_values)}.") + else: + self.properties[sfg] = { + "iteration_count": self.input_fields[sfg]["iteration_count"].value(), + "show_plot": self.input_fields[sfg]["show_plot"].isChecked(), + "all_results": self.input_fields[sfg]["all_results"].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 self.properties[sfg]["show_plot"]: + self.properties[sfg]["all_results"] = True + + continue + + self._window.logger.info(f"Skipping simulation of sfg with name: {sfg.name}, due to previous errors.") self.accept() self.simulate.emit() diff --git a/b_asic/save_load_structure.py b/b_asic/save_load_structure.py index 5311d3fe72b1bb711b21c0aa3070b538db81d2d6..8c416b8643a127c2b07754934050a3eb54f8dfc0 100644 --- a/b_asic/save_load_structure.py +++ b/b_asic/save_load_structure.py @@ -62,8 +62,9 @@ def sfg_to_python(sfg: SFG, counter: int = 0, suffix: str = None) -> str: inputs = "[" + ", ".join(op.graph_id for op in sfg.input_operations) + "]" outputs = "[" + ", ".join(op.graph_id for op in sfg.output_operations) + "]" sfg_name = sfg.name if sfg.name else "sfg" + str(counter) if counter > 0 else 'sfg' - result += f"\n{sfg_name} = SFG(inputs={inputs}, outputs={outputs}, name='{sfg_name}')\n" - result += "\n# SFG Properties:\n" + "prop = {'name':" + f"{sfg_name}" + "}" + sfg_name_var = sfg_name.replace(" ", "_") + result += f"\n{sfg_name_var} = SFG(inputs={inputs}, outputs={outputs}, name='{sfg_name}')\n" + result += "\n# SFG Properties:\n" + "prop = {'name':" + f"{sfg_name_var}" + "}" if suffix is not None: result += "\n" + suffix + "\n" @@ -75,4 +76,4 @@ def python_to_sfg(path: str) -> SFG: code = compile(f.read(), path, 'exec') exec(code, globals(), locals()) - return locals()["prop"]["name"], locals()["positions"] if "positions" in locals() else None \ No newline at end of file + return locals()["prop"]["name"], locals()["positions"] if "positions" in locals() else {} \ No newline at end of file