From d4ddbeb7273648274222281c35f2ac0d427bf288 Mon Sep 17 00:00:00 2001 From: Bryan Date: Tue, 4 Apr 2023 18:14:21 -0400 Subject: [PATCH 01/10] example project --- Example_Project/Add_node.py | 13 ++++++++++ Example_Project/test.json | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 Example_Project/Add_node.py create mode 100644 Example_Project/test.json diff --git a/Example_Project/Add_node.py b/Example_Project/Add_node.py new file mode 100644 index 0000000..d1cd508 --- /dev/null +++ b/Example_Project/Add_node.py @@ -0,0 +1,13 @@ +from node_editor.gui.node import Node + + +class Add_Node(Node): + def __init__(self): + super().__init__() + + self.title = "Add" + self.type_text = "Logic Nodes" + self.add_port(name="input A", is_output=False) + self.add_port(name="input B", is_output=False) + self.add_port(name="output", is_output=True) + self.build() diff --git a/Example_Project/test.json b/Example_Project/test.json new file mode 100644 index 0000000..0ab9e3a --- /dev/null +++ b/Example_Project/test.json @@ -0,0 +1,48 @@ +{ + "nodes": [ + { + "type": "Add_Node", + "x": 5012, + "y": 4886, + "uuid": "be2827cf-6e4a-4902-b3f7-3c0d9c19ef2f" + }, + { + "type": "Add_Node", + "x": 4807, + "y": 5164, + "uuid": "69ef5cfe-9f44-411b-83d6-f6aa8790d92e" + }, + { + "type": "Add_Node", + "x": 4494, + "y": 4991, + "uuid": "cc0a484c-c044-4dce-89a0-cab99bb0becb" + }, + { + "type": "Add_Node", + "x": 4639, + "y": 4847, + "uuid": "85902a44-f54d-4c46-ac76-b58e365daa06" + } + ], + "connections": [ + { + "start_id": "69ef5cfe-9f44-411b-83d6-f6aa8790d92e", + "end_id": "be2827cf-6e4a-4902-b3f7-3c0d9c19ef2f", + "start_port": "output", + "end_port": "input B" + }, + { + "start_id": "85902a44-f54d-4c46-ac76-b58e365daa06", + "end_id": "be2827cf-6e4a-4902-b3f7-3c0d9c19ef2f", + "start_port": "output", + "end_port": "input A" + }, + { + "start_id": "cc0a484c-c044-4dce-89a0-cab99bb0becb", + "end_id": "85902a44-f54d-4c46-ac76-b58e365daa06", + "start_port": "output", + "end_port": "input B" + } + ] +} \ No newline at end of file From c827e3ea3e30103a76a32d4359ee472569cf5439 Mon Sep 17 00:00:00 2001 From: Bryan Date: Tue, 4 Apr 2023 18:14:55 -0400 Subject: [PATCH 02/10] Loading project, and saving scene description --- main.py | 51 +++++++++++- node_editor/gui/connection.py | 2 +- node_editor/gui/node.py | 1 + node_editor/gui/node_list.py | 23 +++++- node_editor/gui/node_widget.py | 138 +++++++++++++-------------------- node_editor/gui/view.py | 6 +- 6 files changed, 129 insertions(+), 92 deletions(-) diff --git a/main.py b/main.py index 97e9856..3adf711 100644 --- a/main.py +++ b/main.py @@ -11,6 +11,7 @@ """ import logging +from pathlib import Path from PySide6 import QtCore, QtGui, QtWidgets @@ -22,9 +23,12 @@ class NodeEditor(QtWidgets.QMainWindow): + OnProjectPathUpdate = QtCore.Signal(Path) + def __init__(self, parent=None): super().__init__(parent) self.settings = None + self.project_path = None icon = QtGui.QIcon("resources\\app.ico") self.setWindowIcon(icon) @@ -32,6 +36,18 @@ def __init__(self, parent=None): self.setWindowTitle("Simple Node Editor") settings = QtCore.QSettings("node-editor", "NodeEditor") + # create a "File" menu and add an "Export CSV" action to it + file_menu = QtWidgets.QMenu("File", self) + self.menuBar().addMenu(file_menu) + + load_action = QtGui.QAction("Load Project", self) + load_action.triggered.connect(self.load_project) + file_menu.addAction(load_action) + + save_action = QtGui.QAction("Save Project", self) + save_action.triggered.connect(self.save_project) + file_menu.addAction(save_action) + # Layouts main_widget = QtWidgets.QWidget() self.setCentralWidget(main_widget) @@ -41,7 +57,7 @@ def __init__(self, parent=None): left_layout.setContentsMargins(0, 0, 0, 0) # Widgets - self.node_list = NodeList() + self.node_list = NodeList(self) left_widget = QtWidgets.QWidget() self.splitter = QtWidgets.QSplitter() self.node_widget = NodeWidget(self) @@ -56,8 +72,11 @@ def __init__(self, parent=None): left_layout.addWidget(new_node_type_btn) main_layout.addWidget(self.splitter) - # Logic + # Signals new_node_type_btn.clicked.connect(self.new_node_cmd) + self.OnProjectPathUpdate.connect(self.node_list.update_project_path) + + self.load_project("C:/Users/Howard/simple-node-editor/Example_project") # Restore GUI from last state if settings.contains("geometry"): @@ -66,6 +85,30 @@ def __init__(self, parent=None): s = settings.value("splitterSize") self.splitter.restoreState(s) + def save_project(self): + file_dialog = QtWidgets.QFileDialog() + file_dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) + file_dialog.setDefaultSuffix("json") + file_dialog.setNameFilter("JSON files (*.json)") + file_path, _ = file_dialog.getSaveFileName() + self.node_widget.save_project(file_path) + + def load_project(self, project_path=None): + if not project_path: + return + + project_path = Path(project_path) + if project_path.exists() and project_path.is_dir(): + self.project_path = project_path + self.OnProjectPathUpdate.emit(project_path) + + def get_project_path(self): + project_path = QtWidgets.QFileDialog.getExistingDirectory(None, "Select Project Folder", "") + if not project_path: + return + + self.load_project(project_path) + def new_node_cmd(self): """ Handles the New Node Type button click event by showing the NodeTypeEditor dialog. @@ -90,6 +133,10 @@ def closeEvent(self, event): Returns: None. """ + + # debugging lets save the scene: + self.node_widget.save_project("C:/Users/Howard/simple-node-editor/Example_Project/test.json") + self.settings = QtCore.QSettings("node-editor", "NodeEditor") self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("splitterSize", self.splitter.saveState()) diff --git a/node_editor/gui/connection.py b/node_editor/gui/connection.py index ba0f7f4..2c36d31 100644 --- a/node_editor/gui/connection.py +++ b/node_editor/gui/connection.py @@ -80,7 +80,7 @@ def nodes(self): Returns: tuple: A tuple of the two Node objects connected by this Connection. """ - return (self._start_port().node(), self._end_port().node()) + return (self._start_port.node(), self._end_port.node()) def update_start_and_end_pos(self): """ diff --git a/node_editor/gui/node.py b/node_editor/gui/node.py index b46fe1f..eeac8fa 100644 --- a/node_editor/gui/node.py +++ b/node_editor/gui/node.py @@ -46,6 +46,7 @@ def __init__(self): self._width = 30 # The Width of the node self._height = 30 # the height of the node self._ports = [] # A list of ports + self.uuid = None # An identifier to used when saving and loading the scene self.node_color = QtGui.QColor(20, 20, 20, 200) diff --git a/node_editor/gui/node_list.py b/node_editor/gui/node_list.py index 85d8541..39cfae0 100644 --- a/node_editor/gui/node_list.py +++ b/node_editor/gui/node_list.py @@ -1,16 +1,30 @@ from PySide6 import QtCore, QtGui, QtWidgets +import sys +import importlib +import inspect class NodeList(QtWidgets.QListWidget): def __init__(self, parent=None): super().__init__(parent) + self.setDragEnabled(True) # enable dragging - for node in ["Input", "Output", "And", "Not", "Nor", "Empty"]: - item = QtWidgets.QListWidgetItem(node) + def update_project_path(self, project_path): + print("project path updated") + for file in project_path.glob("*.py"): + print(f"File: {file.stem}") + spec = importlib.util.spec_from_file_location(file.stem, file) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + item = QtWidgets.QListWidgetItem(file.stem) + item.module = module + for name, obj in inspect.getmembers(module): + if inspect.isclass(obj): + item.class_name = obj + break self.addItem(item) - self.setDragEnabled(True) # enable dragging - def mousePressEvent(self, event): item = self.itemAt(event.pos()) if item and item.text(): @@ -19,6 +33,7 @@ def mousePressEvent(self, event): drag = QtGui.QDrag(self) mime_data = QtCore.QMimeData() mime_data.setText(name) + mime_data.item = item drag.setMimeData(mime_data) # Drag needs a pixmap or else it'll error due to a null pixmap diff --git a/node_editor/gui/node_widget.py b/node_editor/gui/node_widget.py index 9005a76..ba781b4 100644 --- a/node_editor/gui/node_widget.py +++ b/node_editor/gui/node_widget.py @@ -1,65 +1,14 @@ +import json +import uuid from PySide6 import QtGui, QtWidgets from node_editor.gui.node import Node from node_editor.gui.node_editor import NodeEditor from node_editor.gui.view import View - -def create_input(): - node = Node() - node.title = "A" - node.type_text = "input" - node.add_port(name="output", is_output=True) - node.build() - return node - - -def create_output(): - node = Node() - node.title = "A" - node.type_text = "output" - node.add_port(name="input", is_output=False) - node.build() - return node - - -def create_and(): - node = Node() - node.title = "AND" - node.type_text = "built-in" - node.add_port(name="input A", is_output=False) - node.add_port(name="input B", is_output=False) - node.add_port(name="output", is_output=True) - node.build() - return node - - -def create_not(): - node = Node() - node.title = "NOT" - node.type_text = "built-in" - node.add_port(name="input", is_output=False) - node.add_port(name="output", is_output=True) - node.build() - return node - - -def create_nor(): - node = Node() - node.title = "NOR" - node.type_text = "built-in" - node.add_port(name="input", is_output=False) - node.add_port(name="output", is_output=True) - node.build() - return node - - -def create_empty(): - node = Node() - node.title = "NOR" - node.type_text = "empty node" - node.build() - return node +from node_editor.gui.connection import Connection +from node_editor.gui.node import Node +from node_editor.gui.port import Port class NodeScene(QtWidgets.QGraphicsScene): @@ -110,32 +59,57 @@ def __init__(self, parent): self.view.request_node.connect(self.create_node) - def create_node(self, name): - """ - Creates a new node and adds it to the node editor. - - Args: - name (str): The name of the node to be created. - """ - print("creating node:", name) - - if name == "Input": - node = create_input() - elif name == "Output": - node = create_output() - elif name == "And": - node = create_and() - elif name == "Not": - node = create_not() - elif name == "Nor": - node = create_nor() - elif name == "Empty": - node = create_empty() - else: - print(f"Can't find a premade node for {name}") - return - + def create_node(self, node): + node.uuid = uuid.uuid4() self.scene.addItem(node) - pos = self.view.mapFromGlobal(QtGui.QCursor.pos()) node.setPos(self.view.mapToScene(pos)) + + def save_project(self, json_path): + # print(f"json path: {json_path}") + scene = {"nodes": [], "connections": []} + + # Need the nodes, and connections of ports to nodes + for item in self.scene.items(): + # Connections + if isinstance(item, Connection): + # print(f"Name: {item}") + nodes = item.nodes() + start_id = str(nodes[0].uuid) + end_id = str(nodes[1].uuid) + start_port = item.start_port.name() + end_port = item.end_port.name() + # print(f"Node ids {start_id, end_id}") + # print(f"connected ports {item.start_port.name(), item.end_port.name()}") + + connection = { + "start_id": start_id, + "end_id": end_id, + "start_port": start_port, + "end_port": end_port, + } + scene["connections"].append(connection) + continue + + # Ports + if isinstance(item, Port): + continue + + # Nodes + if isinstance(item, Node): + print("found node") + pos = item.pos().toPoint() + x, y = pos.x(), pos.y() + print(f"pos: {x, y}") + + obj_type = type(item).__name__ + print(f"node type: {obj_type}") + + node_id = str(item.uuid) + + node = {"type": obj_type, "x": x, "y": y, "uuid": node_id} + scene["nodes"].append(node) + + # Write the items_info dictionary to a JSON file + with open(json_path, "w") as f: + json.dump(scene, f, indent=4) diff --git a/node_editor/gui/view.py b/node_editor/gui/view.py index 141d8d9..03de4ec 100644 --- a/node_editor/gui/view.py +++ b/node_editor/gui/view.py @@ -18,7 +18,7 @@ class View(QtWidgets.QGraphicsView): _mouse_wheel_zoom_rate = 0.0015 - request_node = QtCore.Signal(str) + request_node = QtCore.Signal(object) def __init__(self, parent): super().__init__(parent) @@ -184,8 +184,8 @@ def dropEvent(self, e): This method is called when a drag and drop event is dropped onto the view. It retrieves the name of the dropped node from the mime data and emits a signal to request the creation of the corresponding node. """ - drop_node_name = e.mimeData().text() - self.request_node.emit(drop_node_name) + node = e.mimeData().item.class_name + self.request_node.emit(node()) def mousePressEvent(self, event): """ From 64b81840ab892129a3560a0ac0e9391ac3ba7cae Mon Sep 17 00:00:00 2001 From: Bryan Date: Wed, 5 Apr 2023 17:54:05 -0400 Subject: [PATCH 03/10] scene updated --- Example_Project/test.json | 72 +++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/Example_Project/test.json b/Example_Project/test.json index 0ab9e3a..f519605 100644 --- a/Example_Project/test.json +++ b/Example_Project/test.json @@ -2,47 +2,83 @@ "nodes": [ { "type": "Add_Node", - "x": 5012, - "y": 4886, - "uuid": "be2827cf-6e4a-4902-b3f7-3c0d9c19ef2f" + "x": 4794, + "y": 5130, + "uuid": "f20b2350-0bc9-40be-9ddd-510d16ae8cc8" }, { "type": "Add_Node", - "x": 4807, - "y": 5164, - "uuid": "69ef5cfe-9f44-411b-83d6-f6aa8790d92e" + "x": 4666, + "y": 4864, + "uuid": "24ac8fd5-b91a-4c35-bfdc-bf96961da280" }, { "type": "Add_Node", - "x": 4494, - "y": 4991, - "uuid": "cc0a484c-c044-4dce-89a0-cab99bb0becb" + "x": 4617, + "y": 5014, + "uuid": "a06586f8-3c66-48ca-8be7-fd99b9ee82c3" }, { "type": "Add_Node", - "x": 4639, - "y": 4847, - "uuid": "85902a44-f54d-4c46-ac76-b58e365daa06" + "x": 4943, + "y": 5114, + "uuid": "e06066a5-6375-4a40-8d14-c7e5e9c28f02" + }, + { + "type": "Add_Node", + "x": 4949, + "y": 4926, + "uuid": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b" + }, + { + "type": "Add_Node", + "x": 5165, + "y": 5007, + "uuid": "0fe0381a-29bc-4987-ba94-ba4a14e33a13" + }, + { + "type": "Add_Node", + "x": 5358, + "y": 4944, + "uuid": "6e09cc9a-8b79-4129-bf61-637c6b8eb39b" } ], "connections": [ { - "start_id": "69ef5cfe-9f44-411b-83d6-f6aa8790d92e", - "end_id": "be2827cf-6e4a-4902-b3f7-3c0d9c19ef2f", + "start_id": "f20b2350-0bc9-40be-9ddd-510d16ae8cc8", + "end_id": "e06066a5-6375-4a40-8d14-c7e5e9c28f02", "start_port": "output", "end_port": "input B" }, { - "start_id": "85902a44-f54d-4c46-ac76-b58e365daa06", - "end_id": "be2827cf-6e4a-4902-b3f7-3c0d9c19ef2f", + "start_id": "0fe0381a-29bc-4987-ba94-ba4a14e33a13", + "end_id": "6e09cc9a-8b79-4129-bf61-637c6b8eb39b", + "start_port": "output", + "end_port": "input B" + }, + { + "start_id": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b", + "end_id": "0fe0381a-29bc-4987-ba94-ba4a14e33a13", "start_port": "output", "end_port": "input A" }, { - "start_id": "cc0a484c-c044-4dce-89a0-cab99bb0becb", - "end_id": "85902a44-f54d-4c46-ac76-b58e365daa06", + "start_id": "e06066a5-6375-4a40-8d14-c7e5e9c28f02", + "end_id": "0fe0381a-29bc-4987-ba94-ba4a14e33a13", + "start_port": "output", + "end_port": "input B" + }, + { + "start_id": "a06586f8-3c66-48ca-8be7-fd99b9ee82c3", + "end_id": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b", "start_port": "output", "end_port": "input B" + }, + { + "start_id": "24ac8fd5-b91a-4c35-bfdc-bf96961da280", + "end_id": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b", + "start_port": "output", + "end_port": "input A" } ] } \ No newline at end of file From 4f45ddbce493e7ac6f2ccf1b1e0350d80f544b1b Mon Sep 17 00:00:00 2001 From: Bryan Date: Wed, 5 Apr 2023 18:02:12 -0400 Subject: [PATCH 04/10] moded loading modules here and now reading first json file --- main.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 3adf711..6819f7e 100644 --- a/main.py +++ b/main.py @@ -12,6 +12,8 @@ import logging from pathlib import Path +import importlib +import inspect from PySide6 import QtCore, QtGui, QtWidgets @@ -29,6 +31,7 @@ def __init__(self, parent=None): super().__init__(parent) self.settings = None self.project_path = None + self.imports = None # we will store the project import node types here for now. icon = QtGui.QIcon("resources\\app.ico") self.setWindowIcon(icon) @@ -74,7 +77,6 @@ def __init__(self, parent=None): # Signals new_node_type_btn.clicked.connect(self.new_node_cmd) - self.OnProjectPathUpdate.connect(self.node_list.update_project_path) self.load_project("C:/Users/Howard/simple-node-editor/Example_project") @@ -100,7 +102,25 @@ def load_project(self, project_path=None): project_path = Path(project_path) if project_path.exists() and project_path.is_dir(): self.project_path = project_path - self.OnProjectPathUpdate.emit(project_path) + + self.imports = {} + + for file in project_path.glob("*.py"): + spec = importlib.util.spec_from_file_location(file.stem, file) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + for name, obj in inspect.getmembers(module): + if inspect.isclass(obj): + self.imports[obj.__name__] = {"class": obj, "module": module} + break + + self.node_list.update_project(self.imports) + + # work on just the first json file. add the ablitity to work on multiple json files later + for json_path in project_path.glob("*.json"): + self.node_widget.load_scene(json_path, self.imports) + break def get_project_path(self): project_path = QtWidgets.QFileDialog.getExistingDirectory(None, "Select Project Folder", "") @@ -135,7 +155,7 @@ def closeEvent(self, event): """ # debugging lets save the scene: - self.node_widget.save_project("C:/Users/Howard/simple-node-editor/Example_Project/test.json") + # self.node_widget.save_project("C:/Users/Howard/simple-node-editor/Example_Project/test.json") self.settings = QtCore.QSettings("node-editor", "NodeEditor") self.settings.setValue("geometry", self.saveGeometry()) From 366297f7d4898ca1bfd12bfb2b358f44c29ed994 Mon Sep 17 00:00:00 2001 From: Bryan Date: Wed, 5 Apr 2023 18:02:20 -0400 Subject: [PATCH 05/10] update --- Example_Project/test.json | 64 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Example_Project/test.json b/Example_Project/test.json index f519605..0aee684 100644 --- a/Example_Project/test.json +++ b/Example_Project/test.json @@ -2,21 +2,21 @@ "nodes": [ { "type": "Add_Node", - "x": 4794, - "y": 5130, - "uuid": "f20b2350-0bc9-40be-9ddd-510d16ae8cc8" + "x": 5374, + "y": 4897, + "uuid": "6e09cc9a-8b79-4129-bf61-637c6b8eb39b" }, { "type": "Add_Node", - "x": 4666, - "y": 4864, - "uuid": "24ac8fd5-b91a-4c35-bfdc-bf96961da280" + "x": 5165, + "y": 5007, + "uuid": "0fe0381a-29bc-4987-ba94-ba4a14e33a13" }, { "type": "Add_Node", - "x": 4617, - "y": 5014, - "uuid": "a06586f8-3c66-48ca-8be7-fd99b9ee82c3" + "x": 4949, + "y": 4926, + "uuid": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b" }, { "type": "Add_Node", @@ -26,59 +26,59 @@ }, { "type": "Add_Node", - "x": 4949, - "y": 4926, - "uuid": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b" + "x": 4617, + "y": 5014, + "uuid": "a06586f8-3c66-48ca-8be7-fd99b9ee82c3" }, { "type": "Add_Node", - "x": 5165, - "y": 5007, - "uuid": "0fe0381a-29bc-4987-ba94-ba4a14e33a13" + "x": 4666, + "y": 4864, + "uuid": "24ac8fd5-b91a-4c35-bfdc-bf96961da280" }, { "type": "Add_Node", - "x": 5358, - "y": 4944, - "uuid": "6e09cc9a-8b79-4129-bf61-637c6b8eb39b" + "x": 4794, + "y": 5130, + "uuid": "f20b2350-0bc9-40be-9ddd-510d16ae8cc8" } ], "connections": [ { - "start_id": "f20b2350-0bc9-40be-9ddd-510d16ae8cc8", - "end_id": "e06066a5-6375-4a40-8d14-c7e5e9c28f02", + "start_id": "24ac8fd5-b91a-4c35-bfdc-bf96961da280", + "end_id": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b", "start_port": "output", - "end_port": "input B" + "end_port": "input A" }, { - "start_id": "0fe0381a-29bc-4987-ba94-ba4a14e33a13", - "end_id": "6e09cc9a-8b79-4129-bf61-637c6b8eb39b", + "start_id": "a06586f8-3c66-48ca-8be7-fd99b9ee82c3", + "end_id": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b", "start_port": "output", "end_port": "input B" }, { - "start_id": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b", + "start_id": "e06066a5-6375-4a40-8d14-c7e5e9c28f02", "end_id": "0fe0381a-29bc-4987-ba94-ba4a14e33a13", "start_port": "output", - "end_port": "input A" + "end_port": "input B" }, { - "start_id": "e06066a5-6375-4a40-8d14-c7e5e9c28f02", + "start_id": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b", "end_id": "0fe0381a-29bc-4987-ba94-ba4a14e33a13", "start_port": "output", - "end_port": "input B" + "end_port": "input A" }, { - "start_id": "a06586f8-3c66-48ca-8be7-fd99b9ee82c3", - "end_id": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b", + "start_id": "0fe0381a-29bc-4987-ba94-ba4a14e33a13", + "end_id": "6e09cc9a-8b79-4129-bf61-637c6b8eb39b", "start_port": "output", "end_port": "input B" }, { - "start_id": "24ac8fd5-b91a-4c35-bfdc-bf96961da280", - "end_id": "0a3b1313-e73c-4f3a-b878-7a7a8a76518b", + "start_id": "f20b2350-0bc9-40be-9ddd-510d16ae8cc8", + "end_id": "e06066a5-6375-4a40-8d14-c7e5e9c28f02", "start_port": "output", - "end_port": "input A" + "end_port": "input B" } ] } \ No newline at end of file From 7dac9beba96ccf4600d9d601042e29d78616cf2b Mon Sep 17 00:00:00 2001 From: Bryan Date: Wed, 5 Apr 2023 18:02:50 -0400 Subject: [PATCH 06/10] removed importing modules. Just add to the list the class nodes now. --- node_editor/gui/node_list.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/node_editor/gui/node_list.py b/node_editor/gui/node_list.py index 39cfae0..e89bad8 100644 --- a/node_editor/gui/node_list.py +++ b/node_editor/gui/node_list.py @@ -9,20 +9,13 @@ def __init__(self, parent=None): super().__init__(parent) self.setDragEnabled(True) # enable dragging - def update_project_path(self, project_path): - print("project path updated") - for file in project_path.glob("*.py"): - print(f"File: {file.stem}") - spec = importlib.util.spec_from_file_location(file.stem, file) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - item = QtWidgets.QListWidgetItem(file.stem) - item.module = module - for name, obj in inspect.getmembers(module): - if inspect.isclass(obj): - item.class_name = obj - break + def update_project(self, imports): + # make an item for each custom class + + for name, data in imports.items(): + item = QtWidgets.QListWidgetItem(name) + item.module = data["module"] + item.class_name = data["class"] self.addItem(item) def mousePressEvent(self, event): From ec9917f50a3b43b95fa4c9c4b8551734117867b0 Mon Sep 17 00:00:00 2001 From: Bryan Date: Wed, 5 Apr 2023 18:03:15 -0400 Subject: [PATCH 07/10] loading scene by json description --- node_editor/gui/node_widget.py | 47 +++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/node_editor/gui/node_widget.py b/node_editor/gui/node_widget.py index ba781b4..6284ff3 100644 --- a/node_editor/gui/node_widget.py +++ b/node_editor/gui/node_widget.py @@ -1,5 +1,7 @@ import json import uuid +from collections import OrderedDict + from PySide6 import QtGui, QtWidgets from node_editor.gui.node import Node @@ -44,6 +46,8 @@ def __init__(self, parent): parent (QWidget): The parent widget. """ super().__init__(parent) + + self.node_lookup = {} # A dictionary of nodes, by uuids for faster looking up. Refactor this in the future main_layout = QtWidgets.QVBoxLayout() main_layout.setContentsMargins(0, 0, 0, 0) self.setLayout(main_layout) @@ -65,8 +69,45 @@ def create_node(self, node): pos = self.view.mapFromGlobal(QtGui.QCursor.pos()) node.setPos(self.view.mapToScene(pos)) + def load_scene(self, json_path, imports): + # load the scene json file + data = None + with open(json_path) as f: + data = json.load(f) + + # clear out the node lookup + self.node_lookup = {} + + # Add the nodes + if data: + for node in data["nodes"]: + info = imports[node["type"]] + node_item = info["class"]() + node_item.uuid = node["uuid"] + self.scene.addItem(node_item) + node_item.setPos(node["x"], node["y"]) + + self.node_lookup[node["uuid"]] = node_item + + # Add the connections + for c in data["connections"]: + connection = Connection(None) + self.scene.addItem(connection) + + start_port = self.node_lookup[c["start_id"]].get_port(c["start_port"]) + end_port = self.node_lookup[c["end_id"]].get_port(c["end_port"]) + + connection.start_port = start_port + connection.end_port = end_port + connection.update_start_and_end_pos() + def save_project(self, json_path): # print(f"json path: {json_path}") + + from collections import OrderedDict + + # TODO possibly an ordered dict so things stay in order (better for git changes, and manual editing) + # Maybe connections will need a uuid for each so they can be sorted and kept in order. scene = {"nodes": [], "connections": []} # Need the nodes, and connections of ports to nodes @@ -97,13 +138,13 @@ def save_project(self, json_path): # Nodes if isinstance(item, Node): - print("found node") + # print("found node") pos = item.pos().toPoint() x, y = pos.x(), pos.y() - print(f"pos: {x, y}") + # print(f"pos: {x, y}") obj_type = type(item).__name__ - print(f"node type: {obj_type}") + # print(f"node type: {obj_type}") node_id = str(item.uuid) From 4b92b86695f9eabb9912deb705714bf8e096ba6a Mon Sep 17 00:00:00 2001 From: Bryan Date: Wed, 5 Apr 2023 18:03:44 -0400 Subject: [PATCH 08/10] helper to get port. TODO should store ports in dict for faster lookup. --- node_editor/gui/node.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/node_editor/gui/node.py b/node_editor/gui/node.py index eeac8fa..f704ddc 100644 --- a/node_editor/gui/node.py +++ b/node_editor/gui/node.py @@ -97,6 +97,11 @@ def paint(self, painter, option=None, widget=None): painter.drawPath(self.type_path) painter.drawPath(self.misc_path) + def get_port(self, name): + for port in self._ports: + if port.name() == name: + return port + def add_port(self, name, is_output=False, flags=0, ptr=None): """ Adds a new port to the node. From 9bbbb0223bd9890618dd1e158ab94f2f1e91e1d6 Mon Sep 17 00:00:00 2001 From: Bryan Date: Wed, 5 Apr 2023 18:05:39 -0400 Subject: [PATCH 09/10] nodes will not be made in the GUI. They will be python classes in the project folder --- main.py | 17 ------------- node_editor/gui/node_type_editor.py | 39 ----------------------------- 2 files changed, 56 deletions(-) delete mode 100644 node_editor/gui/node_type_editor.py diff --git a/main.py b/main.py index 6819f7e..6b3b7ed 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,6 @@ from PySide6 import QtCore, QtGui, QtWidgets from node_editor.gui.node_list import NodeList -from node_editor.gui.node_type_editor import NodeTypeEditor from node_editor.gui.node_widget import NodeWidget logging.basicConfig(level=logging.DEBUG) @@ -76,8 +75,6 @@ def __init__(self, parent=None): main_layout.addWidget(self.splitter) # Signals - new_node_type_btn.clicked.connect(self.new_node_cmd) - self.load_project("C:/Users/Howard/simple-node-editor/Example_project") # Restore GUI from last state @@ -129,20 +126,6 @@ def get_project_path(self): self.load_project(project_path) - def new_node_cmd(self): - """ - Handles the New Node Type button click event by showing the NodeTypeEditor dialog. - - Returns: - None. - """ - node_editor = NodeTypeEditor() - - if node_editor.exec() == QtWidgets.QDialog.Accepted: - print("Dialog accepted") - else: - print("Dialog canceled") - def closeEvent(self, event): """ Handles the close event by saving the GUI state and closing the application. diff --git a/node_editor/gui/node_type_editor.py b/node_editor/gui/node_type_editor.py deleted file mode 100644 index 78ea850..0000000 --- a/node_editor/gui/node_type_editor.py +++ /dev/null @@ -1,39 +0,0 @@ -from PySide6 import QtWidgets - - -class NodeTypeEditor(QtWidgets.QDialog): - """ - A dialog window for editing node types. - - Attributes: - edit (QtWidgets.QLineEdit): A line edit widget for editing the node type. - """ - - def __init__(self, parent=None): - """ - Constructs a NodeTypeEditor object. - - Args: - parent (QWidget): The parent widget of this dialog. - """ - super().__init__(parent) - - self.setWindowTitle("Node Type Editor") - - # create the UI elements - label = QtWidgets.QLabel("Node Type:") - self.edit = QtWidgets.QLineEdit() - button_ok = QtWidgets.QPushButton("OK") - button_cancel = QtWidgets.QPushButton("Cancel") - - # set the layout - layout = QtWidgets.QHBoxLayout() - layout.addWidget(label) - layout.addWidget(self.edit) - layout.addWidget(button_ok) - layout.addWidget(button_cancel) - self.setLayout(layout) - - # connect the signals and slots - button_ok.clicked.connect(self.accept) - button_cancel.clicked.connect(self.reject) From 612b1d2807a5f9c340a9ca1ca44aa398ca05ce09 Mon Sep 17 00:00:00 2001 From: Bryan Date: Wed, 5 Apr 2023 18:06:40 -0400 Subject: [PATCH 10/10] removing new node button --- main.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/main.py b/main.py index 6b3b7ed..d95f416 100644 --- a/main.py +++ b/main.py @@ -63,15 +63,12 @@ def __init__(self, parent=None): left_widget = QtWidgets.QWidget() self.splitter = QtWidgets.QSplitter() self.node_widget = NodeWidget(self) - new_node_type_btn = QtWidgets.QPushButton("New Node Type") - new_node_type_btn.setFixedHeight(50) # Add Widgets to layouts self.splitter.addWidget(left_widget) self.splitter.addWidget(self.node_widget) left_widget.setLayout(left_layout) left_layout.addWidget(self.node_list) - left_layout.addWidget(new_node_type_btn) main_layout.addWidget(self.splitter) # Signals