Borealis-Legacy/borealis.py

175 lines
6.3 KiB
Python

# -*- coding: utf-8 -*-
#!/usr/bin/env python3
import sys
import pkgutil
import importlib
import inspect
import os
from Qt import QtWidgets, QtCore, QtGui
# ------------------------------------------------------------------
# MONKEY-PATCH to fix "module 'qtpy.QtGui' has no attribute 'QUndoStack'"
try:
from qtpy.QtWidgets import QUndoStack
import qtpy
qtpy.QtGui.QUndoStack = QUndoStack
except ImportError:
print("WARNING: Could not monkey-patch QUndoStack. You may see an error if OdenGraphQt needs it.")
# ------------------------------------------------------------------
# Import your data_manager so we can start the Flask server
from Modules import data_manager
# --- BEGIN ROBUST PATCH FOR QGraphicsScene.setSelectionArea ---
_original_setSelectionArea = QtWidgets.QGraphicsScene.setSelectionArea
def _patched_setSelectionArea(self, *args, **kwargs):
"""
A robust patch that handles various call signatures for QGraphicsScene.setSelectionArea().
"""
try:
return _original_setSelectionArea(self, *args, **kwargs)
except TypeError:
if not args:
raise
painterPath = args[0]
selection_op = QtCore.Qt.ReplaceSelection
selection_mode = QtCore.Qt.IntersectsItemShape
transform = QtGui.QTransform()
return _original_setSelectionArea(self, painterPath, selection_op, selection_mode, transform)
QtWidgets.QGraphicsScene.setSelectionArea = _patched_setSelectionArea
# --- END PATCH ---
from OdenGraphQt import NodeGraph, BaseNode
def import_nodes_from_folder(package_name):
"""
Recursively import all modules from the given package.
Returns a dictionary where keys are subfolder names, and values are lists of BaseNode subclasses.
"""
nodes_by_category = {}
package = importlib.import_module(package_name)
package_path = package.__path__[0]
for root, _, files in os.walk(package_path):
rel_path = os.path.relpath(root, package_path).replace(os.sep, '.')
module_prefix = f"{package_name}.{rel_path}" if rel_path != '.' else package_name
category_name = os.path.basename(root)
for file in files:
if file.endswith(".py") and file != "__init__.py":
module_name = f"{module_prefix}.{file[:-3]}"
try:
module = importlib.import_module(module_name)
for name, obj in inspect.getmembers(module, inspect.isclass):
if issubclass(obj, BaseNode) and obj.__module__ == module.__name__:
if category_name not in nodes_by_category:
nodes_by_category[category_name] = []
nodes_by_category[category_name].append(obj)
except Exception as e:
print(f"Failed to import {module_name}: {e}")
return nodes_by_category
def make_node_command(graph, node_type_str):
"""
Return a function that creates a node of the given type at the current cursor position.
Ensures that only one FlyffCharacterStatusNode exists.
"""
def real_create():
if node_type_str.startswith("bunny-lab.io.flyff_character_status_node"):
for node in graph.all_nodes():
if node.__class__.__name__ == "FlyffCharacterStatusNode":
QtWidgets.QMessageBox.critical(
None,
"Error",
"Only one Flyff Character Status Collector node is allowed."
)
return
try:
pos = graph.cursor_pos()
graph.create_node(node_type_str, pos=pos)
except Exception as e:
QtWidgets.QMessageBox.critical(None, "Error", str(e))
def command():
if QtWidgets.QApplication.instance():
real_create()
else:
QtCore.QTimer.singleShot(0, real_create)
return command
if __name__ == "__main__":
app = QtWidgets.QApplication([])
# Start Flask API Server
data_manager.start_api_server()
# Create the NodeGraph
graph = NodeGraph()
graph.widget.setWindowTitle("Project Borealis - Workflow Automation System")
# Dynamically import custom node classes from the 'Nodes' package.
custom_nodes_by_category = import_nodes_from_folder("Nodes")
# Register each node in its category
for category, node_classes in custom_nodes_by_category.items():
for node_class in node_classes:
graph.register_node(node_class)
# Create categorized context menu
graph_context_menu = graph.get_context_menu("graph")
for category, node_classes in custom_nodes_by_category.items():
category_menu = graph_context_menu.add_menu(category) # Create submenu for category
for node_class in node_classes:
node_type = f"{node_class.__identifier__}.{node_class.__name__}"
node_name = node_class.NODE_NAME
category_menu.add_command(
f"Create: {node_name}",
make_node_command(graph, node_type)
)
# Add a "Remove Selected Node" command
graph_context_menu.add_command(
"Remove Selected Node",
lambda: [graph.remove_node(node) for node in graph.selected_nodes()] if graph.selected_nodes() else None
)
# Grid styling changes
graph.set_background_color(20, 20, 20) # Dark gray
graph.set_grid_color(60, 60, 60) # Gray grid lines
# Add gradient background
scene = graph.scene()
gradient = QtGui.QLinearGradient(0, 0, 0, 1)
gradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
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))
scene.setBackgroundBrush(QtGui.QBrush(gradient))
# Resize and show the graph widget
graph.widget.resize(1600, 900)
graph.widget.show()
# Global update function
def global_update():
for node in graph.all_nodes():
if hasattr(node, "process_input"):
try:
node.process_input()
except Exception as e:
print("Error updating node", node, e)
timer = QtCore.QTimer()
timer.timeout.connect(global_update)
timer.start(500)
sys.exit(app.exec_())