Mini Milestone

This commit is contained in:
Nicole Rappe 2025-02-14 03:11:29 -07:00
parent faee07b720
commit 9db8cc6b19
3 changed files with 265 additions and 150 deletions

193
blueprint_grid.py Normal file
View File

@ -0,0 +1,193 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import pkgutil
import importlib
import inspect
import types
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QWidget
from PyQt5.QtCore import Qt, QUrl, QTimer
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQuick import QQuickView
# OdenGraphQt Fix: Monkey-patch QUndoStack
import OdenGraphQt.base.graph as base_graph
from PyQt5 import QtWidgets
base_graph.QtGui.QUndoStack = QtWidgets.QUndoStack
import OdenGraphQt.base.commands as base_commands
_original_redo = base_commands.NodesRemovedCmd.redo
_original_undo = base_commands.NodesRemovedCmd.undo
def _patched_redo(self):
try:
_original_redo(self)
except TypeError as e:
if "unexpected type" in str(e) and hasattr(self, 'node'):
node_ids = []
if isinstance(self.node, list):
node_ids = [getattr(n, 'id', str(n)) for n in self.node]
else:
node_ids = [getattr(self.node, 'id', str(self.node))]
self.graph.nodes_deleted.emit(node_ids)
else:
raise
def _patched_undo(self):
try:
_original_undo(self)
except TypeError as e:
if "unexpected type" in str(e) and hasattr(self, 'node'):
node_ids = []
if isinstance(self.node, list):
node_ids = [getattr(n, 'id', str(n)) for n in self.node]
else:
node_ids = [getattr(self.node, 'id', str(self.node))]
self.graph.nodes_deleted.emit(node_ids)
else:
raise
base_commands.NodesRemovedCmd.redo = _patched_redo
base_commands.NodesRemovedCmd.undo = _patched_undo
# OdenGraphQt Transparent Viewer
from OdenGraphQt.widgets.viewer import NodeViewer
class TransparentViewer(NodeViewer):
"""A NodeViewer that does not paint anything in drawBackground() -> Fully transparent."""
def drawBackground(self, painter, rect):
pass # Do nothing, ensuring transparency.
# NodeGraph & Node Import Helpers
from OdenGraphQt import NodeGraph, BaseNode
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
def make_node_command(graph, node_type):
def command():
try:
graph.create_node(node_type)
except Exception as e:
print(f"Error creating node of type {node_type}: {e}")
return command
# Edit Mode Button
class EditButton(QPushButton):
"""A small, frameless button to toggle edit mode."""
def __init__(self, parent=None):
super().__init__("Toggle Edit Mode", parent)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
# Dark gray background with white text.
self.setStyleSheet("background-color: #444444; border: 1px solid black; color: white;")
self.resize(140, 40)
# Main Overlay Window
class MainWindow(QMainWindow):
"""A frameless, transparent overlay with OdenGraphQt nodes & edit mode toggle."""
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)
# QML Background
self.qml_view = QQuickView()
self.qml_view.setSource(QUrl("qml/background_grid.qml"))
self.qml_view.setFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.qml_view.setClearBeforeRendering(True)
self.qml_view.setColor(Qt.transparent)
self.qml_view.show()
# Save the QML root object for later property sync
self.qml_root = self.qml_view.rootObject()
# NodeGraph with TransparentViewer
self.graph = NodeGraph(viewer=TransparentViewer())
self.nodeGraphWidget = self.graph.widget
self.nodeGraphWidget.setStyleSheet("background: transparent; border: none;")
# Transparent central widget
central = QWidget(self)
central.setAttribute(Qt.WA_TranslucentBackground, True)
self.setCentralWidget(central)
self.nodeGraphWidget.setParent(central)
self.nodeGraphWidget.setGeometry(central.rect())
# Edit Mode Button (Python controlled)
self.editButton = EditButton(self)
self.editButton.move(10, 10)
self.editButton.clicked.connect(self.toggleEditMode)
self.isEditMode = True # Set edit mode enabled by default
# Ensure QML grid overlay is enabled at startup
if self.qml_root:
self.qml_root.setProperty("editMode", self.isEditMode)
# Import custom nodes
try:
custom_nodes = import_nodes_from_folder('Nodes')
for node_class in custom_nodes:
self.graph.register_node(node_class)
graph_menu = self.graph.get_context_menu('graph')
for node_class in custom_nodes:
node_type = f"{node_class.__identifier__}.{node_class.__name__}"
node_name = node_class.NODE_NAME
graph_menu.add_command(
f"Add {node_name}",
make_node_command(self.graph, node_type)
)
except Exception as e:
print(f"Error setting up custom nodes: {e}")
# Global update timer
self.timer = QTimer(self)
self.timer.timeout.connect(self.global_update)
self.timer.start(500)
# Timer to ensure the button stays on top (hacky, but effective)
self.raiseTimer = QTimer(self)
self.raiseTimer.timeout.connect(self.editButton.raise_)
self.raiseTimer.start(1000) # Raise the button every 1 second
self.show()
self.nodeGraphWidget.setAttribute(Qt.WA_TransparentForMouseEvents, not self.isEditMode)
def toggleEditMode(self):
"""Toggle edit mode (pass-through clicks vs interactive)."""
self.isEditMode = not self.isEditMode
self.nodeGraphWidget.setAttribute(Qt.WA_TransparentForMouseEvents, not self.isEditMode)
# Button text remains constant.
self.editButton.setText("Toggle Edit Mode")
if self.qml_root:
self.qml_root.setProperty("editMode", self.isEditMode)
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_())

