Restructured project and implemented virtual python environments to isolate application. Added launch scripts too.

This commit is contained in:
2025-02-27 19:38:25 -07:00
parent a2c0080662
commit 17b99ca836
76 changed files with 264 additions and 5 deletions

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python3
from OdenGraphQt import BaseNode
from Qt import QtCore
class BlueprintNode(BaseNode):
"""
A placeholder node used to preview placement before spawning
the real node. It has a distinct color and minimal UI.
"""
__identifier__ = 'bunny-lab.io.blueprint'
NODE_NAME = 'Blueprint Node'
def __init__(self):
super(BlueprintNode, self).__init__()
# Display a name so the user sees "Click to Place Node"
self.set_name("Click to Place Node")
# Give it a bluish color + white text, for visibility
self.set_color(60, 120, 220) # R, G, B
self.view.text_color = (255, 255, 255, 200)
self.view.border_color = (255, 255, 255, 180)
# Make it slightly transparent if desired (alpha=150)
self.view._bg_color = (60, 120, 220, 150)
# Remove any default inputs/outputs (make it minimal)
for port in self.input_ports() + self.output_ports():
self.model.delete_port(port.name(), port.port_type)
# Store the "actual node" we want to spawn
self.create_property("actual_node_type", "", widget_type=0)
def process_input(self):
"""
We do nothing here; it is purely a placeholder node.
"""
pass

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""
Flyff EXP Node (Final Combined Version)
- Pulls the EXP value directly from data_manager.py
- Outputs only the "exp" value as a string
- Uses color (48, 116, 143) for its output port
- Displays "exp" in a text field labeled "Value"
- Retrieves the port with self.outputs().get('value')
"""
import time
import traceback
from OdenGraphQt import BaseNode
from Modules import data_manager # Importing data_manager from Modules
class FlyffEXPCurrentNode(BaseNode):
__identifier__ = 'bunny-lab.io.flyff_exp_current_node'
NODE_NAME = 'Flyff - EXP'
def __init__(self):
super(FlyffEXPCurrentNode, self).__init__()
# 1) Text input property named "value" for UI display
self.add_text_input('value', 'Value', text='N/A')
# 2) Output port also named "value"
self.add_output('value', color=(48, 116, 143))
self.set_name("Flyff - EXP")
def process_input(self):
try:
new_value = data_manager.get_data().get("exp", "N/A")
new_value_str = str(new_value)
self.set_property('value', new_value_str)
self.transmit_data(new_value_str)
except Exception as e:
tb = traceback.format_exc()
print(f"[ERROR] Exception in FlyffEXPCurrentNode: {e}\nTraceback:\n{tb}")
def transmit_data(self, data):
output_port = self.outputs().get('value')
if output_port and output_port.connected_ports():
for connected_port in output_port.connected_ports():
connected_node = connected_port.node()
if hasattr(connected_node, 'receive_data'):
try:
connected_node.receive_data(data, source_port_name='value')
except Exception as e:
print(f"[ERROR] Error transmitting data to {connected_node}: {e}")

View File

@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""
Flyff FP Current Node (Final Combined Version)
- Polls the API at http://127.0.0.1:5000/data
- Outputs only the "fp_current" value as a string
- Uses color (36, 116, 32) for its output port
- Displays "fp_current" in a text field labeled "Value"
- Retrieves the port with self.outputs().get('value')
"""
import time
import requests
import traceback
from OdenGraphQt import BaseNode
class FlyffFPCurrentNode(BaseNode):
__identifier__ = 'bunny-lab.io.flyff_fp_current_node'
NODE_NAME = 'Flyff - FP Current'
def __init__(self):
super(FlyffFPCurrentNode, self).__init__()
# 1) Text input property named "value" for UI display
self.add_text_input('value', 'Value', text='N/A')
# 2) Output port also named "value"
self.add_output('value', color=(36, 116, 32))
self._api_down = True
self._last_api_attempt = 0.0
self._retry_interval = 5.0
self._last_error_printed = 0.0
self.set_name("Flyff - FP Current (API Disconnected)")
def process_input(self):
current_time = time.time()
if self._api_down and (current_time - self._last_api_attempt < self._retry_interval):
return
self._last_api_attempt = current_time
try:
response = requests.get("http://127.0.0.1:5000/data", timeout=1)
status_code = response.status_code
print(f"[DEBUG] FlyffFPCurrentNode: HTTP Status Code = {status_code}")
if status_code == 200:
try:
data = response.json() or {}
except ValueError:
data = {}
if isinstance(data, list):
data = {}
self._api_down = False
self.set_name("Flyff - FP Current (API Connected)")
new_value = data.get("fp_current", "N/A")
print(f"[DEBUG] FlyffFPCurrentNode: fp_current = {new_value}")
new_value_str = str(new_value)
self.set_property('value', new_value_str)
self.transmit_data(new_value_str)
else:
self._handle_api_error(f"HTTP {status_code} from FlyffFPCurrentNode")
self._api_down = True
except Exception as e:
tb = traceback.format_exc()
self._handle_api_error(f"Exception in FlyffFPCurrentNode: {e}\nTraceback:\n{tb}")
self._api_down = True
def transmit_data(self, data):
output_port = self.outputs().get('value')
if output_port and output_port.connected_ports():
for connected_port in output_port.connected_ports():
connected_node = connected_port.node()
if hasattr(connected_node, 'receive_data'):
try:
connected_node.receive_data(data, source_port_name='value')
except Exception as e:
print(f"[ERROR] Error transmitting data to {connected_node}: {e}")
def _handle_api_error(self, msg):
current_time = time.time()
if (current_time - self._last_error_printed) >= self._retry_interval:
print(f"[ERROR] {msg}")
self._last_error_printed = current_time
self.set_name("Flyff - FP Current (API Disconnected)")

View File

