Re-implemented Backdrop Node
This commit is contained in:
parent
ca5152b89a
commit
9c2e287b72
192
Experimental/accept_reject_example.py
Normal file
192
Experimental/accept_reject_example.py
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
import signal
|
||||||
|
|
||||||
|
from qtpy import QtWidgets
|
||||||
|
|
||||||
|
from OdenGraphQt import BaseNode, NodeGraph
|
||||||
|
from OdenGraphQt.constants import PortTypeEnum
|
||||||
|
from OdenGraphQt.qgraphics.node_base import NodeItem
|
||||||
|
|
||||||
|
|
||||||
|
class PublishWriteNodeItem(NodeItem):
|
||||||
|
def _align_widgets_horizontal(self, v_offset: int):
|
||||||
|
if not self._widgets:
|
||||||
|
return
|
||||||
|
|
||||||
|
rect = self.boundingRect()
|
||||||
|
y = rect.y() + v_offset
|
||||||
|
for widget in self._widgets.values():
|
||||||
|
if not widget.isVisible():
|
||||||
|
continue
|
||||||
|
|
||||||
|
widget_rect = widget.boundingRect()
|
||||||
|
x = rect.center().x() - (widget_rect.width() / 2)
|
||||||
|
widget.widget().setTitleAlign('center')
|
||||||
|
widget.setPos(x, y)
|
||||||
|
y += widget_rect.height()
|
||||||
|
|
||||||
|
|
||||||
|
class PrevNextNode(BaseNode):
|
||||||
|
__identifier__ = "action"
|
||||||
|
NODE_NAME = "Action Node"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# create an input port.
|
||||||
|
input_port = self.add_input("_prev", color=(180, 80, 0), multi_input=False)
|
||||||
|
# create an output port.
|
||||||
|
output_port = self.add_output("_next", multi_output=False)
|
||||||
|
|
||||||
|
input_port.port_item.set_allow_partial_match_constraint(True)
|
||||||
|
input_port.port_item.set_accept_constraint(
|
||||||
|
port_name=output_port.name(),
|
||||||
|
port_type=PortTypeEnum.OUT.value,
|
||||||
|
node_identifier=self.__identifier__,
|
||||||
|
)
|
||||||
|
|
||||||
|
output_port.port_item.set_allow_partial_match_constraint(True)
|
||||||
|
output_port.port_item.set_accept_constraint(
|
||||||
|
port_name=input_port.name(),
|
||||||
|
port_type=PortTypeEnum.IN.value,
|
||||||
|
node_identifier=self.__identifier__,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IngredientNode(BaseNode):
|
||||||
|
__identifier__ = "ingredient"
|
||||||
|
|
||||||
|
|
||||||
|
class SpamNode(IngredientNode):
|
||||||
|
__identifier__ = "spam"
|
||||||
|
NODE_NAME = "Spam"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
spam_port = self.add_output(
|
||||||
|
"spam",
|
||||||
|
color=(50, 150, 222),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EggNode(IngredientNode):
|
||||||
|
__identifier__ = "egg"
|
||||||
|
NODE_NAME = "Egg"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
egg_port = self.add_output(
|
||||||
|
"egg",
|
||||||
|
color=(50, 150, 222),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MealNode(BaseNode):
|
||||||
|
NODE_NAME = "Meal"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
spam_port = self.add_input("spam", color=(222, 15, 0), multi_input=False)
|
||||||
|
spam_port.port_item.set_reject_constraint(
|
||||||
|
port_name="egg",
|
||||||
|
port_type=PortTypeEnum.OUT.value,
|
||||||
|
node_identifier="egg",
|
||||||
|
)
|
||||||
|
egg_port = self.add_input("egg", color=(222, 15, 0), multi_input=False)
|
||||||
|
egg_port.port_item.set_reject_constraint(
|
||||||
|
port_name="spam",
|
||||||
|
port_type=PortTypeEnum.OUT.value,
|
||||||
|
node_identifier="spam",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BasePublishNode(PrevNextNode):
|
||||||
|
__identifier__ = "publish"
|
||||||
|
allow_multiple_write = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
port = self.add_output(
|
||||||
|
"write",
|
||||||
|
color=(184, 150, 0),
|
||||||
|
multi_output=self.allow_multiple_write,
|
||||||
|
)
|
||||||
|
port.port_item.set_accept_constraint(
|
||||||
|
port_name="src",
|
||||||
|
port_type=PortTypeEnum.IN.value,
|
||||||
|
node_identifier="publish",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PubNode(PrevNextNode):
|
||||||
|
__identifier__ = "pub"
|
||||||
|
NODE_NAME = "Not Tavern"
|
||||||
|
|
||||||
|
|
||||||
|
class PublishFileActionNode(BasePublishNode):
|
||||||
|
NODE_NAME = "Publish File"
|
||||||
|
allow_multiple_write = False
|
||||||
|
|
||||||
|
|
||||||
|
class PublishFileToManyActionNode(BasePublishNode):
|
||||||
|
NODE_NAME = "Publish File to Many"
|
||||||
|
allow_multiple_write = True
|
||||||
|
|
||||||
|
|
||||||
|
class PublishWriteNode(BaseNode):
|
||||||
|
__identifier__ = "publish"
|
||||||
|
NODE_NAME = "Publish Write"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(qgraphics_item=PublishWriteNodeItem)
|
||||||
|
self.set_color(164, 130, 0)
|
||||||
|
self.add_text_input("write", "Path:")
|
||||||
|
|
||||||
|
port = self.add_input("src", multi_input=False)
|
||||||
|
port.port_item.set_accept_constraint(
|
||||||
|
port_name="write",
|
||||||
|
port_type=PortTypeEnum.OUT.value,
|
||||||
|
node_identifier="publish",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
# handle SIGINT to make the app terminate on CTRL+C
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
|
||||||
|
app = QtWidgets.QApplication([])
|
||||||
|
|
||||||
|
# create graph controller.
|
||||||
|
graph = NodeGraph()
|
||||||
|
|
||||||
|
# set up context menu for the node graph.
|
||||||
|
graph.set_context_menu_from_file('../examples/hotkeys/hotkeys.json')
|
||||||
|
|
||||||
|
# registered example nodes.
|
||||||
|
graph.register_nodes([
|
||||||
|
SpamNode,
|
||||||
|
EggNode,
|
||||||
|
MealNode,
|
||||||
|
PubNode,
|
||||||
|
PublishFileActionNode,
|
||||||
|
PublishFileToManyActionNode,
|
||||||
|
PublishWriteNode,
|
||||||
|
])
|
||||||
|
|
||||||
|
# add nodes
|
||||||
|
graph.add_node(SpamNode())
|
||||||
|
graph.add_node(EggNode())
|
||||||
|
graph.add_node(MealNode())
|
||||||
|
graph.add_node(PubNode())
|
||||||
|
graph.add_node(PublishFileToManyActionNode())
|
||||||
|
graph.add_node(PublishFileActionNode())
|
||||||
|
graph.add_node(PublishWriteNode())
|
||||||
|
graph.auto_layout_nodes()
|
||||||
|
graph.clear_selection()
|
||||||
|
|
||||||
|
# show the node graph widget.
|
||||||
|
graph_widget = graph.widget
|
||||||
|
graph_widget.resize(1100, 800)
|
||||||
|
graph_widget.show()
|
||||||
|
|
||||||
|
app.exec_()
|
BIN
Nodes/__pycache__/backdrop_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/backdrop_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/basic_nodes.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/basic_nodes.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/custom_ports_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/custom_ports_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/flyff_character_status_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/flyff_character_status_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/group_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/group_node.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/__pycache__/widget_node.cpython-312.pyc
Normal file
BIN
Nodes/__pycache__/widget_node.cpython-312.pyc
Normal file
Binary file not shown.
184
Nodes/backdrop_node.py
Normal file
184
Nodes/backdrop_node.py
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Enhanced Backdrop Node (Inherited from BaseNode)
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Inherits from `BaseNode` so it can be discovered in your node scanning.
|
||||||
|
- Custom context menu to rename (set title) or pick a new color.
|
||||||
|
- Forces geometry updates to reduce "ghosting" or partial redraws.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from Qt import QtWidgets, QtGui
|
||||||
|
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.
|
||||||
|
- Provides a custom context menu for renaming and recoloring (via on_context_menu).
|
||||||
|
"""
|
||||||
|
|
||||||
|
__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)
|
||||||
|
|
||||||
|
# Multi-line text property for storing the backdrop text.
|
||||||
|
self.create_property(
|
||||||
|
'backdrop_text',
|
||||||
|
'',
|
||||||
|
widget_type=NodePropWidgetEnum.QTEXT_EDIT.value,
|
||||||
|
tab='Backdrop'
|
||||||
|
)
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Custom Context Menu
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
def on_context_menu(self, menu):
|
||||||
|
"""
|
||||||
|
Called manually by the node context menu callback in older NodeGraphQt versions.
|
||||||
|
"""
|
||||||
|
rename_action = menu.addAction("Set Title...")
|
||||||
|
rename_action.triggered.connect(self._change_title)
|
||||||
|
|
||||||
|
color_action = menu.addAction("Set Color...")
|
||||||
|
color_action.triggered.connect(self._change_color)
|
||||||
|
|
||||||
|
def _change_title(self):
|
||||||
|
"""
|
||||||
|
Prompt for a new backdrop title (header).
|
||||||
|
"""
|
||||||
|
new_title, ok = QtWidgets.QInputDialog.getText(
|
||||||
|
None, "Backdrop Title", "Enter new backdrop title:"
|
||||||
|
)
|
||||||
|
if ok and new_title:
|
||||||
|
self.set_name(new_title)
|
||||||
|
|
||||||
|
def _change_color(self):
|
||||||
|
"""
|
||||||
|
Prompt for a new backdrop color via QColorDialog.
|
||||||
|
"""
|
||||||
|
current_color = QtGui.QColor(*self.model.color)
|
||||||
|
color = QtWidgets.QColorDialog.getColor(
|
||||||
|
current_color, None, "Select Backdrop Color"
|
||||||
|
)
|
||||||
|
if color.isValid():
|
||||||
|
self.model.color = (color.red(), color.green(), color.blue(), color.alpha())
|
||||||
|
self.view.update()
|
86
Nodes/basic_nodes.py
Normal file
86
Nodes/basic_nodes.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
from OdenGraphQt import BaseNode, BaseNodeCircle
|
||||||
|
|
||||||
|
|
||||||
|
class BasicNodeA(BaseNode):
|
||||||
|
"""
|
||||||
|
A node class with 2 inputs and 2 outputs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# unique node identifier.
|
||||||
|
__identifier__ = 'nodes.basic'
|
||||||
|
|
||||||
|
# initial default node name.
|
||||||
|
NODE_NAME = 'node A'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(BasicNodeA, self).__init__()
|
||||||
|
|
||||||
|
# create node inputs.
|
||||||
|
self.add_input('in A')
|
||||||
|
self.add_input('in B')
|
||||||
|
|
||||||
|
# create node outputs.
|
||||||
|
self.add_output('out A')
|
||||||
|
self.add_output('out B')
|
||||||
|
|
||||||
|
|
||||||
|
class BasicNodeB(BaseNode):
|
||||||
|
"""
|
||||||
|
A node class with 3 inputs and 3 outputs.
|
||||||
|
The last input and last output can take in multiple pipes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# unique node identifier.
|
||||||
|
__identifier__ = 'nodes.basic'
|
||||||
|
|
||||||
|
# initial default node name.
|
||||||
|
NODE_NAME = 'node B'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(BasicNodeB, self).__init__()
|
||||||
|
|
||||||
|
# create node inputs
|
||||||
|
self.add_input('single 1')
|
||||||
|
self.add_input('single 2')
|
||||||
|
self.add_input('multi in', multi_input=True)
|
||||||
|
|
||||||
|
# create node outputs
|
||||||
|
self.add_output('single 1', multi_output=False)
|
||||||
|
self.add_output('single 2', multi_output=False)
|
||||||
|
self.add_output('multi out')
|
||||||
|
|
||||||
|
|
||||||
|
class CircleNode(BaseNodeCircle):
|
||||||
|
"""
|
||||||
|
A node class with 3 inputs and 3 outputs.
|
||||||
|
This node is a circular design.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# unique node identifier.
|
||||||
|
__identifier__ = 'nodes.basic'
|
||||||
|
|
||||||
|
# initial default node name.
|
||||||
|
NODE_NAME = 'Circle Node'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(CircleNode, self).__init__()
|
||||||
|
self.set_color(10, 24, 38)
|
||||||
|
|
||||||
|
# create node inputs
|
||||||
|
p = self.add_input('in 1')
|
||||||
|
p.add_accept_port_type(
|
||||||
|
port_name='single 1',
|
||||||
|
port_type='out',
|
||||||
|
node_type='nodes.basic.BasicNodeB'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add_input('in 2')
|
||||||
|
self.add_input('in 3', multi_input=True)
|
||||||
|
self.add_input('in 4', display_name=False)
|
||||||
|
self.add_input('in 5', display_name=False)
|
||||||
|
|
||||||
|
# create node outputs
|
||||||
|
self.add_output('out 1')
|
||||||
|
self.add_output('out 2', multi_output=False)
|
||||||
|
self.add_output('out 3', multi_output=True, display_name=False)
|
||||||
|
self.add_output('out 4', multi_output=True, display_name=False)
|
121
Nodes/custom_ports_node.py
Normal file
121
Nodes/custom_ports_node.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from qtpy import QtCore, QtGui
|
||||||
|
|
||||||
|
from OdenGraphQt import BaseNode
|
||||||
|
|
||||||
|
|
||||||
|
def draw_triangle_port(painter, rect, info):
|
||||||
|
"""
|
||||||
|
Custom paint function for drawing a Triangle shaped port.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
painter (QtGui.QPainter): painter object.
|
||||||
|
rect (QtCore.QRectF): port rect used to describe parameters
|
||||||
|
needed to draw.
|
||||||
|
info (dict): information describing the ports current state.
|
||||||
|
{
|
||||||
|
'port_type': 'in',
|
||||||
|
'color': (0, 0, 0),
|
||||||
|
'border_color': (255, 255, 255),
|
||||||
|
'multi_connection': False,
|
||||||
|
'connected': False,
|
||||||
|
'hovered': False,
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
painter.save()
|
||||||
|
|
||||||
|
size = int(rect.height() / 2)
|
||||||
|
triangle = QtGui.QPolygonF()
|
||||||
|
triangle.append(QtCore.QPointF(-size, size))
|
||||||
|
triangle.append(QtCore.QPointF(0.0, -size))
|
||||||
|
triangle.append(QtCore.QPointF(size, size))
|
||||||
|
|
||||||
|
transform = QtGui.QTransform()
|
||||||
|
transform.translate(rect.center().x(), rect.center().y())
|
||||||
|
port_poly = transform.map(triangle)
|
||||||
|
|
||||||
|
# mouse over port color.
|
||||||
|
if info['hovered']:
|
||||||
|
color = QtGui.QColor(14, 45, 59)
|
||||||
|
border_color = QtGui.QColor(136, 255, 35)
|
||||||
|
# port connected color.
|
||||||
|
elif info['connected']:
|
||||||
|
color = QtGui.QColor(195, 60, 60)
|
||||||
|
border_color = QtGui.QColor(200, 130, 70)
|
||||||
|
# default port color
|
||||||
|
else:
|
||||||
|
color = QtGui.QColor(*info['color'])
|
||||||
|
border_color = QtGui.QColor(*info['border_color'])
|
||||||
|
|
||||||
|
pen = QtGui.QPen(border_color, 1.8)
|
||||||
|
pen.setJoinStyle(QtCore.Qt.PenJoinStyle.MiterJoin)
|
||||||
|
|
||||||
|
painter.setPen(pen)
|
||||||
|
painter.setBrush(color)
|
||||||
|
painter.drawPolygon(port_poly)
|
||||||
|
|
||||||
|
painter.restore()
|
||||||
|
|
||||||
|
|
||||||
|
def draw_square_port(painter, rect, info):
|
||||||
|
"""
|
||||||
|
Custom paint function for drawing a Square shaped port.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
painter (QtGui.QPainter): painter object.
|
||||||
|
rect (QtCore.QRectF): port rect used to describe parameters
|
||||||
|
needed to draw.
|
||||||
|
info (dict): information describing the ports current state.
|
||||||
|
{
|
||||||
|
'port_type': 'in',
|
||||||
|
'color': (0, 0, 0),
|
||||||
|
'border_color': (255, 255, 255),
|
||||||
|
'multi_connection': False,
|
||||||
|
'connected': False,
|
||||||
|
'hovered': False,
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
painter.save()
|
||||||
|
|
||||||
|
# mouse over port color.
|
||||||
|
if info['hovered']:
|
||||||
|
color = QtGui.QColor(14, 45, 59)
|
||||||
|
border_color = QtGui.QColor(136, 255, 35, 255)
|
||||||
|
# port connected color.
|
||||||
|
elif info['connected']:
|
||||||
|
color = QtGui.QColor(195, 60, 60)
|
||||||
|
border_color = QtGui.QColor(200, 130, 70)
|
||||||
|
# default port color
|
||||||
|
else:
|
||||||
|
color = QtGui.QColor(*info['color'])
|
||||||
|
border_color = QtGui.QColor(*info['border_color'])
|
||||||
|
|
||||||
|
pen = QtGui.QPen(border_color, 1.8)
|
||||||
|
pen.setJoinStyle(QtCore.Qt.PenJoinStyle.MiterJoin)
|
||||||
|
|
||||||
|
painter.setPen(pen)
|
||||||
|
painter.setBrush(color)
|
||||||
|
painter.drawRect(rect)
|
||||||
|
|
||||||
|
painter.restore()
|
||||||
|
|
||||||
|
|
||||||
|
class CustomPortsNode(BaseNode):
|
||||||
|
"""
|
||||||
|
example test node with custom shaped ports.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# set a unique node identifier.
|
||||||
|
__identifier__ = 'nodes.custom.ports'
|
||||||
|
|
||||||
|
# set the initial default node name.
|
||||||
|
NODE_NAME = 'node'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(CustomPortsNode, self).__init__()
|
||||||
|
|
||||||
|
# create input and output port.
|
||||||
|
self.add_input('in', color=(200, 10, 0))
|
||||||
|
self.add_output('default')
|
||||||
|
self.add_output('square', painter_func=draw_square_port)
|
||||||
|
self.add_output('triangle', painter_func=draw_triangle_port)
|
@ -1,13 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
|
||||||
Character Status Node
|
|
||||||
|
|
||||||
This node represents the character's status. It has seven output ports:
|
|
||||||
- HP: Current, HP: Total, MP: Current, MP: Total, FP: Current, FP: Total, 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 OdenGraphQt import BaseNode
|
from OdenGraphQt import BaseNode
|
||||||
from Qt import QtCore, QtGui
|
from Qt import QtCore, QtGui
|
||||||
@ -44,7 +35,7 @@ def get_draw_stat_port(color, border_color=None, alpha=127):
|
|||||||
return painter_func
|
return painter_func
|
||||||
|
|
||||||
class CharacterStatusNode(BaseNode):
|
class CharacterStatusNode(BaseNode):
|
||||||
__identifier__ = 'bunny-lab.io.status_node'
|
__identifier__ = 'bunny-lab.io.flyff_character_status_node'
|
||||||
NODE_NAME = 'Character Status'
|
NODE_NAME = 'Character Status'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
21
Nodes/group_node.py
Normal file
21
Nodes/group_node.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from OdenGraphQt import GroupNode
|
||||||
|
|
||||||
|
|
||||||
|
class MyGroupNode(GroupNode):
|
||||||
|
"""
|
||||||
|
example test group node with a in port and out port.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# set a unique node identifier.
|
||||||
|
__identifier__ = 'nodes.group'
|
||||||
|
|
||||||
|
# set the initial default node name.
|
||||||
|
NODE_NAME = 'group node'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(MyGroupNode, self).__init__()
|
||||||
|
self.set_color(50, 8, 25)
|
||||||
|
|
||||||
|
# create input and output port.
|
||||||
|
self.add_input('in')
|
||||||
|
self.add_output('out')
|
155
Nodes/widget_node.py
Normal file
155
Nodes/widget_node.py
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
from OdenGraphQt import BaseNode
|
||||||
|
from OdenGraphQt.constants import NodePropWidgetEnum
|
||||||
|
from OdenGraphQt.widgets.node_widgets import NodeLineEditValidatorCheckBox
|
||||||
|
|
||||||
|
|
||||||
|
class DropdownMenuNode(BaseNode):
|
||||||
|
"""
|
||||||
|
An example node with a embedded added QCombobox menu.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# unique node identifier.
|
||||||
|
__identifier__ = 'nodes.widget'
|
||||||
|
|
||||||
|
# initial default node name.
|
||||||
|
NODE_NAME = 'menu'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(DropdownMenuNode, self).__init__()
|
||||||
|
|
||||||
|
# create input & output ports
|
||||||
|
self.add_input('in 1')
|
||||||
|
self.add_output('out 1')
|
||||||
|
self.add_output('out 2')
|
||||||
|
|
||||||
|
# create the QComboBox menu.
|
||||||
|
items = ["item 1", "item 2", "item 3"]
|
||||||
|
self.add_combo_menu(
|
||||||
|
"my_menu",
|
||||||
|
"Menu Test",
|
||||||
|
items=items,
|
||||||
|
tooltip="example custom tooltip",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TextInputNode(BaseNode):
|
||||||
|
"""
|
||||||
|
An example of a node with a embedded QLineEdit.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# unique node identifier.
|
||||||
|
__identifier__ = 'nodes.widget'
|
||||||
|
|
||||||
|
# initial default node name.
|
||||||
|
NODE_NAME = 'text'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
pattern = r"^[A-Za-z0-9]*$"
|
||||||
|
placeholder = ""
|
||||||
|
tooltip = "Valid characters: A-Z a-z 0-9"
|
||||||
|
is_case_sensitive = True
|
||||||
|
checkbox_label = "Use Parser?"
|
||||||
|
|
||||||
|
# create input & output ports
|
||||||
|
self.add_input('in')
|
||||||
|
self.add_output('out')
|
||||||
|
|
||||||
|
# create QLineEdit text input widget.
|
||||||
|
self.add_text_input('my_input', 'Text Input', tab='widgets')
|
||||||
|
|
||||||
|
tool_btn_kwargs = {
|
||||||
|
"func": self._callback,
|
||||||
|
"tooltip": "Awesome"
|
||||||
|
}
|
||||||
|
kwargs = {
|
||||||
|
"validator": {
|
||||||
|
"pattern": pattern,
|
||||||
|
"placeholder": placeholder,
|
||||||
|
"tooltip": tooltip,
|
||||||
|
"is_case_insensitive": is_case_sensitive,
|
||||||
|
"checkbox_visible": True,
|
||||||
|
"tool_btn_visible": True,
|
||||||
|
},
|
||||||
|
"checkbox_label": checkbox_label,
|
||||||
|
"tool_btn": tool_btn_kwargs,
|
||||||
|
}
|
||||||
|
node_widget = NodeLineEditValidatorCheckBox(
|
||||||
|
"src_path",
|
||||||
|
pattern,
|
||||||
|
placeholder,
|
||||||
|
tooltip,
|
||||||
|
is_case_sensitive,
|
||||||
|
checkbox_label,
|
||||||
|
checkbox_visible=True,
|
||||||
|
tool_btn_visible=True,
|
||||||
|
widget_label="src_path",
|
||||||
|
parent=self.view,
|
||||||
|
)
|
||||||
|
node_widget.get_custom_widget().set_tool_btn(**tool_btn_kwargs)
|
||||||
|
self.add_custom_widget(
|
||||||
|
node_widget,
|
||||||
|
NodePropWidgetEnum.LINEEDIT_VALIDATOR_CHECKBOX.value,
|
||||||
|
"widgets",
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs2 = {
|
||||||
|
"validator": {
|
||||||
|
"pattern": pattern,
|
||||||
|
"placeholder": placeholder,
|
||||||
|
"tooltip": tooltip,
|
||||||
|
"is_case_insensitive": is_case_sensitive,
|
||||||
|
"checkbox_visible": False,
|
||||||
|
"tool_btn_visible": False,
|
||||||
|
},
|
||||||
|
"checkbox_label": "Check In Luggage?",
|
||||||
|
"tool_btn": tool_btn_kwargs,
|
||||||
|
}
|
||||||
|
node_widget2 = NodeLineEditValidatorCheckBox(
|
||||||
|
"dst_path",
|
||||||
|
pattern,
|
||||||
|
placeholder,
|
||||||
|
tooltip,
|
||||||
|
is_case_sensitive,
|
||||||
|
"Check In Luggage?",
|
||||||
|
checkbox_visible=False,
|
||||||
|
tool_btn_visible=False,
|
||||||
|
widget_label="dst_path",
|
||||||
|
parent=self.view,
|
||||||
|
)
|
||||||
|
node_widget2.get_custom_widget().set_tool_btn(**tool_btn_kwargs)
|
||||||
|
node_widget2.set_checkbox_visible(False)
|
||||||
|
node_widget2.set_tool_btn_visible(False)
|
||||||
|
self.add_custom_widget(
|
||||||
|
node_widget2,
|
||||||
|
NodePropWidgetEnum.LINEEDIT_VALIDATOR_CHECKBOX.value,
|
||||||
|
"widgets",
|
||||||
|
**kwargs2,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _callback(self):
|
||||||
|
print(f"YOU HAVE CLICKED ON '{self.NODE_NAME}'")
|
||||||
|
|
||||||
|
|
||||||
|
class CheckboxNode(BaseNode):
|
||||||
|
"""
|
||||||
|
An example of a node with 2 embedded QCheckBox widgets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# set a unique node identifier.
|
||||||
|
__identifier__ = 'nodes.widget'
|
||||||
|
|
||||||
|
# set the initial default node name.
|
||||||
|
NODE_NAME = 'checkbox'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(CheckboxNode, self).__init__()
|
||||||
|
|
||||||
|
# create the checkboxes.
|
||||||
|
self.add_checkbox('cb_1', '', 'Checkbox 1', True)
|
||||||
|
self.add_checkbox('cb_2', '', 'Checkbox 2', False)
|
||||||
|
|
||||||
|
# create input and output port.
|
||||||
|
self.add_input('in', color=(200, 100, 0))
|
||||||
|
self.add_output('out', color=(0, 100, 200))
|
44
borealis.py
44
borealis.py
@ -1,27 +1,27 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# --- Patch QGraphicsScene.setSelectionArea to handle selection arguments ---
|
## --- Patch QGraphicsScene.setSelectionArea to handle selection arguments ---
|
||||||
from Qt import QtWidgets, QtCore, QtGui
|
#from Qt import QtWidgets, QtCore, QtGui
|
||||||
|
#
|
||||||
_original_setSelectionArea = QtWidgets.QGraphicsScene.setSelectionArea
|
#_original_setSelectionArea = QtWidgets.QGraphicsScene.setSelectionArea
|
||||||
|
#
|
||||||
def _patched_setSelectionArea(self, painterPath, second_arg, *args, **kwargs):
|
#def _patched_setSelectionArea(self, painterPath, second_arg, *args, **kwargs):
|
||||||
try:
|
# try:
|
||||||
# Try calling the original method with the provided arguments.
|
# # Try calling the original method with the provided arguments.
|
||||||
return _original_setSelectionArea(self, painterPath, second_arg, *args, **kwargs)
|
# return _original_setSelectionArea(self, painterPath, second_arg, *args, **kwargs)
|
||||||
except TypeError as e:
|
# except TypeError as e:
|
||||||
# If a TypeError is raised, assume the call was made with only a QPainterPath
|
# # If a TypeError is raised, assume the call was made with only a QPainterPath
|
||||||
# and an ItemSelectionMode, and patch it by supplying defaults.
|
# # and an ItemSelectionMode, and patch it by supplying defaults.
|
||||||
# Default operation: ReplaceSelection, default transform: QTransform()
|
# # Default operation: ReplaceSelection, default transform: QTransform()
|
||||||
return _original_setSelectionArea(self, painterPath,
|
# return _original_setSelectionArea(self, painterPath,
|
||||||
QtCore.Qt.ReplaceSelection,
|
# QtCore.Qt.ReplaceSelection,
|
||||||
second_arg,
|
# second_arg,
|
||||||
QtGui.QTransform())
|
# QtGui.QTransform())
|
||||||
|
#
|
||||||
# Monkey-patch the setSelectionArea method.
|
## Monkey-patch the setSelectionArea method.
|
||||||
QtWidgets.QGraphicsScene.setSelectionArea = _patched_setSelectionArea
|
#QtWidgets.QGraphicsScene.setSelectionArea = _patched_setSelectionArea
|
||||||
|
#
|
||||||
# --- End of patch section ---
|
## --- End of patch section ---
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import pkgutil
|
import pkgutil
|
||||||
|
@ -188,7 +188,6 @@ def collect_ocr_data():
|
|||||||
|
|
||||||
# Run OCR
|
# Run OCR
|
||||||
text = pytesseract.image_to_string(processed, config='--psm 4 --oem 1')
|
text = pytesseract.image_to_string(processed, config='--psm 4 --oem 1')
|
||||||
print("OCR Output:", text) # Debugging
|
|
||||||
|
|
||||||
stats = parse_all_stats(text.strip())
|
stats = parse_all_stats(text.strip())
|
||||||
hp_cur, hp_max = stats["hp"]
|
hp_cur, hp_max = stats["hp"]
|
||||||
@ -207,7 +206,8 @@ def collect_ocr_data():
|
|||||||
"exp": exp_val
|
"exp": exp_val
|
||||||
}
|
}
|
||||||
|
|
||||||
print(f"OCR Updated: HP: {hp_cur}/{hp_max}, MP: {mp_cur}/{mp_max}, FP: {fp_cur}/{fp_max}, EXP: {exp_val}") # Debug
|
# DEBUG OUTPUT
|
||||||
|
print(f"Flyff - Character Status: HP: {hp_cur}/{hp_max}, MP: {mp_cur}/{mp_max}, FP: {fp_cur}/{fp_max}, EXP: {exp_val}%")
|
||||||
|
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
@ -286,7 +286,6 @@ class OverlayCanvas(QWidget):
|
|||||||
self.region["y"] = event.y() - self.drag_offset.y()
|
self.region["y"] = event.y() - self.drag_offset.y()
|
||||||
region_lock.unlock()
|
region_lock.unlock()
|
||||||
|
|
||||||
print(f"Region Moved: x={self.region['x']}, y={self.region['y']}, w={self.region['w']}, h={self.region['h']}") # Debugging
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
def mouseReleaseEvent(self, event):
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
Loading…
x
Reference in New Issue
Block a user