View File

@ -1,98 +1,73 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import sys
import pkgutil
import importlib
import inspect
import types
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QWidget
from PyQt5.QtCore import Qt, QUrl, QTimer
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQuick import QQuickView
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QGraphicsView, QGraphicsScene, QGraphicsItem
from PyQt5.QtCore import Qt, QTimer, QRectF, QPointF
from PyQt5.QtGui import QColor, QPainter, QPen, QBrush, QGradient, QLinearGradient
from PyQt5 import QtWidgets, QtCore, QtGui
# OdenGraphQt Fix: Monkey-patch QUndoStack
import OdenGraphQt.base.graph as base_graph
from PyQt5 import QtWidgets
base_graph.QtGui.QUndoStack = QtWidgets.QUndoStack
import OdenGraphQt.base.commands as base_commands
_original_redo = base_commands.NodesRemovedCmd.redo
_original_undo = base_commands.NodesRemovedCmd.undo
def _patched_redo(self):
try:
_original_redo(self)
except TypeError as e:
if "unexpected type" in str(e) and hasattr(self, 'node'):
node_ids = []
if isinstance(self.node, list):
node_ids = [getattr(n, 'id', str(n)) for n in self.node]
else:
node_ids = [getattr(self.node, 'id', str(self.node))]
self.graph.nodes_deleted.emit(node_ids)
else:
raise
def _patched_undo(self):
try:
_original_undo(self)
except TypeError as e:
if "unexpected type" in str(e) and hasattr(self, 'node'):
node_ids = []
if isinstance(self.node, list):
node_ids = [getattr(n, 'id', str(n)) for n in self.node]
else:
node_ids = [getattr(self.node, 'id', str(self.node))]
self.graph.nodes_deleted.emit(node_ids)
else:
raise
base_commands.NodesRemovedCmd.redo = _patched_redo
base_commands.NodesRemovedCmd.undo = _patched_undo
# OdenGraphQt Transparent Viewer
from OdenGraphQt.widgets.viewer import NodeViewer
class TransparentViewer(NodeViewer):
"""A NodeViewer that does not paint anything in drawBackground() -> Fully transparent."""
def drawBackground(self, painter, rect):
pass # Do nothing, ensuring transparency.
# NodeGraph & Node Import Helpers
from OdenGraphQt import NodeGraph, BaseNode
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
def make_node_command(graph, node_type):
def command():
try:
graph.create_node(node_type)
except Exception as e:
print(f"Error creating node of type {node_type}: {e}")
return command
# Edit Mode Button
class EditButton(QPushButton):
"""A small, frameless button to toggle edit mode."""
# --- 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__("Enter Edit Mode", parent)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setStyleSheet("background-color: rgba(255,255,255,200); border: 1px solid black;")
self.resize(140, 40)
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
# Main Overlay Window
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()
# --- Custom Graph View ---
class CustomGraphView(QGraphicsView):
"""
Custom view for the graph that applies full transparency.
"""
def __init__(self, scene, parent=None):
super().__init__(scene, parent)
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)
# --- Main Window ---
class MainWindow(QMainWindow):
"""A frameless, transparent overlay with OdenGraphQt nodes & edit mode toggle."""
"""A frameless, transparent overlay with a custom graph."""
def __init__(self):
super().__init__()
@ -105,81 +80,28 @@ class MainWindow(QMainWindow):
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground, True)
# QML Background
self.qml_view = QQuickView()
self.qml_view.setSource(QUrl("qml/background_grid.qml"))
self.qml_view.setFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.qml_view.setClearBeforeRendering(True)
self.qml_view.setColor(Qt.transparent)
self.qml_view.show()
# Save the QML root object for later property sync
self.qml_root = self.qml_view.rootObject()
# NodeGraph with TransparentViewer
self.graph = NodeGraph(viewer=TransparentViewer())
self.nodeGraphWidget = self.graph.widget
self.nodeGraphWidget.setStyleSheet("background: transparent; border: none;")
# Transparent central widget
central = QWidget(self)
central.setAttribute(Qt.WA_TranslucentBackground, True)
layout = QVBoxLayout(central)
layout.setContentsMargins(0, 0, 0, 0)
self.setCentralWidget(central)
self.nodeGraphWidget.setParent(central)
self.nodeGraphWidget.setGeometry(central.rect())
# Edit Mode Button (Python controlled)
self.editButton = EditButton(self)
self.editButton.move(10, 10)
self.editButton.clicked.connect(self.toggleEditMode)
self.isEditMode = True # Set edit mode enabled by default
self.editButton.setText("Exit Edit Mode") # Reflect that grid is active
# Ensure QML grid overlay is enabled at startup
if self.qml_root:
self.qml_root.setProperty("editMode", self.isEditMode)
# Import custom nodes
try:
custom_nodes = import_nodes_from_folder('Nodes')
for node_class in custom_nodes:
self.graph.register_node(node_class)
graph_menu = self.graph.get_context_menu('graph')
for node_class in custom_nodes:
node_type = f"{node_class.__identifier__}.{node_class.__name__}"
node_name = node_class.NODE_NAME
graph_menu.add_command(
f"Add {node_name}",
make_node_command(self.graph, node_type)
)
except Exception as e:
print(f"Error setting up custom nodes: {e}")
# Initialize Custom Graph Scene & View
self.scene = CustomGraphScene()
self.view = CustomGraphView(self.scene)
layout.addWidget(self.view)
# Global update timer
self.timer = QTimer(self)
self.timer.timeout.connect(self.global_update)
self.timer.start(500)
self.show()
self.nodeGraphWidget.setAttribute(Qt.WA_TransparentForMouseEvents, not self.isEditMode)
def toggleEditMode(self):
"""Toggle edit mode (pass-through clicks vs interactive)."""
self.isEditMode = not self.isEditMode
self.nodeGraphWidget.setAttribute(Qt.WA_TransparentForMouseEvents, not self.isEditMode)
self.editButton.setText("Exit Edit Mode" if self.isEditMode else "Enter Edit Mode")
if self.qml_root:
self.qml_root.setProperty("editMode", self.isEditMode)
def global_update(self):
"""Update all nodes periodically."""
for node in self.graph.all_nodes():
if hasattr(node, "process_input"):
node.process_input()
"""Update all nodes periodically (to be implemented)."""
pass
# Entry Point
# --- Entry Point ---
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()