diff --git a/Workflows/test.json b/Workflows/test.json new file mode 100644 index 0000000..ad50743 --- /dev/null +++ b/Workflows/test.json @@ -0,0 +1,101 @@ +{ + "graph":{ + "layout_direction":0, + "acyclic":true, + "pipe_collision":false, + "pipe_slicing":true, + "pipe_style":1, + "accept_connection_types":{}, + "reject_connection_types":{} + }, + "nodes":{ + "0x25032ceeae0":{ + "type_":"bunny-lab.io.data_node.DataNode", + "icon":null, + "name":"Data Node", + "color":[ + 13, + 18, + 23, + 255 + ], + "border_color":[ + 74, + 84, + 85, + 255 + ], + "text_color":[ + 255, + 255, + 255, + 180 + ], + "disabled":false, + "selected":false, + "visible":true, + "width":269.0, + "height":74.2, + "pos":[ + -140.9796961059538, + 314.0813482866816 + ], + "layout_direction":0, + "port_deletion_allowed":false, + "subgraph_session":{}, + "custom":{ + "value":"56" + } + }, + "0x250626018b0":{ + "type_":"bunny-lab.io.data_node.DataNode", + "icon":null, + "name":"Data Node 1", + "color":[ + 13, + 18, + 23, + 255 + ], + "border_color":[ + 74, + 84, + 85, + 255 + ], + "text_color":[ + 255, + 255, + 255, + 180 + ], + "disabled":false, + "selected":false, + "visible":true, + "width":269.0, + "height":74.2, + "pos":[ + 436.3232366823503, + 297.12809369166183 + ], + "layout_direction":0, + "port_deletion_allowed":false, + "subgraph_session":{}, + "custom":{ + "value":"56" + } + } + }, + "connections":[ + { + "out":[ + "0x25032ceeae0", + "Output" + ], + "in":[ + "0x250626018b0", + "Input" + ] + } + ] +} \ No newline at end of file diff --git a/Workflows/test2.json b/Workflows/test2.json new file mode 100644 index 0000000..fe8f3be --- /dev/null +++ b/Workflows/test2.json @@ -0,0 +1,139 @@ +{ + "graph":{ + "layout_direction":0, + "acyclic":true, + "pipe_collision":false, + "pipe_slicing":true, + "pipe_style":1, + "accept_connection_types":{}, + "reject_connection_types":{} + }, + "nodes":{ + "0x25062603f80":{ + "type_":"bunny-lab.io.data_node.DataNode", + "icon":null, + "name":"Data Node", + "color":[ + 13, + 18, + 23, + 255 + ], + "border_color":[ + 74, + 84, + 85, + 255 + ], + "text_color":[ + 255, + 255, + 255, + 180 + ], + "disabled":false, + "selected":false, + "visible":true, + "width":269.0, + "height":74.2, + "pos":[ + -183.12907239097774, + 203.3685322819906 + ], + "layout_direction":0, + "port_deletion_allowed":false, + "subgraph_session":{}, + "custom":{ + "value":"25" + } + }, + "0x250626141a0":{ + "type_":"bunny-lab.io.data_node.DataNode", + "icon":null, + "name":"Data Node 1", + "color":[ + 13, + 18, + 23, + 255 + ], + "border_color":[ + 74, + 84, + 85, + 255 + ], + "text_color":[ + 255, + 255, + 255, + 180 + ], + "disabled":false, + "selected":false, + "visible":true, + "width":269.0, + "height":74.2, + "pos":[ + 279.96246102035116, + 512.9884977805091 + ], + "layout_direction":0, + "port_deletion_allowed":false, + "subgraph_session":{}, + "custom":{ + "value":"25" + } + }, + "0x250626159d0":{ + "type_":"bunny-lab.io.backdrop.BackdropNode", + "icon":null, + "name":"flask", + "color":[ + 5, + 129, + 138, + 255 + ], + "border_color":[ + 74, + 84, + 85, + 255 + ], + "text_color":[ + 255, + 255, + 255, + 180 + ], + "disabled":false, + "selected":false, + "visible":true, + "width":804.65254462634, + "height":483.4329838785975, + "pos":[ + -205.22360825550223, + 146.33335545174936 + ], + "layout_direction":0, + "port_deletion_allowed":false, + "subgraph_session":{}, + "custom":{ + "backdrop_text":"" + } + } + }, + "connections":[ + { + "out":[ + "0x25062603f80", + "Output" + ], + "in":[ + "0x250626141a0", + "Input" + ] + } + ] +} \ No newline at end of file diff --git a/borealis.py b/borealis.py index 1d5dd20..8701a3c 100644 --- a/borealis.py +++ b/borealis.py @@ -19,6 +19,17 @@ except ImportError: print("WARNING: Could not monkey-patch QUndoStack. You may see an error if OdenGraphQt needs it.") # ------------------------------------------------------------------ +# ------------------------------------------------------------------ +# MONKEY-PATCH to fix "'BackdropNodeItem' object has no attribute 'widgets'" +# by giving BackdropNodeItem a trivial widgets dictionary. +try: + from OdenGraphQt.nodes.backdrop_node import BackdropNodeItem + if not hasattr(BackdropNodeItem, "widgets"): + BackdropNodeItem.widgets = {} +except ImportError: + print("WARNING: Could not monkey-patch BackdropNodeItem to add `widgets`.") +# ------------------------------------------------------------------ + # Import your data_manager so we can start the Flask server from Modules import data_manager @@ -44,6 +55,7 @@ QtWidgets.QGraphicsScene.setSelectionArea = _patched_setSelectionArea # --- END PATCH --- from OdenGraphQt import NodeGraph, BaseNode +from OdenGraphQt.widgets.dialogs import FileDialog def import_nodes_from_folder(package_name): """ @@ -103,6 +115,57 @@ def make_node_command(graph, node_type_str): return command +def ensure_workflows_folder(): + """ + Ensures a 'Workflows' subfolder exists. + """ + if not os.path.exists("Workflows"): + os.makedirs("Workflows") + +def close_workflow(graph: NodeGraph): + """ + Closes the current workflow (removes all nodes and connections). + """ + graph.clear_session() + +def save_workflow(graph: NodeGraph): + """ + Saves the current workflow (including custom names, positions, wires, etc.) into a JSON file + in the 'Workflows' subfolder. + """ + ensure_workflows_folder() + file_filter = "JSON Files (*.json);;All Files (*.*)" + dlg = FileDialog.getSaveFileName(None, "Save Workflow", os.path.join("Workflows", ""), file_filter) + file_path = dlg[0] + if not file_path: + return # User canceled + + if not file_path.lower().endswith(".json"): + file_path += ".json" + + try: + graph.save_session(file_path) + print(f"Workflow saved to {file_path}") + except Exception as e: + QtWidgets.QMessageBox.critical(None, "Error Saving Workflow", str(e)) + +def load_workflow(graph: NodeGraph): + """ + Loads a workflow (including node values, connections, positions, etc.) from a specified JSON file. + """ + ensure_workflows_folder() + file_filter = "JSON Files (*.json);;All Files (*.*)" + dlg = FileDialog.getOpenFileName(None, "Load Workflow", os.path.join("Workflows", ""), file_filter) + file_path = dlg[0] + if not file_path: + return # User canceled + + try: + graph.load_session(file_path) + print(f"Workflow loaded from {file_path}") + except Exception as e: + QtWidgets.QMessageBox.critical(None, "Error Loading Workflow", str(e)) + if __name__ == "__main__": app = QtWidgets.QApplication([]) @@ -140,6 +203,12 @@ if __name__ == "__main__": lambda: [graph.remove_node(node) for node in graph.selected_nodes()] if graph.selected_nodes() else None ) + # Add workflow menu commands + workflow_menu = graph_context_menu.add_menu("Workflow") + workflow_menu.add_command("Load Workflow", lambda: load_workflow(graph)) + workflow_menu.add_command("Save Workflow", lambda: save_workflow(graph)) + workflow_menu.add_command("Close Workflow", lambda: close_workflow(graph)) + # Grid styling changes graph.set_background_color(20, 20, 20) # Dark gray graph.set_grid_color(60, 60, 60) # Gray grid lines @@ -151,7 +220,7 @@ if __name__ == "__main__": gradient.setColorAt(0.0, QtGui.QColor(9, 44, 68)) gradient.setColorAt(0.3, QtGui.QColor(30, 30, 30)) gradient.setColorAt(0.7, QtGui.QColor(30, 30, 30)) - gradient.setColorAt(1.0, QtGui.QColor(9, 44, 68)) + gradient.setColorAt(1.0, QtGui.QColor(9, 44, 68)) scene.setBackgroundBrush(QtGui.QBrush(gradient)) # Resize and show the graph widget