Fixed a bunch of crap
This commit is contained in:
parent
6c757e72a4
commit
584e229c1c
0
Nodes/__init__.py
Normal file
0
Nodes/__init__.py
Normal file
BIN
Nodes/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/array_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/array_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/average_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/average_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/character_status_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/character_status_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/convert_to_percent_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/convert_to_percent_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/data_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/data_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/display_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/display_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/display_string_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/display_string_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/display_text_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/display_text_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/display_value_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/display_value_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/multiply_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/multiply_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/number_input_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/number_input_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/subtract_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/subtract_node.cpython-312.pyc
Normal file
Binary file not shown.
49
Nodes/array_node.py
Normal file
49
Nodes/array_node.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from NodeGraphQt import BaseNode
|
||||||
|
|
||||||
|
class ArrayNode(BaseNode):
|
||||||
|
"""
|
||||||
|
Array Node:
|
||||||
|
- Inputs: 'in' (value to store), 'ArraySize' (defines maximum length)
|
||||||
|
- Output: 'Array' (the current array as a string)
|
||||||
|
- Stores incoming values in an array with size defined by ArraySize.
|
||||||
|
When full, it removes the oldest value.
|
||||||
|
"""
|
||||||
|
__identifier__ = 'io.github.nicole.array'
|
||||||
|
NODE_NAME = 'Array'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(ArrayNode, self).__init__()
|
||||||
|
self.values = {} # Ensure values is a dictionary.
|
||||||
|
self.add_input('in')
|
||||||
|
self.add_input('ArraySize')
|
||||||
|
self.add_output('Array')
|
||||||
|
self.array = []
|
||||||
|
self.value = "[]" # Output as a string.
|
||||||
|
self.array_size = 10 # Default array size.
|
||||||
|
self.set_name("Array: []")
|
||||||
|
|
||||||
|
def process_input(self):
|
||||||
|
# Get array size from 'ArraySize' input if available.
|
||||||
|
size_port = self.input('ArraySize')
|
||||||
|
connected_size = size_port.connected_ports() if size_port is not None else []
|
||||||
|
if connected_size:
|
||||||
|
connected_port = connected_size[0]
|
||||||
|
parent_node = connected_port.node()
|
||||||
|
try:
|
||||||
|
self.array_size = int(float(getattr(parent_node, 'value', 10)))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
self.array_size = 10
|
||||||
|
|
||||||
|
# Get new value from 'in' input if available.
|
||||||
|
in_port = self.input('in')
|
||||||
|
connected_in = in_port.connected_ports() if in_port is not None else []
|
||||||
|
if connected_in:
|
||||||
|
connected_port = connected_in[0]
|
||||||
|
parent_node = connected_port.node()
|
||||||
|
new_value = getattr(parent_node, 'value', None)
|
||||||
|
if new_value is not None:
|
||||||
|
self.array.append(new_value)
|
||||||
|
while len(self.array) > self.array_size:
|
||||||
|
self.array.pop(0)
|
||||||
|
self.value = str(self.array)
|
||||||
|
self.set_name(f"Array: {self.value}")
|
36
Nodes/average_node.py
Normal file
36
Nodes/average_node.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from NodeGraphQt import BaseNode
|
||||||
|
|
||||||
|
class AverageNode(BaseNode):
|
||||||
|
"""
|
||||||
|
Average Node:
|
||||||
|
- Inputs: A, B, C (adjustable as needed)
|
||||||
|
- Output: Result (the average of the inputs)
|
||||||
|
"""
|
||||||
|
__identifier__ = 'io.github.nicole.average'
|
||||||
|
NODE_NAME = 'Average'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(AverageNode, self).__init__()
|
||||||
|
self.values = {} # Ensure values is a dictionary.
|
||||||
|
self.add_input('A')
|
||||||
|
self.add_input('B')
|
||||||
|
self.add_input('C')
|
||||||
|
self.add_output('Result')
|
||||||
|
self.value = 0
|
||||||
|
self.set_name("Average: 0")
|
||||||
|
|
||||||
|
def process_input(self):
|
||||||
|
values = []
|
||||||
|
for port_name in ['A', 'B', 'C']:
|
||||||
|
port = self.input(port_name)
|
||||||
|
connected = port.connected_ports() if port is not None else []
|
||||||
|
if connected:
|
||||||
|
connected_port = connected[0]
|
||||||
|
parent_node = connected_port.node()
|
||||||
|
try:
|
||||||
|
values.append(float(getattr(parent_node, 'value', 0)))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
avg = sum(values) / len(values) if values else 0
|
||||||
|
self.value = avg
|
||||||
|
self.set_name(f"Average: {avg}")
|
89
Nodes/character_status_node.py
Normal file
89
Nodes/character_status_node.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Character Status Node
|
||||||
|
|
||||||
|
This node represents the character's status. It has no input ports and four output ports:
|
||||||
|
- HP, MP, FP, EXP.
|
||||||
|
It polls an API endpoint (http://127.0.0.1:5000/data) every 500 ms to update its values.
|
||||||
|
If the API call is successful, the node's title is set to "Character Status (API Connected)".
|
||||||
|
If the API is down or returns an error, the title is set to "Character Status (API Disconnected)".
|
||||||
|
"""
|
||||||
|
|
||||||
|
from NodeGraphQt import BaseNode
|
||||||
|
from Qt import QtCore, QtGui
|
||||||
|
import requests
|
||||||
|
|
||||||
|
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 and then draws text (port label and current value)
|
||||||
|
next to it.
|
||||||
|
"""
|
||||||
|
if border_color is None:
|
||||||
|
border_color = color
|
||||||
|
|
||||||
|
def painter_func(painter, rect, info):
|
||||||
|
painter.save()
|
||||||
|
# Draw the port circle.
|
||||||
|
pen = QtGui.QPen(QtGui.QColor(*border_color))
|
||||||
|
pen.setWidth(1.8)
|
||||||
|
painter.setPen(pen)
|
||||||
|
semi_transparent_color = QtGui.QColor(color[0], color[1], color[2], alpha)
|
||||||
|
painter.setBrush(semi_transparent_color)
|
||||||
|
painter.drawEllipse(rect)
|
||||||
|
# Draw the label and current value.
|
||||||
|
port = info.get('port')
|
||||||
|
if port is not None:
|
||||||
|
node = port.node()
|
||||||
|
stat = port.name()
|
||||||
|
# Use the node's 'values' dictionary if available.
|
||||||
|
value = node.values.get(stat, "N/A") if hasattr(node, 'values') else "N/A"
|
||||||
|
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
|
||||||
|
|
||||||
|
class CharacterStatusNode(BaseNode):
|
||||||
|
__identifier__ = 'io.github.nicole.status'
|
||||||
|
NODE_NAME = 'Character Status'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(CharacterStatusNode, self).__init__()
|
||||||
|
# Initialize the output values as a dictionary.
|
||||||
|
self.values = {"HP": "N/A", "MP": "N/A", "FP": "N/A", "EXP": "N/A"}
|
||||||
|
# Add output ports for each stat with custom painters.
|
||||||
|
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 an initial title.
|
||||||
|
self.set_name("Character Status (API Disconnected)")
|
||||||
|
# Create a QTimer that polls the API every 500ms.
|
||||||
|
self.timer = QtCore.QTimer()
|
||||||
|
self.timer.timeout.connect(self.poll_api)
|
||||||
|
self.timer.start(500)
|
||||||
|
|
||||||
|
def poll_api(self):
|
||||||
|
"""
|
||||||
|
Polls the API endpoint to retrieve the latest character stats and updates
|
||||||
|
the node's internal values. Expects a JSON response with keys:
|
||||||
|
- "hp", "mp", "fp", "exp"
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = requests.get("http://127.0.0.1:5000/data", timeout=1)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
# Update the values dictionary.
|
||||||
|
self.values["HP"] = data.get("hp", "0/0")
|
||||||
|
self.values["MP"] = data.get("mp", "0/0")
|
||||||
|
self.values["FP"] = data.get("fp", "0/0")
|
||||||
|
self.values["EXP"] = data.get("exp", "0.0000")
|
||||||
|
self.set_name("Character Status (API Connected)")
|
||||||
|
self.update()
|
||||||
|
else:
|
||||||
|
self.set_name("Character Status (API Disconnected)")
|
||||||
|
except Exception as e:
|
||||||
|
self.set_name("Character Status (API Disconnected)")
|
||||||
|
print("Error polling API in CharacterStatusNode:", e)
|
62
Nodes/convert_to_percent_node.py
Normal file
62
Nodes/convert_to_percent_node.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Convert to Percent Node
|
||||||
|
|
||||||
|
This node takes an input string formatted as "numerator/denom" (e.g., "50/50"),
|
||||||
|
splits it into two numbers, computes (numerator / denom) * 100, and outputs the result
|
||||||
|
as a float formatted to 4 decimal places. If an error occurs, an error message is stored.
|
||||||
|
The node's title is always "Convert to Percent".
|
||||||
|
"""
|
||||||
|
|
||||||
|
from NodeGraphQt import BaseNode
|
||||||
|
from Qt import QtCore
|
||||||
|
|
||||||
|
class ConvertToPercentNode(BaseNode):
|
||||||
|
__identifier__ = 'io.github.nicole.convert'
|
||||||
|
NODE_NAME = 'Convert to Percent'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(ConvertToPercentNode, self).__init__()
|
||||||
|
# Add one input port (expects a string in "numerator/denom" format).
|
||||||
|
self.add_input("in")
|
||||||
|
# Add one output port.
|
||||||
|
self.add_output("Percent")
|
||||||
|
# Initialize internal value.
|
||||||
|
self.value = "No Input"
|
||||||
|
# Set the node title to a static string.
|
||||||
|
self.set_name(self.NODE_NAME)
|
||||||
|
# Initialize a values dictionary so that connected Display nodes can read output.
|
||||||
|
self.values = {}
|
||||||
|
|
||||||
|
def process_input(self):
|
||||||
|
input_port = self.input(0)
|
||||||
|
connected_ports = input_port.connected_ports() if input_port is not None else []
|
||||||
|
if connected_ports:
|
||||||
|
connected_output = connected_ports[0]
|
||||||
|
parent_node = connected_output.node()
|
||||||
|
port_name = connected_output.name()
|
||||||
|
# Use parent's values dictionary if available, else use its value attribute.
|
||||||
|
if hasattr(parent_node, 'values') and isinstance(parent_node.values, dict):
|
||||||
|
input_value = parent_node.values.get(port_name, "")
|
||||||
|
else:
|
||||||
|
input_value = getattr(parent_node, 'value', "")
|
||||||
|
input_str = str(input_value).strip()
|
||||||
|
try:
|
||||||
|
parts = input_str.split('/')
|
||||||
|
if len(parts) != 2:
|
||||||
|
raise ValueError("Input must be in the format 'num/denom'")
|
||||||
|
numerator = float(parts[0].strip())
|
||||||
|
denominator = float(parts[1].strip())
|
||||||
|
if denominator == 0:
|
||||||
|
raise ZeroDivisionError("Division by zero")
|
||||||
|
percent = (numerator / denominator) * 100
|
||||||
|
formatted_percent = f"{percent:.4f}"
|
||||||
|
self.value = formatted_percent
|
||||||
|
except Exception as e:
|
||||||
|
self.value = f"Error: {e}"
|
||||||
|
else:
|
||||||
|
self.value = "No Input"
|
||||||
|
# Always keep the title static.
|
||||||
|
self.set_name(self.NODE_NAME)
|
||||||
|
# Store the computed value in the values dictionary under the output port key.
|
||||||
|
self.values["Percent"] = self.value
|
113
Nodes/data_node.py
Normal file
113
Nodes/data_node.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Data Node:
|
||||||
|
- Input: Accepts a value (string, integer, or float) from an input port.
|
||||||
|
- Output: Outputs the current value, either from the input port or set manually via a text box.
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
- If both input and output are connected:
|
||||||
|
- Acts as a passthrough, displaying the input value and transmitting it to the output.
|
||||||
|
- Manual input is disabled.
|
||||||
|
- If only the output is connected:
|
||||||
|
- Allows manual value entry, which is sent to the output.
|
||||||
|
- If only the input is connected:
|
||||||
|
- Displays the input value but does not transmit it further.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from NodeGraphQt import BaseNode
|
||||||
|
|
||||||
|
class DataNode(BaseNode):
|
||||||
|
__identifier__ = 'io.github.nicole.data'
|
||||||
|
NODE_NAME = 'Data Node'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(DataNode, self).__init__()
|
||||||
|
# Add input and output ports.
|
||||||
|
self.add_input('Input')
|
||||||
|
self.add_output('Output')
|
||||||
|
# Add a text input widget for manual entry.
|
||||||
|
self.add_text_input('value', 'Value', text='')
|
||||||
|
# Initialize the value from the widget property.
|
||||||
|
self.process_widget_event()
|
||||||
|
self.set_name(f"Data Node: {self.value}")
|
||||||
|
|
||||||
|
def post_create(self):
|
||||||
|
"""
|
||||||
|
Called after the node's widget is fully created.
|
||||||
|
Connect the text input widget's textChanged signal to process_widget_event.
|
||||||
|
"""
|
||||||
|
text_widget = self.get_widget('value')
|
||||||
|
if text_widget is not None:
|
||||||
|
try:
|
||||||
|
# Connect the textChanged signal if available.
|
||||||
|
text_widget.textChanged.connect(self.process_widget_event)
|
||||||
|
except Exception as e:
|
||||||
|
print("Error connecting textChanged signal:", e)
|
||||||
|
|
||||||
|
def process_widget_event(self, event=None):
|
||||||
|
"""
|
||||||
|
Reads the current text from the node's property and updates the node's internal value.
|
||||||
|
"""
|
||||||
|
current_text = self.get_property('value')
|
||||||
|
self.value = current_text
|
||||||
|
self.set_name(f"Data Node: {self.value}")
|
||||||
|
|
||||||
|
def property_changed(self, property_name):
|
||||||
|
"""
|
||||||
|
Called when a node property changes. If the 'value' property changes,
|
||||||
|
update the internal value.
|
||||||
|
"""
|
||||||
|
if property_name == 'value':
|
||||||
|
self.process_widget_event()
|
||||||
|
|
||||||
|
def update_stream(self):
|
||||||
|
"""
|
||||||
|
Updates the node's behavior based on the connection states.
|
||||||
|
"""
|
||||||
|
input_port = self.input(0)
|
||||||
|
output_port = self.output(0)
|
||||||
|
|
||||||
|
if input_port.connected_ports() and output_port.connected_ports():
|
||||||
|
# Both input and output are connected; act as passthrough.
|
||||||
|
self.set_property('value', '')
|
||||||
|
self.get_widget('value').setEnabled(False)
|
||||||
|
input_value = input_port.connected_ports()[0].node().get_property('value')
|
||||||
|
self.set_property('value', input_value)
|
||||||
|
output_port.send_data(input_value)
|
||||||
|
elif output_port.connected_ports():
|
||||||
|
# Only output is connected; allow manual input.
|
||||||
|
self.get_widget('value').setEnabled(True)
|
||||||
|
output_port.send_data(self.get_property('value'))
|
||||||
|
elif input_port.connected_ports():
|
||||||
|
# Only input is connected; display input value.
|
||||||
|
self.get_widget('value').setEnabled(False)
|
||||||
|
input_value = input_port.connected_ports()[0].node().get_property('value')
|
||||||
|
self.set_property('value', input_value)
|
||||||
|
else:
|
||||||
|
# Neither input nor output is connected; allow manual input.
|
||||||
|
self.get_widget('value').setEnabled(True)
|
||||||
|
|
||||||
|
def on_input_connected(self, input_port, output_port):
|
||||||
|
"""
|
||||||
|
Called when an input port is connected.
|
||||||
|
"""
|
||||||
|
self.update_stream()
|
||||||
|
|
||||||
|
def on_input_disconnected(self, input_port, output_port):
|
||||||
|
"""
|
||||||
|
Called when an input port is disconnected.
|
||||||
|
"""
|
||||||
|
self.update_stream()
|
||||||
|
|
||||||
|
def on_output_connected(self, output_port, input_port):
|
||||||
|
"""
|
||||||
|
Called when an output port is connected.
|
||||||
|
"""
|
||||||
|
self.update_stream()
|
||||||
|
|
||||||
|
def on_output_disconnected(self, output_port, input_port):
|
||||||
|
"""
|
||||||
|
Called when an output port is disconnected.
|
||||||
|
"""
|
||||||
|
self.update_stream()
|
36
Nodes/multiply_node.py
Normal file
36
Nodes/multiply_node.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from NodeGraphQt import BaseNode
|
||||||
|
|
||||||
|
class MultiplyNode(BaseNode):
|
||||||
|
"""
|
||||||
|
Multiply Node:
|
||||||
|
- Inputs: A, B
|
||||||
|
- Output: Result (A * B)
|
||||||
|
"""
|
||||||
|
__identifier__ = 'io.github.nicole.multiply'
|
||||||
|
NODE_NAME = 'Multiply'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(MultiplyNode, self).__init__()
|
||||||
|
self.values = {} # Ensure values is a dictionary.
|
||||||
|
self.add_input('A')
|
||||||
|
self.add_input('B')
|
||||||
|
self.add_output('Result')
|
||||||
|
self.value = 0
|
||||||
|
|
||||||
|
def process_input(self):
|
||||||
|
inputs = {}
|
||||||
|
for port_name in ['A', 'B']:
|
||||||
|
port = self.input(port_name)
|
||||||
|
connected = port.connected_ports() if port is not None else []
|
||||||
|
if connected:
|
||||||
|
connected_port = connected[0]
|
||||||
|
parent_node = connected_port.node()
|
||||||
|
try:
|
||||||
|
inputs[port_name] = float(getattr(parent_node, 'value', 0))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
inputs[port_name] = 0.0
|
||||||
|
else:
|
||||||
|
inputs[port_name] = 0.0
|
||||||
|
result = inputs['A'] * inputs['B']
|
||||||
|
self.value = result
|
||||||
|
self.set_name(f"Multiply: {result}")
|
37
Nodes/subtract_node.py
Normal file
37
Nodes/subtract_node.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Nodes/subtract_node.py
|
||||||
|
|
||||||
|
from NodeGraphQt import BaseNode
|
||||||
|
|
||||||
|
class SubtractNode(BaseNode):
|
||||||
|
"""
|
||||||
|
Subtract Node:
|
||||||
|
- Inputs: A, B
|
||||||
|
- Output: Result (A - B)
|
||||||
|
"""
|
||||||
|
__identifier__ = 'io.github.nicole.subtract'
|
||||||
|
NODE_NAME = 'Subtract'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(SubtractNode, self).__init__()
|
||||||
|
self.add_input('A')
|
||||||
|
self.add_input('B')
|
||||||
|
self.add_output('Result')
|
||||||
|
self.value = 0
|
||||||
|
|
||||||
|
def process_input(self):
|
||||||
|
inputs = {}
|
||||||
|
for port_name in ['A', 'B']:
|
||||||
|
port = self.input(port_name)
|
||||||
|
connected = port.connected_ports() if port is not None else []
|
||||||
|
if connected:
|
||||||
|
connected_port = connected[0]
|
||||||
|
parent_node = connected_port.node()
|
||||||
|
try:
|
||||||
|
inputs[port_name] = float(getattr(parent_node, 'value', 0))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
inputs[port_name] = 0.0
|
||||||
|
else:
|
||||||
|
inputs[port_name] = 0.0
|
||||||
|
result = inputs['A'] - inputs['B']
|
||||||
|
self.value = result
|
||||||
|
self.set_name(f"Subtract: {result}")
|
304
flow_UI.py
304
flow_UI.py
@ -1,239 +1,117 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Modified Flow UI Application:
|
Main Application (flow_UI.py)
|
||||||
- The character node is now named "Character Status" (display title).
|
|
||||||
- DisplayValueNodes dynamically update their title to show the value from the connected output port.
|
This file dynamically imports custom node classes from the 'Nodes' package,
|
||||||
- Newly spawned DisplayValueNodes (via the context menu) follow the same dynamic behavior.
|
registers them with NodeGraphQt, and sets up an empty graph.
|
||||||
- Node creation strings now include the class name to match NodeGraphQt requirements.
|
Nodes can be added dynamically via the graph’s right-click context menu,
|
||||||
|
and a "Remove Selected Node" option is provided.
|
||||||
|
A global update timer periodically calls process_input() on nodes.
|
||||||
|
Additionally, this file patches QGraphicsScene.setSelectionArea to handle
|
||||||
|
selection behavior properly (so that multiple nodes can be selected).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
# --- Patch QGraphicsScene.setSelectionArea to handle selection arguments ---
|
||||||
import requests
|
|
||||||
from Qt import QtWidgets, QtCore, QtGui
|
from Qt import QtWidgets, QtCore, QtGui
|
||||||
|
|
||||||
|
_original_setSelectionArea = QtWidgets.QGraphicsScene.setSelectionArea
|
||||||
|
|
||||||
|
def _patched_setSelectionArea(self, painterPath, second_arg, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
# Try calling the original method with the provided arguments.
|
||||||
|
return _original_setSelectionArea(self, painterPath, second_arg, *args, **kwargs)
|
||||||
|
except TypeError as e:
|
||||||
|
# If a TypeError is raised, assume the call was made with only a QPainterPath
|
||||||
|
# and an ItemSelectionMode, and patch it by supplying defaults.
|
||||||
|
# Default operation: ReplaceSelection, default transform: QTransform()
|
||||||
|
return _original_setSelectionArea(self, painterPath,
|
||||||
|
QtCore.Qt.ReplaceSelection,
|
||||||
|
second_arg,
|
||||||
|
QtGui.QTransform())
|
||||||
|
|
||||||
|
# Monkey-patch the setSelectionArea method.
|
||||||
|
QtWidgets.QGraphicsScene.setSelectionArea = _patched_setSelectionArea
|
||||||
|
|
||||||
|
# --- End of patch section ---
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import pkgutil
|
||||||
|
import importlib
|
||||||
|
import inspect
|
||||||
|
from Qt import QtWidgets, QtCore
|
||||||
from NodeGraphQt import NodeGraph, BaseNode
|
from NodeGraphQt import NodeGraph, BaseNode
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
def import_nodes_from_folder(package_name):
|
||||||
# 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,
|
Dynamically import all modules from the given package and return a list of
|
||||||
then draws text (port label and current value) next to it.
|
classes that subclass BaseNode.
|
||||||
|
|
||||||
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:
|
imported_nodes = []
|
||||||
border_color = color
|
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 painter_func(painter, rect, info):
|
def make_node_command(graph, nt):
|
||||||
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
|
Given a NodeGraph instance and a node type string nt, return a command
|
||||||
|
function that creates a node of that type.
|
||||||
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'
|
def command():
|
||||||
NODE_NAME = 'Character Status'
|
try:
|
||||||
|
node = graph.create_node(nt)
|
||||||
|
# (No need to force top-level if nodes are created as top-level by default.)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating node of type {nt}: {e}")
|
||||||
|
return command
|
||||||
|
|
||||||
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__':
|
if __name__ == '__main__':
|
||||||
app = QtWidgets.QApplication([])
|
app = QtWidgets.QApplication([])
|
||||||
|
|
||||||
# --- Section: Setup NodeGraphQT ---
|
# Create the NodeGraph controller.
|
||||||
graph = NodeGraph()
|
graph = NodeGraph()
|
||||||
graph.widget.setWindowTitle("Project Borealis - Flyff Information Overlay")
|
graph.widget.setWindowTitle("Modular Nodes Demo")
|
||||||
# Register nodes using their classes.
|
|
||||||
graph.register_node(CharacterStatusNode)
|
|
||||||
graph.register_node(DisplayValueNode)
|
|
||||||
|
|
||||||
# --- Section: Setup Context Menu for Adding DisplayValueNodes ---
|
# 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)
|
||||||
|
|
||||||
|
# Add context menu commands for dynamic node creation.
|
||||||
graph_context_menu = graph.get_context_menu('graph')
|
graph_context_menu = graph.get_context_menu('graph')
|
||||||
|
for node_class in custom_nodes:
|
||||||
|
# Build the node type string: "<__identifier__>.<ClassName>"
|
||||||
|
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.
|
||||||
graph_context_menu.add_command(
|
graph_context_menu.add_command(
|
||||||
'Add Display Value Node',
|
"Remove Selected Node",
|
||||||
lambda: add_display_node(graph),
|
lambda: [graph.remove_node(node) for node in graph.selected_nodes()] if graph.selected_nodes() else None
|
||||||
shortcut='Ctrl+D'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Section: Create and Connect Nodes ---
|
# Resize and show the graph widget.
|
||||||
# Create the Character Status node.
|
graph.widget.resize(1200, 800)
|
||||||
character_status_node = graph.create_node('io.github.nicole.status.CharacterStatusNode',
|
graph.widget.show()
|
||||||
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 ---
|
# Global update timer:
|
||||||
|
# - Call process_input() on every node that implements it.
|
||||||
|
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 = QtCore.QTimer()
|
||||||
timer.timeout.connect(lambda: update_nodes(graph, character_status_node))
|
timer.timeout.connect(global_update)
|
||||||
timer.start(500)
|
timer.start(500)
|
||||||
|
|
||||||
graph.widget.resize(1000, 600)
|
sys.exit(app.exec())
|
||||||
graph.widget.show()
|
|
||||||
app.exec_()
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user