@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""
Flyff FP Total Node (Final Combined Version)
- Polls the API at http://127.0.0.1:5000/data
- Outputs only the "fp_total" value as a string
- Uses color (36, 116, 32) for its output port
- Displays "fp_total" in a text field labeled "Value"
- Retrieves the port with self.outputs().get('value')
"""
import time
import requests
import traceback
from OdenGraphQt import BaseNode
class FlyffFPTotalNode(BaseNode):
__identifier__ = 'bunny-lab.io.flyff_fp_total_node'
NODE_NAME = 'Flyff - FP Total'
def __init__(self):
super(FlyffFPTotalNode, self).__init__()
# 1) Text input property named "value" for UI display
self.add_text_input('value', 'Value', text='N/A')
# 2) Output port also named "value"
self.add_output('value', color=(36, 116, 32))
self._api_down = True
self._last_api_attempt = 0.0
self._retry_interval = 5.0
self._last_error_printed = 0.0
self.set_name("Flyff - FP Total (API Disconnected)")
def process_input(self):
current_time = time.time()
if self._api_down and (current_time - self._last_api_attempt < self._retry_interval):
return
self._last_api_attempt = current_time
try:
response = requests.get("http://127.0.0.1:5000/data", timeout=1)
status_code = response.status_code
print(f"[DEBUG] FlyffFPTotalNode: HTTP Status Code = {status_code}")
if status_code == 200:
try:
data = response.json() or {}
except ValueError:
data = {}
if isinstance(data, list):
data = {}
self._api_down = False
self.set_name("Flyff - FP Total (API Connected)")
new_value = data.get("fp_total", "N/A")
print(f"[DEBUG] FlyffFPTotalNode: fp_total = {new_value}")
new_value_str = str(new_value)
self.set_property('value', new_value_str)
self.transmit_data(new_value_str)
else:
self._handle_api_error(f"HTTP {status_code} from FlyffFPTotalNode")
self._api_down = True
except Exception as e:
tb = traceback.format_exc()
self._handle_api_error(f"Exception in FlyffFPTotalNode: {e}\nTraceback:\n{tb}")
self._api_down = True
def transmit_data(self, data):
output_port = self.outputs().get('value')
if output_port and output_port.connected_ports():
for connected_port in output_port.connected_ports():
connected_node = connected_port.node()
if hasattr(connected_node, 'receive_data'):
try:
connected_node.receive_data(data, source_port_name='value')
except Exception as e:
print(f"[ERROR] Error transmitting data to {connected_node}: {e}")
def _handle_api_error(self, msg):
current_time = time.time()
if (current_time - self._last_error_printed) >= self._retry_interval:
print(f"[ERROR] {msg}")
self._last_error_printed = current_time
self.set_name("Flyff - FP Total (API Disconnected)")

View File

@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""
Flyff HP Current Node (Final Combined Version)
- Polls the API at http://127.0.0.1:5000/data
- Outputs only the "hp_current" value as a string
- Uses color (126, 36, 57) for its output port
- Displays "hp_current" in a text field labeled "Value"
- Avoids "list indices must be integers" by retrieving the port with self.outputs().get('value')
"""
import time
import requests
import traceback
from OdenGraphQt import BaseNode
class FlyffHPCurrentNode(BaseNode):
__identifier__ = 'bunny-lab.io.flyff_hp_current_node'
NODE_NAME = 'Flyff - HP Current'
def __init__(self):
super(FlyffHPCurrentNode, self).__init__()
# 1) Add a text input property named "value" for UI display
self.add_text_input('value', 'Value', text='N/A')
# 2) Add an output port also named "value"
self.add_output('value', color=(126, 36, 57))
# Start in "disconnected" state
self._api_down = True
self._last_api_attempt = 0.0
self._retry_interval = 5.0
self._last_error_printed = 0.0
# Default node title
self.set_name("Flyff - HP Current (API Disconnected)")
def process_input(self):
"""
Called periodically by the global timer in borealis.py
"""
current_time = time.time()
if self._api_down and (current_time - self._last_api_attempt < self._retry_interval):
return
self._last_api_attempt = current_time
try:
response = requests.get("http://127.0.0.1:5000/data", timeout=1)
status_code = response.status_code
print(f"[DEBUG] FlyffHPCurrentNode: HTTP Status Code = {status_code}")
if status_code == 200:
# Attempt to parse JSON
try:
data = response.json() or {}
except ValueError:
data = {}
# If data is a list, ignore or convert to {}
if isinstance(data, list):
data = {}
# Mark node as connected
self._api_down = False
self.set_name("Flyff - HP Current (API Connected)")
# Retrieve hp_current (default "N/A" if missing)
new_value = data.get("hp_current", "N/A")
print(f"[DEBUG] FlyffHPCurrentNode: hp_current = {new_value}")
# Convert to string
new_value_str = str(new_value)
# 3) Update the text input property so the user sees it
self.set_property('value', new_value_str)
# 4) Transmit to downstream nodes
self.transmit_data(new_value_str)
else:
# Non-200 => disconnected
self._handle_api_error(f"HTTP {status_code} from FlyffHPCurrentNode")
self._api_down = True
except Exception as e:
tb = traceback.format_exc()
self._handle_api_error(f"Exception in FlyffHPCurrentNode: {e}\nTraceback:\n{tb}")
self._api_down = True
def transmit_data(self, data):
"""
Sends 'data' to any connected node via the "value" port.
(Uses self.outputs().get('value') instead of self.output('value'))
"""
output_port = self.outputs().get('value')
if output_port and output_port.connected_ports():
for connected_port in output_port.connected_ports():
connected_node = connected_port.node()
if hasattr(connected_node, 'receive_data'):
try:
connected_node.receive_data(data, source_port_name='value')
except Exception as e:
print(f"[ERROR] Error transmitting data to {connected_node}: {e}")
def _handle_api_error(self, msg):
current_time = time.time()
if (current_time - self._last_error_printed) >= self._retry_interval:
print(f"[ERROR] {msg}")
self._last_error_printed = current_time
self.set_name("Flyff - HP Current (API Disconnected)")

View File

