import sys import pkgutil import importlib import inspect from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QGraphicsView, QGraphicsScene, QGraphicsItem, QMenu from PyQt5.QtCore import Qt, QTimer, QRectF, QPointF from PyQt5.QtGui import QColor, QPainter, QPen, QBrush, QGradient, QLinearGradient from PyQt5 import QtWidgets, QtCore, QtGui from OdenGraphQt import NodeGraph, BaseNode # --- Fix Missing QUndoStack in QtGui --- import OdenGraphQt.base.graph as base_graph base_graph.QtGui.QUndoStack = QtWidgets.QUndoStack # Monkey-patch the missing QUndoStack # --- Custom Graph Scene --- class CustomGraphScene(QGraphicsScene): """ Custom scene that draws a blueprint-style transparent grid with gradient shading. """ def __init__(self, parent=None): super().__init__(parent) self.setBackgroundBrush(QtCore.Qt.transparent) self.grid_color = QtGui.QColor(100, 160, 160, 160) # Blueprint grid color (10% more transparent) self.grid_size = 115 def drawBackground(self, painter, rect): """ Custom draw function to render a blueprint-style grid with gradient shading. """ painter.save() painter.setRenderHint(QPainter.Antialiasing, False) painter.setBrush(QtCore.Qt.NoBrush) # No background fill pen = QPen(self.grid_color, 0.5) left = int(rect.left()) - (int(rect.left()) % self.grid_size) top = int(rect.top()) - (int(rect.top()) % self.grid_size) # Draw vertical lines lines = [] for x in range(left, int(rect.right()), self.grid_size): lines.append(QtCore.QLineF(x, rect.top(), x, rect.bottom())) # Draw horizontal lines for y in range(top, int(rect.bottom()), self.grid_size): lines.append(QtCore.QLineF(rect.left(), y, rect.right(), y)) painter.setPen(pen) painter.drawLines(lines) # Draw gradient shading (top and bottom) gradient = QLinearGradient(QPointF(rect.left(), rect.top()), QPointF(rect.left(), rect.bottom())) gradient.setColorAt(0.0, QColor(0, 40, 100, 220)) # Darker blue at the top gradient.setColorAt(0.5, QColor(0, 0, 0, 0)) # Transparent in the middle gradient.setColorAt(1.0, QColor(0, 40, 100, 220)) # Darker blue at the bottom painter.fillRect(rect, QBrush(gradient)) painter.restore() # --- Node Management --- def import_nodes_from_folder(package_name): imported_nodes = [] package = importlib.import_module(package_name) for loader, module_name, is_pkg in pkgutil.walk_packages(package.__path__, package.__name__ + "."): module = importlib.import_module(module_name) for name, obj in inspect.getmembers(module, inspect.isclass): if issubclass(obj, BaseNode) and obj.__module__ == module.__name__: imported_nodes.append(obj) return imported_nodes # --- Custom Graph View --- class CustomGraphView(QGraphicsView): """ Custom view for the graph that applies full transparency and handles right-click context menu. """ def __init__(self, scene, graph, parent=None): super().__init__(scene, parent) self.graph = graph # Reference to NodeGraph self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform) self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setStyleSheet("background: transparent; border: none;") self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True) # Enable context menu on right-click self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.show_context_menu) def show_context_menu(self, position): """ Displays the node creation context menu with dynamically loaded nodes. """ menu = QMenu() for node_class in self.graph.registered_nodes(): node_name = getattr(node_class, "NODE_NAME", node_class.__name__) menu.addAction(f"Create {node_name}", lambda nc=node_class: self.create_node(nc)) menu.exec_(self.mapToGlobal(position)) def create_node(self, node_class): """ Creates a node instance of the given class in the NodeGraph. """ try: node = self.graph.create_node(f"{node_class.__identifier__}.{node_class.__name__}") print(f"Created node: {node_class.__name__}") except Exception as e: print(f"Error creating node: {e}") # --- Main Window --- class MainWindow(QMainWindow): """A frameless, transparent overlay with a custom graph.""" def __init__(self): super().__init__() # Full-screen overlay app = QApplication.instance() screen_geo = app.primaryScreen().geometry() self.setGeometry(screen_geo) # Frameless, top-most, fully transparent self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setAttribute(Qt.WA_TranslucentBackground, True) # Transparent central widget central = QWidget(self) central.setAttribute(Qt.WA_TranslucentBackground, True) layout = QVBoxLayout(central) layout.setContentsMargins(0, 0, 0, 0) self.setCentralWidget(central) # Initialize NodeGraph self.graph = NodeGraph() # Load custom nodes custom_nodes = import_nodes_from_folder('Nodes') for node_class in custom_nodes: self.graph.register_node(node_class) # Initialize Custom Graph Scene & View self.scene = CustomGraphScene() self.view = CustomGraphView(self.scene, self.graph, self) layout.addWidget(self.view) # Global update timer self.timer = QTimer(self) self.timer.timeout.connect(self.global_update) self.timer.start(500) def global_update(self): """Update all nodes periodically.""" for node in self.graph.all_nodes(): if hasattr(node, "process_input"): node.process_input() # --- Entry Point --- if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())