From 9c2e287b7253d2fc6f9312ddc3811569b7b5f0c1 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Wed, 12 Feb 2025 23:25:02 -0700 Subject: [PATCH] Re-implemented Backdrop Node --- Experimental/accept_reject_example.py | 192 ++++++++++++++++++ .../Orphaned Code/data_collector.py | 0 {Legacy_Code => Legacy}/borealis_overlay.py | 0 .../__pycache__/backdrop_node.cpython-312.pyc | Bin 0 -> 9706 bytes Nodes/__pycache__/basic_nodes.cpython-312.pyc | Bin 0 -> 3188 bytes .../custom_ports_node.cpython-312.pyc | Bin 0 -> 5487 bytes ...lyff_character_status_node.cpython-312.pyc | Bin 0 -> 7910 bytes Nodes/__pycache__/group_node.cpython-312.pyc | Bin 0 -> 957 bytes Nodes/__pycache__/widget_node.cpython-312.pyc | Bin 0 -> 4584 bytes Nodes/backdrop_node.py | 184 +++++++++++++++++ Nodes/basic_nodes.py | 86 ++++++++ Nodes/custom_ports_node.py | 121 +++++++++++ ...node.py => flyff_character_status_node.py} | 11 +- Nodes/group_node.py | 21 ++ Nodes/widget_node.py | 155 ++++++++++++++ borealis.py | 44 ++-- data_collector_v2.py | 5 +- debug_processed.png | Bin 2834 -> 2821 bytes debug_screenshot.png | Bin 14133 -> 13118 bytes 19 files changed, 784 insertions(+), 35 deletions(-) create mode 100644 Experimental/accept_reject_example.py rename {Legacy_Code => Legacy}/Orphaned Code/data_collector.py (100%) rename {Legacy_Code => Legacy}/borealis_overlay.py (100%) create mode 100644 Nodes/__pycache__/backdrop_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/basic_nodes.cpython-312.pyc create mode 100644 Nodes/__pycache__/custom_ports_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/flyff_character_status_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/group_node.cpython-312.pyc create mode 100644 Nodes/__pycache__/widget_node.cpython-312.pyc create mode 100644 Nodes/backdrop_node.py create mode 100644 Nodes/basic_nodes.py create mode 100644 Nodes/custom_ports_node.py rename Nodes/{character_status_node.py => flyff_character_status_node.py} (92%) create mode 100644 Nodes/group_node.py create mode 100644 Nodes/widget_node.py diff --git a/Experimental/accept_reject_example.py b/Experimental/accept_reject_example.py new file mode 100644 index 0000000..fd6f028 --- /dev/null +++ b/Experimental/accept_reject_example.py @@ -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_() \ No newline at end of file diff --git a/Legacy_Code/Orphaned Code/data_collector.py b/Legacy/Orphaned Code/data_collector.py similarity index 100% rename from Legacy_Code/Orphaned Code/data_collector.py rename to Legacy/Orphaned Code/data_collector.py diff --git a/Legacy_Code/borealis_overlay.py b/Legacy/borealis_overlay.py similarity index 100% rename from Legacy_Code/borealis_overlay.py rename to Legacy/borealis_overlay.py diff --git a/Nodes/__pycache__/backdrop_node.cpython-312.pyc b/Nodes/__pycache__/backdrop_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f205ca43a47bcab4ac33e87c5662e6c1a2ac6c2 GIT binary patch literal 9706 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 L?#LemTj}pFGA5qW literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/custom_ports_node.cpython-312.pyc b/Nodes/__pycache__/custom_ports_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ea85b274afeb1374268c51a448e3f79f9debca2 GIT binary patch literal 5487 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 literal 0 HcmV?d00001 diff --git a/Nodes/__pycache__/group_node.cpython-312.pyc b/Nodes/__pycache__/group_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..236137e55da656a3afa381da042fdeb26dab961c GIT binary patch literal 957 zcmZ8fO=}cE5Urk%&FpSAAsPfhlL>+{3JZ#-AVI|#JY=JI8v@NZ-AN|S&Wt^?m|Z;V zAz%WEIVjn`Ai-baNr)hgoa4-6Oyp-x;vK!YV<+BPtqlLNiabG4@7m`5sbh%7FNxRKOi#{GV}kJ=#U zT}J0QcIDs_2Cl}4t250tnC_Z8utu1%ZMlUV*dp%OLvT!4UT%(BwiZe@8sGBjNyzPt zrAG4q7c1gB6t zx`vb^OTk$LsRfbLQLIytX4*)&5K4u#C=H?@qf{1Co>4yzV{OWxXZ*o7Qw`0M*Fi2O`6Q`^B3aQ#^HED_p@>|pY zJXm!&e-bn4Ji>7sK3kO+k2}{tSk9q!<&!o3YGQX{Pjs$!9vxb9-2xOV`AK$Yom1J# z-N}ym(VFRDH8_^i6nS-yno5+`W7Y_jKTheBh8GSpW0bPk$KwgS0OL^>tOZ=CHPlw_ zEYjMo<;DE6vN90s^Dv~;A>x#}sCtc?;xuBu#vBZlun~^8;fJP~R<{IaF0^ZS;i>cO zd%ul>IokyUF+HrSIl3&b;P@rsB^wViq5i78*<0wd)Cg(a=&M)|GZ?DbscO;H2_au# Q?q^|&%l z@N?Xs4rIiHC=eexOIYA0VWKXq_}%%5Ag2a_8mgj(IF$l5Tt%hVrC8(%PK*f-J*}B~ zJjZmv8?b-N$j$yJ#in#?I6IdCY9~_9rLy`kOIeq+bc$(K&KR85lUGmY7Lu+2F7%Z? zo*e|_Jj|mAs^CbezpAZXVQULjBB3K{`LLzjL#6+iV2)1Rp`RsW;JBJ(Q zPf7~3fvlo0Xqnlxu4I9#%%`krMN{<5gwB}G6ph0RgSpH^ZX&m!WMFy^pZTqf6U?4@ zu8R}Ek)%*1Z4huY-?Kjf_>FLfSmi;>Q-my%Uq%R7DUAs5JrgRkl@TValvd^(;qpKk zVZN7g_$wS!aKOJ4mIT-hcHi2V$byRk3q7DK0e4Iw*GViK&j(UjoX{cBDrb6rKNis^(rO3)F_ zxmn#X&>jv|)l@cRsj3r(BLRGN&T=F~K^VlNh$r!w_?|Jp6EOAkq#@(%u&O50nrW)4 zi6^2cH@-b^rvEZr#Th+msi$*>4)@Nyj2FYa?4GEK$J;Y|-Kd2b`Sz{%r^gcrJdT)G z0DNn0noQ{inl3&zG_1x4&JS~?oSKqZl6pF=s`mw>33b7ncFJf5$W6}y z-epq(@Xbx~)aMiFxk?R{3sj5}Xzf>^TR%;6)bKYYhqlH~|Zm+vKcxO}MlE16*^n zv)+n;lR_M=I4J|yVZJ3F^Dp^XU@5@FB@tZ7i?y(n*I2OPbT4r2&?b)UT${2mlO9C8 zSjgSq%PO_!Z zRr;A772Ly^H0g(xL+;9+F029mKx5We(l}hDaZ{PAk@GdZRAUJwmi`}&VRok{aiwPs zRVOaZ?5d1-ST)BBttS1?RtvjUgOJ`5&qsgylYzqt?Qs6c;l3*e_c|os$#bxhoYo92 zY3YX9uKLOz#9qv9h%iF*edHpX46_ypU$NjM1CA}8dIj4gc<@?H}$NU zvQpP{r!I-*0VHJgTFOjKq=ABYL!Gd)9tAQ$_?lNuIMMQiv^Jrq9qO?=)HISRY(E_f z7_Y*H28Jj{E+qk%J2v>rM$|>jOrlzzh{iH*gY4Q2@NvzI5AL7B)s6!0h z#Z65ZaY|_`4Em(g6I$}Bfg!^QTubTmPCZmas+$$nQcWS7Kq?Db&odlPn9HR>BA8%KcbagevI#t!ajLg|*>1AYh@u}t za2NqUhI;Q9%7h zhkYB(kyfb?S#KlZ|0bwzBH3Ly}&?f7#d?y@^~?F?8mZm)<|V71LJf|;5VNaK#F?{`+=Nq-Es>(@BIY~n(Y91;rFPndzt+q z``hfJ?&A+HJc@qr3wnZU6bl=e)ePh`Fqat}2zDaCZQXEzI~iRFFdH?n#4>_pJ z@X=>h*J0EPNt;f+{BZg+2)?-%`|b}X22~mhR30*LZ?=12iNF_Rp-p@*sKp*gD7Ck%jr8$u2h zleu)xF!u3bPBbp!@Y_fTy2kL#6oHUZm}J_bu;UPCp!4rG5M{Q$-tIrTtYgc=-o~=qf#0 zj>F4W3bkL**W63n3cEb#16(Nt@1Y6;4X$!;<2z38H0DwSwowh zzF-*`QJm%~N>4JEH7zY`nQlf^s>i%*Tb0gCp10lC5%*3m-U(RN-4)+X4*kCSJo};X zE_@2`ZypD*?iU2%F;O0qV~@!j8^IldbbDcgK)2Da2}0lPkqv_V#)!X3IClHQ*91Dg G?0*2CM=T%! literal 0 HcmV?d00001 diff --git a/Nodes/backdrop_node.py b/Nodes/backdrop_node.py new file mode 100644 index 0000000..5eaca22 --- /dev/null +++ b/Nodes/backdrop_node.py @@ -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() diff --git a/Nodes/basic_nodes.py b/Nodes/basic_nodes.py new file mode 100644 index 0000000..4d36de1 --- /dev/null +++ b/Nodes/basic_nodes.py @@ -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) \ No newline at end of file diff --git a/Nodes/custom_ports_node.py b/Nodes/custom_ports_node.py new file mode 100644 index 0000000..ac3eb43 --- /dev/null +++ b/Nodes/custom_ports_node.py @@ -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) \ No newline at end of file diff --git a/Nodes/character_status_node.py b/Nodes/flyff_character_status_node.py similarity index 92% rename from Nodes/character_status_node.py rename to Nodes/flyff_character_status_node.py index ab5e0fd..b9d57f4 100644 --- a/Nodes/character_status_node.py +++ b/Nodes/flyff_character_status_node.py @@ -1,13 +1,4 @@ #!/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 Qt import QtCore, QtGui @@ -44,7 +35,7 @@ def get_draw_stat_port(color, border_color=None, alpha=127): return painter_func class CharacterStatusNode(BaseNode): - __identifier__ = 'bunny-lab.io.status_node' + __identifier__ = 'bunny-lab.io.flyff_character_status_node' NODE_NAME = 'Character Status' def __init__(self): diff --git a/Nodes/group_node.py b/Nodes/group_node.py new file mode 100644 index 0000000..9b937b5 --- /dev/null +++ b/Nodes/group_node.py @@ -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') \ No newline at end of file diff --git a/Nodes/widget_node.py b/Nodes/widget_node.py new file mode 100644 index 0000000..eff1ac2 --- /dev/null +++ b/Nodes/widget_node.py @@ -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)) \ No newline at end of file diff --git a/borealis.py b/borealis.py index 0b5c030..dad0a31 100644 --- a/borealis.py +++ b/borealis.py @@ -1,27 +1,27 @@ #!/usr/bin/env python3 -# --- Patch QGraphicsScene.setSelectionArea to handle selection arguments --- -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 --- +## --- Patch QGraphicsScene.setSelectionArea to handle selection arguments --- +#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 diff --git a/data_collector_v2.py b/data_collector_v2.py index abeb253..da9a44e 100644 --- a/data_collector_v2.py +++ b/data_collector_v2.py @@ -188,7 +188,6 @@ def collect_ocr_data(): # Run OCR text = pytesseract.image_to_string(processed, config='--psm 4 --oem 1') - print("OCR Output:", text) # Debugging stats = parse_all_stats(text.strip()) hp_cur, hp_max = stats["hp"] @@ -207,7 +206,8 @@ def collect_ocr_data(): "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) @@ -286,7 +286,6 @@ class OverlayCanvas(QWidget): self.region["y"] = event.y() - self.drag_offset.y() 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() def mouseReleaseEvent(self, event): diff --git a/debug_processed.png b/debug_processed.png index b905d3f16f8514ca838ba7dbbaa2383419dfbcee..4d17f78253d4074bf5116129b304c02165269104 100644 GIT binary patch delta 2664 zcmV-u3YYbg7KIj&B!A3FL_t(|obBA(mg6W4KvC#b|NqO(LnRg42q6p@kvaQeItiDk zK*rZh2mk;80000000000000000Pqr52T+iH{}IDWr+4g!#9A(kgff za-N?>->XUCH6Agf5Rx!714b*>CZ_UDo`Rgk;cn$`*kAwVtEyqNY)kR|Vc#OiUjC9J zKis^Ip@U)HBFJw3k|PAmZn#^2KU)OZ#bt8D^Xv9)XV5mOcHC_8K+))`R87gwlC; zEeAnP<#pwVDgKU+bYc2;iz<{pxWR1^WCw>U$7ijM*?*u%-fuqCx=}M*C)d7AJ$DfARHOwY&)FFHc6Lv#cP)7>aS8{}O6R}SleUN{YW^~PN#aa5GdFO0wP^so4$m!fAM_UM|!4WPM>ZPLsXn78BO_1~XOOEUP z%W~n&VXt)SBgonOCC72iPOtZ;!c*z0Vc#OiUOwYKZi`ujYT>z>+}5(kXnf#fL68q{ zxN?Nbvl7M!DPpPZsA1nCNR#mbE0JIYf19{EfE&{Lg&cgbIE75lpa1Dj?B%I2Ci=HJ zfOen3O~N=Ii%SLiXU6iR*BTneB2IHwk-{tL6oL@{`%}5}Qm^*+r*efS!&t>D@$aX8o zIMw-kX6pPd9RKI!FmOC^Px!jzs9d=Gl+2xYCt>vYy5zXbMRDxwlTy9{_GlOjeOGdv zhPu36&OVrM5=OVZlOr4WsFZzue;C7Ub}CyHj~irRQXbf1orKY8>y@LjRcafo<&eWg z7%T0(aulYoHsGTd`kP@`>nybK$|2tDetxaK8;1YA%Ff9Vf;1_|F;CK?V#Y~Pp7VJ! zjHUKX4r%X@kGMPs1DA6rVN9`Ya+D@z+510=N zW3|naqxpu`oE^K9FlO5OUFADnF|OxT&s0sPQria zvRiVf2l~PpVk#H+eAkmyl%fZH^nK*(^D#OR_SX1DA6rVa&E! za)c22hPSP)x^O%f#wuH01p5qanqFech8IpW_o_kL?1 z+DqG@w^h4s>q>3wIXiYc=IB|6&uyi1h)25ey(>A|Y~^^=KC?K<=E*T`A-NcMv20@7 z$u>`p5awN}Y@D?(ykPnT$$?qEDLKSV{rOefNf-lsQ*vOiuTGAxUHQ0e-RXZ3Mco%9 zhm%tUCt-}V*+sB)8OZ!OZ}pjQy{)nA!7vuvZ{@HLX3E|TnuxZpriC%xe#vnhTJnxvjO#(AB6gmfJBo(&=CIx6H-=F@oM!FM2YJrM$*f z@)^z7_{O|A26@ygZBG>|upDdVJ!5V8xXlZOaznh0xN$2007{=xHv+szYth zjl92OTSFgG7R2k0q#+iP;{iRn5!4TgZBI4t9`%qM0&xfb(AQo?;gzjzl$yns;|P-D z7JKH{vr0a;vG-<~_O=`+Ihw_`&Cg1nH{&-MC%tj~BRSg5JHxx%v-zO6?F&ebxjK(k zV)2byv#Zhv$uU4}%q@Qz(s;)Eh@aH~cob6zAssJsmFLa0G<+(R`myFlk}9cJwmQ}> zR*sqhxzSPoZl5vF$m^|_I#v!zyxi{6w)ci>m!}0u=&1ggT};1U)pWi5g5(gLFPY1u zo7B>*yKu_6`C>ZfZp$6F7j`Rk`k=Pl^SIuoY3)c{bN%6II%ge!6iUpnl&BpAgge zt%l?%x!ew|Jfzp&tF@q~Q$f1?t6{fNFmZRF?uME>GuHY%Pxi`nLlmqWxoo^AA2$@{ zcharjXz!flkllZMc+08yxeE8EzUGh|K3FqMJ#89|5NpDrI?gyrx z1#`Aykg|8h2fc-+mghpJp2*&~tMZGLqgDF*PfLR>ksLYkS4>6qxliKD<~VMzFwwlu zU633F)|;o@>q4c2l)V`I1qzZwaK2vN|G1B>D?=YyQ^Rk>AFCYiB^u%&IV7L#c>RA3 zqFDGoM^uLSc=0Lk${fc1w90-c+i=XZ!{xmtPQ!N{rQ%PMrQ?Fi@0j&N33py`43(K`y1DIO4R2Hyi!DT5GNKR-Qng`!Ii>ll=PwXn7Y%I7lICFJ)hE&t3m06S-16 zBFJ&lGe?2hgp}+(tw^mDC`JQih`M=HN7+4Fx`bm1i6}UaWs#Y z`vz9q`Dhc3qko2inPY(iUe-M%NRNDRwEtqHaWko3kIYI389Zv82~s7cC#2P%18ILq zZuysLeL*4HfKS}8o#sJ3klpk+sw zj4>{eR+m~a-8R!FsjcQV(y(G`M;$?C_RtaJb}EX)nQZq|6PNFv){ZmAxQ6N|Y_6$o zdJi2z?x&(SvS!9qenwfmr+q>0_D?zhQy4vsbA8`}K}Q6+n1bRsBJD>6xtiL;%2eVy z8xQYmXMd0akXuR|4HEiwIN>|Gvg|~((Buf?ym1kL68!p zi{pm*+8AT3?Mi%Q-5Y|mraQ>}gilCc*ZH}DSx#N9?ec`*5v206c#*$avBuciw4KXj zp=~?Y&{5(+ijY05^gKTqZ9DCVAa5Z)a}-CdHGih`U}D?Ud_<7ziOw9ghi0$5TANx` z!Nj2`$a_d%t(3-NFG)SuQCMRTbG!$#Sbc~g1m#^%yH8# zjm@-DK6mF963thn$si1M3QT|2rjNFj2?G5LO+_(A3FwhD+9M}KX&>!*cjFHamw zf`1eteYFC`q>96t%P4ck^qjuUv`ua6mL07OQi|vuQWyNyGY!wQ?Q%qr_mG-7^rT5o zB%FRT^>?e9Db6Oru~Q@XDBTW@9l_U z+9w~Iws&XdCE?qH6eRwdV2(BJl2fHa(mJC*$Z=|BjySWnw9s3Wdv|kbZzXqRl?4ft zVFfCa5Ca8&Fvv;=P~ox>y3eQN8kNP(yV)9sGXJClD0A2JhD#D3TNmfomJCB1-SzB@ zDUL{+Tjld@ehb0srFC|Np^fT*E1hD$pKZMK_q%v$a&r^4u`Ud4G#^&-6S3D_HeB`E zDe=;-9c>Lm9mU1b*eMy_;+bwy5{3l5#nGP0pnrmYKAW4&osKYMXf2Lvr&#~9D=w{F zr;aRI9)>Vosvms`OLrjx**?RYZwaYEDmFgG1jWcu97e$ z=qwIvj2-Nep%I&DFcC&!tLIR9&e5M&m%cqYw5Yd3e7`Oj&hJ zju^eW+82gOM;6B=@2Pp?yVxzMjohfehLL!$xhD*rbO&7hNr^F;HEPS>D>WC#jbW&D zlR`-t&(!fd=Aq5EMsei%B$HtUDw7Zc1%DAM9e_0F{WY5oSKQ3Z(&}Ds z=*g!ShhH1%bhzd$eYciqQCwJETnHmwRc^j(f2Oza7Zmg8N(mC z@m}CJYmYjQnWL72`Wo?yi8j}-;yIw)wit$ceee9mZbjGro8H3l>b242+Iu!?81!wh z)pFZn7=Q07u&Ym>%;SrrI=}{J=dg#PabMVOsyeF2l-fpzJiIt+`p!362W#P=G7yGM zZPczfU!XWj`i7$QKilHc!evs_wtq%gGlx9;YNedRo(xCFr}9C>2FIL2+mVi5bbMjv z$a-IUt}(owp)l4&O>tydV~n-p7yBb&EQlg44? zVlI_7`$u@Jh>sejwq3rLD$f6N1jVt3R~ryhx8DH(00000 l0000000000003aM|9XfgTuPJSxC{UQ002ovPDHLkV1m;ZREPio diff --git a/debug_screenshot.png b/debug_screenshot.png index ad9b0a00af11705887f2a2b0f83137f1b40f606d..199633cb4adc7b5cbe438c984ecef15cfaf3c26d 100644 GIT binary patch literal 13118 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 Y0lA7F+_W;-S^xk507*qoM6N<$f*r=L>i_@% literal 14133 zcmV-5H_FI~P)ZZFAABME^3Apw-CG`pfmK)Q?Tx@(srM4En=Ca!{rQmm_hfTAety0!pH z@n_!^5kycFB~(umJi2Zr&Xk~$&KY9FAZP*te*Oq( zc2RCXN&w)?E1G4h~khtQhM{@cr12ePAZWe950&Td&PfIr% z-ip@C$xK%kx@CfA(1d!#A2&&s_4~(udS>Sr6*U!WzdX|8{IpKFNrVw5D!C~kpcI6F z2#hELjHq(#J6gSI$H_0gI^NNflo=E&j~6I0sT+uT*rM23&69Oi1FFjwUmSIhhoRKA(Br5{3V2g4Z zQh6og+Z9Vl@v+4TLtnoQ37aGN)}NOyW6G33j^P5#Dl(b{9H)V;fjAi;f-z3Yf3G|G zRrN`ii+WtPxdq)BgT&;Xgvmmu6d>f{yWa)ha-1rw{mUs^rXZG7{G`3q1jMeBjkRD5 z0mTTX=mCKyY2A+)VzgUWF-Obc>9m@h7_Ap!VD-GU4ZhSzGNwwD|3R-4+|PnPS`Gua zW&rgBqPGZhS_t4u7S4;1f24+L>cW%~0F;3-gu+rJA_gN;T}TvFTPQamrRXf$A+j*) zqLES28U~Ir17`69%%BSC_l?PVbUI9QV%j6ck1vpUn!di&Kct@=fBc5wHLKR~(rpnu z#AghCvH3)=p&F8;rylX%eVZJvm^QomiG|QB2hqzl<;b^{cmK6+)p~I8-g`$U%${Zj zSvKvF$|n}e;gr{xcm^vVPu?)RX4N`g@Hu*rwHr3??=?hA&3t-R^4)jHNk^v6I{x^A z7|LN(9W+Qklq_K&I-~{CfHc@&LxfUL3NSE27-OO+9WSXpbgJf9acy;7SZ3zEvhfE! zKZE$k(b0*=7fi4v5RL$R{`u$Wk4}FP_4y4xv(sOB#o5QE%)Rgjy%dU<1; zzi%{vRU0?tQ}^A14?va)!my(j?mJ@$_^yjY9H6PmP1@#_ zGBG=NsCn>E%Iixs9`~TW0KPl0zrC6_a^Nt4PHJ8=32k$yjvd=Ue=h_1CyuHsF8O1@ z=-W&nFN_T`y=`6@6Z3+H+5`_beSK-Pv_X9Ve1BknTU8@N2Mz~E|baY9aJl0a^yfJj37Ue>hNok#E2+C6q(3C zDRsGQ0>SZ9HGZF>?!U2eB>iO=fWI$)xf|8Jf3+Jn7Zem^4(-AjiK9vLqA831fOFDTlvBZb7P2lrq80N|PBFJDBRe0*@j=7NHP zjKTc?cuXMosvwITS##EG+Ee)MWkUy#i~JmrzW@;c5`!o~lpslP*lj5Rx5F-c@ag{V zcb=4)s3F6dMnK{h>pZ{h!JS2gg@uEthmSwdYsfdBZX5dlfu(ZqmcNB9ftC6cs>+g5$XhCe(m_}{xt8eH^orDluYVyhMovMpV3JVM4 z-*3HCSG#H*^sT)uI}1RpytX#2XV0D6mBt%z*xR)Je#ed+Vl$6_ldt(>cR>MpqI;*B z27vrfs;e}Cbi@VuOArHOQ-?qaC2-nk(-cqJ>_D?L-w!*L`L=F|ZgV;HZ#p4>tpW2` z-6i2CtAuYo43TN$M*{eG;}>~?LvXoxp1krsRjb#(aq}1%LsrMmyLJ`@2<__$xCdOD zwqjw~-{w?4y+qF9xVT$QNl9U0;Q&ttvAM##_IByg2R!Q7l3~5fC006 z{D|!^o<_@;G>OSv=yAh>EcVsD&JkRdi^^$7s%?L(Xn&U&Y}RJ z`JRAhz_n>B7MA>NPTA8-WP7Fi;%?O?C545By*=rI%@y3Ww@a5Up9@5AxftNNpwp3} zT>!4}CYsD&dJ7mc&RXl8%`z@dz9h5`j8RJD(inq4;j&YoTW~srx?=M(K?&cvkU^cz zshyX%4a8h)@vR+E4Il=!;LWCR=ClA%R8(|bhjw^p-ZK-RCnKLsjpLCh2-~0;;?iKsmJla8L>#XJMPo9u5YB z!3cr6MuL&rkW^U{sSAm2mrWJDrW=gB22vaEK3-v=N#@Rltv%%4?$L{5Ew z=A#22oOWkY22Jn*sG9Tljgi`WX3xUxSpeRAXx7+AANgBaOPb&}0H%4Z-kLwJgEv7- zGFP;qf*hLFj`)3qV<^3~;Mj|guTeqvPe@r)S+Xpvoz3rKK<&U=_Pj8EwhD6H%tr@5 zIPKOzhTZRDK(-O;o93H*@yY=O#_IJL@ij54}{q^SRa+ =U#?;4cz_pCN=eMnXSh* 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