@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""
Flyff HP Total Node (Final Combined Version)
- Polls the API at http://127.0.0.1:5000/data
- Outputs only the "hp_total" value as a string
- Uses color (126, 36, 57) for its output port
- Displays "hp_total" in a text field labeled "Value"
- Retrieves the port with self.outputs().get('value')
"""
import time
import requests
import traceback
from OdenGraphQt import BaseNode
class FlyffHPTotalNode(BaseNode):
__identifier__ = 'bunny-lab.io.flyff_hp_total_node'
NODE_NAME = 'Flyff - HP Total'
def __init__(self):
super(FlyffHPTotalNode, self).__init__()
# 1) Text input property named "value" for UI display
self.add_text_input('value', 'Value', text='N/A')
# 2) Output port also named "value"
self.add_output('value', color=(126, 36, 57))
self._api_down = True
self._last_api_attempt = 0.0
self._retry_interval = 5.0
self._last_error_printed = 0.0
self.set_name("Flyff - HP Total (API Disconnected)")
def process_input(self):
current_time = time.time()
if self._api_down and (current_time - self._last_api_attempt < self._retry_interval):
return
self._last_api_attempt = current_time
try:
response = requests.get("http://127.0.0.1:5000/data", timeout=1)
status_code = response.status_code
print(f"[DEBUG] FlyffHPTotalNode: HTTP Status Code = {status_code}")
if status_code == 200:
try:
data = response.json() or {}
except ValueError:
data = {}
if isinstance(data, list):
data = {}
self._api_down = False
self.set_name("Flyff - HP Total (API Connected)")
new_value = data.get("hp_total", "N/A")
print(f"[DEBUG] FlyffHPTotalNode: hp_total = {new_value}")
new_value_str = str(new_value)
self.set_property('value', new_value_str)
self.transmit_data(new_value_str)
else:
self._handle_api_error(f"HTTP {status_code} from FlyffHPTotalNode")
self._api_down = True
except Exception as e:
tb = traceback.format_exc()
self._handle_api_error(f"Exception in FlyffHPTotalNode: {e}\nTraceback:\n{tb}")
self._api_down = True
def transmit_data(self, data):
output_port = self.outputs().get('value')
if output_port and output_port.connected_ports():
for connected_port in output_port.connected_ports():
connected_node = connected_port.node()
if hasattr(connected_node, 'receive_data'):
try:
connected_node.receive_data(data, source_port_name='value')
except Exception as e:
print(f"[ERROR] Error transmitting data to {connected_node}: {e}")
def _handle_api_error(self, msg):
current_time = time.time()
if (current_time - self._last_error_printed) >= self._retry_interval:
print(f"[ERROR] {msg}")
self._last_error_printed = current_time
self.set_name("Flyff - HP Total (API Disconnected)")

View File

@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""
Flyff MP Current Node (Final Combined Version)
- Polls the API at http://127.0.0.1:5000/data
- Outputs only the "mp_current" value as a string
- Uses color (35, 89, 144) for its output port
- Displays "mp_current" in a text field labeled "Value"
- Retrieves the port with self.outputs().get('value')
"""
import time
import requests
import traceback
from OdenGraphQt import BaseNode
class FlyffMPCurrentNode(BaseNode):
__identifier__ = 'bunny-lab.io.flyff_mp_current_node'
NODE_NAME = 'Flyff - MP Current'
def __init__(self):
super(FlyffMPCurrentNode, self).__init__()
# 1) Text input property named "value" for UI display
self.add_text_input('value', 'Value', text='N/A')
# 2) Output port also named "value"
self.add_output('value', color=(35, 89, 144))
self._api_down = True
self._last_api_attempt = 0.0
self._retry_interval = 5.0
self._last_error_printed = 0.0
self.set_name("Flyff - MP Current (API Disconnected)")
def process_input(self):
current_time = time.time()
if self._api_down and (current_time - self._last_api_attempt < self._retry_interval):
return
self._last_api_attempt = current_time
try:
response = requests.get("http://127.0.0.1:5000/data", timeout=1)
status_code = response.status_code
print(f"[DEBUG] FlyffMPCurrentNode: HTTP Status Code = {status_code}")
if status_code == 200:
try:
data = response.json() or {}
except ValueError:
data = {}
if isinstance(data, list):
data = {}
self._api_down = False
self.set_name("Flyff - MP Current (API Connected)")
new_value = data.get("mp_current", "N/A")
print(f"[DEBUG] FlyffMPCurrentNode: mp_current = {new_value}")
new_value_str = str(new_value)
self.set_property('value', new_value_str)
self.transmit_data(new_value_str)
else:
self._handle_api_error(f"HTTP {status_code} from FlyffMPCurrentNode")
self._api_down = True
except Exception as e:
tb = traceback.format_exc()
self._handle_api_error(f"Exception in FlyffMPCurrentNode: {e}\nTraceback:\n{tb}")
self._api_down = True
def transmit_data(self, data):
output_port = self.outputs().get('value')
if output_port and output_port.connected_ports():
for connected_port in output_port.connected_ports():
connected_node = connected_port.node()
if hasattr(connected_node, 'receive_data'):
try:
connected_node.receive_data(data, source_port_name='value')
except Exception as e:
print(f"[ERROR] Error transmitting data to {connected_node}: {e}")
def _handle_api_error(self, msg):
current_time = time.time()
if (current_time - self._last_error_printed) >= self._retry_interval:
print(f"[ERROR] {msg}")
self._last_error_printed = current_time
self.set_name("Flyff - MP Current (API Disconnected)")

View File

