leon-thomm / Ryven

Flow-based visual scripting for Python
https://ryven.org
MIT License
3.81k stars 443 forks source link

set_output_val does nothing/ends def update_event #195

Open Robsel opened 2 months ago

Robsel commented 2 months ago

nodes.py:

from typing import Tuple
from ryven.node_env import *

from random import random

from ryvencore.Flow import Flow
from ryvencore.Session import Session
#from dialog_system import *
#from ... import lambda_function

from random import random
from ryven import *

class Datenbank(Data):
    def __init__(self, value=None, load_from=None):
        super().__init__(value, load_from)
    def serialize(self):
        # Convert self.value to a format that can be easily saved/transferred
        return {"data": self.value}

    def deserialize(self, data):
        # Convert the transferred data back to the original format
        self.value = data["data"]

class RandNode(Node):
    """Generates scaled random float values"""

    title = 'Rand'
    tags = ['random', 'numbers']
    init_inputs = [NodeInputType()]
    init_outputs = [NodeOutputType()]

    def update_event(self, inp=-1):
        self.set_output_val(0,random() * self.input(0).payload)       

def placeholdprint(text):
    print(text)

class PrintNode(Node):
    title = 'Print'
    init_inputs = [NodeInputType()]

    def update_event(self, inp=-1):
        print(self.input(0))
        print('received!')

class Textbox(Node):
    title='Textbox'
    init_inputs = [NodeInputType(type_= 'data', label= 'Textblock:'),
                   NodeInputType(type_= 'data', label= 'Inhalt:')]
    init_outputs = [NodeOutputType(type_= 'data', label='anDa')]

    def __init__(self, params: Tuple[Flow | Session]):
        super().__init__(params)

    def update_event(self, inp=-1):
        print('ok')
        self.set_output_val(0,'lol')
        print('lol')
        self.set_output_val('anDa','anDa')

class Datenbank(Node):
    title='Datenbank'
    init_inputs = [NodeInputType(type_='data', label='Datenbank')]
    init_outputs = [NodeOutputType(type_='data', label='DatenOutput')]

    def __init__(self, params: Tuple[Flow | Session]):
        super().__init__(params)

        self.data = {}

    def update_event(self, inp=-1):
        self.data.update(self.input(0))
        print(self.data)
        self.set_output_val(0,self.data)

export_nodes([
    PrintNode,
    Textbox,
    Datenbank,
    RandNode,
])

Session.register_data_type

@on_gui_load
def load_gui():
    # import gui sources here only
    from . import gui

#if __name__ == '__main__':
 #   letsgo = Session()
  #  Session.register_data_type(Datenbank)
   # run_ryven(letsgo)

gui.py:

from qtpy.QtWidgets import *
from qtpy.QtCore import *

from ryven.gui_env import *

from . import nodes

class ButtonNode_MainWidget(NodeMainWidget, QPushButton):

    def __init__(self, params):
        NodeMainWidget.__init__(self, params)
        QPushButton.__init__(self,'Textblock hinzufügen')

        self.clicked.connect(self.update_node)

@node_gui(nodes.Textbox)
class TextboxGui(NodeGUI):
    main_widget_class = ButtonNode_MainWidget
    main_widget_pos = 'between ports'
    color = '#99dd55'

    input_widget_classes = {
        'textboxname': inp_widgets.Builder.str_line_edit(),
        'textboxbox': inp_widgets.Builder.str_line_edit(),
    }

    style = 'normal'
    color = '#c69a15'

    def __init__(self, params):
        super().__init__(params)

        self.input_widgets[self.node.inputs[-2]]= {'name': 'textboxbox', 'pos': 'besides'}
        self.input_widgets[self.node.inputs[-1]]= {'name': 'textboxname', 'pos': 'below'}

#@node_gui(nodes.woinfoalter)
class woinfoalterGUI(NodeGUI):
    input_widget_classes = {
        'varname': inp_widgets.Builder.str_line_edit(),
    #    'val': inp_widgets.Builder.str_line_edit(),
    }
    # init_input_widgets = {
    #     1: {'name': 'varname', 'pos': 'besides'},
    #     2: {'name': 'val', 'pos': 'besides'}
    # }
    style = 'normal'
    color = '#c69a15'

    def __init__(self, params):
        super().__init__(params)

        self.input_widgets[self.node.inputs[-2]] = {'name': 'varname', 'pos': 'below'}
        #self.input_widgets[self.node.inputs[-1]] = {'name': 'val', 'pos': 'besides'}

