diff --git a/Modules/__pycache__/data_collector.cpython-312.pyc b/Modules/__pycache__/data_collector.cpython-312.pyc index d93ef58..9d4d97e 100644 Binary files a/Modules/__pycache__/data_collector.cpython-312.pyc and b/Modules/__pycache__/data_collector.cpython-312.pyc differ diff --git a/Modules/__pycache__/data_manager.cpython-312.pyc b/Modules/__pycache__/data_manager.cpython-312.pyc index 16fcb0f..22b6786 100644 Binary files a/Modules/__pycache__/data_manager.cpython-312.pyc and b/Modules/__pycache__/data_manager.cpython-312.pyc differ diff --git a/Modules/data_collector.py b/Modules/data_collector.py index 9aa1598..1a041c4 100644 --- a/Modules/data_collector.py +++ b/Modules/data_collector.py @@ -113,15 +113,15 @@ class OCRRegionWidget(QWidget): def paintEvent(self, event): painter = QPainter(self) - pen = QPen(QColor(0, 0, 255)) - pen.setWidth(3) + pen = QPen(QColor(255, 255, 0)) # COLOR OF THE BOX ITSELF + pen.setWidth(5) # WIDTH OF THE BOX BORDER painter.setPen(pen) # Draw main rectangle painter.drawRect(0, 0, self.width(), self.height()) # Draw resize handles - painter.setBrush(QColor(0, 0, 255)) + painter.setBrush(QColor(255, 255, 0)) # COLOR OF THE RESIZE HANDLES for handle in self._resize_handles(): painter.drawRect(handle) diff --git a/Modules/data_manager.py b/Modules/data_manager.py index 72e9521..b07b4a5 100644 --- a/Modules/data_manager.py +++ b/Modules/data_manager.py @@ -31,12 +31,49 @@ def data_api(): """ return jsonify(get_data()) +@app.route('/exp') +def exp_api(): + """ + Returns the EXP data. + """ + return jsonify({"exp": get_data()["exp"]}) + +@app.route('/hp') +def hp_api(): + """ + Returns the HP data. + """ + return jsonify({ + "hp_current": get_data()["hp_current"], + "hp_total": get_data()["hp_total"] + }) + +@app.route('/mp') +def mp_api(): + """ + Returns the MP data. + """ + return jsonify({ + "mp_current": get_data()["mp_current"], + "mp_total": get_data()["mp_total"] + }) + +@app.route('/fp') +def fp_api(): + """ + Returns the FP data. + """ + return jsonify({ + "fp_current": get_data()["fp_current"], + "fp_total": get_data()["fp_total"] + }) + def start_api_server(): """ Starts the Flask API server in a separate daemon thread. """ def run(): - app.run(host="127.0.0.1", port=5000) + app.run(host="0.0.0.0", port=5000) # Allows external connections t = threading.Thread(target=run, daemon=True) t.start() diff --git a/Nodes/blueprint_node.py b/Nodes/Experimental/blueprint_node.py similarity index 100% rename from Nodes/blueprint_node.py rename to Nodes/Experimental/blueprint_node.py diff --git a/Nodes/Flyff/__pycache__/flyff_character_status_node.cpython-312.pyc b/Nodes/Flyff/__pycache__/flyff_character_status_node.cpython-312.pyc index d98ebf3..cbe246a 100644 Binary files a/Nodes/Flyff/__pycache__/flyff_character_status_node.cpython-312.pyc and b/Nodes/Flyff/__pycache__/flyff_character_status_node.cpython-312.pyc differ diff --git a/Nodes/Flyff/__pycache__/flyff_leveling_predictor_node.cpython-312.pyc b/Nodes/Flyff/__pycache__/flyff_leveling_predictor_node.cpython-312.pyc index c4f5299..a75da59 100644 Binary files a/Nodes/Flyff/__pycache__/flyff_leveling_predictor_node.cpython-312.pyc and b/Nodes/Flyff/__pycache__/flyff_leveling_predictor_node.cpython-312.pyc differ diff --git a/Nodes/comparison_node.py b/Nodes/General Purpose/comparison_node.py similarity index 100% rename from Nodes/comparison_node.py rename to Nodes/General Purpose/comparison_node.py diff --git a/Nodes/data_node.py b/Nodes/General Purpose/data_node.py similarity index 100% rename from Nodes/data_node.py rename to Nodes/General Purpose/data_node.py diff --git a/Nodes/math_operation_node.py b/Nodes/General Purpose/math_operation_node.py similarity index 100% rename from Nodes/math_operation_node.py rename to Nodes/General Purpose/math_operation_node.py diff --git a/borealis.py b/borealis.py index f60d6c9..1d5dd20 100644 --- a/borealis.py +++ b/borealis.py @@ -11,12 +11,9 @@ from Qt import QtWidgets, QtCore, QtGui # ------------------------------------------------------------------ # MONKEY-PATCH to fix "module 'qtpy.QtGui' has no attribute 'QUndoStack'" -# OdenGraphQt tries to do QtGui.QUndoStack(self). -# We'll import QUndoStack from QtWidgets and attach it to QtGui. try: from qtpy.QtWidgets import QUndoStack import qtpy - # Force QtGui.QUndoStack to reference QtWidgets.QUndoStack qtpy.QtGui.QUndoStack = QUndoStack except ImportError: print("WARNING: Could not monkey-patch QUndoStack. You may see an error if OdenGraphQt needs it.") @@ -25,70 +22,42 @@ except ImportError: # Import your data_manager so we can start the Flask server from Modules import data_manager -# --- BEGIN MONKEY PATCH FOR PIPE COLOR (Optional) --- -# If you want custom pipe colors, uncomment this patch: -""" -try: - from OdenGraphQt.qgraphics.pipe import PipeItem - - _orig_pipeitem_init = PipeItem.__init__ - - def _new_pipeitem_init(self, *args, **kwargs): - _orig_pipeitem_init(self, *args, **kwargs) - new_color = QtGui.QColor(29, 202, 151) - self._pen = QtGui.QPen(new_color, 2.0) - self._pen_dragging = QtGui.QPen(new_color, 2.0) - - PipeItem.__init__ = _new_pipeitem_init - -except ImportError: - print("WARNING: Could not patch PipeItem color - OdenGraphQt.qgraphics.pipe not found.") -""" -# --- END MONKEY PATCH FOR PIPE COLOR --- - # --- 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(). - We try calling the original method with whatever arguments are provided. - If a TypeError occurs, we assume it was missing some arguments and re-call with defaults. """ try: - # First, try the original call with the given arguments. return _original_setSelectionArea(self, *args, **kwargs) except TypeError: - # If a TypeError occurs, the caller likely used a minimal signature. - # We'll fallback to a known signature with default arguments. if not args: - raise # If no args at all, we cannot fix it. - - painterPath = args[0] # QPainterPath + 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 ROBUST PATCH FOR QGraphicsScene.setSelectionArea --- +# --- END PATCH --- from OdenGraphQt import NodeGraph, BaseNode def import_nodes_from_folder(package_name): """ - Recursively import all modules from the given package - and return a list of classes that subclass BaseNode. + Recursively import all modules from the given package. + Returns a dictionary where keys are subfolder names, and values are lists of BaseNode subclasses. """ - imported_nodes = [] + nodes_by_category = {} package = importlib.import_module(package_name) - - # Get the root directory of the package 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": @@ -97,31 +66,27 @@ def import_nodes_from_folder(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) + 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 imported_nodes + 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. - For the Flyff Character Status Collector node, check if one already exists. - If so, schedule an error message to be shown. - - Also ensure that node creation is delayed until after QApplication is up, to avoid - 'QWidget: Must construct a QApplication before a QWidget' errors. + Ensures that only one FlyffCharacterStatusNode exists. """ def real_create(): - # Check if we are about to create a duplicate Character Status Collector node. if node_type_str.startswith("bunny-lab.io.flyff_character_status_node"): for node in graph.all_nodes(): if node.__class__.__name__ == "FlyffCharacterStatusNode": - # Show error message about duplicates QtWidgets.QMessageBox.critical( None, "Error", - "Only one Flyff Character Status Collector node is allowed. If you added more, things would break (really) badly." + "Only one Flyff Character Status Collector node is allowed." ) return try: @@ -131,68 +96,69 @@ def make_node_command(graph, node_type_str): QtWidgets.QMessageBox.critical(None, "Error", str(e)) def command(): - # If there's already a QApplication running, just create the node now. if QtWidgets.QApplication.instance(): real_create() else: - # Otherwise, schedule the node creation for the next event cycle. QtCore.QTimer.singleShot(0, real_create) return command if __name__ == "__main__": - # Create the QApplication first app = QtWidgets.QApplication([]) - # Start the Flask server from data_manager so /data is always available + # Start Flask API Server data_manager.start_api_server() - # Create the NodeGraph controller - # (the monkey-patch ensures NodeGraph won't crash if it tries QtGui.QUndoStack(self)) + # Create the NodeGraph graph = NodeGraph() graph.widget.setWindowTitle("Project Borealis - Workflow Automation System") # Dynamically import custom node classes from the 'Nodes' package. - custom_nodes = import_nodes_from_folder("Nodes") - for node_class in custom_nodes: - graph.register_node(node_class) + custom_nodes_by_category = import_nodes_from_folder("Nodes") - # Add context menu commands for dynamic node creation. + # 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 node_class in custom_nodes: - node_type = f"{node_class.__identifier__}.{node_class.__name__}" - node_name = node_class.NODE_NAME - graph_context_menu.add_command( - f"Add {node_name}", - make_node_command(graph, node_type) - ) - # Add a "Remove Selected Node" command to the graph context menu. + 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 - # 1) Dark background color graph.set_background_color(20, 20, 20) # Dark gray - # 2) Subdued grid color graph.set_grid_color(60, 60, 60) # Gray grid lines - # Optionally, create a subtle gradient in the scene: + # 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)) # Very Top Gradient - gradient.setColorAt(0.3, QtGui.QColor(30, 30, 30)) # Middle Gradient - gradient.setColorAt(0.7, QtGui.QColor(30, 30, 30)) # Middle Gradient - gradient.setColorAt(1.0, QtGui.QColor(9, 44, 68)) # Very Bottom Gradient + 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. + # 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"):