@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""
Flyff MP Total Node (Final Combined Version)
- Polls the API at http://127.0.0.1:5000/data
- Outputs only the "mp_total" value as a string
- Uses color (35, 89, 144) for its output port
- Displays "mp_total" in a text field labeled "Value"
- Retrieves the port with self.outputs().get('value')
"""
import time
import requests
import traceback
from OdenGraphQt import BaseNode
class FlyffMPTotalNode(BaseNode):
__identifier__ = 'bunny-lab.io.flyff_mp_total_node'
NODE_NAME = 'Flyff - MP Total'
def __init__(self):
super(FlyffMPTotalNode, self).__init__()
# 1) Text input property named "value" for UI display
self.add_text_input('value', 'Value', text='N/A')
# 2) Output port also named "value"
self.add_output('value', color=(35, 89, 144))
self._api_down = True
self._last_api_attempt = 0.0
self._retry_interval = 5.0
self._last_error_printed = 0.0
self.set_name("Flyff - MP Total (API Disconnected)")
def process_input(self):
current_time = time.time()
if self._api_down and (current_time - self._last_api_attempt < self._retry_interval):
return
self._last_api_attempt = current_time
try:
response = requests.get("http://127.0.0.1:5000/data", timeout=1)
status_code = response.status_code
print(f"[DEBUG] FlyffMPTotalNode: HTTP Status Code = {status_code}")
if status_code == 200:
try:
data = response.json() or {}
except ValueError:
data = {}
if isinstance(data, list):
data = {}
self._api_down = False
self.set_name("Flyff - MP Total (API Connected)")
new_value = data.get("mp_total", "N/A")
print(f"[DEBUG] FlyffMPTotalNode: mp_total = {new_value}")
new_value_str = str(new_value)
self.set_property('value', new_value_str)
self.transmit_data(new_value_str)
else:
self._handle_api_error(f"HTTP {status_code} from FlyffMPTotalNode")
self._api_down = True
except Exception as e:
tb = traceback.format_exc()
self._handle_api_error(f"Exception in FlyffMPTotalNode: {e}\nTraceback:\n{tb}")
self._api_down = True
def transmit_data(self, data):
output_port = self.outputs().get('value')
if output_port and output_port.connected_ports():
for connected_port in output_port.connected_ports():
connected_node = connected_port.node()
if hasattr(connected_node, 'receive_data'):
try:
connected_node.receive_data(data, source_port_name='value')
except Exception as e:
print(f"[ERROR] Error transmitting data to {connected_node}: {e}")
def _handle_api_error(self, msg):
current_time = time.time()
if (current_time - self._last_error_printed) >= self._retry_interval:
print(f"[ERROR] {msg}")
self._last_error_printed = current_time
self.set_name("Flyff - MP Total (API Disconnected)")

View File

@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""
Flyff Character Status Node:
- Creates an OCR region in data_collector.
- Periodically grabs raw text from that region and updates status.
"""
import re
from OdenGraphQt import BaseNode
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtCore import QTimer # Corrected import
from Modules import data_manager, data_collector
class FlyffCharacterStatusNode(BaseNode):
__identifier__ = "bunny-lab.io.flyff_character_status_node"
NODE_NAME = "Flyff - Character Status"
def __init__(self):
super(FlyffCharacterStatusNode, self).__init__()
if data_manager.character_status_collector_exists:
QMessageBox.critical(None, "Error", "Only one Flyff Character Status Collector node is allowed.")
raise Exception("Duplicate Character Status Node.")
data_manager.character_status_collector_exists = True
self.add_text_input("hp", "HP", text="HP: 0/0")
self.add_text_input("mp", "MP", text="MP: 0/0")
self.add_text_input("fp", "FP", text="FP: 0/0")
self.add_text_input("exp", "EXP", text="EXP: 0%")
self.region_id = "character_status"
data_collector.create_ocr_region(self.region_id, x=250, y=50, w=180, h=130, color=(255, 255, 0), thickness=2)
data_collector.start_collector()
self.set_name("Flyff - Character Status")
# Set up a timer to periodically update character stats
self.timer = QTimer()
self.timer.timeout.connect(self.process_input)
self.timer.start(1000) # Update every second
def parse_character_stats(self, raw_text):
"""
Extract HP, MP, FP, EXP from the raw OCR text lines.
"""
lines = [l.strip() for l in raw_text.splitlines() if l.strip()]
hp_current, hp_total = 0, 0
mp_current, mp_total = 0, 0
fp_current, fp_total = 0, 0
exp_value = 0.0
if len(lines) >= 4:
# line 1: HP
hp_match = re.search(r"(\d+)\s*/\s*(\d+)", lines[0])
if hp_match:
hp_current = int(hp_match.group(1))
hp_total = int(hp_match.group(2))
# line 2: MP
mp_match = re.search(r"(\d+)\s*/\s*(\d+)", lines[1])
if mp_match:
mp_current = int(mp_match.group(1))
mp_total = int(mp_match.group(2))
# line 3: FP
fp_match = re.search(r"(\d+)\s*/\s*(\d+)", lines[2])
if fp_match:
fp_current = int(fp_match.group(1))
fp_total = int(fp_match.group(2))
# line 4: EXP
exp_match = re.search(r"(\d+(?:\.\d+)?)", lines[3])
if exp_match:
val = float(exp_match.group(1))
if val < 0: val = 0
if val > 100: val = 100
exp_value = val
return hp_current, hp_total, mp_current, mp_total, fp_current, fp_total, exp_value
def process_input(self):
"""
Called periodically to update character status from OCR.
"""
raw_text = data_collector.get_raw_text(self.region_id)
# print("Raw OCR Text:", raw_text) # Debugging OCR text reading
hp_c, hp_t, mp_c, mp_t, fp_c, fp_t, exp_v = self.parse_character_stats(raw_text)
# Update the data manager with the parsed values
data_manager.set_data_bulk({
"hp_current": hp_c,
"hp_total": hp_t,
"mp_current": mp_c,
"mp_total": mp_t,
"fp_current": fp_c,
"fp_total": fp_t,
"exp": exp_v
})
# Update the node's UI text fields
self.set_property("hp", f"HP: {hp_c}/{hp_t}")
self.set_property("mp", f"MP: {mp_c}/{mp_t}")
self.set_property("fp", f"FP: {fp_c}/{fp_t}")
self.set_property("exp", f"EXP: {exp_v}%")

View File