class RandSliderWidget(NodeInputWidget, QSlider):
    """a standard Qt slider widget, which updates the node
    input it is attached to, every time the slider value changes"""

    def __init__(self, params):
        NodeInputWidget.__init__(self, params)
        QSlider.__init__(self)

        self.setOrientation(Qt.Horizontal)
        self.setMinimumWidth(100)
        self.setMinimum(0)
        self.setMaximum(100)
        self.setValue(50)
        self.valueChanged.connect(self.value_changed)

    def value_changed(self, val):
        # updates the node input this widget is attached to
        self.update_node_input(Data(val))

    def get_state(self) -> dict:
        # return the state of the widget
        return {'value': self.value()}

    def set_state(self, state: dict):
        # set the state of the widget
        self.setValue(state['value'])

@node_gui(nodes.RandNode)
class RandNodeGui(NodeGUI):
    color = '#fcba03'

    # register the input widget class
    input_widget_classes = { 'slider': RandSliderWidget }

    # attach the slider widget to the first node input
    # display it _below_ the input pin
    init_input_widgets = {
        0: {'name': 'slider', 'pos': 'below'}
    }

I can import the nodes just fine and I've checked for errors in the code but it's not that complicated, any use of set_output_val results in nothing.

I put in prints in the "Textbox" node, and they only ever confirm 'ok', which means as soon as self.set_output_val is called, it stops the rest of the update_event def

I'm sorry for messy code. :°

Robsel commented 1 month ago

i have realized i need to subclass Data, but I don't understand where

leon-thomm commented 1 month ago

It's a bit hard for me to help without precisely understanding the problem.

Robsel commented 1 month ago

Thank you for looking into it! I've managed to understand my mistakes and got it working like I wanted since posting.


class Data_sub(Data):
    def __init__(self, value=None, load_from=None):
        super().__init__(value, load_from)
    def serialize(self, data):
        self.value ={"data": data}

    def deserialize(self):
        return self.value["data"]

After subclassing Data to serialize what I want to transfer to another node into a Data object I can then send via


self.set_output_val(index: int, data: Data)

To access the sent data via self.input(index: int) I needed to add .payload which isn't specified in the Readme. Without that it is a Data object.

Before being able to use the payload, it needs to be deserialized still:


class PrintNode(Node):
    title = 'Print'
    init_inputs = [NodeInputType()]
    def __init__(self, params: Tuple[Flow | Session]):
        self.dat= Data_sub()
        super().__init__(params)

    def update_event(self, inp=-1):

        print(self.input(0).payload) #prints my Data_sub object

        self.dat=self.input(0)
        k = self.dat.deserialize()
        print(k) #prints the data

With this I managed to transfer a dict between nodes. It seems superfluous to have to subclass Data instead of (de)serialize being a def to call onto to send custom data between nodes. There is also no error in console when using set_output_val() wrong, which would be helpful.

Unrelated, but before I struggle for another month I'd rather ask: I plan to use Ryven as a Design GUI, so one can model "a conversation" via nodes with content and paths between them. Is there a quick way to export this, with names for the paths and the nodes (with content)?

leon-thomm commented 1 month ago

I don't fully understand what you intend to use this serialize/deserialize for, there is no need to serialize data structure when passing them from one node to another. You can just pass the Data object to set_output_val and retrieve it in the receiving node with input().

I plan to use Ryven as a Design GUI, so one can model "a conversation" via nodes with content and paths between them. Is there a quick way to export this, with names for the paths and the nodes (with content)?

So Ryven saves projects in a JSON format that should be easy to work with for further processing. Otherwise, you can build it directly into the nodes. For example:

say we have a node "oo:plus:o" (2 in, 1 out) and "oo:minus:o" and they apply "+" and "-" on numeric input values. Now you want to add the ability to instead generate a parameterized expression, e.g."(inp1 + inp2) - (inp3 + inp4)". I would probably define a new data type class Expr(Data) (as opposed to Numeric(Data) or something) which you treat differently in your update_event() in plus/minus.[^1] This is just an example to get you started, there are many completely different approaches.

[^1]: The very Pythonic way would be to not change your plus/minus nodes at all but overload the __add__ and __sub__ methods in Expr to secretly generate the expression instead or performing the calculation, when the respective operations are applied to two Exprs, but this might make life more difficult than it needs to be.

Robsel commented 1 month ago

You can just pass the Data object to set_output_val and retrieve it in the receiving node with input().

My point was that I need to subclass Data in the first place to pass custom data, an info I only found thoroughly searching and reading the source code. For example arithmatic is a complex data structure:


def update_event(self):

self.arithmatic.serialize
self.set_output_val(0, self.arithmatic)

But I guess at that point it would be just as possible to implement this into set_output_val, so the Data subclass didnt have to exist in the first place.

I will probably use the JSON provided, but thats a great way to explain it :) Thank you for the feedback, it's much appreciated.