rvencore-qt
is a library for building flow-based visual scripting editors for Python with Qt. It comes from the Ryven project and will be the foundation for future Ryven versions, amongst other specialized editors. With ryvencore-qt you can create Ryven-like editors to optimize for a specific domain. Technically, ryvencore-qt
provides a Qt-based frontend for what is now referred to as ryvencore
. However, ryvencore
itself is currently still included in this repository until the API is solid enough to give it its own public repo. ryvencore
might be the base for implementing other frontends in the future.
pip install ryvencore-qt
or from sources
git clone https://github.com/leon-thomm/ryvencore-qt
cd ryvencore-qt
python setup.py install
ryvencore-qt
currently uses QtPy, which is a wrapper for Qt enabling you to choose between different bindings. (However, due to some really bad inheritance restrictions in PyQt5, you cannot run ryvencore-qt
with PyQt5. I plan to use QtPy to enable seamless switching between PySide2 and PySide6.)
Saved flows can be deployed directly on the backend (ryvencore
) which does not have a single dependency so far!
All serialization and loading of projects. Data is stored using json
, and for some parts pickle
.
project: dict = my_session.serialize()
with open(filepath, 'w') as f:
f.write(json.dumps(project))
All information of a node is part of its class. A minimal node definition can be as simple as this
import ryvencore_qt as rc
class PrintNode(rc.Node):
"""Prints your data."""
title = 'Print'
init_inputs = [
rc.NodeInputBP()
]
color = '#A9D5EF'
def update_event(self, inp=-1):
print(self.input(0))
see also example below.
You can register and unregister nodes at any time. Registered nodes can be placed in a flow.
my_session.register_nodes( [ <your_nodes> ] )
You can define macros which have their own flow plus input and output node, which get registered as nodes themselves, just like this
Macros are like normal scripts plus input and output node
which can be edited through the API at any time
self.actions[f'remove input {i}'] = {
'method': self.rem_input,
'data': i,
}
# with some method...
def rem_input(self, index):
self.delete_input(index)
del self.actions[f'remove input {len(self.inputs)}']
You can add custom QWidgets for your nodes.
class MyNode(rc.Node):
main_widget_class = MyNodeMainWidget
main_widget_pos = 'below ports' # alternatively 'between ports'
# ...
See Features.
While data flows should be the most common use case, exec flows (like UnrealEngine BluePrints) are also supported.
import logging
class MyNode(rc.Node):
def __init__(self, params):
super().__init__(params)
self.my_logger = self.new_logger(title='nice log')
def update_event(self, inp=-1):
self.my_logger.log(logging.INFO, 'updated!')
with an update mechanism to build nodes that automatically adapt to change of variables
import logging
class MyNode(rc.Node):
# ...
def __init__(self, params):
super().__init__(params)
self.my_logger = self.new_log(title='nice log')
# assuming a 'messages' var had been created in the flow's script
self.register_var_receiver(name='messages', method=self.new_msg)
def new_msg(self, msgs: list):
self.my_logger.log(logging.INFO, f'received msg: {msgs[-1]}')
All communication between frontend (ryvencore-qt
) and backend (ryvencore
) is based on Qt signals. Therefore, there exists rudimentary threading compatibility, i.e. you can keep your session object in a separate thread to improve concurrency and prevent the backend from being slown down signicantly by the frontend, and all changes you perform directly on the backend are automatically noticed by the frontend. While this is currently extremely experimental and far from production ready, it opens the door to the world of realtime data processing and first successful tests have been made.
import sys
import os
from random import random
os.environ['QT_API'] = 'pyside2' # tells qtpy to use PySide2
import ryvencore_qt as rc
from qtpy.QtWidgets import QMainWindow, QApplication
class PrintNode(rc.Node):
"""Prints your data"""
# all basic properties
title = 'Print'
init_inputs = [
rc.NodeInputBP()
]
init_outputs = []
color = '#A9D5EF'
# see API doc for a full list of properties
# we could also skip the constructor here
def __init__(self, params):
super().__init__(params)
def update_event(self, inp=-1):
data = self.input(0) # get data from the first input
print(data)
class RandNode(rc.Node):
"""Generates random float"""
title = 'Rand'
init_inputs = [
rc.NodeInputBP(dtype=rc.dtypes.Float(default=1.0, bounds=(0, 100)))
]
init_outputs = [
rc.NodeOutputBP()
]
color = '#fcba03'
def update_event(self, inp=-1):
# random float between 0 and value at input
val = random() * self.input(0)
# setting the value of the first output
self.set_output_val(0, val)
if __name__ == "__main__":
# creating the application and a window
app = QApplication()
mw = QMainWindow()
# creating the session, registering, creating script
session = rc.Session()
session.design.set_flow_theme(name='pure light')
session.register_nodes([PrintNode, RandNode])
script = session.create_script('hello world', flow_view_size=[800, 500])
mw.setCentralWidget(session.flow_views[script])
mw.show()
sys.exit(app.exec_())
For a detailed overview visit the docs page. For a precise definition of the flows and features, see Features.
Cheers!
To give a quick overview over the most important class relations, see the below class diagram. The DrawIO diagram file is in the repository.