Initial Flow UI Testing
This commit is contained in:
parent
b239ace188
commit
6c757e72a4
62
data_collector.py
Normal file
62
data_collector.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Collector Process:
|
||||||
|
- Runs the OCR engine.
|
||||||
|
- Updates OCR data every 0.5 seconds.
|
||||||
|
- Exposes the latest data via an HTTP API using Flask.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from flask import Flask, jsonify
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Global variable to hold the latest stats (HP, MP, FP, EXP)
|
||||||
|
latest_data = {
|
||||||
|
"hp": "0/0",
|
||||||
|
"mp": "0/0",
|
||||||
|
"fp": "0/0",
|
||||||
|
"exp": "0.0000"
|
||||||
|
}
|
||||||
|
|
||||||
|
def ocr_collector():
|
||||||
|
"""
|
||||||
|
This function simulates the OCR process.
|
||||||
|
Replace the code below with your actual OCR logic.
|
||||||
|
"""
|
||||||
|
global latest_data
|
||||||
|
counter = 0
|
||||||
|
while True:
|
||||||
|
# Simulate updating stats:
|
||||||
|
hp_current = 50 + counter % 10
|
||||||
|
hp_max = 100
|
||||||
|
mp_current = 30 + counter % 5
|
||||||
|
mp_max = 50
|
||||||
|
fp_current = 20 # fixed, for example
|
||||||
|
fp_max = 20
|
||||||
|
exp_val = round(10.0 + (counter * 0.1), 4)
|
||||||
|
|
||||||
|
latest_data = {
|
||||||
|
"hp": f"{hp_current}/{hp_max}",
|
||||||
|
"mp": f"{mp_current}/{mp_max}",
|
||||||
|
"fp": f"{fp_current}/{fp_max}",
|
||||||
|
"exp": f"{exp_val:.4f}"
|
||||||
|
}
|
||||||
|
|
||||||
|
counter += 1
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
@app.route('/data')
|
||||||
|
def get_data():
|
||||||
|
"""Return the latest OCR data as JSON."""
|
||||||
|
return jsonify(latest_data)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Start the OCR collector in a background thread.
|
||||||
|
collector_thread = threading.Thread(target=ocr_collector)
|
||||||
|
collector_thread.daemon = True
|
||||||
|
collector_thread.start()
|
||||||
|
|
||||||
|
# Run the Flask app on localhost:5000.
|
||||||
|
app.run(host="127.0.0.1", port=5000)
|
239
flow_UI.py
Normal file
239
flow_UI.py
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Modified Flow UI Application:
|
||||||
|
- The character node is now named "Character Status" (display title).
|
||||||
|
- DisplayValueNodes dynamically update their title to show the value from the connected output port.
|
||||||
|
- Newly spawned DisplayValueNodes (via the context menu) follow the same dynamic behavior.
|
||||||
|
- Node creation strings now include the class name to match NodeGraphQt requirements.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
from Qt import QtWidgets, QtCore, QtGui
|
||||||
|
from NodeGraphQt import NodeGraph, BaseNode
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Custom Port Painter Function for Character Status Node Ports (Semi-Transparent)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
def get_draw_stat_port(color, border_color=None, alpha=127):
|
||||||
|
"""
|
||||||
|
Returns a custom port painter function that draws a circular port with a semi-transparent fill,
|
||||||
|
then draws text (port label and current value) next to it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
color (tuple): RGB tuple for the fill color.
|
||||||
|
border_color (tuple, optional): RGB tuple for the border color.
|
||||||
|
Defaults to the same as color if not provided.
|
||||||
|
alpha (int, optional): Alpha value (0-255) for transparency. Default is 127.
|
||||||
|
"""
|
||||||
|
if border_color is None:
|
||||||
|
border_color = color
|
||||||
|
|
||||||
|
def painter_func(painter, rect, info):
|
||||||
|
painter.save()
|
||||||
|
# --- Section: Port Drawing ---
|
||||||
|
pen = QtGui.QPen(QtGui.QColor(*border_color))
|
||||||
|
pen.setWidth(1.8)
|
||||||
|
painter.setPen(pen)
|
||||||
|
# Create semi-transparent fill using the provided alpha value.
|
||||||
|
semi_transparent_color = QtGui.QColor(color[0], color[1], color[2], alpha)
|
||||||
|
painter.setBrush(semi_transparent_color)
|
||||||
|
painter.drawEllipse(rect)
|
||||||
|
# --- Section: Port Label and Value Drawing ---
|
||||||
|
port = info.get('port')
|
||||||
|
if port is not None:
|
||||||
|
node = port.node() # Parent node (e.g. Character Status)
|
||||||
|
stat = port.name() # Port label (e.g. "HP")
|
||||||
|
# Retrieve the current value from the node's values dict (if available)
|
||||||
|
value = node.values.get(stat, "N/A") if hasattr(node, 'values') else "N/A"
|
||||||
|
# Create a text rectangle to the right of the port.
|
||||||
|
text_rect = rect.adjusted(rect.width() + 4, 0, rect.width() + 70, 0)
|
||||||
|
painter.setPen(QtGui.QColor(0, 0, 0))
|
||||||
|
painter.drawText(text_rect, QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft,
|
||||||
|
f"{stat}: {value}")
|
||||||
|
painter.restore()
|
||||||
|
return painter_func
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Custom Node Classes
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class CharacterStatusNode(BaseNode):
|
||||||
|
"""
|
||||||
|
Character Status Node
|
||||||
|
|
||||||
|
This node represents the character's current status.
|
||||||
|
It has no input ports and four output ports: "HP", "MP", "FP", and "EXP".
|
||||||
|
The current values are stored in self.values and are drawn using a custom painter.
|
||||||
|
"""
|
||||||
|
__identifier__ = 'io.github.nicole.status'
|
||||||
|
NODE_NAME = 'Character Status'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(CharacterStatusNode, self).__init__()
|
||||||
|
# --- Section: Initialization of Stat Values and Ports ---
|
||||||
|
self.values = {"HP": "N/A", "MP": "N/A", "FP": "N/A", "EXP": "N/A"}
|
||||||
|
self.add_output("HP", painter_func=get_draw_stat_port((255, 0, 0))) # Red for HP
|
||||||
|
self.add_output("MP", painter_func=get_draw_stat_port((0, 0, 255))) # Blue for MP
|
||||||
|
self.add_output("FP", painter_func=get_draw_stat_port((0, 255, 0))) # Green for FP
|
||||||
|
self.add_output("EXP", painter_func=get_draw_stat_port((127, 255, 212))) # Aquamarine for EXP
|
||||||
|
# Set the node title to "Character Status"
|
||||||
|
self.set_name("Character Status")
|
||||||
|
|
||||||
|
def update_value(self, stat, value):
|
||||||
|
"""
|
||||||
|
Update the value for a given stat.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stat (str): Stat name (e.g., "HP", "MP", etc.).
|
||||||
|
value (str): New value for the stat.
|
||||||
|
"""
|
||||||
|
self.values[stat] = value
|
||||||
|
|
||||||
|
class DisplayValueNode(BaseNode):
|
||||||
|
"""
|
||||||
|
Display Value Node
|
||||||
|
|
||||||
|
This node displays a single input value.
|
||||||
|
Its title is updated to show the value from the connected output port.
|
||||||
|
If no port is connected, it displays "Display Value: No Input Configured".
|
||||||
|
"""
|
||||||
|
__identifier__ = 'io.github.nicole.display'
|
||||||
|
NODE_NAME = 'Display Value Node'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(DisplayValueNode, self).__init__()
|
||||||
|
# --- Section: Initialization and Default State ---
|
||||||
|
self.add_input("in")
|
||||||
|
self.add_output("out")
|
||||||
|
self.value = "No Input Configured"
|
||||||
|
self.update_display()
|
||||||
|
|
||||||
|
def update_display(self):
|
||||||
|
"""
|
||||||
|
Update the node's title to reflect the current displayed value.
|
||||||
|
"""
|
||||||
|
self.set_name(f"Display Value: {self.value}")
|
||||||
|
|
||||||
|
def process_input(self):
|
||||||
|
"""
|
||||||
|
Processes the input connection:
|
||||||
|
- If connected, retrieve the value from the connected node's output port.
|
||||||
|
- If not, set the value to "No Input Configured".
|
||||||
|
"""
|
||||||
|
input_port = self.input(0)
|
||||||
|
connections = input_port.connections() if input_port is not None else []
|
||||||
|
if connections:
|
||||||
|
# --- Section: Processing Connected Input ---
|
||||||
|
connection = connections[0]
|
||||||
|
out_port = connection.out_port()
|
||||||
|
if out_port:
|
||||||
|
parent_node = out_port.node()
|
||||||
|
port_name = out_port.name()
|
||||||
|
self.value = parent_node.values.get(port_name, "N/A")
|
||||||
|
else:
|
||||||
|
self.value = "No Input Configured"
|
||||||
|
else:
|
||||||
|
# --- Section: No Connection Found ---
|
||||||
|
self.value = "No Input Configured"
|
||||||
|
self.update_display()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# API Polling Functions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
def poll_api():
|
||||||
|
"""
|
||||||
|
Polls the Flask API for JSON data.
|
||||||
|
Expected JSON format: {"hp": "150/150", "mp": "75/100", "fp": "20/20", "exp": "10.0000"}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The JSON data, or None on error.
|
||||||
|
"""
|
||||||
|
api_url = "http://127.0.0.1:5000/data"
|
||||||
|
try:
|
||||||
|
response = requests.get(api_url, timeout=1)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print("API error: status code", response.status_code)
|
||||||
|
except Exception as e:
|
||||||
|
print("Error polling API:", e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def update_nodes(graph, character_status_node):
|
||||||
|
"""
|
||||||
|
Polls the API, updates the Character Status node's values,
|
||||||
|
and then updates all DisplayValueNodes in the graph.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
graph (NodeGraph): The NodeGraphQt graph instance.
|
||||||
|
character_status_node (CharacterStatusNode): The character status node.
|
||||||
|
"""
|
||||||
|
data = poll_api()
|
||||||
|
if data is None:
|
||||||
|
return
|
||||||
|
# --- Section: Update Character Status Node with Latest Data ---
|
||||||
|
character_status_node.update_value("HP", data.get('hp', "N/A"))
|
||||||
|
character_status_node.update_value("MP", data.get('mp', "N/A"))
|
||||||
|
character_status_node.update_value("FP", data.get('fp', "N/A"))
|
||||||
|
character_status_node.update_value("EXP", data.get('exp', "N/A"))
|
||||||
|
|
||||||
|
# --- Section: Update All DisplayValueNodes ---
|
||||||
|
for node in graph.all_nodes():
|
||||||
|
if hasattr(node, "process_input"):
|
||||||
|
node.process_input()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Context Menu: Add DisplayValueNode Command
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
def add_display_node(graph):
|
||||||
|
"""
|
||||||
|
Adds a new DisplayValueNode to the graph at a default position.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
graph (NodeGraph): The NodeGraphQt graph instance.
|
||||||
|
"""
|
||||||
|
# Use NodeGraphQt's create_node() with the correct node type string.
|
||||||
|
new_node = graph.create_node('io.github.nicole.display.DisplayValueNode',
|
||||||
|
name='Display Value Node', pos=[400, 300])
|
||||||
|
print("Added a new Display Value node.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Main Application
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = QtWidgets.QApplication([])
|
||||||
|
|
||||||
|
# --- Section: Setup NodeGraphQT ---
|
||||||
|
graph = NodeGraph()
|
||||||
|
graph.widget.setWindowTitle("Project Borealis - Flyff Information Overlay")
|
||||||
|
# Register nodes using their classes.
|
||||||
|
graph.register_node(CharacterStatusNode)
|
||||||
|
graph.register_node(DisplayValueNode)
|
||||||
|
|
||||||
|
# --- Section: Setup Context Menu for Adding DisplayValueNodes ---
|
||||||
|
graph_context_menu = graph.get_context_menu('graph')
|
||||||
|
graph_context_menu.add_command(
|
||||||
|
'Add Display Value Node',
|
||||||
|
lambda: add_display_node(graph),
|
||||||
|
shortcut='Ctrl+D'
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Section: Create and Connect Nodes ---
|
||||||
|
# Create the Character Status node.
|
||||||
|
character_status_node = graph.create_node('io.github.nicole.status.CharacterStatusNode',
|
||||||
|
name='Character Status', pos=[0, 0])
|
||||||
|
# Create a Display Value node.
|
||||||
|
display_node = graph.create_node('io.github.nicole.display.DisplayValueNode',
|
||||||
|
name='Display Value Node', pos=[500, 200])
|
||||||
|
# Connect the EXP output (index 3) from the Character Status node to the display node's input.
|
||||||
|
character_status_node.set_output(3, display_node.input(0))
|
||||||
|
|
||||||
|
# --- Section: Timer Setup for API Polling and Node Updates ---
|
||||||
|
timer = QtCore.QTimer()
|
||||||
|
timer.timeout.connect(lambda: update_nodes(graph, character_status_node))
|
||||||
|
timer.start(500)
|
||||||
|
|
||||||
|
graph.widget.resize(1000, 600)
|
||||||
|
graph.widget.show()
|
||||||
|
app.exec_()
|
Loading…
x
Reference in New Issue
Block a user