@ -0,0 +1,141 @@
#!/usr/bin/env python3
"""
Flyff - Leveling Predictor Node:
- Tracks the last N changes in EXP values.
- Calculates the average change rate and time intervals.
- Predicts the estimated time to reach level 100.
"""
import time
import numpy as np
from OdenGraphQt import BaseNode
from PyQt5.QtCore import QTimer
from Modules import data_manager
class FlyffLevelingPredictorNode(BaseNode):
__identifier__ = "bunny-lab.io.flyff_leveling_predictor_node"
NODE_NAME = "Flyff - Leveling Predictor"
def __init__(self):
super(FlyffLevelingPredictorNode, self).__init__()
# Input port for EXP values
self.add_input("exp", "EXP")
# User-defined number of changes to track
self.add_text_input("exp_track_count", "# of EXP Changes to Track", text="7")
# Output widgets
self.add_text_input("time_to_level", "Time to Level", text="Calculating...")
self.add_text_input("time_between_kills", "Time Between Kills", text="N/A")
self.add_text_input("exp_per_kill", "EXP Per Kill", text="N/A")
# Internal tracking lists
self.exp_history = []
self.time_intervals = []
self.last_exp_value = None
self.last_update_time = None
# Timer to periodically process EXP changes
self.timer = QTimer()
self.timer.timeout.connect(self.process_exp_change)
self.timer.start(1000) # Check for updates every second
def reset_tracking_arrays(self):
"""
Resets the EXP history and time interval arrays when a level-up is detected.
"""
self.exp_history.clear()
self.time_intervals.clear()
self.last_exp_value = None
self.last_update_time = None
def process_exp_change(self):
"""
Monitors changes in EXP values and calculates various statistics.
"""
exp_value = data_manager.get_data().get("exp", None)
if exp_value is None:
return
exp_track_count = self.get_property("exp_track_count")
try:
exp_track_count = int(exp_track_count)
except ValueError:
exp_track_count = 7 # Default to 7 if invalid input
# Reset if EXP value decreases (indicating a level-up)
if self.last_exp_value is not None and exp_value < self.last_exp_value:
self.reset_tracking_arrays()
if self.last_exp_value is not None and exp_value != self.last_exp_value:
current_time = time.time()
# Store EXP change history
self.exp_history.append(exp_value)
if len(self.exp_history) > exp_track_count:
self.exp_history.pop(0)
# Store time intervals
if self.last_update_time is not None:
interval = current_time - self.last_update_time
self.time_intervals.append(interval)
if len(self.time_intervals) > exp_track_count:
self.time_intervals.pop(0)
# Perform calculations
self.calculate_time_to_level()
self.calculate_additional_metrics()
# Update last tracking values
self.last_update_time = current_time
self.last_exp_value = exp_value
def calculate_time_to_level(self):
"""
Calculates the estimated time to reach level 100 based on EXP change history.
"""
if len(self.exp_history) < 2 or len(self.time_intervals) < 1:
self.set_property("time_to_level", "Insufficient data")
return
exp_deltas = np.diff(self.exp_history) # Compute EXP change per interval
avg_exp_change = np.mean(exp_deltas) if len(exp_deltas) > 0 else 0
avg_time_change = np.mean(self.time_intervals)
if avg_exp_change <= 0:
self.set_property("time_to_level", "Not gaining EXP")
return
current_exp = self.exp_history[-1]
remaining_exp = 100.0 - current_exp # Distance to level 100
estimated_time = (remaining_exp / avg_exp_change) * avg_time_change
# Convert estimated time into hours, minutes, and seconds
hours = int(estimated_time // 3600)
minutes = int((estimated_time % 3600) // 60)
seconds = int(estimated_time % 60)
time_str = f"{hours}h {minutes}m {seconds}s"
self.set_property("time_to_level", time_str)
def calculate_additional_metrics(self):
"""
Calculates and updates the "Time Between Kills" and "EXP Per Kill".
"""
if len(self.time_intervals) > 0:
avg_time_between_kills = np.mean(self.time_intervals)
minutes = int(avg_time_between_kills // 60)
seconds = int(avg_time_between_kills % 60)
self.set_property("time_between_kills", f"{minutes}m {seconds}s")
else:
self.set_property("time_between_kills", "N/A")
if len(self.exp_history) > 1:
exp_deltas = np.diff(self.exp_history)
avg_exp_per_kill = np.mean(exp_deltas) if len(exp_deltas) > 0 else 0
self.set_property("exp_per_kill", f"{avg_exp_per_kill:.2f}%")
else:
self.set_property("exp_per_kill", "N/A")

View File

@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""
Standardized Flyff Low Health Alert Node:
- Monitors an input value (1 = health alert, 0 = normal).
- Displays a visual alert and plays a sound if enabled.
- Uses a global update timer for processing.
- Automatically processes float, int, and string values.
"""
import time
from OdenGraphQt import BaseNode
from Qt import QtCore, QtWidgets, QtGui
try:
import winsound
HAS_WINSOUND = True
except ImportError:
winsound = None
HAS_WINSOUND = False
class OverlayCanvas(QtWidgets.QWidget):
"""
UI overlay for displaying a red warning box, which can be repositioned by dragging.
"""
def __init__(self, parent=None):
super().__init__(parent)
screen_geo = QtWidgets.QApplication.primaryScreen().geometry()
self.setGeometry(screen_geo)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
self.setVisible(False)
self.helper_LowHealthAlert = QtCore.QRect(250, 300, 900, 35)
self.dragging = False
self.drag_offset = None
def paintEvent(self, event):
if not self.isVisible():
return
painter = QtGui.QPainter(self)
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(QtGui.QColor(255, 0, 0))
painter.drawRect(self.helper_LowHealthAlert)
font = QtGui.QFont("Arial", 14, QtGui.QFont.Bold)
painter.setFont(font)
painter.setPen(QtGui.QColor(255, 255, 255))
text_x = self.helper_LowHealthAlert.center().x() - 50
text_y = self.helper_LowHealthAlert.center().y() + 5
painter.drawText(text_x, text_y, "LOW HEALTH")
def toggle_alert(self, state):
self.setVisible(state == 1)
self.update()
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
if self.helper_LowHealthAlert.contains(event.pos()):
self.dragging = True
self.drag_offset = event.pos() - self.helper_LowHealthAlert.topLeft()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.dragging:
new_top_left = event.pos() - self.drag_offset
self.helper_LowHealthAlert.moveTo(new_top_left)
self.update()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.dragging = False
super().mouseReleaseEvent(event)
class FlyffLowHealthAlertNode(BaseNode):
__identifier__ = 'bunny-lab.io.flyff_low_health_alert_node'
NODE_NAME = 'Flyff - Low Health Alert'
overlay_instance = None
last_beep_time = 0
BEEP_INTERVAL_SECONDS = 2
def __init__(self):
super(FlyffLowHealthAlertNode, self).__init__()
self.add_checkbox('cb_1', '', 'Sound Alert', True)
self.add_checkbox('cb_2', '', 'Visual Alert', True)
self.add_input('Toggle (1 = On | 0 = Off)', color=(200, 100, 0))
self.add_text_input('value', 'Current Value', text='0')
self.add_combo_menu('beep_interval', 'Beep Interval', items=["0.5s", "1.0s", "2.0s"])
if not FlyffLowHealthAlertNode.overlay_instance:
FlyffLowHealthAlertNode.overlay_instance = OverlayCanvas()
FlyffLowHealthAlertNode.overlay_instance.show()
def process_input(self):
input_port = self.input(0)
value = input_port.connected_ports()[0].node().get_property('value') if input_port.connected_ports() else "0"
self.receive_data(value)
def receive_data(self, data, source_port_name=None):
try:
if isinstance(data, str):
data = float(data) if '.' in data else int(data)
if isinstance(data, (float, int)):
data = 1 if data > 1 else 0 if data <= 0 else int(data)
else:
data = 0
except ValueError:
data = 0
self.set_property('value', str(data))
if self.get_property('cb_2'):
FlyffLowHealthAlertNode.overlay_instance.toggle_alert(data)
self.handle_beep(data)
def handle_beep(self, input_value):
# Update beep interval from the dropdown property
interval_str = self.get_property('beep_interval')
if interval_str.endswith("s"):
interval_seconds = float(interval_str[:-1])
else:
interval_seconds = float(interval_str)
self.BEEP_INTERVAL_SECONDS = interval_seconds
if input_value == 1 and self.get_property('cb_1'):
current_time = time.time()
if (current_time - FlyffLowHealthAlertNode.last_beep_time) >= self.BEEP_INTERVAL_SECONDS:
FlyffLowHealthAlertNode.last_beep_time = current_time
self.play_beep()
def play_beep(self):
if HAS_WINSOUND:
winsound.Beep(376, 100)
else:
print('\a', end='')

View File

@ -0,0 +1,49 @@
from OdenGraphQt 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 a size defined by ArraySize.
- Updates are now handled via a global update timer.
"""
__identifier__ = 'bunny-lab.io.array_node'
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}")

View File

@ -0,0 +1,122 @@
#!/usr/bin/env python3
"""
Standardized Comparison Node:
- Compares two input values using a selected operator (==, !=, >, <, >=, <=).
- Outputs a result of 1 (True) or 0 (False).
- Uses a global update timer for processing.
- Supports an additional 'Input Type' dropdown to choose between 'Number' and 'String'.
"""
from OdenGraphQt import BaseNode
from Qt import QtCore
class ComparisonNode(BaseNode):
__identifier__ = 'bunny-lab.io.comparison_node'
NODE_NAME = 'Comparison Node'
def __init__(self):
super(ComparisonNode, self).__init__()
self.add_input('A')
self.add_input('B')
self.add_output('Result')
# Add the Input Type dropdown first.
self.add_combo_menu('input_type', 'Input Type', items=['Number', 'String'])
self.add_combo_menu('operator', 'Operator', items=[
'Equal (==)', 'Not Equal (!=)', 'Greater Than (>)',
'Less Than (<)', 'Greater Than or Equal (>=)', 'Less Than or Equal (<=)'
])
# Replace calc_result with a standardized "value" text input.
self.add_text_input('value', 'Value', text='0')
self.value = 0
self.set_name("Comparison Node")
self.processing = False # Guard for process_input
# Set default properties explicitly
self.set_property('input_type', 'Number')
self.set_property('operator', 'Equal (==)')
def process_input(self):
if self.processing:
return
self.processing = True
# Retrieve input values; if no connection or None, default to "0"
input_a = self.input(0)
input_b = self.input(1)
a_raw = (input_a.connected_ports()[0].node().get_property('value')
if input_a.connected_ports() else "0")
b_raw = (input_b.connected_ports()[0].node().get_property('value')
if input_b.connected_ports() else "0")
a_raw = a_raw if a_raw is not None else "0"
b_raw = b_raw if b_raw is not None else "0"
# Get input type property
input_type = self.get_property('input_type')
# Convert values based on input type
if input_type == 'Number':
try:
a_val = float(a_raw)
except (ValueError, TypeError):
a_val = 0.0
try:
b_val = float(b_raw)
except (ValueError, TypeError):
b_val = 0.0
elif input_type == 'String':
a_val = str(a_raw)
b_val = str(b_raw)
else:
try:
a_val = float(a_raw)
except (ValueError, TypeError):
a_val = 0.0
try:
b_val = float(b_raw)
except (ValueError, TypeError):
b_val = 0.0
operator = self.get_property('operator')
# Perform the comparison
result = {
'Equal (==)': a_val == b_val,
'Not Equal (!=)': a_val != b_val,
'Greater Than (>)': a_val > b_val,
'Less Than (<)': a_val < b_val,
'Greater Than or Equal (>=)': a_val >= b_val,
'Less Than or Equal (<=)': a_val <= b_val
}.get(operator, False)
new_value = 1 if result else 0
self.value = new_value
self.set_property('value', str(self.value))
self.transmit_data(self.value)
self.processing = False
def on_input_connected(self, input_port, output_port):
pass
def on_input_disconnected(self, input_port, output_port):
pass
def property_changed(self, property_name):
pass
def receive_data(self, data, source_port_name=None):
pass
def transmit_data(self, data):
output_port = self.output(0)
if output_port and output_port.connected_ports():
for connected_port in output_port.connected_ports():
connected_node = connected_port.node()
if hasattr(connected_node, 'receive_data'):
try:
data_int = int(data)
connected_node.receive_data(data_int, source_port_name='Result')
except ValueError:
pass

View File

@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""
Standardized Data Node:
- Accepts and transmits values consistently.
- Updates its value based on a global update timer.
"""
from OdenGraphQt import BaseNode
from Qt import QtCore
class DataNode(BaseNode):
__identifier__ = 'bunny-lab.io.data_node'
NODE_NAME = 'Data Node'
def __init__(self):
super(DataNode, self).__init__()
self.add_input('Input')
self.add_output('Output')
self.add_text_input('value', 'Value', text='')
self.process_widget_event()
self.set_name("Data Node")
# Removed self-contained update timer; global timer now drives updates.
def post_create(self):
text_widget = self.get_widget('value')
if text_widget is not None:
try:
# Removed textChanged signal connection; global timer will call process_input.
pass
except Exception as e:
print("Error connecting textChanged signal:", e)
def process_widget_event(self, event=None):
current_text = self.get_property('value')
self.value = current_text
self.transmit_data(current_text)
def property_changed(self, property_name):
if property_name == 'value':
# Immediate update removed; relying on global timer.
pass
def process_input(self):
input_port = self.input(0)
output_port = self.output(0)
if input_port.connected_ports():
input_value = input_port.connected_ports()[0].node().get_property('value')
self.set_property('value', input_value)
self.transmit_data(input_value)
elif output_port.connected_ports():
self.transmit_data(self.get_property('value'))
def on_input_connected(self, input_port, output_port):
# Removed immediate update; global timer handles updates.
pass
def on_input_disconnected(self, input_port, output_port):
# Removed immediate update; global timer handles updates.
pass
def receive_data(self, data, source_port_name=None):
self.set_property('value', str(data))
self.transmit_data(data)
def transmit_data(self, data):
output_port = self.output(0)
if output_port and output_port.connected_ports():
for connected_port in output_port.connected_ports():
connected_node = connected_port.node()
if hasattr(connected_node, 'receive_data'):
connected_node.receive_data(data, source_port_name="Output")

View File

@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""
Identification Overlay Node:
- Users can configure threads/slices for parallel processing.
"""
import re
from OdenGraphQt import BaseNode
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QColor
from Modules import data_collector
class IdentificationOverlayNode(BaseNode):
__identifier__ = "bunny-lab.io.identification_overlay_node"
NODE_NAME = "Identification Overlay"
def __init__(self):
super(IdentificationOverlayNode, self).__init__()
# User-configurable options
self.add_text_input("search_term", "Search Term", text="Aibatt")
self.add_text_input("offset_value", "Offset Value (X,Y)", text="0,0") # X,Y Offset
self.add_text_input("margin", "Margin", text="5") # Box Margin
self.add_text_input("polling_freq", "Polling Frequency (ms)", text="500") # Polling Rate
self.add_combo_menu("ocr_engine", "Type", items=["CPU", "GPU"])
self.set_property("ocr_engine", "CPU") # Default to CPU mode
# Custom overlay options
self.add_text_input("overlay_color", "Overlay Color (RGB)", text="0,0,255") # Default blue
self.add_text_input("thickness", "Line Thickness", text="2") # Default 2px
self.add_text_input("threads_slices", "Threads / Slices", text="8") # Default 8 threads/slices
self.region_id = "identification_overlay"
data_collector.create_ocr_region(self.region_id, x=250, y=50, w=300, h=200, color=(0, 0, 255), thickness=2)
data_collector.start_collector()
self.set_name("Identification Overlay")
# Timer for updating overlays
self.timer = QTimer()
self.timer.timeout.connect(self.update_overlay)
# Set initial polling frequency
self.update_polling_frequency()
def update_polling_frequency(self):
polling_text = self.get_property("polling_freq")
try:
polling_interval = max(50, int(polling_text))
except ValueError:
polling_interval = 500
self.timer.start(polling_interval)
def update_overlay(self):
search_term = self.get_property("search_term")
offset_text = self.get_property("offset_value")
margin_text = self.get_property("margin")
ocr_engine = self.get_property("ocr_engine")
threads_slices_text = self.get_property("threads_slices")
self.update_polling_frequency()
try:
offset_x, offset_y = map(int, offset_text.split(","))
except ValueError:
offset_x, offset_y = 0, 0
try:
margin = int(margin_text)
except ValueError:
margin = 5
color_text = self.get_property("overlay_color")
try:
color = tuple(map(int, color_text.split(",")))
except ValueError:
color = (0, 0, 255)
thickness_text = self.get_property("thickness")
try:
thickness = max(1, int(thickness_text))
except ValueError:
thickness = 2
try:
num_slices = max(1, int(threads_slices_text)) # Ensure at least 1 slice
except ValueError:
num_slices = 1
if not search_term:
return
detected_positions = data_collector.find_word_positions(
self.region_id, search_term, offset_x, offset_y, margin, ocr_engine, num_slices
)
# Ensure slice count is updated visually in the region widget
data_collector.update_region_slices(self.region_id, num_slices)
data_collector.draw_identification_boxes(self.region_id, detected_positions, color=color, thickness=thickness)

View File

@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""
Standardized Math Operation Node:
- Performs mathematical operations (+, -, *, /, avg) on two inputs.
- Outputs the computed result.
- Uses a global update timer for processing (defined in borealis.py).
- Ensures it always has a "value" property that the Comparison Node can read.
"""
from OdenGraphQt import BaseNode
from Qt import QtCore
class MathOperationNode(BaseNode):
__identifier__ = 'bunny-lab.io.math_node'
NODE_NAME = 'Math Operation'
def __init__(self):
super(MathOperationNode, self).__init__()
self.add_input('A')
self.add_input('B')
self.add_output('Result')
# Drop-down to choose which operation we do:
self.add_combo_menu('operator', 'Operator', items=[
'Add', 'Subtract', 'Multiply', 'Divide', 'Average'
])
# A text field for showing the result to the user:
self.add_text_input('calc_result', 'Result', text='0')
# IMPORTANT: define a "value" property that the Comparison Node can read
# We do not necessarily need a text input for it, but adding it ensures
# it becomes an official property recognized by OdenGraphQt.
self.add_text_input('value', 'Internal Value', text='0')
# Keep a Python-side float of the current computed result:
self.value = 0
# Give the node a nice name:
self.set_name("Math Operation")
# Removed self-contained timer; global timer calls process_input().
def process_input(self):
# Attempt to read "value" from both inputs:
input_a = self.input(0)
input_b = self.input(1)
a_raw = input_a.connected_ports()[0].node().get_property('value') if input_a.connected_ports() else "0"
b_raw = input_b.connected_ports()[0].node().get_property('value') if input_b.connected_ports() else "0"
try:
a_val = float(a_raw)
except (ValueError, TypeError):
a_val = 0.0
try:
b_val = float(b_raw)
except (ValueError, TypeError):
b_val = 0.0
operator = self.get_property('operator')
if operator == 'Add':
result = a_val + b_val
elif operator == 'Subtract':
result = a_val - b_val
elif operator == 'Multiply':
result = a_val * b_val
elif operator == 'Divide':
result = a_val / b_val if b_val != 0 else 0.0
elif operator == 'Average':
result = (a_val + b_val) / 2.0
else:
result = 0.0
# If the computed result changed, update our internal properties and transmit
if self.value != result:
self.value = result
# Update the two text fields so the user sees the numeric result:
self.set_property('calc_result', str(result))
self.set_property('value', str(result)) # <= This is the critical step
# Let downstream nodes know there's new data:
self.transmit_data(result)
def on_input_connected(self, input_port, output_port):
pass
def on_input_disconnected(self, input_port, output_port):
pass
def property_changed(self, property_name):
pass
def receive_data(self, data, source_port_name=None):
pass
def transmit_data(self, data):
output_port = self.output(0)
if output_port and output_port.connected_ports():
for connected_port in output_port.connected_ports():
connected_node = connected_port.node()
if hasattr(connected_node, 'receive_data'):
try:
# Attempt to convert to int if possible, else float
data_int = int(data)
connected_node.receive_data(data_int, source_port_name='Result')
except ValueError:
connected_node.receive_data(data, source_port_name='Result')

View File

@ -0,0 +1,161 @@
#!/usr/bin/env python3
from Qt import QtWidgets, QtGui, QtCore
from OdenGraphQt import BaseNode
from OdenGraphQt.constants import NodePropWidgetEnum
from OdenGraphQt.qgraphics.node_backdrop import BackdropNodeItem
class BackdropNode(BaseNode):
"""
Backdrop Node:
- Allows grouping or annotating other nodes by resizing a large rectangle.
- Title is set by double-clicking in the title area.
"""
__identifier__ = 'bunny-lab.io.backdrop'
NODE_NAME = 'Backdrop'
def __init__(self):
# Use BackdropNodeItem for the specialized QGraphicsItem.
super(BackdropNode, self).__init__(qgraphics_item=BackdropNodeItem)
# Default color (teal).
self.model.color = (5, 129, 138, 255)
# Set default title without prompting:
self.set_name("Double-Click to Add Name to Backdrop")
# Multi-line text property for storing the backdrop text.
self.create_property(
'backdrop_text',
'',
widget_type=NodePropWidgetEnum.QTEXT_EDIT.value,
tab='Backdrop'
)
# Override the view's double-click event to allow editing the title.
original_double_click = self.view.mouseDoubleClickEvent
def new_double_click_event(event):
# Assume the title is in the top 30 pixels of the node.
if event.pos().y() < 30:
new_title, ok = QtWidgets.QInputDialog.getText(
None, "Edit Title", "Enter new backdrop title:", text=self.name()
)
if ok and new_title:
self.set_name(new_title)
self.view.update() # force immediate update of the node title
else:
if original_double_click:
original_double_click(event)
self.view.mouseDoubleClickEvent = new_double_click_event
# --------------------------------------------------------------------------
# Resizing / Geometry
# --------------------------------------------------------------------------
def on_backdrop_updated(self, update_prop, value=None):
"""
Triggered when the user resizes or double-clicks the backdrop sizer handle.
"""
if not self.graph:
return
if update_prop == 'sizer_mouse_release':
# User finished dragging the resize handle
self.view.prepareGeometryChange()
self.graph.begin_undo(f'resized "{self.name()}"')
self.set_property('width', value['width'])
self.set_property('height', value['height'])
self.set_pos(*value['pos'])
self.graph.end_undo()
self.view.update()
elif update_prop == 'sizer_double_clicked':
# User double-clicked the resize handle (auto-resize)
self.view.prepareGeometryChange()
self.graph.begin_undo(f'"{self.name()}" auto resize')
self.set_property('width', value['width'])
self.set_property('height', value['height'])
self.set_pos(*value['pos'])
self.graph.end_undo()
self.view.update()
def auto_size(self):
"""
Auto-resize the backdrop to fit around intersecting nodes.
"""
if not self.graph:
return
self.view.prepareGeometryChange()
self.graph.begin_undo(f'"{self.name()}" auto resize')
size = self.view.calc_backdrop_size()
self.set_property('width', size['width'])
self.set_property('height', size['height'])
self.set_pos(*size['pos'])
self.graph.end_undo()
self.view.update()
def wrap_nodes(self, nodes):
"""
Fit the backdrop around the specified nodes.
"""
if not self.graph or not nodes:
return
self.view.prepareGeometryChange()
self.graph.begin_undo(f'"{self.name()}" wrap nodes')
size = self.view.calc_backdrop_size([n.view for n in nodes])
self.set_property('width', size['width'])
self.set_property('height', size['height'])
self.set_pos(*size['pos'])
self.graph.end_undo()
self.view.update()
def nodes(self):
"""
Return a list of nodes wrapped by this backdrop.
"""
node_ids = [n.id for n in self.view.get_nodes()]
return [self.graph.get_node_by_id(nid) for nid in node_ids]
def set_text(self, text=''):
"""
Set the multi-line text in the backdrop.
"""
self.set_property('backdrop_text', text)
def text(self):
"""
Return the text content in the backdrop.
"""
return self.get_property('backdrop_text')
def set_size(self, width, height):
"""
Manually set the backdrop size.
"""
if self.graph:
self.view.prepareGeometryChange()
self.graph.begin_undo('backdrop size')
self.set_property('width', width)
self.set_property('height', height)
self.graph.end_undo()
self.view.update()
else:
self.view.width, self.view.height = width, height
self.model.width, self.model.height = width, height
def size(self):
"""
Return (width, height) of the backdrop.
"""
self.model.width = self.view.width
self.model.height = self.view.height
return self.model.width, self.model.height
# No ports for a backdrop:
def inputs(self):
return
def outputs(self):
return

View File

@ -0,0 +1,3 @@
# HIGH-LEVEL OVERVIEW
# - This node takes an input source and either replaces or appends data fed into it into a CSV file on disk.
# - There will be a checkbox to allow the user to change the behavior (Replace / Append)

View File

@ -0,0 +1,4 @@
# HIGH-LEVEL OVERVIEW
# - This node takes an input source and dumps the data to disk in a dropdown menu of various image formats
# - Ability to view image processing results would be an interesting bonus if displayed within the node.
# - Could be used to show the life cycle of an image processing pipeline.

0
Data/Nodes/__init__.py Normal file
View File