diff --git a/.github/workflows/publish-ryven.yml b/.github/workflows/publish-ryven.yml
index e654e50b..047a01f9 100644
--- a/.github/workflows/publish-ryven.yml
+++ b/.github/workflows/publish-ryven.yml
@@ -1,4 +1,4 @@
-name: Publish Ryven wheel to TestPyPi and PyPi
+name: Publish Ryven wheel to PyPi
on:
create:
tags:
@@ -18,12 +18,12 @@ jobs:
- name: Build binary wheel and source tarball
run: python -m build --sdist --wheel --outdir dist/
working-directory: ./ryven-editor
- - name: Publish distribution to TestPyPI
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- password: ${{ secrets.GH_AC_RYVEN_TEST_PYPI_API_TOKEN }}
- repository-url: https://test.pypi.org/legacy/
- packages-dir: ryven-editor/dist/
+ # - name: Publish distribution to TestPyPI
+ # uses: pypa/gh-action-pypi-publish@release/v1
+ # with:
+ # password: ${{ secrets.GH_AC_RYVEN_TEST_PYPI_API_TOKEN }}
+ # repository-url: https://test.pypi.org/legacy/
+ # packages-dir: ryven-editor/dist/
- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
diff --git a/.github/workflows/publish-ryvencore-qt.yml b/.github/workflows/publish-ryvencore-qt.yml
index 0de53e94..d36a8ac5 100644
--- a/.github/workflows/publish-ryvencore-qt.yml
+++ b/.github/workflows/publish-ryvencore-qt.yml
@@ -1,4 +1,4 @@
-name: Publish ryvencore-qt wheel to TestPyPi and PyPi
+name: Publish ryvencore-qt wheel to PyPi
on:
create:
tags:
@@ -18,12 +18,12 @@ jobs:
- name: Build binary wheel and source tarball
run: python -m build --sdist --wheel --outdir dist/
working-directory: ./ryvencore-qt
- - name: Publish distribution to TestPyPI
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- password: ${{ secrets.GH_AC_RCQT_TEST_PYPI_API_TOKEN }}
- repository-url: https://test.pypi.org/legacy/
- packages-dir: ryvencore-qt/dist/
+ # - name: Publish distribution to TestPyPI
+ # uses: pypa/gh-action-pypi-publish@release/v1
+ # with:
+ # password: ${{ secrets.GH_AC_RCQT_TEST_PYPI_API_TOKEN }}
+ # repository-url: https://test.pypi.org/legacy/
+ # packages-dir: ryvencore-qt/dist/
- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
diff --git a/.github/workflows/type-checking.yml b/.github/workflows/type-checking.yml
new file mode 100644
index 00000000..c236d6bc
--- /dev/null
+++ b/.github/workflows/type-checking.yml
@@ -0,0 +1,39 @@
+name: Type-check Ryven and ryvencore-qt using mypy
+on:
+ push:
+ branches:
+ - main
+ - dev
+ pull_request:
+ branches:
+ - '*'
+ workflow_dispatch:
+jobs:
+ Type-check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version: 3.10.x
+ architecture: x64
+ # if we are not on the main branch, install ryvencore from the dev branch from github
+ # otherise, install ryvencore from the main branch from github
+ - if: github.ref != 'refs/heads/main'
+ run: python -m pip install git+https://github.com/leon-thomm/ryvencore.git@dev
+ - if: github.ref == 'refs/heads/main'
+ run: python -m pip install git+https://github.com/leon-thomm/ryvencore.git@main
+ - name: Install ryvencore-qt dependencies
+ run: python -m pip install . --user
+ working-directory: ./ryvencore-qt
+ - name: Install Ryven dependencies
+ run: python -m pip install . --user
+ working-directory: ./ryven-editor
+ - name: Uninstall ryvencore-qt and ryven (keep dependencies)
+ run: python -m pip uninstall ryven ryvencore-qt --yes
+ working-directory: ./ryven-editor
+ - name: Install type-checking dependencies
+ run: python -m pip install mypy pyside2 pyside6 PySide6-stubs types-Pygments --user
+ - name: Typecheck
+ run: mypy
+ working-directory: .
diff --git a/README.md b/README.md
index 08417e42..6400f9ea 100644
--- a/README.md
+++ b/README.md
@@ -111,18 +111,21 @@ For more information, visit https://leon-thomm.github.io/ryvencore/
quick start into to developing node packages
+A Ryven nodes package is simply a typical Python package which contains at least a `nodes.py` file, and calls the Ryven node API to expose node definitions.
+
Navigate to `~/.ryven/nodes/` and create a sub-directory of the following structure
```
~/.ryven/nodes
└── your_nodes_pkg_1
+ ├── __init__.py
├── nodes.py
└── gui.py
```
-With the following contents:
+with the following contents:
-`nodes.py`
+`nodes.py`:
```python
from ryven.node_env import *
@@ -132,21 +135,25 @@ from ryven.node_env import *
export_nodes([
# list your node classes here
])
+
+
+@on_gui_load
+def load_gui():
+ # import gui sources here only
+ from . import gui
```
-`gui.py`
+and `gui.py`:
```python
from ryven.gui_env import *
-# your node gui definitions go here
+from . import nodes
-export_guis([
- # list your node gui classes here
-])
+# your node gui definitions go here
```
-You can now start defining your own nodes. Let's define two basic nodes. One which generates random numbers
+You can now start defining your own nodes. Let's define two basic nodes. One which generates random numbers...
```python
from random import random
@@ -165,7 +172,7 @@ class RandNode(Node):
)
```
-and another one which prints them
+...and another one which prints them
```python
class PrintNode(Node):
@@ -185,22 +192,24 @@ export_nodes([
])
```
-That's it! You can import your nodes package in Ryven (`File -> Import Nodes`), place the nodes in the graph, and wire them up. Now add a `val` node and connect it to the `Rand` node, to feed its input with data. If you type a number into the widget of the `val` node and hit enter, it will send the number to the `Rand` node, which will send a scaled random number to the `Print` node, which will print it to the standard output.
+That's it! You can import your nodes package in Ryven (`File -> Import Nodes`), place the nodes in the graph, and wire them up. Add a `val` node and connect it to the `Rand` node, to feed its input with data. If you type a number into the widget of the `val` node and hit enter, it will send the number to the `Rand` node, which will send a scaled random number to the `Print` node, which will print it to the standard output.
Notice that the standard output is by default the in-editor console, which you can access at the very bottom of the editor window (drag the blue handle up to make it visible).
### Adding GUI
-You can now spice up your nodes with some GUI. Ryven runs on Qt, using the [qtpy](https://github.com/spyder-ide/qtpy) library. You can configure the GUI of your nodes in a separate `gui.py` file, and add custom Qt widgets to your nodes. Make sure to always clearly separate the node logic from the GUI. The `nodes.py` file should NOT have any dependency to Qt. One of the central features of Ryven is to run projects headless (on ryvencore) without any GUI dependencies, if your node packages obey the rules.
+You can now spice up your nodes with some GUI. Ryven runs on Qt, using either PySide2 or PySide6 (through the [qtpy](https://github.com/spyder-ide/qtpy) library). You can configure the GUI of your nodes in a separate file, and add custom Qt widgets to your nodes. Make sure to always clearly separate the node logic from the GUI components. One of the central features of Ryven is to run projects headless (on ryvencore) without any GUI dependencies. In order for this to work, your `nodes.py` files should never depend on Qt directly. Instead, you can attach custom GUI to your nodes from the GUI files as shown below.
Let's give them some color and add a slider to the `Rand` node, in `gui.py`:
```python
-from ryven.gui_env import *
-
from qtpy.QtWidgets import QSlider
from qtpy.QtCore import Qt
+from ryven.gui_env import *
+
+from . import nodes
+
class RandSliderWidget(NodeInputWidget, QSlider):
"""a standard Qt slider widget, which updates the node
@@ -230,6 +239,7 @@ class RandSliderWidget(NodeInputWidget, QSlider):
self.setValue(state['value'])
+@node_gui(nodes.RandNode)
class RandNodeGui(NodeGUI):
color = '#fcba03'
@@ -241,25 +251,11 @@ class RandNodeGui(NodeGUI):
init_input_widgets = {
0: {'name': 'slider', 'pos': 'below'}
}
-
-export_guis([
- RandNodeGui,
-])
-```
-
-and you now just need to reference the `RandNodeGUI` in `nodes.py`:
-
-```python
-guis = import_guis(__file__)
-
-class RandNode(Node):
- ...
- GUI = guis.RandNodeGui
```
-The value provided by an input widget (through `self.update_node_input(val)`) will be returned in `Node` by `self.input(0)` only when the corresponding input is _not_ connected. Otherwise the value of the connected output will be returned.
+and this is it! Ryven will now register `RandNodeGui` as "GUI class" of the `RandNode` class, which serves as a container for all UI things. Your can add custom primary ("main") widgets to your nodes, input widgets, and further customize the look of the nodes.
-So now we can reconstruct the previous example, but we don't need to connect the `val` node to the `Rand` node anymore. Change the slider and see how many different random values are printed.
+The value provided by an input widget (e.g. `self.update_node_input(val)` above) will be returned in the node, when calling `input()` (e.g. `self.input(0)` in the `RandNode`), but only when the corresponding input is _not connected_. Otherwise, the value of the connected output will be returned.
diff --git a/debug.py b/debug.py
new file mode 100755
index 00000000..970f267b
--- /dev/null
+++ b/debug.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+
+# manually debug ryven; ensure that the following packages are
+# not installed in the current environment:
+# * ryven
+# * ryvencore-qt'
+# * ryvencore
+
+RYVEN_PATH = './ryven-editor'
+RYVEN_QT_PATH = './ryvencore-qt'
+RYVENCORE_PATH = '../ryvencore'
+
+import sys
+
+sys.path.insert(0, RYVEN_PATH)
+sys.path.insert(0, RYVEN_QT_PATH)
+sys.path.insert(0, RYVENCORE_PATH)
+
+from ryven import run_ryven
+
+if __name__ == '__main__':
+ run_ryven(
+ f"{RYVEN_PATH}/ryven/example_projects/matrices.json",
+ nodes=[
+ f"{RYVEN_PATH}/ryven/example_nodes/examples",
+ f"{RYVEN_PATH}/ryven/example_nodes/linalg",
+ ],
+ qt_api='pyside6',
+ show_dialog=False,
+ )
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 00000000..4da6fdde
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,21 @@
+# mypy configuration, type-checking both the Ryven editor, and the
+# ryvencore-qt library. ryvencore must be installed for this to work.
+# Simply run `mypy` in the Ryven root directory to check the code.
+
+[mypy]
+warn_return_any = True
+warn_unused_configs = True
+warn_unused_ignores = True
+files = ryven-editor/ryven, ryvencore-qt/ryvencore_qt
+
+[mypy-ryven.*]
+check_untyped_defs = False
+
+[mypy-ryven.example_nodes.*]
+ignore_errors = True
+
+[mypy-ryven.main.packages.built_in.*]
+ignore_errors = True
+
+[mypy-ryven.gui.uic.*]
+ignore_errors = True
diff --git a/ryven-editor/pyproject.toml b/ryven-editor/pyproject.toml
deleted file mode 100644
index f8d89757..00000000
--- a/ryven-editor/pyproject.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-[build-system]
-requires = [
- "setuptools",
- "wheel"
-]
-build-backend = "setuptools.build_meta"
\ No newline at end of file
diff --git a/ryven-editor/ryven/example_nodes/examples/README.md b/ryven-editor/ryven/example_nodes/examples/README.md
new file mode 100644
index 00000000..c121bbaa
--- /dev/null
+++ b/ryven-editor/ryven/example_nodes/examples/README.md
@@ -0,0 +1,2 @@
+This package contains a bunch of nodes for different purposes, to showcase some of Ryven's features.
+Some simple nodes are found in `basic_operators.py`, while more advanced ideas are implemented in `special_nodes.py`.
\ No newline at end of file
diff --git a/ryven-editor/ryven/example_nodes/sub_nodes_example/__init__.py b/ryven-editor/ryven/example_nodes/examples/__init__.py
similarity index 100%
rename from ryven-editor/ryven/example_nodes/sub_nodes_example/__init__.py
rename to ryven-editor/ryven/example_nodes/examples/__init__.py
diff --git a/ryven-editor/ryven/example_nodes/std/basic_operators.py b/ryven-editor/ryven/example_nodes/examples/basic_operators.py
similarity index 93%
rename from ryven-editor/ryven/example_nodes/std/basic_operators.py
rename to ryven-editor/ryven/example_nodes/examples/basic_operators.py
index 5b41221b..8e060c13 100644
--- a/ryven-editor/ryven/example_nodes/std/basic_operators.py
+++ b/ryven-editor/ryven/example_nodes/examples/basic_operators.py
@@ -1,14 +1,12 @@
from ryven.node_env import *
-guis = import_guis(__file__)
-
class OperatorNodeBase(Node):
"""
Base class for nodes implementing a binary operation.
"""
- version = 'v0.2'
+ version = 'v0.3'
init_inputs = [
NodeInputType(),
NodeInputType(),
@@ -16,7 +14,6 @@ class OperatorNodeBase(Node):
init_outputs = [
NodeOutputType(),
]
- GUI = guis.OperatorNodeBaseGui
def __init__(self, params):
super().__init__(params)
@@ -50,7 +47,7 @@ def apply_op(self, elements: list):
class LogicNodeBase(OperatorNodeBase):
- GUI = guis.LogicNodeBaseGui
+ pass
class NOT_Node(LogicNodeBase):
@@ -123,7 +120,7 @@ def apply_op(self, elements: list):
class ArithmeticNodeBase(OperatorNodeBase):
- GUI = guis.ArithNodeBaseGui
+ pass
class Plus_Node(ArithmeticNodeBase):
@@ -194,7 +191,6 @@ def apply_op(self, elements: list):
class ComparatorNodeBase(OperatorNodeBase):
- GUI = guis.CompNodeBaseGui
def apply_op(self, elements: list):
# if len(elements) > 0:
@@ -264,9 +260,20 @@ def comp(self, a, b) -> bool:
export
"""
-
-nodes = [
+node_types = [
*logic_nodes,
*arithmetic_nodes,
*comparator_nodes,
]
+
+# account for old package name
+for n in node_types:
+ n.legacy_identifiers = [
+ *getattr(n, 'legacy_identifiers', []),
+ f'std.{n.__class__.__name__}',
+ ]
+
+export_nodes(
+ node_types=node_types,
+ sub_pkg_name='basic_operators'
+)
diff --git a/ryven-editor/ryven/example_nodes/std/control_structures.py b/ryven-editor/ryven/example_nodes/examples/control_structures.py
similarity index 92%
rename from ryven-editor/ryven/example_nodes/std/control_structures.py
rename to ryven-editor/ryven/example_nodes/examples/control_structures.py
index 9dd19d09..31009d5e 100644
--- a/ryven-editor/ryven/example_nodes/std/control_structures.py
+++ b/ryven-editor/ryven/example_nodes/examples/control_structures.py
@@ -1,11 +1,9 @@
from ryven.node_env import *
-guis = import_guis(__file__)
-
class CSNodeBase(Node):
- version = 'v0.2'
- GUI = guis.CSNodeBaseGui
+ version = 'v0.3'
+
class If_Node(CSNodeBase):
title = 'branch'
@@ -40,7 +38,6 @@ class ForLoop_Node(CSNodeBase):
NodeOutputType('i', type_='data'),
NodeOutputType('finished', type_='exec'),
]
- GUI = guis.ForLoopGui
def __init__(self, params):
super().__init__(params)
@@ -103,7 +100,6 @@ class ForEachLoop_Node(CSNodeBase):
NodeOutputType('e', type_='data'),
NodeOutputType('finished', type_='exec'),
]
- GUI = guis.ForEachLoopGui
def update_event(self, inp=-1):
for e in self.input(0).payload:
@@ -148,11 +144,22 @@ def update_event(self, inp=-1):
self.exec_output(0)
self.exec_output(1)
-
-nodes = [
+node_types = [
If_Node,
ForLoop_Node,
ForEachLoop_Node,
WhileLoop_Node,
DoWhileLoop_Node,
]
+
+# account for old package name
+for n in node_types:
+ n.legacy_identifiers = [
+ *getattr(n, 'legacy_identifiers', []),
+ f'std.{n.__class__.__name__}',
+ ]
+
+export_nodes(
+ node_types=node_types,
+ sub_pkg_name='control_structures'
+)
diff --git a/ryven-editor/ryven/example_nodes/std/gui.py b/ryven-editor/ryven/example_nodes/examples/gui.py
similarity index 93%
rename from ryven-editor/ryven/example_nodes/std/gui.py
rename to ryven-editor/ryven/example_nodes/examples/gui.py
index 8d332bd3..c54e5a24 100644
--- a/ryven-editor/ryven/example_nodes/std/gui.py
+++ b/ryven-editor/ryven/example_nodes/examples/gui.py
@@ -1,14 +1,16 @@
import re
-from ryven.gui_env import *
-
-from special_nodes import *
-
from qtpy.QtGui import QFont
from qtpy.QtCore import Qt, Signal, QEvent
from qtpy.QtWidgets import QPushButton, QComboBox, QSlider, QTextEdit, QPlainTextEdit, QWidget, QVBoxLayout, QLineEdit, \
QDialog, QMessageBox
+from ryven.gui_env import *
+
+from . import special_nodes as special_nodes
+from . import basic_operators as operator_nodes
+from . import control_structures as control_nodes
+
"""
generic base classes
@@ -24,6 +26,7 @@ class GuiBase(NodeGUI):
"""
+@node_gui(operator_nodes.OperatorNodeBase)
class OperatorNodeBaseGui(GuiBase):
input_widget_classes = {
'in': inp_widgets.Builder.evaled_line_edit(size='s', resizing=True),
@@ -68,14 +71,17 @@ def rebuild_remove_actions(self):
{'method': self.remove_operand_input, 'data': i}
+@node_gui(operator_nodes.LogicNodeBase)
class LogicNodeBaseGui(OperatorNodeBaseGui):
color = '#f58142'
+@node_gui(operator_nodes.ArithmeticNodeBase)
class ArithNodeBaseGui(OperatorNodeBaseGui):
color = '#58db53'
+@node_gui(operator_nodes.ComparatorNodeBase)
class CompNodeBaseGui(OperatorNodeBaseGui):
color = '#a1574c'
@@ -85,11 +91,13 @@ class CompNodeBaseGui(OperatorNodeBaseGui):
"""
+@node_gui(control_nodes.CSNodeBase)
class CSNodeBaseGui(GuiBase):
style = 'normal'
color = '#b33a27'
+@node_gui(control_nodes.ForLoop_Node)
class ForLoopGui(CSNodeBaseGui):
input_widget_classes = {
'RangeFrom': inp_widgets.Builder.int_spinbox(0, (0, 1000000)),
@@ -127,6 +135,7 @@ def rebuild_remove_actions(self):
{'method': self.remove_dimension, 'data': i + 1}
+@node_gui(control_nodes.ForEachLoop_Node)
class ForEachLoopGui(CSNodeBaseGui):
input_widget_classes = {
'List': inp_widgets.Builder.evaled_line_edit(),
@@ -141,10 +150,12 @@ class ForEachLoopGui(CSNodeBaseGui):
"""
+@node_gui(special_nodes.NodeBase)
class SpecialNodeGuiBase(GuiBase):
color = '#FFCA00'
+@node_gui(special_nodes.DualNodeBase)
class DualNodeBaseGui(SpecialNodeGuiBase):
def initialized(self):
super().initialized()
@@ -172,6 +183,7 @@ def make_active(self):
self.node.make_active()
+@node_gui(special_nodes.Checkpoint_Node)
class CheckpointNodeGui(DualNodeBaseGui):
style = 'small'
display_title = ''
@@ -197,7 +209,7 @@ def rebuild_remove_actions(self):
{'method': self.remove_output, 'data': i}
-class ButtonNode_MainWidget(QPushButton, NodeMainWidget):
+class ButtonNode_MainWidget(NodeMainWidget, QPushButton):
def __init__(self, params):
NodeMainWidget.__init__(self, params)
@@ -206,6 +218,7 @@ def __init__(self, params):
self.clicked.connect(self.update_node)
+@node_gui(special_nodes.Button_Node)
class ButtonNodeGui(SpecialNodeGuiBase):
main_widget_class = ButtonNode_MainWidget
main_widget_pos = 'between ports'
@@ -221,6 +234,7 @@ def __init__(self, params):
self.clicked.connect(self.node.toggle)
+@node_gui(special_nodes.Clock_Node)
class ClockNodeGui(SpecialNodeGuiBase):
main_widget_class = ClockNode_MainWidget
main_widget_pos = 'below ports'
@@ -247,6 +261,7 @@ def stop(self):
self.node.stop()
+@node_gui(special_nodes.Log_Node)
class LogNodeGui(SpecialNodeGuiBase):
color = '#5d95de'
@@ -265,6 +280,7 @@ def value_changed(self, v):
self.update_node()
+@node_gui(special_nodes.Slider_Node)
class SliderNodeGui(SpecialNodeGuiBase):
main_widget_class = SliderNode_MainWidget
main_widget_pos = 'below ports'
@@ -281,6 +297,7 @@ def initialized(self):
self.main_widget().setValue(self.node.val*1000)
+@node_gui(special_nodes._DynamicPorts_Node)
class DynamicPortsGui(SpecialNodeGuiBase):
def __init__(self, params):
super().__init__(params)
@@ -334,6 +351,7 @@ def set_state(self, data: dict):
self.setPlainText(data['text'])
+@node_gui(special_nodes.Exec_Node)
class ExecNodeGui(DynamicPortsGui):
main_widget_class = ExecNode_MainWidget
main_widget_pos = 'between ports'
@@ -362,6 +380,7 @@ def set_state(self, data: dict):
self.setPlainText(data['text'])
+@node_gui(special_nodes.Eval_Node)
class EvalNodeGui(SpecialNodeGuiBase):
main_widget_class = EvalNode_MainWidget
main_widget_pos = 'between ports'
@@ -383,33 +402,6 @@ def remove_input(self, index: int):
del self.actions['remove input'][str(index)]
-class InterpreterConsole(NodeMainWidget, QWidget):
- def __init__(self, params):
- NodeMainWidget.__init__(self, params)
- QWidget.__init__(self)
-
- self.inp_line_edit = ConsoleInpLineEdit()
- self.output_text_edit = ConsoleOutDisplay()
-
- self.inp_line_edit.returned.connect(self.node.process_input)
-
- self.setLayout(QVBoxLayout())
- self.layout().addWidget(self.output_text_edit)
- self.layout().addWidget(self.inp_line_edit)
-
- self.last_hist_len = 0
-
- def interp_updated(self):
-
- if self.last_hist_len < len(self.node.hist):
- self.output_text_edit.appendPlainText('\n'.join(self.node.hist[self.last_hist_len:]))
- else:
- self.output_text_edit.clear()
- self.output_text_edit.setPlainText('\n'.join(self.node.hist))
-
- self.last_hist_len = len(self.node.hist)
-
-
class ConsoleInpLineEdit(QLineEdit):
returned = Signal(str)
@@ -483,11 +475,40 @@ def __init__(self):
self.setFont(QFont('Source Code Pro', 9))
+class InterpreterConsole(NodeMainWidget, QWidget):
+ def __init__(self, params):
+ NodeMainWidget.__init__(self, params)
+ QWidget.__init__(self)
+
+ self.inp_line_edit = ConsoleInpLineEdit()
+ self.output_text_edit = ConsoleOutDisplay()
+
+ self.inp_line_edit.returned.connect(self.node.process_input)
+
+ self.setLayout(QVBoxLayout())
+ self.layout().addWidget(self.output_text_edit)
+ self.layout().addWidget(self.inp_line_edit)
+
+ self.last_hist_len = 0
+
+ def interp_updated(self):
+
+ if self.last_hist_len < len(self.node.hist):
+ self.output_text_edit.appendPlainText('\n'.join(self.node.hist[self.last_hist_len:]))
+ else:
+ self.output_text_edit.clear()
+ self.output_text_edit.setPlainText('\n'.join(self.node.hist))
+
+ self.last_hist_len = len(self.node.hist)
+
+
+@node_gui(special_nodes.Interpreter_Node)
class InterpreterConsoleGui(SpecialNodeGuiBase):
main_widget_class = InterpreterConsole
main_widget_pos = 'between ports'
+@node_gui(special_nodes.Storage_Node)
class StorageNodeGui(SpecialNodeGuiBase):
color = '#aadd55'
@@ -500,6 +521,7 @@ def clear(self):
self.node.clear()
+@node_gui(special_nodes.LinkIN_Node)
class LinkIN_NodeGui(SpecialNodeGuiBase):
def __init__(self, params):
super().__init__(params)
@@ -525,6 +547,7 @@ def remove_inp(self, index: int):
del self.actions['remove inp'][str(index)]
+@node_gui(special_nodes.LinkOUT_Node)
class LinkOUT_NodeGui(SpecialNodeGuiBase):
class IDInpDialog(QDialog):
@@ -550,7 +573,7 @@ def link_to_ID(self):
d.exec_()
if d.id_str is not None:
- n = LinkIN_Node.INSTANCES.get(d.id_str)
+ n = special_nodes.LinkIN_Node.INSTANCES.get(d.id_str)
if n is not None:
self.node.link_to(n)
else:
@@ -559,37 +582,3 @@ def link_to_ID(self):
title='link failed',
text=f'No node with ID "{d.id_str}" found'
)
-
-
-"""
- export
-"""
-
-
-export_guis([
- DualNodeBaseGui,
-
- CheckpointNodeGui,
- OperatorNodeBaseGui,
- LogicNodeBaseGui,
- ArithNodeBaseGui,
- CompNodeBaseGui,
-
- CSNodeBaseGui,
- ForLoopGui,
- ForEachLoopGui,
-
- SpecialNodeGuiBase,
- ButtonNodeGui,
- ClockNodeGui,
- LogNodeGui,
- SliderNodeGui,
- DynamicPortsGui,
- ExecNodeGui,
- EvalNodeGui,
- InterpreterConsoleGui,
- StorageNodeGui,
- LinkIN_NodeGui,
- LinkOUT_NodeGui,
-])
-
diff --git a/ryven-editor/ryven/example_nodes/examples/nodes.py b/ryven-editor/ryven/example_nodes/examples/nodes.py
new file mode 100644
index 00000000..850f40e5
--- /dev/null
+++ b/ryven-editor/ryven/example_nodes/examples/nodes.py
@@ -0,0 +1,10 @@
+from ryven.node_env import on_gui_load
+
+from . import special_nodes
+from . import basic_operators
+from . import control_structures
+
+
+@on_gui_load
+def load_gui():
+ from . import gui
diff --git a/ryven-editor/ryven/example_nodes/std/special_nodes.py b/ryven-editor/ryven/example_nodes/examples/special_nodes.py
similarity index 94%
rename from ryven-editor/ryven/example_nodes/std/special_nodes.py
rename to ryven-editor/ryven/example_nodes/examples/special_nodes.py
index 03f2f255..a539fdb0 100644
--- a/ryven-editor/ryven/example_nodes/std/special_nodes.py
+++ b/ryven-editor/ryven/example_nodes/examples/special_nodes.py
@@ -1,3 +1,4 @@
+from typing import Dict, Set
import code
from contextlib import redirect_stdout, redirect_stderr
from packaging.version import Version
@@ -5,12 +6,9 @@
from ryvencore.addons.Logging import LoggingAddon
from ryven.node_env import *
-guis = import_guis(__file__)
-
class NodeBase(Node):
- version = 'v0.2'
- GUI = guis.SpecialNodeGuiBase
+ version = 'v0.3'
def have_gui(self):
return hasattr(self, 'gui')
@@ -19,8 +17,6 @@ def have_gui(self):
class DualNodeBase(NodeBase):
"""For nodes that can be active and passive"""
- GUI = guis.DualNodeBaseGui
-
def __init__(self, params, active=True):
super().__init__(params)
@@ -55,8 +51,7 @@ class Checkpoint_Node(DualNodeBase):
init_outputs = [
NodeOutputType(type_='data'),
]
- GUI = guis.CheckpointNodeGui
-
+
def __init__(self, params):
super().__init__(params)
@@ -117,7 +112,6 @@ class Button_Node(NodeBase):
init_outputs = [
NodeOutputType(type_='exec')
]
- GUI = guis.ButtonNodeGui
def update_event(self, inp=-1):
self.exec_output(0)
@@ -153,10 +147,9 @@ class Log_Node(DualNodeBase):
init_outputs = [
NodeOutputType(type_='exec'),
]
- GUI = guis.LogNodeGui
- logs = {} # {int: Logger}
- in_use = set() # make sure we don't reuse numbers on copy & paste
+ logs: Dict[int, logging.Logger] = {} # {int: Logger}
+ in_use: Set[int] = set() # make sure we don't reuse numbers on copy & paste
def __init__(self, params):
super().__init__(params, active=True)
@@ -202,8 +195,11 @@ def set_state(self, data: dict, version):
# the logging addon will have re-created the logger
# for us already
- self.logs[self.number] = \
- self.logs_ext().new_logger(self, 'Log Node')
+ l = self.logs_ext().new_logger(self, 'Log Node')
+ if l is None:
+ print(f'WARNING: logger {self.number} for Log Node {self} already exists')
+ else:
+ self.logs[self.number] = l
class Clock_Node(NodeBase):
@@ -215,7 +211,6 @@ class Clock_Node(NodeBase):
init_outputs = [
NodeOutputType(type_='exec')
]
- GUI = guis.ClockNodeGui
# When running with GUI, this node uses QTime which doesn't
# block the GUI. When running without GUI, it uses time.sleep()
@@ -282,7 +277,6 @@ class Slider_Node(NodeBase):
init_outputs = [
NodeOutputType(),
]
- GUI = guis.SliderNodeGui
def __init__(self, params):
super().__init__(params)
@@ -308,7 +302,6 @@ def set_state(self, data: dict, version):
class _DynamicPorts_Node(NodeBase):
init_inputs = []
init_outputs = []
- GUI = guis.DynamicPortsGui
def add_input(self):
self.create_input()
@@ -325,7 +318,6 @@ def remove_output(self, index):
class Exec_Node(_DynamicPorts_Node):
title = 'exec'
- GUI = guis.ExecNodeGui
def __init__(self, params):
super().__init__(params)
@@ -354,7 +346,6 @@ class Eval_Node(NodeBase):
init_outputs = [
NodeOutputType(),
]
- GUI = guis.EvalNodeGui
def __init__(self, params):
super().__init__(params)
@@ -401,7 +392,6 @@ class Interpreter_Node(NodeBase):
title = 'interpreter'
init_inputs = []
init_outputs = []
- GUI = guis.InterpreterConsoleGui
"""
commands
@@ -439,7 +429,7 @@ def _hist_updated(self):
def process_input(self, cmds: str):
m = self.COMMANDS.get(cmds)
if m is not None:
- m()
+ m(self)
else:
for l in cmds.splitlines():
self.write(l) # print input
@@ -452,7 +442,7 @@ def run_src():
self.buffer.clear()
if self.session.gui:
- with redirect_stdout(self), redirect_stderr(self):
+ with redirect_stdout(self), redirect_stderr(self): # type: ignore
run_src()
else:
run_src()
@@ -475,7 +465,6 @@ class Storage_Node(NodeBase):
init_outputs = [
NodeOutputType(),
]
- GUI = guis.StorageNodeGui
def __init__(self, params):
super().__init__(params)
@@ -516,10 +505,9 @@ class LinkIN_Node(NodeBase):
NodeInputType(),
]
init_outputs = [] # no outputs
- GUI = guis.LinkIN_NodeGui
# instances registration
- INSTANCES = {} # {UUID: node}
+ INSTANCES: Dict[str, Node] = {}
def __init__(self, params):
super().__init__(params)
@@ -575,12 +563,11 @@ class LinkOUT_Node(NodeBase):
"""The complement to the link IN node"""
title = 'link OUT'
- init_inputs = [] # no inputs
- init_outputs = [] # will be synchronized with linked IN node
- GUI = guis.LinkOUT_NodeGui
+ init_inputs: List[NodeInputType] = [] # no inputs
+ init_outputs: List[NodeOutputType] = [] # will be synchronized with linked IN node
- INSTANCES = []
- PENDING_LINK_BUILDS = {}
+ INSTANCES: List['LinkOUT_Node'] = []
+ PENDING_LINK_BUILDS: Dict['LinkOUT_Node', str] = {}
# because a link OUT node might get initialized BEFORE it's corresponding
# link IN, it then stores itself together with the ID of the link IN it's
# waiting for in PENDING_LINK_BUILDS
@@ -656,7 +643,7 @@ def remove_event(self):
self.linked_node = None
-nodes = [
+node_types = [
Checkpoint_Node,
Button_Node,
Print_Node,
@@ -670,3 +657,15 @@ def remove_event(self):
LinkOUT_Node,
Interpreter_Node,
]
+
+# account for old package name
+for n in node_types:
+ n.legacy_identifiers = [
+ *getattr(n, 'legacy_identifiers', []),
+ f'std.{n.__class__.__name__}',
+ ]
+
+export_nodes(
+ node_types=node_types,
+ sub_pkg_name='special_nodes',
+)
diff --git a/ryven-editor/ryven/example_nodes/sub_nodes_example/sub2/__init__.py b/ryven-editor/ryven/example_nodes/linalg/__init__.py
similarity index 100%
rename from ryven-editor/ryven/example_nodes/sub_nodes_example/sub2/__init__.py
rename to ryven-editor/ryven/example_nodes/linalg/__init__.py
diff --git a/ryven-editor/ryven/example_nodes/linalg/gui.py b/ryven-editor/ryven/example_nodes/linalg/gui.py
index 24e87239..c8f02e90 100644
--- a/ryven-editor/ryven/example_nodes/linalg/gui.py
+++ b/ryven-editor/ryven/example_nodes/linalg/gui.py
@@ -1,10 +1,11 @@
-from ryven.gui_env import *
+import numpy as np
from qtpy.QtWidgets import QTextEdit
from qtpy.QtGui import QFontMetrics
-import numpy as np
+from ryven.gui_env import *
+from . import matrices as nodes
class MatrixWidget(NodeMainWidget, QTextEdit):
@@ -126,22 +127,7 @@ def set_state(self, data):
self.hide()
-class EditMatrixWidget(MatrixWidget):
- def __init__(self, params):
- super().__init__(params, 100, 80)
-
- self.setReadOnly(False)
- self.textChanged.connect(self.text_changed)
-
- def text_changed(self):
- self.node.parse_matrix(self.toPlainText())
- self.resize_to_content(lines=self.toPlainText().splitlines())
-
- def focusOutEvent(self, e):
- self.update_matrix(self.node.expression_matrix)
- QTextEdit.focusOutEvent(self, e)
-
-
+@node_gui(nodes.MatrixNodeBase)
class MatrixNodeBaseGui(NodeGUI):
main_widget_class = MatrixWidget
main_widget_pos = 'below ports'
@@ -186,13 +172,24 @@ def set_state(self, data):
# shown by default
+class EditMatrixWidget(MatrixWidget):
+ def __init__(self, params):
+ super().__init__(params, 100, 80)
+
+ self.setReadOnly(False)
+ self.textChanged.connect(self.text_changed)
+
+ def text_changed(self):
+ self.node.parse_matrix(self.toPlainText())
+ self.resize_to_content(lines=self.toPlainText().splitlines())
+
+ def focusOutEvent(self, e):
+ self.update_matrix(self.node.expression_matrix)
+ QTextEdit.focusOutEvent(self, e)
+
+
+@node_gui(nodes.EditMatrixNode)
class EditMatrixNodeGui(MatrixNodeBaseGui):
main_widget_class = EditMatrixWidget
main_widget_pos = 'below ports'
color = '#3344ff'
-
-
-export_guis([
- MatrixNodeBaseGui,
- EditMatrixNodeGui,
-])
diff --git a/ryven-editor/ryven/example_nodes/linalg/matrices.py b/ryven-editor/ryven/example_nodes/linalg/matrices.py
new file mode 100644
index 00000000..b2a88569
--- /dev/null
+++ b/ryven-editor/ryven/example_nodes/linalg/matrices.py
@@ -0,0 +1,464 @@
+import numpy as np
+from itertools import chain
+from typing import Optional, List
+
+import ryvencore.addons.Variables
+
+from ryven.node_env import *
+
+
+class MatrixData(Data):
+ # currently, there should not be any implicit
+ # data sharing between nodes, because their
+ # operations are copying the data anyway
+
+ # notice that numpy arrays are pickle serializable
+
+ pass
+
+
+class MatrixNodeBase(Node):
+ """
+ Base class for nodes handling matrices.
+ It implements basic forward propagation of a mutated matrix,
+ defined by some operation in the get_mat() method, and a GUI
+ for displaying the matrix.
+ """
+
+ version = 'v0.3'
+ init_inputs = [
+ NodeInputType()
+ ]
+ init_outputs = [
+ NodeOutputType()
+ ]
+
+ def __init__(self, params):
+ super().__init__(params)
+ self.mat = None
+
+ def get_mat(self):
+ """
+ Returns the processed matrix.
+ Pre: self.inputs_ready() is True.
+ """
+ raise NotImplementedError
+
+ def inputs_ready(self):
+ """Returns True if no input is None."""
+ return all(self.input(i) is not None for i in range(len(self.inputs)))
+
+ def update_event(self, inp=-1):
+ if not self.inputs_ready():
+ return
+
+ self.mat = self.get_mat()
+
+ if self.have_gui():
+ self.gui.show_matrix(self.mat)
+
+ self.set_output_val(0, MatrixData(self.mat))
+
+ def have_gui(self):
+ return hasattr(self, 'gui')
+
+
+class ShowMatrix(MatrixNodeBase):
+ """Simply displays a matrix"""
+ title = 'Show Matrix'
+
+ def get_mat(self):
+ return self.input(0).payload
+
+
+class Conjugate(MatrixNodeBase):
+ """Conjugates a matrix"""
+ title = 'Conjugate'
+
+ def get_mat(self):
+ return np.conjugate(self.input(0).payload)
+
+
+class Transpose(MatrixNodeBase):
+ """Transposes a matrix"""
+ title = 'Transpose'
+
+ def get_mat(self):
+ return np.transpose(self.input(0).payload)
+
+
+class DetOfMatrix(MatrixNodeBase):
+ """Computes the determinant of a matrix."""
+ title = 'Determinant'
+
+ def get_mat(self):
+ return np.linalg.det(self.input(0).payload)
+
+
+class DotProduct(MatrixNodeBase):
+ """Computes the dot product of a matrix."""
+ title = 'Dot Product'
+ init_inputs = [
+ NodeInputType(),
+ NodeInputType(),
+ ]
+
+ def get_mat(self):
+ return np.dot(
+ self.input(0).payload,
+ self.input(1).payload
+ )
+
+
+class HermMatrix(MatrixNodeBase):
+ """Computes the hermetian matrix."""
+ title = 'Herm'
+
+ def get_mat(self):
+ return np.transpose(np.conjugate(self.input(0).payload))
+
+
+class IDMatrix(MatrixNodeBase):
+ """Creates an identity matrix."""
+ title = 'ID Matrix'
+
+ def get_mat(self):
+ return np.identity(self.input(0).payload)
+
+
+class ImagMatrix(MatrixNodeBase):
+ """Extracts the imaginary parts of the matrix."""
+ title = 'Imag'
+
+ def get_mat(self):
+ return np.imag(self.input(0).payload)
+
+
+class RealMatrix(MatrixNodeBase):
+ """Extracts the real parts of the matrix."""
+ title = 'Real'
+
+ def get_mat(self):
+ return np.real(self.input(0).payload)
+
+
+class InnerProduct(MatrixNodeBase):
+ """Computes the inner product of the input matrices."""
+ title = 'Inner'
+ init_inputs = [
+ NodeInputType(),
+ NodeInputType(),
+ ]
+
+ def get_mat(self):
+ return np.inner(
+ self.input(0).payload,
+ self.input(1).payload
+ )
+
+
+class OuterProduct(MatrixNodeBase):
+ """Creates the outer product of two matrices."""
+ title = 'Outer'
+ init_inputs = [
+ NodeInputType(),
+ NodeInputType(),
+ ]
+
+ def get_mat(self):
+ return np.outer(
+ self.input(0).payload,
+ self.input(1).payload
+ )
+
+
+class InverseMatrix(MatrixNodeBase):
+ """Computes the inverse matrix"""
+ title = 'Inverse'
+
+ def get_mat(self):
+ return np.linalg.inv(
+ self.input(0).payload
+ )
+
+
+class KronMatrix(MatrixNodeBase):
+ """"""
+ title = 'Kron'
+
+ def get_mat(self):
+ return np.kron(
+ self.input(0).payload,
+ self.input(1).payload
+ )
+
+
+class MaskLower(MatrixNodeBase):
+ """"""
+ title = 'Mask Lower'
+
+ def get_mat(self):
+ return np.tril(
+ self.input(0).payload
+ )
+
+
+class MaskUpper(MatrixNodeBase):
+ """"""
+ title = 'Mask Upper'
+
+ def get_mat(self):
+ return np.triu(
+ self.input(0).payload
+ )
+
+
+class MatMul(MatrixNodeBase):
+ """Performs a matrix multiplication."""
+ title = 'Mult'
+ init_inputs = [
+ NodeInputType(),
+ NodeInputType(),
+ ]
+
+ def get_mat(self):
+ return np.matmul(
+ self.input(0).payload,
+ self.input(1).payload
+ )
+
+
+class MatPower(MatrixNodeBase):
+ """Powers a matrix."""
+ title = 'Power'
+ init_inputs = [
+ NodeInputType(),
+ NodeInputType(),
+ ]
+
+ def get_mat(self):
+ return np.linalg.matrix_power(
+ self.input(0).payload,
+ self.input(1).payload
+ )
+
+
+class NullMatrix(MatrixNodeBase):
+ """Creates a matrix of zeros."""
+ title = 'Null'
+
+ def get_mat(self):
+ return np.zeros(shape=(self.input(0).payload, self.input(1).payload))
+
+
+class OnesMatrix(MatrixNodeBase):
+ """Creates a matrix of ones."""
+ title = 'Ones'
+
+ def get_mat(self):
+ return np.ones(shape=(self.input(0).payload, self.input(1).payload))
+
+
+class RandomMatrix(MatrixNodeBase):
+ """Creates a matrix with random values between 0 and 1."""
+ title = 'Rand'
+ init_inputs = [
+ NodeInputType(),
+ NodeInputType(),
+ ]
+
+ def get_mat(self):
+ return np.random.rand(
+ self.input(0).payload,
+ self.input(1).payload
+ )
+
+
+class SolveLEq(MatrixNodeBase):
+ """Solves a linear equation system."""
+ title = 'Solve'
+ init_inputs = [
+ NodeInputType(),
+ NodeInputType(),
+ ]
+
+ def get_mat(self):
+ return np.linalg.solve(
+ self.input(0).payload,
+ self.input(1).payload
+ )
+
+
+class EditMatrixNode(Node):
+ """
+ A special node for hand-designing matrices with specific
+ numerical values. It also supports Ryven variables and
+ automatically adapts the matrix when they change.
+ """
+ title = 'Matrix'
+ identifier = 'EditMatrix_Node'
+ legacy_identifiers = ['Matrix_Node', 'linalg.Matrix_Node']
+ init_inputs = []
+ init_outputs = [
+ NodeOutputType()
+ ]
+
+ def __init__(self, params):
+ super().__init__(params)
+
+ self.expression_matrix: Optional[np.ndarray] = None
+ self.evaluated_matrix: Optional[MatrixData] = None
+
+ self.used_variable_names = []
+
+ def update_event(self, inp=-1):
+ if self.expression_matrix is not None:
+ self.set_output_val(0, self.evaluated_matrix)
+
+ def parse_matrix(self, s): # called from gui
+ lines = s.splitlines()
+
+ try:
+ # this throws a ValueError if the matrix is not rectangular
+ exp_mat = np.array([
+ list(filter(lambda s: s != '', l.split(' '))) # array of expression strings
+ for l in lines
+ ])
+
+ # check if all expressions are valid
+ for exp in self.flatten_2d(exp_mat):
+ if not self.exp_is_var(exp):
+ eval(exp)
+ elif not self.var_exists(exp):
+ raise NameError
+ except (ValueError, NameError):
+ # something like 2+ (which could become 2+1j) can't get parsed yet
+ return
+
+ self.expression_matrix = exp_mat
+ self.register_vars_and_eval_matrix()
+ self.update()
+
+ def register_vars_and_eval_matrix(self):
+ if not self.register_vars(self.expression_matrix):
+ return # abort if vars registration failed
+ self.evaluated_matrix = self.eval_matrix(self.expression_matrix)
+
+ def register_vars(self, lines: np.ndarray) -> bool:
+ """Updates subscriptions for the variables used in the matrix."""
+
+ for _ in range(len(self.used_variable_names)):
+ self.unsub_var(self.used_variable_names[0])
+
+ var_names = set()
+ for exp in filter(self.exp_is_var, self.flatten_2d(lines)):
+ if self.var_exists(exp):
+ var_names.add(exp)
+ else:
+ return False
+
+ for var_name in var_names:
+ self.sub_var(var_name)
+
+ return True
+
+ def eval_matrix(self, lines) -> MatrixData:
+ """
+ Evaluates a matrix from string expressions of numerals and variables.
+ """
+
+ # replace old v('name') syntax with new 'name' syntax
+ lines = [
+ [exp.replace('v(', '').replace(')', '') if 'v(' in exp else exp
+ for exp in l]
+ for l in lines
+ ]
+
+ # evaluate expressions
+ evaled_exp_array = [
+ [
+ eval(exp)
+ if not self.exp_is_var(exp) else
+ self.var_val(exp)
+ for exp in l
+ ]
+ for l in lines
+ ]
+
+ # convert ints to floats; leave complex numbers
+ int_to_float = lambda v: float(v) if isinstance(v, int) else v
+ float_exp_array = [
+ list(map(int_to_float, l))
+ for l in evaled_exp_array
+ ]
+
+ # build matrix and wrap in MatrixData
+ return MatrixData(np.array(float_exp_array))
+
+ def var_exists(self, name):
+ return self.get_addon('Variables').var_exists(self.flow, name)
+
+ def sub_var(self, name) -> bool:
+ if not self.var_exists(name):
+ return False
+
+ self.get_addon('Variables').subscribe(self, name, self.var_val_updated)
+ self.used_variable_names.append(name)
+ return True
+
+ def unsub_var(self, name):
+ self.get_addon('Variables').unsubscribe(self, name, self.var_val_updated)
+ self.used_variable_names.remove(name)
+
+ def var_val_updated(self, var: ryvencore.addons.Variables.Variable):
+ self.evaluated_matrix = self.eval_matrix(self.expression_matrix)
+ self.update()
+
+ def var(self, name) -> ryvencore.addons.Variables.Variable:
+ return self.get_addon('Variables').var(self.flow, name)
+
+ def var_val(self, name):
+ return self.var(name).get()
+
+ def exp_is_var(self, s: str):
+ return s.isidentifier()
+
+ def flatten_2d(self, mat: np.ndarray) -> np.ndarray:
+ return np.array(list(chain(*mat)))
+
+ def get_state(self):
+ data = {'expression matrix': serialize(self.expression_matrix)}
+ return data
+
+ def set_state(self, data, version):
+ self.expression_matrix = deserialize(data['expression matrix'])
+ self.register_vars_and_eval_matrix()
+
+
+export_nodes(
+ node_types=[
+ EditMatrixNode,
+ ShowMatrix,
+ Conjugate,
+ Transpose,
+ DetOfMatrix,
+ DotProduct,
+ HermMatrix,
+ IDMatrix,
+ ImagMatrix,
+ RealMatrix,
+ InnerProduct,
+ OuterProduct,
+ InverseMatrix,
+ KronMatrix,
+ MaskLower,
+ MaskUpper,
+ MatMul,
+ MatPower,
+ NullMatrix,
+ OnesMatrix,
+ RandomMatrix,
+ SolveLEq,
+ ],
+ data_types=[MatrixData]
+)
diff --git a/ryven-editor/ryven/example_nodes/linalg/nodes.py b/ryven-editor/ryven/example_nodes/linalg/nodes.py
index d603fde8..44fbcbb1 100644
--- a/ryven-editor/ryven/example_nodes/linalg/nodes.py
+++ b/ryven-editor/ryven/example_nodes/linalg/nodes.py
@@ -1,467 +1,8 @@
-from typing import Optional, List
-
-import ryvencore.addons.Variables
-
from ryven.node_env import *
-guis = import_guis(__file__)
-
-import numpy as np
-from itertools import chain
-
-
-class MatrixData(Data):
- # currently, there should not be any implicit
- # data sharing between nodes, because their
- # operations are copying the data anyway
-
- # notice that numpy arrays are pickle serializable
-
- pass
-
-
-class MatrixNodeBase(Node):
- """
- Base class for nodes handling matrices.
- It implements basic forward propagation of a mutated matrix,
- defined by some operation in the get_mat() method, and a GUI
- for displaying the matrix.
- """
-
- version = 'v0.2'
- init_inputs = [
- NodeInputType()
- ]
- init_outputs = [
- NodeOutputType()
- ]
- GUI = guis.MatrixNodeBaseGui
-
- def __init__(self, params):
- super().__init__(params)
- self.mat = None
-
- def get_mat(self):
- """
- Returns the processed matrix.
- Pre: self.inputs_ready() is True.
- """
- raise NotImplementedError
-
- def inputs_ready(self):
- """Returns True if no input is None."""
- return all(self.input(i) is not None for i in range(len(self.inputs)))
-
- def update_event(self, inp=-1):
- if not self.inputs_ready():
- return
-
- self.mat = self.get_mat()
-
- if self.have_gui():
- self.gui.show_matrix(self.mat)
-
- self.set_output_val(0, MatrixData(self.mat))
-
- def have_gui(self):
- return hasattr(self, 'gui')
-
-
-class ShowMatrix(MatrixNodeBase):
- """Simply displays a matrix"""
- title = 'Show Matrix'
-
- def get_mat(self):
- return self.input(0).payload
-
-
-class Conjugate(MatrixNodeBase):
- """Conjugates a matrix"""
- title = 'Conjugate'
-
- def get_mat(self):
- return np.conjugate(self.input(0).payload)
-
-
-class Transpose(MatrixNodeBase):
- """Transposes a matrix"""
- title = 'Transpose'
-
- def get_mat(self):
- return np.transpose(self.input(0).payload)
-
-
-class DetOfMatrix(MatrixNodeBase):
- """Computes the determinant of a matrix."""
- title = 'Determinant'
-
- def get_mat(self):
- return np.linalg.det(self.input(0).payload)
-
-
-class DotProduct(MatrixNodeBase):
- """Computes the dot product of a matrix."""
- title = 'Dot Product'
- init_inputs = [
- NodeInputType(),
- NodeInputType(),
- ]
-
- def get_mat(self):
- return np.dot(
- self.input(0).payload,
- self.input(1).payload
- )
-
-
-class HermMatrix(MatrixNodeBase):
- """Computes the hermetian matrix."""
- title = 'Herm'
-
- def get_mat(self):
- return np.transpose(np.conjugate(self.input(0).payload))
-
-
-class IDMatrix(MatrixNodeBase):
- """Creates an identity matrix."""
- title = 'ID Matrix'
-
- def get_mat(self):
- return np.identity(self.input(0).payload)
-
-
-class ImagMatrix(MatrixNodeBase):
- """Extracts the imaginary parts of the matrix."""
- title = 'Imag'
-
- def get_mat(self):
- return np.imag(self.input(0).payload)
-
-
-class RealMatrix(MatrixNodeBase):
- """Extracts the real parts of the matrix."""
- title = 'Real'
-
- def get_mat(self):
- return np.real(self.input(0).payload)
-
-
-class InnerProduct(MatrixNodeBase):
- """Computes the inner product of the input matrices."""
- title = 'Inner'
- init_inputs = [
- NodeInputType(),
- NodeInputType(),
- ]
-
- def get_mat(self):
- return np.inner(
- self.input(0).payload,
- self.input(1).payload
- )
-
-
-class OuterProduct(MatrixNodeBase):
- """Creates the outer product of two matrices."""
- title = 'Outer'
- init_inputs = [
- NodeInputType(),
- NodeInputType(),
- ]
-
- def get_mat(self):
- return np.outer(
- self.input(0).payload,
- self.input(1).payload
- )
-
-
-class InverseMatrix(MatrixNodeBase):
- """Computes the inverse matrix"""
- title = 'Inverse'
-
- def get_mat(self):
- return np.linalg.inv(
- self.input(0).payload
- )
-
-
-class KronMatrix(MatrixNodeBase):
- """"""
- title = 'Kron'
-
- def get_mat(self):
- return np.kron(
- self.input(0).payload,
- self.input(1).payload
- )
-
-
-class MaskLower(MatrixNodeBase):
- """"""
- title = 'Mask Lower'
-
- def get_mat(self):
- return np.tril(
- self.input(0).payload
- )
-
-
-class MaskUpper(MatrixNodeBase):
- """"""
- title = 'Mask Upper'
-
- def get_mat(self):
- return np.triu(
- self.input(0).payload
- )
-
-
-class MatMul(MatrixNodeBase):
- """Performs a matrix multiplication."""
- title = 'Mult'
- init_inputs = [
- NodeInputType(),
- NodeInputType(),
- ]
-
- def get_mat(self):
- return np.matmul(
- self.input(0).payload,
- self.input(1).payload
- )
-
-
-class MatPower(MatrixNodeBase):
- """Powers a matrix."""
- title = 'Power'
- init_inputs = [
- NodeInputType(),
- NodeInputType(),
- ]
-
- def get_mat(self):
- return np.linalg.matrix_power(
- self.input(0).payload,
- self.input(1).payload
- )
-
-
-class NullMatrix(MatrixNodeBase):
- """Creates a matrix of zeros."""
- title = 'Null'
-
- def get_mat(self):
- return np.zeros(shape=(self.input(0).payload, self.input(1).payload))
-
-
-class OnesMatrix(MatrixNodeBase):
- """Creates a matrix of ones."""
- title = 'Ones'
-
- def get_mat(self):
- return np.ones(shape=(self.input(0).payload, self.input(1).payload))
-
-
-class RandomMatrix(MatrixNodeBase):
- """Creates a matrix with random values between 0 and 1."""
- title = 'Rand'
- init_inputs = [
- NodeInputType(),
- NodeInputType(),
- ]
-
- def get_mat(self):
- return np.random.rand(
- self.input(0).payload,
- self.input(1).payload
- )
-
-
-class SolveLEq(MatrixNodeBase):
- """Solves a linear equation system."""
- title = 'Solve'
- init_inputs = [
- NodeInputType(),
- NodeInputType(),
- ]
-
- def get_mat(self):
- return np.linalg.solve(
- self.input(0).payload,
- self.input(1).payload
- )
-
-
-class EditMatrixNode(Node):
- """
- A special node for hand-designing matrices with specific
- numerical values. It also supports Ryven variables and
- automatically adapts the matrix when they change.
- """
- title = 'Matrix'
- identifier = 'EditMatrix_Node'
- legacy_identifiers = ['Matrix_Node', 'linalg.Matrix_Node']
- init_inputs = []
- init_outputs = [
- NodeOutputType()
- ]
- GUI = guis.EditMatrixNodeGui
-
- def __init__(self, params):
- super().__init__(params)
-
- self.expression_matrix: Optional[np.ndarray] = None
- self.evaluated_matrix: Optional[MatrixData] = None
-
- self.used_variable_names = []
-
- def update_event(self, inp=-1):
- if self.expression_matrix is not None:
- self.set_output_val(0, self.evaluated_matrix)
-
- def parse_matrix(self, s): # called from gui
- lines = s.splitlines()
-
- try:
- # this throws a ValueError if the matrix is not rectangular
- exp_mat = np.array([
- list(filter(lambda s: s != '', l.split(' '))) # array of expression strings
- for l in lines
- ])
-
- # check if all expressions are valid
- for exp in self.flatten_2d(exp_mat):
- if not self.exp_is_var(exp):
- eval(exp)
- elif not self.var_exists(exp):
- raise NameError
- except (ValueError, NameError):
- # something like 2+ (which could become 2+1j) can't get parsed yet
- return
-
- self.expression_matrix = exp_mat
- self.register_vars_and_eval_matrix()
- self.update()
-
- def register_vars_and_eval_matrix(self):
- if not self.register_vars(self.expression_matrix):
- return # abort if vars registration failed
- self.evaluated_matrix = self.eval_matrix(self.expression_matrix)
-
- def register_vars(self, lines: np.ndarray) -> bool:
- """Updates subscriptions for the variables used in the matrix."""
-
- for _ in range(len(self.used_variable_names)):
- self.unsub_var(self.used_variable_names[0])
-
- var_names = set()
- for exp in filter(self.exp_is_var, self.flatten_2d(lines)):
- if self.var_exists(exp):
- var_names.add(exp)
- else:
- return False
-
- for var_name in var_names:
- self.sub_var(var_name)
-
- return True
-
- def eval_matrix(self, lines) -> MatrixData:
- """
- Evaluates a matrix from string expressions of numerals and variables.
- """
-
- # replace old v('name') syntax with new 'name' syntax
- lines = [
- [exp.replace('v(', '').replace(')', '') if 'v(' in exp else exp
- for exp in l]
- for l in lines
- ]
-
- # evaluate expressions
- evaled_exp_array = [
- [
- eval(exp)
- if not self.exp_is_var(exp) else
- self.var_val(exp)
- for exp in l
- ]
- for l in lines
- ]
-
- # convert ints to floats; leave complex numbers
- int_to_float = lambda v: float(v) if isinstance(v, int) else v
- float_exp_array = [
- list(map(int_to_float, l))
- for l in evaled_exp_array
- ]
-
- # build matrix and wrap in MatrixData
- return MatrixData(np.array(float_exp_array))
-
- def var_exists(self, name):
- return self.get_addon('Variables').var_exists(self.flow, name)
-
- def sub_var(self, name) -> bool:
- if not self.var_exists(name):
- return False
-
- self.get_addon('Variables').subscribe(self, name, self.var_val_updated)
- self.used_variable_names.append(name)
- return True
-
- def unsub_var(self, name):
- self.get_addon('Variables').unsubscribe(self, name, self.var_val_updated)
- self.used_variable_names.remove(name)
-
- def var_val_updated(self, var: ryvencore.addons.Variables.Variable):
- self.evaluated_matrix = self.eval_matrix(self.expression_matrix)
- self.update()
-
- def var(self, name) -> ryvencore.addons.Variables.Variable:
- return self.get_addon('Variables').var(self.flow, name)
-
- def var_val(self, name):
- return self.var(name).get()
-
- def exp_is_var(self, s: str):
- return s.isidentifier()
-
- def flatten_2d(self, mat: np.ndarray) -> np.ndarray:
- return np.array(list(chain(*mat)))
-
- def get_state(self):
- data = {'expression matrix': serialize(self.expression_matrix)}
- return data
- def set_state(self, data, version):
- self.expression_matrix = deserialize(data['expression matrix'])
- self.register_vars_and_eval_matrix()
+from . import matrices
-export_nodes([
- EditMatrixNode,
- ShowMatrix,
- Conjugate,
- Transpose,
- DetOfMatrix,
- DotProduct,
- HermMatrix,
- IDMatrix,
- ImagMatrix,
- RealMatrix,
- InnerProduct,
- OuterProduct,
- InverseMatrix,
- KronMatrix,
- MaskLower,
- MaskUpper,
- MatMul,
- MatPower,
- NullMatrix,
- OnesMatrix,
- RandomMatrix,
- SolveLEq,
-],
- data_types=[MatrixData]
-)
+@on_gui_load
+def load_gui():
+ from . import gui
diff --git a/ryven-editor/ryven/example_nodes/std/README.md b/ryven-editor/ryven/example_nodes/std/README.md
deleted file mode 100644
index d3bbcc3a..00000000
--- a/ryven-editor/ryven/example_nodes/std/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-This package contains a bunch of nodes for different purposes.
-It showcases many features of Ryven's nodes system and tries to push the boundaries.
-Some easy nodes are found in `basic_operators.py`, while more advanced ideas are implemented in `special_nodes.py`.
\ No newline at end of file
diff --git a/ryven-editor/ryven/example_nodes/std/nodes.py b/ryven-editor/ryven/example_nodes/std/nodes.py
deleted file mode 100644
index 71502242..00000000
--- a/ryven-editor/ryven/example_nodes/std/nodes.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from ryven.node_env import *
-# widgets = import_gui(__file__)
-
-import sys
-import os
-sys.path.append(os.path.dirname(__file__))
-
-from special_nodes import nodes as special_nodes
-from basic_operators import nodes as operator_nodes
-from control_structures import nodes as cs_nodes
-
-export_nodes([
- *special_nodes,
- *operator_nodes,
- *cs_nodes,
-])
diff --git a/ryven-editor/ryven/example_nodes/sub_nodes_example/nodes.py b/ryven-editor/ryven/example_nodes/sub_nodes_example/nodes.py
deleted file mode 100644
index 7d6391d7..00000000
--- a/ryven-editor/ryven/example_nodes/sub_nodes_example/nodes.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from ryven.node_env import on_gui_load
-
-from .sub1 import nodes
-from .sub2 import nodes
-
-@on_gui_load
-def load_gui():
- # this function will be called by Ryven once GUI can be imported
- # when running in headless mode, this function will not be called
- from .sub1 import gui
- from .sub2 import gui
\ No newline at end of file
diff --git a/ryven-editor/ryven/example_nodes/sub_nodes_example/sub1/gui.py b/ryven-editor/ryven/example_nodes/sub_nodes_example/sub1/gui.py
deleted file mode 100644
index a222bbcf..00000000
--- a/ryven-editor/ryven/example_nodes/sub_nodes_example/sub1/gui.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from ryven.gui_env import *
-from .nodes import *
-
-from qtpy.QtWidgets import QLabel
-
-
-class Sub1NodeLabelWidget(NodeMainWidget, QLabel):
- def __init__(self, node):
- QLabel.__init__(self, "Sub1 Node's main widget")
- NodeMainWidget.__init__(self, node)
-
-
-@node_gui(Sub1Node)
-class MatrixNodeBaseGui(NodeGUI):
- main_widget_class = Sub1NodeLabelWidget
- main_widget_pos = 'below ports'
diff --git a/ryven-editor/ryven/example_nodes/sub_nodes_example/sub1/nodes.py b/ryven-editor/ryven/example_nodes/sub_nodes_example/sub1/nodes.py
deleted file mode 100644
index 224767c7..00000000
--- a/ryven-editor/ryven/example_nodes/sub_nodes_example/sub1/nodes.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from ryven.node_env import *
-
-class Sub1Node(Node):
- title = 'Node from sub-package 1'
-
-
-export_nodes(
- sub_pkg_name='A',
- node_types=[Sub1Node],
-)
diff --git a/ryven-editor/ryven/example_nodes/sub_nodes_example/sub2/gui.py b/ryven-editor/ryven/example_nodes/sub_nodes_example/sub2/gui.py
deleted file mode 100644
index 870b325b..00000000
--- a/ryven-editor/ryven/example_nodes/sub_nodes_example/sub2/gui.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from ryven.gui_env import *
-from .nodes import *
-
-from qtpy.QtWidgets import QLabel
-
-
-class Sub1NodeLabelWidget(NodeMainWidget, QLabel):
- def __init__(self, node):
- QLabel.__init__(self, "Sub2 Node's main widget")
- NodeMainWidget.__init__(self, node)
-
-
-@node_gui(Sub2Node)
-class MatrixNodeBaseGui(NodeGUI):
- main_widget_class = Sub1NodeLabelWidget
- main_widget_pos = 'below ports'
diff --git a/ryven-editor/ryven/example_nodes/sub_nodes_example/sub2/nodes.py b/ryven-editor/ryven/example_nodes/sub_nodes_example/sub2/nodes.py
deleted file mode 100644
index 43d6d7ad..00000000
--- a/ryven-editor/ryven/example_nodes/sub_nodes_example/sub2/nodes.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from ryven.node_env import *
-
-class Sub2Node(Node):
- title = 'Node from sub-package 2'
-
-
-export_nodes(
- sub_pkg_name='B',
- node_types=[Sub2Node],
-)
diff --git a/ryven-editor/ryven/example_projects/basics.json b/ryven-editor/ryven/example_projects/basics.json
index 14c3f347..af52c9a6 100644
--- a/ryven-editor/ryven/example_projects/basics.json
+++ b/ryven-editor/ryven/example_projects/basics.json
@@ -1,16 +1,16 @@
{
"general info": {
"type": "Ryven project file",
- "ryven version": "3.3.0a1"
+ "ryven version": "3.4.3"
},
"required packages": [
{
- "name": "std",
- "dir": "/home/leon/projects/ryven_projects/Ryven/ryven/example_nodes/std"
+ "name": "examples",
+ "dir": "/home/leon/projects/ryven_projects/Ryven/ryven-editor/ryven/example_nodes/examples"
}
],
"GID": 2,
- "version": "0.4.0a12",
+ "version": "0.4.0",
"flows": {
"hello world": {
"GID": 5,
@@ -18,9 +18,9 @@
"nodes": [
{
"GID": 6,
- "version": "v0.2",
- "identifier": "std.Storage_Node",
- "state data": "gASVUggAAAAAAAB9lIwEZGF0YZRdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxNPgxNPwxNQAxNQQxNQgxNQwxNRAxNRQxNRgxNRwxNSAxNSQxNSgxNSwxNTAxNTQxNTgxNTwxNUAxNUQxNUgxNUwxNVAxNVQxNVgxNVwxNWAxNWQxNWgxNWwxNXAxNXQxNXgxNXwxNYAxNYQxNYgxNYwxNZAxNZQxNZgxNZwxNaAxNaQxNagxNawxNbAxNbQxNbgxNbwxNcAxNcQxNcgxNcwxNdAxNdQxNdgxNdwxNeAxNeQxNegxNewxNfAxNfQxNfgxNfwxNgAxNgQxNggxNgwxNhAxNhQxNhgxNhwxNiAxNiQxNigxNiwxNjAxNjQxNjgxNjwxNkAxNkQxNkgxNkwxNlAxNlQxNlgxNlwxNmAxNmQxNmgxNmwxNnAxNnQxNngxNnwxNoAxNoQxNogxNowxNpAxNpQxNpgxNpwxNqAxNqQxNqgxNqwxNrAxNrQxNrgxNrwxNsAxNsQxNsgxNswxNtAxNtQxNtgxNtwxNuAxNuQxNugxNuwxNvAxNvQxNvgxNvwxNwAxNwQxNwgxNwwxNxAxNxQxNxgxNxwxNyAxNyQxNygxNywxNzAxNzQxNzgxNzwxN0AxN0QxN0gxN0wxN1AxN1QxN1gxN1wxN2AxN2QxN2gxN2wxN3AxN3QxN3gxN3wxN4AxN4QxN4gxN4wxN5AxN5QxN5gxN5wxN6AxN6QxN6gxN6wxN7AxN7QxN7gxN7wxN8AxN8QxN8gxN8wxN9AxN9QxN9gxN9wxN+AxN+QxN+gxN+wxN/AxN/QxN/gxN/wxNAA1NAQ1NAg1NAw1NBA1NBQ1NBg1NBw1NCA1NCQ1NCg1NCw1NDA1NDQ1NDg1NDw1NEA1NEQ1NEg1NEw1NFA1NFQ1NFg1NFw1NGA1NGQ1NGg1NGw1NHA1NHQ1NHg1NHw1NIA1NIQ1NIg1NIw1NJA1NJQ1NJg1NJw1NKA1NKQ1NKg1NKw1NLA1NLQ1NLg1NLw1NMA1NMQ1NMg1NMw1NNA1NNQ1NNg1NNw1NOA1NOQ1NOg1NOw1NPA1NPQ1NPg1NPw1NQA1NQQ1NQg1NQw1NRA1NRQ1NRg1NRw1NSA1NSQ1NSg1NSw1NTA1NTQ1NTg1NTw1NUA1NUQ1NUg1NUw1NVA1NVQ1NVg1NVw1NWA1NWQ1NWg1NWw1NXA1NXQ1NXg1NXw1NYA1NYQ1NYg1NYw1NZA1NZQ1NZg1NZw1NaA1NaQ1Nag1Naw1NbA1NbQ1Nbg1Nbw1NcA1NcQ1Ncg1Ncw1NdA1NdQ1Ndg1Ndw1NeA1NeQ1Neg1New1NfA1NfQ1Nfg1Nfw1NgA1NgQ1Ngg1Ngw1NhA1NhQ1Nhg1Nhw1NiA1NiQ1Nig1Niw1NjA1NjQ1Njg1Njw1NkA1NkQ1Nkg1Nkw1NlA1NlQ1Nlg1Nlw1NmA1NmQ1Nmg1Nmw1NnA1NnQ1Nng1Nnw1NoA1NoQ1Nog1Now1NpA1NpQ1Npg1Npw1NqA1NqQ1Nqg1Nqw1NrA1NrQ1Nrg1Nrw1NsA1NsQ1Nsg1Nsw1NtA1NtQ1Ntg1Ntw1NuA1NuQ1Nug1Nuw1NvA1NvQ1Nvg1Nvw1NwA1NwQ1Nwg1Nww1NxA1NxQ1Nxg1Nxw1NyA1NyQ1Nyg1Nyw1NzA1NzQ1Nzg1Nzw1N0A1N0Q1N0g1N0w1N1A1N1Q1N1g1N1w1N2A1N2Q1N2g1N2w1N3A1N3Q1N3g1N3w1N4A1N4Q1N4g1N4w1N5A1N5Q1N5g1N5w1N6A1N6Q1N6g1N6w1N7A1N7Q1N7g1N7w1N8A1N8Q1N8g1N8w1N9A1N9Q1N9g1N9w1N+A1N+Q1N+g1N+w1N/A1N/Q1N/g1N/w1NAA5NAQ5NAg5NAw5NBA5NBQ5NBg5NBw5NCA5NCQ5NCg5NCw5NDA5NDQ5NDg5NDw5NEA5NEQ5NEg5NEw5NFA5NFQ5NFg5NFw5NGA5NGQ5NGg5NGw5NHA5NHQ5NHg5NHw5NIA5NIQ5NIg5NIw5NJA5NJQ5NJg5NJw5NKA5NKQ5NKg5NKw5NLA5NLQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5lcy4=",
+ "version": "v0.3",
+ "identifier": "examples.special_nodes.Storage_Node",
+ "state data": "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Kg5NKw5NLA5NLQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5Nmw5Nmw5NnA5NnA5NnQ5NnQ5Nng5Nng5Nnw5Nnw5NoA5NoA5NoQ5NoQ5Nog5Nog5Now5Now5NpA5NpA5NpQ5NpQ5Npg5Npg5Npw5Npw5NqA5NqA5NqQ5NqQ5Nqg5Nqg5Nqw5Nqw5NrA5NrA5NrQ5NrQ5Nrg5Nrg5Nrw5Nrw5NsA5NsA5NsQ5NsQ5Nsg5Nsg5Nsw5Nsw5NtA5NtA5NtQ5NtQ5Ntg5Ntg5Ntw5Ntw5NuA5NuA5NuQ5NuQ5Nug5Nug5Nuw5Nuw5NvA5NvA5NvQ5NvQ5Nvg5Nvg5Nvw5Nvw5NwA5NwA5NwQ5NwQ5Nwg5Nwg5Nww5Nww5NxA5NxA5NxQ5NxQ5Nxg5Nxg5Nxw5Nxw5NyA5NyA5NyQ5NyQ5Nyg5Nyg5Nyw5Nyw5NzA5NzA5NzQ5NzQ5Nzg5Nzg5Nzw5Nzw5N0A5N0A5N0Q5N0Q5N0g5N0g5N0w5N0w5N1A5N1A5N1Q5N1Q5N1g5N1g5N1w5N1w5N2A5N2A5N2Q5N2Q5N2g5N2g5N2w5N2w5N3A5N3A5N3Q5N3Q5N3g5N3g5N3w5N3w5N4A5N4A5N4Q5N4Q5N4g5N4g5N4w5N4w5N5A5N5A5N5Q5N5Q5N5g5N5g5N5w5N5w5N6A5N6A5N6Q5N6Q5lcy4=",
"additional data": {},
"inputs": [
{
@@ -61,12 +61,13 @@
"method": "clear"
}
},
- "display title": "store"
+ "display title": "store",
+ "inspector widget": {}
},
{
"GID": 13,
- "version": "v0.2",
- "identifier": "std.Eval_Node",
+ "version": "v0.3",
+ "identifier": "examples.special_nodes.Eval_Node",
"state data": "gASVOQAAAAAAAAB9lCiMEG51bSBwYXJhbSBpbnB1dHOUSwGMD2V4cHJlc3Npb24gY29kZZSMCmlucFswXVstMV2UdS4=",
"additional data": {},
"inputs": [
@@ -110,7 +111,8 @@
},
"remove input": {}
},
- "display title": "eval"
+ "display title": "eval",
+ "inspector widget": {}
},
{
"GID": 17,
@@ -149,13 +151,14 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "result"
+ "display title": "result",
+ "inspector widget": {}
},
{
"GID": 20,
- "version": "v0.2",
- "identifier": "std.Slider_Node",
- "state data": "gASVEwAAAAAAAAB9lIwDdmFslEc/6FocrAgxJ3Mu",
+ "version": "v0.3",
+ "identifier": "examples.special_nodes.Slider_Node",
+ "state data": "gASVEwAAAAAAAAB9lIwDdmFslEc/4zMzMzMzM3Mu",
"additional data": {},
"inputs": [
{
@@ -163,33 +166,33 @@
"type": "data",
"label": "scl",
"default": {
- "GID": 27,
+ "GID": 30,
"identifier": "Data",
"serialized": "gARLAS4="
},
"has widget": true,
"widget name": "scale",
"widget pos": "besides",
- "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTRsSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
+ "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSQCjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
},
{
- "GID": 25,
+ "GID": 26,
"type": "data",
"label": "round",
"default": {
- "GID": 123,
+ "GID": 32,
"identifier": "Data",
"serialized": "gASJLg=="
},
"has widget": true,
"widget name": "round",
"widget pos": "besides",
- "widget data": "gASVaQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTRwSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUiXVicy4="
+ "widget data": "gASVaQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSUCjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUiXVicy4="
}
],
"outputs": [
{
- "GID": 26,
+ "GID": 28,
"type": "data",
"label": ""
}
@@ -216,17 +219,18 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "slider"
+ "display title": "slider",
+ "inspector widget": {}
},
{
- "GID": 29,
+ "GID": 33,
"version": "v0.2",
"identifier": "built_in.Result_Node",
"state data": "gAR9lC4=",
"additional data": {},
"inputs": [
{
- "GID": 31,
+ "GID": 35,
"type": "data",
"label": "",
"has widget": false
@@ -255,47 +259,48 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "result"
+ "display title": "result",
+ "inspector widget": {}
},
{
- "GID": 32,
+ "GID": 36,
"version": "v0.1",
"identifier": "built_in.SetVar_Node",
"state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIlzLg==",
"additional data": {},
"inputs": [
{
- "GID": 38,
+ "GID": 42,
"type": "data",
"label": "var",
"default": {
- "GID": 3801,
+ "GID": 47,
"identifier": "Data",
"serialized": "gASVBQAAAAAAAACMAWGULg=="
},
"has widget": true,
"widget name": "varname",
"widget pos": "besides",
- "widget data": "gASVbAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTR0SjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAFhlHVicy4="
+ "widget data": "gASVbAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSYCjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAFhlHVicy4="
},
{
- "GID": 39,
+ "GID": 44,
"type": "data",
"label": "val",
"default": {
- "GID": 4628,
+ "GID": 537,
"identifier": "Data",
- "serialized": "gASVjRAAAAAAAABYhhAAAFszMDM1LCAzMDM2LCAzMDM3LCAzMDM4LCAzMDM5LCAzMDQwLCAzMDQxLCAzMDQyLCAzMDQzLCAzMDQ0LCAzMDQ1LCAzMDQ2LCAzMDQ3LCAzMDQ4LCAzMDQ5LCAzMDUwLCAzMDUxLCAzMDUyLCAzMDUzLCAzMDU0LCAzMDU1LCAzMDU2LCAzMDU3LCAzMDU4LCAzMDU5LCAzMDYwLCAzMDYxLCAzMDYyLCAzMDYzLCAzMDY0LCAzMDY1LCAzMDY2LCAzMDY3LCAzMDY4LCAzMDY5LCAzMDcwLCAzMDcxLCAzMDcyLCAzMDczLCAzMDc0LCAzMDc1LCAzMDc2LCAzMDc3LCAzMDc4LCAzMDc5LCAzMDgwLCAzMDgxLCAzMDgyLCAzMDgzLCAzMDg0LCAzMDg1LCAzMDg2LCAzMDg3LCAzMDg4LCAzMDg5LCAzMDkwLCAzMDkxLCAzMDkyLCAzMDkzLCAzMDk0LCAzMDk1LCAzMDk2LCAzMDk3LCAzMDk4LCAzMDk5LCAzMTAwLCAzMTAxLCAzMTAyLCAzMTAzLCAzMTA0LCAzMTA1LCAzMTA2LCAzMTA3LCAzMTA4LCAzMTA5LCAzMTEwLCAzMTExLCAzMTEyLCAzMTEzLCAzMTE0LCAzMTE1LCAzMTE2LCAzMTE3LCAzMTE4LCAzMTE5LCAzMTIwLCAzMTIxLCAzMTIyLCAzMTIzLCAzMTI0LCAzMTI1LCAzMTI2LCAzMTI3LCAzMTI4LCAzMTI5LCAzMTMwLCAzMTMxLCAzMTMyLCAzMTMzLCAzMTM0LCAzMTM0LCAzMTM1LCAzMTM2LCAzMTM3LCAzMTM4LCAzMTM5LCAzMTQwLCAzMTQxLCAzMTQyLCAzMTQzLCAzMTQ0LCAzMTQ1LCAzMTQ2LCAzMTQ3LCAzMTQ4LCAzMTQ5LCAzMTUwLCAzMTUxLCAzMTUyLCAzMTUzLCAzMTU0LCAzMTU1LCAzMTU2LCAzMTU3LCAzMTU4LCAzMTU5LCAzMTYwLCAzMTYxLCAzMTYyLCAzMTYzLCAzMTY0LCAzMTY1LCAzMTY2LCAzMTY3LCAzMTY4LCAzMTY5LCAzMTcwLCAzMTcxLCAzMTcyLCAzMTczLCAzMTc0LCAzMTc1LCAzMTc2LCAzMTc3LCAzMTc4LCAzMTc5LCAzMTgwLCAzMTgxLCAzMTgyLCAzMTgzLCAzMTg0LCAzMTg1LCAzMTg2LCAzMTg3LCAzMTg4LCAzMTg5LCAzMTkwLCAzMTkxLCAzMTkyLCAzMTkzLCAzMTk0LCAzMTk1LCAzMTk2LCAzMTk3LCAzMTk4LCAzMTk5LCAzMjAwLCAzMjAxLCAzMjAyLCAzMjAzLCAzMjA0LCAzMjA1LCAzMjA2LCAzMjA3LCAzMjA4LCAzMjA5LCAzMjEwLCAzMjExLCAzMjEyLCAzMjEzLCAzMjE0LCAzMjE1LCAzMjE2LCAzMjE3LCAzMjE4LCAzMjE5LCAzMjIwLCAzMjIxLCAzMjIyLCAzMjIzLCAzMjI0LCAzMjI1LCAzMjI2LCAzMjI3LCAzMjI4LCAzMjI5LCAzMjMwLCAzMjMxLCAzMjMyLCAzMjMzLCAzMjM0LCAzMjM1LCAzMjM2LCAzMjM3LCAzMjM4LCAzMjM5LCAzMjQwLCAzMjQxLCAzMjQyLCAzMjQzLCAzMjQ0LCAzMjQ1LCAzMjQ2LCAzMjQ3LCAzMjQ4LCAzMjQ5LCAzMjUwLCAzMjUxLCAzMjUyLCAzMjUzLCAzMjU0LCAzMjU1LCAzMjU2LCAzMjU3LCAzMjU4LCAzMjU5LCAzMjYwLCAzMjYxLCAzMjYyLCAzMjYzLCAzMjY0LCAzMjY1LCAzMjY2LCAzMjY3LCAzMjY4LCAzMjY5LCAzMjcwLCAzMjcxLCAzMjcyLCAzMjczLCAzMjc0LCAzMjc1LCAzMjc2LCAzMjc3LCAzMjc4LCAzMjc5LCAzMjgwLCAzMjgxLCAzMjgyLCAzMjgzLCAzMjg0LCAzMjg1LCAzMjg2LCAzMjg3LCAzMjg4LCAzMjg5LCAzMjkwLCAzMjkxLCAzMjkyLCAzMjkzLCAzMjk0LCAzMjk1LCAzMjk2LCAzMjk3LCAzMjk4LCAzMjk5LCAzMzAwLCAzMzAxLCAzMzAyLCAzMzAzLCAzMzA0LCAzMzA1LCAzMzA2LCAzMzA3LCAzMzA4LCAzMzA5LCAzMzEwLCAzMzExLCAzMzEyLCAzMzEzLCAzMzE0LCAzMzE1LCAzMzE2LCAzMzE3LCAzMzE4LCAzMzE5LCAzMzIwLCAzMzIxLCAzMzIyLCAzMzIzLCAzMzI0LCAzMzI1LCAzMzI2LCAzMzI3LCAzMzI4LCAzMzI5LCAzMzMwLCAzMzMxLCAzMzMyLCAzMzMzLCAzMzM0LCAzMzM1LCAzMzM2LCAzMzM3LCAzMzM4LCAzMzM5LCAzMzQwLCAzMzQxLCAzMzQyLCAzMzQzLCAzMzQ0LCAzMzQ1LCAzMzQ2LCAzMzQ3LCAzMzQ4LCAzMzQ5LCAzMzUwLCAzMzUxLCAzMzUyLCAzMzUzLCAzMzU0LCAzMzU1LCAzMzU2LCAzMzU3LCAzMzU4LCAzMzU5LCAzMzYwLCAzMzYxLCAzMzYyLCAzMzYzLCAzMzY0LCAzMzY1LCAzMzY2LCAzMzY3LCAzMzY4LCAzMzY5LCAzMzcwLCAzMzcxLCAzMzcyLCAzMzczLCAzMzc0LCAzMzc1LCAzMzc2LCAzMzc3LCAzMzc4LCAzMzc5LCAzMzgwLCAzMzgxLCAzMzgyLCAzMzgzLCAzMzg0LCAzMzg1LCAzMzg2LCAzMzg3LCAzMzg4LCAzMzg5LCAzMzkwLCAzMzkxLCAzMzkyLCAzMzkzLCAzMzk0LCAzMzk1LCAzMzk2LCAzMzk3LCAzMzk4LCAzMzk5LCAzNDAwLCAzNDAxLCAzNDAyLCAzNDAzLCAzNDA0LCAzNDA1LCAzNDA2LCAzNDA3LCAzNDA4LCAzNDA5LCAzNDEwLCAzNDExLCAzNDEyLCAzNDEzLCAzNDE0LCAzNDE1LCAzNDE2LCAzNDE3LCAzNDE4LCAzNDE5LCAzNDIwLCAzNDIxLCAzNDIyLCAzNDIzLCAzNDI0LCAzNDI1LCAzNDI2LCAzNDI3LCAzNDI4LCAzNDI5LCAzNDMwLCAzNDMxLCAzNDMyLCAzNDMzLCAzNDM0LCAzNDM1LCAzNDM2LCAzNDM3LCAzNDM4LCAzNDM5LCAzNDQwLCAzNDQxLCAzNDQyLCAzNDQzLCAzNDQ0LCAzNDQ1LCAzNDQ2LCAzNDQ3LCAzNDQ4LCAzNDQ5LCAzNDUwLCAzNDUxLCAzNDUyLCAzNDUzLCAzNDU0LCAzNDU1LCAzNDU2LCAzNDU3LCAzNDU4LCAzNDU5LCAzNDYwLCAzNDYxLCAzNDYyLCAzNDYzLCAzNDY0LCAzNDY1LCAzNDY2LCAzNDY3LCAzNDY4LCAzNDY5LCAzNDcwLCAzNDcxLCAzNDcyLCAzNDczLCAzNDc0LCAzNDc1LCAzNDc2LCAzNDc3LCAzNDc4LCAzNDc5LCAzNDgwLCAzNDgxLCAzNDgyLCAzNDgzLCAzNDg0LCAzNDg1LCAzNDg2LCAzNDg3LCAzNDg4LCAzNDg5LCAzNDkwLCAzNDkxLCAzNDkyLCAzNDkzLCAzNDk0LCAzNDk1LCAzNDk2LCAzNDk3LCAzNDk4LCAzNDk5LCAzNTAwLCAzNTAxLCAzNTAyLCAzNTAzLCAzNTA0LCAzNTA1LCAzNTA2LCAzNTA3LCAzNTA4LCAzNTA5LCAzNTEwLCAzNTExLCAzNTEyLCAzNTEzLCAzNTE0LCAzNTE1LCAzNTE2LCAzNTE3LCAzNTE4LCAzNTE5LCAzNTIwLCAzNTIxLCAzNTIyLCAzNTIzLCAzNTI0LCAzNTI1LCAzNTI2LCAzNTI3LCAzNTI4LCAzNTI5LCAzNTMwLCAzNTMxLCAzNTMyLCAzNTMzLCAzNTM0LCAzNTM1LCAzNTM2LCAzNTM3LCAzNTM4LCAzNTM5LCAzNTQwLCAzNTQxLCAzNTQyLCAzNTQzLCAzNTQ0LCAzNTQ1LCAzNTQ2LCAzNTQ3LCAzNTQ4LCAzNTQ5LCAzNTUwLCAzNTUxLCAzNTUyLCAzNTUzLCAzNTU0LCAzNTU1LCAzNTU2LCAzNTU3LCAzNTU4LCAzNTU5LCAzNTYwLCAzNTYxLCAzNTYyLCAzNTYzLCAzNTY0LCAzNTY1LCAzNTY2LCAzNTY3LCAzNTY4LCAzNTY5LCAzNTcwLCAzNTcxLCAzNTcyLCAzNTczLCAzNTc0LCAzNTc1LCAzNTc2LCAzNTc3LCAzNTc4LCAzNTc5LCAzNTgwLCAzNTgxLCAzNTgyLCAzNTgzLCAzNTg0LCAzNTg1LCAzNTg2LCAzNTg3LCAzNTg4LCAzNTg5LCAzNTkwLCAzNTkxLCAzNTkyLCAzNTkzLCAzNTk0LCAzNTk1LCAzNTk2LCAzNTk3LCAzNTk4LCAzNTk5LCAzNjAwLCAzNjAxLCAzNjAyLCAzNjAzLCAzNjA0LCAzNjA1LCAzNjA2LCAzNjA3LCAzNjA4LCAzNjA5LCAzNjEwLCAzNjExLCAzNjEyLCAzNjEzLCAzNjE0LCAzNjE1LCAzNjE2LCAzNjE3LCAzNjE4LCAzNjE5LCAzNjIwLCAzNjIxLCAzNjIyLCAzNjIzLCAzNjI0LCAzNjI1LCAzNjI2LCAzNjI3LCAzNjI4LCAzNjI5LCAzNjMwLCAzNjMxLCAzNjMyLCAzNjMzLCAzNjM0LCAzNjM1LCAzNjM2LCAzNjM3LCAzNjM4LCAzNjM5LCAzNjQwLCAzNjQxLCAzNjQyLCAzNjQzLCAzNjQ0LCAzNjQ1LCAzNjQ2LCAzNjQ3LCAzNjQ4LCAzNjQ5LCAzNjUwLCAzNjUxLCAzNjUyLCAzNjUzLCAzNjU0LCAzNjU1LCAzNjU2LCAzNjU3LCAzNjU4LCAzNjU5LCAzNjYwLCAzNjYxLCAzNjYyLCAzNjYzLCAzNjY0LCAzNjY1LCAzNjY2LCAzNjY3LCAzNjY4LCAzNjY5LCAzNjcwLCAzNjcxLCAzNjcyLCAzNjczLCAzNjc0LCAzNjc1LCAzNjc2LCAzNjc3LCAzNjc4LCAzNjc5LCAzNjgwLCAzNjgxLCAzNjgyLCAzNjgzLCAzNjg0LCAzNjg1LCAzNjg2LCAzNjg3LCAzNjg4LCAzNjg5LCAzNjkwLCAzNjkxLCAzNjkyLCAzNjkzLCAzNjk0LCAzNjk1LCAzNjk2LCAzNjk3LCAzNjk4LCAzNjk5LCAzNzAwLCAzNzAxLCAzNzAyLCAzNzAzLCAzNzA0LCAzNzA1LCAzNzA2LCAzNzA3LCAzNzA4LCAzNzA5LCAzNzEwLCAzNzExLCAzNzEyLCAzNzEzLCAzNzE0LCAzNzE1LCAzNzE2LCAzNzE3LCAzNzE4LCAzNzE5LCAzNzIwLCAzNzIxLCAzNzIyLCAzNzIzLCAzNzI0LCAzNzI1LCAzNzI2LCAzNzI3LCAzNzI4LCAzNzI5LCAzNzMwLCAzNzMxLCAzNzMyLCAzNzMzLCAzNzM0LCAzNzM1LCAzNzM2LCAzNzM3LCAzNzM4XZQu"
+ "serialized": "gASVQRQAAAAAAABYOhQAAFszMDM1LCAzMDM2LCAzMDM3LCAzMDM4LCAzMDM5LCAzMDQwLCAzMDQxLCAzMDQyLCAzMDQzLCAzMDQ0LCAzMDQ1LCAzMDQ2LCAzMDQ3LCAzMDQ4LCAzMDQ5LCAzMDUwLCAzMDUxLCAzMDUyLCAzMDUzLCAzMDU0LCAzMDU1LCAzMDU2LCAzMDU3LCAzMDU4LCAzMDU5LCAzMDYwLCAzMDYxLCAzMDYyLCAzMDYzLCAzMDY0LCAzMDY1LCAzMDY2LCAzMDY3LCAzMDY4LCAzMDY5LCAzMDcwLCAzMDcxLCAzMDcyLCAzMDczLCAzMDc0LCAzMDc1LCAzMDc2LCAzMDc3LCAzMDc4LCAzMDc5LCAzMDgwLCAzMDgxLCAzMDgyLCAzMDgzLCAzMDg0LCAzMDg1LCAzMDg2LCAzMDg3LCAzMDg4LCAzMDg5LCAzMDkwLCAzMDkxLCAzMDkyLCAzMDkzLCAzMDk0LCAzMDk1LCAzMDk2LCAzMDk3LCAzMDk4LCAzMDk5LCAzMTAwLCAzMTAxLCAzMTAyLCAzMTAzLCAzMTA0LCAzMTA1LCAzMTA2LCAzMTA3LCAzMTA4LCAzMTA5LCAzMTEwLCAzMTExLCAzMTEyLCAzMTEzLCAzMTE0LCAzMTE1LCAzMTE2LCAzMTE3LCAzMTE4LCAzMTE5LCAzMTIwLCAzMTIxLCAzMTIyLCAzMTIzLCAzMTI0LCAzMTI1LCAzMTI2LCAzMTI3LCAzMTI4LCAzMTI5LCAzMTMwLCAzMTMxLCAzMTMyLCAzMTMzLCAzMTM0LCAzMTM0LCAzMTM1LCAzMTM2LCAzMTM3LCAzMTM4LCAzMTM5LCAzMTQwLCAzMTQxLCAzMTQyLCAzMTQzLCAzMTQ0LCAzMTQ1LCAzMTQ2LCAzMTQ3LCAzMTQ4LCAzMTQ5LCAzMTUwLCAzMTUxLCAzMTUyLCAzMTUzLCAzMTU0LCAzMTU1LCAzMTU2LCAzMTU3LCAzMTU4LCAzMTU5LCAzMTYwLCAzMTYxLCAzMTYyLCAzMTYzLCAzMTY0LCAzMTY1LCAzMTY2LCAzMTY3LCAzMTY4LCAzMTY5LCAzMTcwLCAzMTcxLCAzMTcyLCAzMTczLCAzMTc0LCAzMTc1LCAzMTc2LCAzMTc3LCAzMTc4LCAzMTc5LCAzMTgwLCAzMTgxLCAzMTgyLCAzMTgzLCAzMTg0LCAzMTg1LCAzMTg2LCAzMTg3LCAzMTg4LCAzMTg5LCAzMTkwLCAzMTkxLCAzMTkyLCAzMTkzLCAzMTk0LCAzMTk1LCAzMTk2LCAzMTk3LCAzMTk4LCAzMTk5LCAzMjAwLCAzMjAxLCAzMjAyLCAzMjAzLCAzMjA0LCAzMjA1LCAzMjA2LCAzMjA3LCAzMjA4LCAzMjA5LCAzMjEwLCAzMjExLCAzMjEyLCAzMjEzLCAzMjE0LCAzMjE1LCAzMjE2LCAzMjE3LCAzMjE4LCAzMjE5LCAzMjIwLCAzMjIxLCAzMjIyLCAzMjIzLCAzMjI0LCAzMjI1LCAzMjI2LCAzMjI3LCAzMjI4LCAzMjI5LCAzMjMwLCAzMjMxLCAzMjMyLCAzMjMzLCAzMjM0LCAzMjM1LCAzMjM2LCAzMjM3LCAzMjM4LCAzMjM5LCAzMjQwLCAzMjQxLCAzMjQyLCAzMjQzLCAzMjQ0LCAzMjQ1LCAzMjQ2LCAzMjQ3LCAzMjQ4LCAzMjQ5LCAzMjUwLCAzMjUxLCAzMjUyLCAzMjUzLCAzMjU0LCAzMjU1LCAzMjU2LCAzMjU3LCAzMjU4LCAzMjU5LCAzMjYwLCAzMjYxLCAzMjYyLCAzMjYzLCAzMjY0LCAzMjY1LCAzMjY2LCAzMjY3LCAzMjY4LCAzMjY5LCAzMjcwLCAzMjcxLCAzMjcyLCAzMjczLCAzMjc0LCAzMjc1LCAzMjc2LCAzMjc3LCAzMjc4LCAzMjc5LCAzMjgwLCAzMjgxLCAzMjgyLCAzMjgzLCAzMjg0LCAzMjg1LCAzMjg2LCAzMjg3LCAzMjg4LCAzMjg5LCAzMjkwLCAzMjkxLCAzMjkyLCAzMjkzLCAzMjk0LCAzMjk1LCAzMjk2LCAzMjk3LCAzMjk4LCAzMjk5LCAzMzAwLCAzMzAxLCAzMzAyLCAzMzAzLCAzMzA0LCAzMzA1LCAzMzA2LCAzMzA3LCAzMzA4LCAzMzA5LCAzMzEwLCAzMzExLCAzMzEyLCAzMzEzLCAzMzE0LCAzMzE1LCAzMzE2LCAzMzE3LCAzMzE4LCAzMzE5LCAzMzIwLCAzMzIxLCAzMzIyLCAzMzIzLCAzMzI0LCAzMzI1LCAzMzI2LCAzMzI3LCAzMzI4LCAzMzI5LCAzMzMwLCAzMzMxLCAzMzMyLCAzMzMzLCAzMzM0LCAzMzM1LCAzMzM2LCAzMzM3LCAzMzM4LCAzMzM5LCAzMzQwLCAzMzQxLCAzMzQyLCAzMzQzLCAzMzQ0LCAzMzQ1LCAzMzQ2LCAzMzQ3LCAzMzQ4LCAzMzQ5LCAzMzUwLCAzMzUxLCAzMzUyLCAzMzUzLCAzMzU0LCAzMzU1LCAzMzU2LCAzMzU3LCAzMzU4LCAzMzU5LCAzMzYwLCAzMzYxLCAzMzYyLCAzMzYzLCAzMzY0LCAzMzY1LCAzMzY2LCAzMzY3LCAzMzY4LCAzMzY5LCAzMzcwLCAzMzcxLCAzMzcyLCAzMzczLCAzMzc0LCAzMzc1LCAzMzc2LCAzMzc3LCAzMzc4LCAzMzc5LCAzMzgwLCAzMzgxLCAzMzgyLCAzMzgzLCAzMzg0LCAzMzg1LCAzMzg2LCAzMzg3LCAzMzg4LCAzMzg5LCAzMzkwLCAzMzkxLCAzMzkyLCAzMzkzLCAzMzk0LCAzMzk1LCAzMzk2LCAzMzk3LCAzMzk4LCAzMzk5LCAzNDAwLCAzNDAxLCAzNDAyLCAzNDAzLCAzNDA0LCAzNDA1LCAzNDA2LCAzNDA3LCAzNDA4LCAzNDA5LCAzNDEwLCAzNDExLCAzNDEyLCAzNDEzLCAzNDE0LCAzNDE1LCAzNDE2LCAzNDE3LCAzNDE4LCAzNDE5LCAzNDIwLCAzNDIxLCAzNDIyLCAzNDIzLCAzNDI0LCAzNDI1LCAzNDI2LCAzNDI3LCAzNDI4LCAzNDI5LCAzNDMwLCAzNDMxLCAzNDMyLCAzNDMzLCAzNDM0LCAzNDM1LCAzNDM2LCAzNDM3LCAzNDM4LCAzNDM5LCAzNDQwLCAzNDQxLCAzNDQyLCAzNDQzLCAzNDQ0LCAzNDQ1LCAzNDQ2LCAzNDQ3LCAzNDQ4LCAzNDQ5LCAzNDUwLCAzNDUxLCAzNDUyLCAzNDUzLCAzNDU0LCAzNDU1LCAzNDU2LCAzNDU3LCAzNDU4LCAzNDU5LCAzNDYwLCAzNDYxLCAzNDYyLCAzNDYzLCAzNDY0LCAzNDY1LCAzNDY2LCAzNDY3LCAzNDY4LCAzNDY5LCAzNDcwLCAzNDcxLCAzNDcyLCAzNDczLCAzNDc0LCAzNDc1LCAzNDc2LCAzNDc3LCAzNDc4LCAzNDc5LCAzNDgwLCAzNDgxLCAzNDgyLCAzNDgzLCAzNDg0LCAzNDg1LCAzNDg2LCAzNDg3LCAzNDg4LCAzNDg5LCAzNDkwLCAzNDkxLCAzNDkyLCAzNDkzLCAzNDk0LCAzNDk1LCAzNDk2LCAzNDk3LCAzNDk4LCAzNDk5LCAzNTAwLCAzNTAxLCAzNTAyLCAzNTAzLCAzNTA0LCAzNTA1LCAzNTA2LCAzNTA3LCAzNTA4LCAzNTA5LCAzNTEwLCAzNTExLCAzNTEyLCAzNTEzLCAzNTE0LCAzNTE1LCAzNTE2LCAzNTE3LCAzNTE4LCAzNTE5LCAzNTIwLCAzNTIxLCAzNTIyLCAzNTIzLCAzNTI0LCAzNTI1LCAzNTI2LCAzNTI3LCAzNTI4LCAzNTI5LCAzNTMwLCAzNTMxLCAzNTMyLCAzNTMzLCAzNTM0LCAzNTM1LCAzNTM2LCAzNTM3LCAzNTM4LCAzNTM5LCAzNTQwLCAzNTQxLCAzNTQyLCAzNTQzLCAzNTQ0LCAzNTQ1LCAzNTQ2LCAzNTQ3LCAzNTQ4LCAzNTQ5LCAzNTUwLCAzNTUxLCAzNTUyLCAzNTUzLCAzNTU0LCAzNTU1LCAzNTU2LCAzNTU3LCAzNTU4LCAzNTU5LCAzNTYwLCAzNTYxLCAzNTYyLCAzNTYzLCAzNTY0LCAzNTY1LCAzNTY2LCAzNTY3LCAzNTY4LCAzNTY5LCAzNTcwLCAzNTcxLCAzNTcyLCAzNTczLCAzNTc0LCAzNTc1LCAzNTc2LCAzNTc3LCAzNTc4LCAzNTc5LCAzNTgwLCAzNTgxLCAzNTgyLCAzNTgzLCAzNTg0LCAzNTg1LCAzNTg2LCAzNTg3LCAzNTg4LCAzNTg5LCAzNTkwLCAzNTkxLCAzNTkyLCAzNTkzLCAzNTk0LCAzNTk1LCAzNTk2LCAzNTk3LCAzNTk4LCAzNTk5LCAzNjAwLCAzNjAxLCAzNjAyLCAzNjAzLCAzNjA0LCAzNjA1LCAzNjA2LCAzNjA3LCAzNjA4LCAzNjA5LCAzNjEwLCAzNjExLCAzNjEyLCAzNjEzLCAzNjE0LCAzNjE1LCAzNjE2LCAzNjE3LCAzNjE4LCAzNjE5LCAzNjIwLCAzNjIxLCAzNjIyLCAzNjIzLCAzNjI0LCAzNjI1LCAzNjI2LCAzNjI3LCAzNjI4LCAzNjI5LCAzNjMwLCAzNjMxLCAzNjMyLCAzNjMzLCAzNjM0LCAzNjM1LCAzNjM2LCAzNjM3LCAzNjM4LCAzNjM5LCAzNjQwLCAzNjQxLCAzNjQyLCAzNjQzLCAzNjQ0LCAzNjQ1LCAzNjQ2LCAzNjQ3LCAzNjQ4LCAzNjQ5LCAzNjUwLCAzNjUxLCAzNjUyLCAzNjUzLCAzNjU0LCAzNjU1LCAzNjU2LCAzNjU3LCAzNjU4LCAzNjU5LCAzNjYwLCAzNjYxLCAzNjYyLCAzNjYzLCAzNjY0LCAzNjY1LCAzNjY2LCAzNjY3LCAzNjY4LCAzNjY5LCAzNjcwLCAzNjcxLCAzNjcyLCAzNjczLCAzNjc0LCAzNjc1LCAzNjc2LCAzNjc3LCAzNjc4LCAzNjc5LCAzNjgwLCAzNjgxLCAzNjgyLCAzNjgzLCAzNjg0LCAzNjg1LCAzNjg2LCAzNjg3LCAzNjg4LCAzNjg5LCAzNjkwLCAzNjkxLCAzNjkyLCAzNjkzLCAzNjk0LCAzNjk1LCAzNjk2LCAzNjk3LCAzNjk4LCAzNjk5LCAzNzAwLCAzNzAxLCAzNzAyLCAzNzAzLCAzNzA0LCAzNzA1LCAzNzA2LCAzNzA3LCAzNzA4LCAzNzA5LCAzNzEwLCAzNzExLCAzNzEyLCAzNzEzLCAzNzE0LCAzNzE1LCAzNzE2LCAzNzE3LCAzNzE4LCAzNzE5LCAzNzIwLCAzNzIxLCAzNzIyLCAzNzIzLCAzNzI0LCAzNzI1LCAzNzI2LCAzNzI3LCAzNzI4LCAzNzI5LCAzNzMwLCAzNzMxLCAzNzMyLCAzNzMzLCAzNzM0LCAzNzM1LCAzNzM2LCAzNzM3LCAzNzM4LCAzNzM5LCAzNzM5LCAzNzQwLCAzNzQwLCAzNzQxLCAzNzQxLCAzNzQyLCAzNzQyLCAzNzQzLCAzNzQzLCAzNzQ0LCAzNzQ0LCAzNzQ1LCAzNzQ1LCAzNzQ2LCAzNzQ2LCAzNzQ3LCAzNzQ3LCAzNzQ4LCAzNzQ4LCAzNzQ5LCAzNzQ5LCAzNzUwLCAzNzUwLCAzNzUxLCAzNzUxLCAzNzUyLCAzNzUyLCAzNzUzLCAzNzUzLCAzNzU0LCAzNzU0LCAzNzU1LCAzNzU1LCAzNzU2LCAzNzU2LCAzNzU3LCAzNzU3LCAzNzU4LCAzNzU4LCAzNzU5LCAzNzU5LCAzNzYwLCAzNzYwLCAzNzYxLCAzNzYxLCAzNzYyLCAzNzYyLCAzNzYzLCAzNzYzLCAzNzY0LCAzNzY0LCAzNzY1LCAzNzY1LCAzNzY2LCAzNzY2LCAzNzY3LCAzNzY3LCAzNzY4LCAzNzY4LCAzNzY5LCAzNzY5LCAzNzcwLCAzNzcwLCAzNzcxLCAzNzcxLCAzNzcyLCAzNzcyLCAzNzczLCAzNzczLCAzNzc0LCAzNzc0LCAzNzc1LCAzNzc1LCAzNzc2LCAzNzc2LCAzNzc3LCAzNzc3LCAzNzc4LCAzNzc4LCAzNzc5LCAzNzc5LCAzNzgwLCAzNzgwLCAzNzgxLCAzNzgxLCAzNzgyLCAzNzgyLCAzNzgzLCAzNzgzLCAzNzg0LCAzNzg0LCAzNzg1LCAzNzg1LCAzNzg2LCAzNzg2LCAzNzg3LCAzNzg3LCAzNzg4LCAzNzg4LCAzNzg5LCAzNzg5LCAzNzkwLCAzNzkwLCAzNzkxLCAzNzkxLCAzNzkyLCAzNzkyLCAzNzkzLCAzNzkzLCAzNzk0LCAzNzk0LCAzNzk1LCAzNzk1LCAzNzk2LCAzNzk2LCAzNzk3LCAzNzk3LCAzNzk4LCAzNzk4LCAzNzk5LCAzNzk5LCAzODAwLCAzODAwLCAzODAxLCAzODAxLCAzODAyLCAzODAyLCAzODAzLCAzODAzLCAzODA0LCAzODA0LCAzODA1LCAzODA1LCAzODA2LCAzODA2LCAzODA3LCAzODA3LCAzODA4LCAzODA4LCAzODA5LCAzODA5LCAzODEwLCAzODEwLCAzODExLCAzODExLCAzODEyLCAzODEyLCAzODEzLCAzODEzLCAzODE0LCAzODE0LCAzODE1LCAzODE1LCAzODE2LCAzODE2LCAzODE3LCAzODE3XZQu"
},
"has widget": true,
"widget name": "val",
"widget pos": "besides",
- "widget data": "gASV9BAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTR4SjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUWIYQAABbMzAzNSwgMzAzNiwgMzAzNywgMzAzOCwgMzAzOSwgMzA0MCwgMzA0MSwgMzA0MiwgMzA0MywgMzA0NCwgMzA0NSwgMzA0NiwgMzA0NywgMzA0OCwgMzA0OSwgMzA1MCwgMzA1MSwgMzA1MiwgMzA1MywgMzA1NCwgMzA1NSwgMzA1NiwgMzA1NywgMzA1OCwgMzA1OSwgMzA2MCwgMzA2MSwgMzA2MiwgMzA2MywgMzA2NCwgMzA2NSwgMzA2NiwgMzA2NywgMzA2OCwgMzA2OSwgMzA3MCwgMzA3MSwgMzA3MiwgMzA3MywgMzA3NCwgMzA3NSwgMzA3NiwgMzA3NywgMzA3OCwgMzA3OSwgMzA4MCwgMzA4MSwgMzA4MiwgMzA4MywgMzA4NCwgMzA4NSwgMzA4NiwgMzA4NywgMzA4OCwgMzA4OSwgMzA5MCwgMzA5MSwgMzA5MiwgMzA5MywgMzA5NCwgMzA5NSwgMzA5NiwgMzA5NywgMzA5OCwgMzA5OSwgMzEwMCwgMzEwMSwgMzEwMiwgMzEwMywgMzEwNCwgMzEwNSwgMzEwNiwgMzEwNywgMzEwOCwgMzEwOSwgMzExMCwgMzExMSwgMzExMiwgMzExMywgMzExNCwgMzExNSwgMzExNiwgMzExNywgMzExOCwgMzExOSwgMzEyMCwgMzEyMSwgMzEyMiwgMzEyMywgMzEyNCwgMzEyNSwgMzEyNiwgMzEyNywgMzEyOCwgMzEyOSwgMzEzMCwgMzEzMSwgMzEzMiwgMzEzMywgMzEzNCwgMzEzNCwgMzEzNSwgMzEzNiwgMzEzNywgMzEzOCwgMzEzOSwgMzE0MCwgMzE0MSwgMzE0MiwgMzE0MywgMzE0NCwgMzE0NSwgMzE0NiwgMzE0NywgMzE0OCwgMzE0OSwgMzE1MCwgMzE1MSwgMzE1MiwgMzE1MywgMzE1NCwgMzE1NSwgMzE1NiwgMzE1NywgMzE1OCwgMzE1OSwgMzE2MCwgMzE2MSwgMzE2MiwgMzE2MywgMzE2NCwgMzE2NSwgMzE2NiwgMzE2NywgMzE2OCwgMzE2OSwgMzE3MCwgMzE3MSwgMzE3MiwgMzE3MywgMzE3NCwgMzE3NSwgMzE3NiwgMzE3NywgMzE3OCwgMzE3OSwgMzE4MCwgMzE4MSwgMzE4MiwgMzE4MywgMzE4NCwgMzE4NSwgMzE4NiwgMzE4NywgMzE4OCwgMzE4OSwgMzE5MCwgMzE5MSwgMzE5MiwgMzE5MywgMzE5NCwgMzE5NSwgMzE5NiwgMzE5NywgMzE5OCwgMzE5OSwgMzIwMCwgMzIwMSwgMzIwMiwgMzIwMywgMzIwNCwgMzIwNSwgMzIwNiwgMzIwNywgMzIwOCwgMzIwOSwgMzIxMCwgMzIxMSwgMzIxMiwgMzIxMywgMzIxNCwgMzIxNSwgMzIxNiwgMzIxNywgMzIxOCwgMzIxOSwgMzIyMCwgMzIyMSwgMzIyMiwgMzIyMywgMzIyNCwgMzIyNSwgMzIyNiwgMzIyNywgMzIyOCwgMzIyOSwgMzIzMCwgMzIzMSwgMzIzMiwgMzIzMywgMzIzNCwgMzIzNSwgMzIzNiwgMzIzNywgMzIzOCwgMzIzOSwgMzI0MCwgMzI0MSwgMzI0MiwgMzI0MywgMzI0NCwgMzI0NSwgMzI0NiwgMzI0NywgMzI0OCwgMzI0OSwgMzI1MCwgMzI1MSwgMzI1MiwgMzI1MywgMzI1NCwgMzI1NSwgMzI1NiwgMzI1NywgMzI1OCwgMzI1OSwgMzI2MCwgMzI2MSwgMzI2MiwgMzI2MywgMzI2NCwgMzI2NSwgMzI2NiwgMzI2NywgMzI2OCwgMzI2OSwgMzI3MCwgMzI3MSwgMzI3MiwgMzI3MywgMzI3NCwgMzI3NSwgMzI3NiwgMzI3NywgMzI3OCwgMzI3OSwgMzI4MCwgMzI4MSwgMzI4MiwgMzI4MywgMzI4NCwgMzI4NSwgMzI4NiwgMzI4NywgMzI4OCwgMzI4OSwgMzI5MCwgMzI5MSwgMzI5MiwgMzI5MywgMzI5NCwgMzI5NSwgMzI5NiwgMzI5NywgMzI5OCwgMzI5OSwgMzMwMCwgMzMwMSwgMzMwMiwgMzMwMywgMzMwNCwgMzMwNSwgMzMwNiwgMzMwNywgMzMwOCwgMzMwOSwgMzMxMCwgMzMxMSwgMzMxMiwgMzMxMywgMzMxNCwgMzMxNSwgMzMxNiwgMzMxNywgMzMxOCwgMzMxOSwgMzMyMCwgMzMyMSwgMzMyMiwgMzMyMywgMzMyNCwgMzMyNSwgMzMyNiwgMzMyNywgMzMyOCwgMzMyOSwgMzMzMCwgMzMzMSwgMzMzMiwgMzMzMywgMzMzNCwgMzMzNSwgMzMzNiwgMzMzNywgMzMzOCwgMzMzOSwgMzM0MCwgMzM0MSwgMzM0MiwgMzM0MywgMzM0NCwgMzM0NSwgMzM0NiwgMzM0NywgMzM0OCwgMzM0OSwgMzM1MCwgMzM1MSwgMzM1MiwgMzM1MywgMzM1NCwgMzM1NSwgMzM1NiwgMzM1NywgMzM1OCwgMzM1OSwgMzM2MCwgMzM2MSwgMzM2MiwgMzM2MywgMzM2NCwgMzM2NSwgMzM2NiwgMzM2NywgMzM2OCwgMzM2OSwgMzM3MCwgMzM3MSwgMzM3MiwgMzM3MywgMzM3NCwgMzM3NSwgMzM3NiwgMzM3NywgMzM3OCwgMzM3OSwgMzM4MCwgMzM4MSwgMzM4MiwgMzM4MywgMzM4NCwgMzM4NSwgMzM4NiwgMzM4NywgMzM4OCwgMzM4OSwgMzM5MCwgMzM5MSwgMzM5MiwgMzM5MywgMzM5NCwgMzM5NSwgMzM5NiwgMzM5NywgMzM5OCwgMzM5OSwgMzQwMCwgMzQwMSwgMzQwMiwgMzQwMywgMzQwNCwgMzQwNSwgMzQwNiwgMzQwNywgMzQwOCwgMzQwOSwgMzQxMCwgMzQxMSwgMzQxMiwgMzQxMywgMzQxNCwgMzQxNSwgMzQxNiwgMzQxNywgMzQxOCwgMzQxOSwgMzQyMCwgMzQyMSwgMzQyMiwgMzQyMywgMzQyNCwgMzQyNSwgMzQyNiwgMzQyNywgMzQyOCwgMzQyOSwgMzQzMCwgMzQzMSwgMzQzMiwgMzQzMywgMzQzNCwgMzQzNSwgMzQzNiwgMzQzNywgMzQzOCwgMzQzOSwgMzQ0MCwgMzQ0MSwgMzQ0MiwgMzQ0MywgMzQ0NCwgMzQ0NSwgMzQ0NiwgMzQ0NywgMzQ0OCwgMzQ0OSwgMzQ1MCwgMzQ1MSwgMzQ1MiwgMzQ1MywgMzQ1NCwgMzQ1NSwgMzQ1NiwgMzQ1NywgMzQ1OCwgMzQ1OSwgMzQ2MCwgMzQ2MSwgMzQ2MiwgMzQ2MywgMzQ2NCwgMzQ2NSwgMzQ2NiwgMzQ2NywgMzQ2OCwgMzQ2OSwgMzQ3MCwgMzQ3MSwgMzQ3MiwgMzQ3MywgMzQ3NCwgMzQ3NSwgMzQ3NiwgMzQ3NywgMzQ3OCwgMzQ3OSwgMzQ4MCwgMzQ4MSwgMzQ4MiwgMzQ4MywgMzQ4NCwgMzQ4NSwgMzQ4NiwgMzQ4NywgMzQ4OCwgMzQ4OSwgMzQ5MCwgMzQ5MSwgMzQ5MiwgMzQ5MywgMzQ5NCwgMzQ5NSwgMzQ5NiwgMzQ5NywgMzQ5OCwgMzQ5OSwgMzUwMCwgMzUwMSwgMzUwMiwgMzUwMywgMzUwNCwgMzUwNSwgMzUwNiwgMzUwNywgMzUwOCwgMzUwOSwgMzUxMCwgMzUxMSwgMzUxMiwgMzUxMywgMzUxNCwgMzUxNSwgMzUxNiwgMzUxNywgMzUxOCwgMzUxOSwgMzUyMCwgMzUyMSwgMzUyMiwgMzUyMywgMzUyNCwgMzUyNSwgMzUyNiwgMzUyNywgMzUyOCwgMzUyOSwgMzUzMCwgMzUzMSwgMzUzMiwgMzUzMywgMzUzNCwgMzUzNSwgMzUzNiwgMzUzNywgMzUzOCwgMzUzOSwgMzU0MCwgMzU0MSwgMzU0MiwgMzU0MywgMzU0NCwgMzU0NSwgMzU0NiwgMzU0NywgMzU0OCwgMzU0OSwgMzU1MCwgMzU1MSwgMzU1MiwgMzU1MywgMzU1NCwgMzU1NSwgMzU1NiwgMzU1NywgMzU1OCwgMzU1OSwgMzU2MCwgMzU2MSwgMzU2MiwgMzU2MywgMzU2NCwgMzU2NSwgMzU2NiwgMzU2NywgMzU2OCwgMzU2OSwgMzU3MCwgMzU3MSwgMzU3MiwgMzU3MywgMzU3NCwgMzU3NSwgMzU3NiwgMzU3NywgMzU3OCwgMzU3OSwgMzU4MCwgMzU4MSwgMzU4MiwgMzU4MywgMzU4NCwgMzU4NSwgMzU4NiwgMzU4NywgMzU4OCwgMzU4OSwgMzU5MCwgMzU5MSwgMzU5MiwgMzU5MywgMzU5NCwgMzU5NSwgMzU5NiwgMzU5NywgMzU5OCwgMzU5OSwgMzYwMCwgMzYwMSwgMzYwMiwgMzYwMywgMzYwNCwgMzYwNSwgMzYwNiwgMzYwNywgMzYwOCwgMzYwOSwgMzYxMCwgMzYxMSwgMzYxMiwgMzYxMywgMzYxNCwgMzYxNSwgMzYxNiwgMzYxNywgMzYxOCwgMzYxOSwgMzYyMCwgMzYyMSwgMzYyMiwgMzYyMywgMzYyNCwgMzYyNSwgMzYyNiwgMzYyNywgMzYyOCwgMzYyOSwgMzYzMCwgMzYzMSwgMzYzMiwgMzYzMywgMzYzNCwgMzYzNSwgMzYzNiwgMzYzNywgMzYzOCwgMzYzOSwgMzY0MCwgMzY0MSwgMzY0MiwgMzY0MywgMzY0NCwgMzY0NSwgMzY0NiwgMzY0NywgMzY0OCwgMzY0OSwgMzY1MCwgMzY1MSwgMzY1MiwgMzY1MywgMzY1NCwgMzY1NSwgMzY1NiwgMzY1NywgMzY1OCwgMzY1OSwgMzY2MCwgMzY2MSwgMzY2MiwgMzY2MywgMzY2NCwgMzY2NSwgMzY2NiwgMzY2NywgMzY2OCwgMzY2OSwgMzY3MCwgMzY3MSwgMzY3MiwgMzY3MywgMzY3NCwgMzY3NSwgMzY3NiwgMzY3NywgMzY3OCwgMzY3OSwgMzY4MCwgMzY4MSwgMzY4MiwgMzY4MywgMzY4NCwgMzY4NSwgMzY4NiwgMzY4NywgMzY4OCwgMzY4OSwgMzY5MCwgMzY5MSwgMzY5MiwgMzY5MywgMzY5NCwgMzY5NSwgMzY5NiwgMzY5NywgMzY5OCwgMzY5OSwgMzcwMCwgMzcwMSwgMzcwMiwgMzcwMywgMzcwNCwgMzcwNSwgMzcwNiwgMzcwNywgMzcwOCwgMzcwOSwgMzcxMCwgMzcxMSwgMzcxMiwgMzcxMywgMzcxNCwgMzcxNSwgMzcxNiwgMzcxNywgMzcxOCwgMzcxOSwgMzcyMCwgMzcyMSwgMzcyMiwgMzcyMywgMzcyNCwgMzcyNSwgMzcyNiwgMzcyNywgMzcyOCwgMzcyOSwgMzczMCwgMzczMSwgMzczMiwgMzczMywgMzczNCwgMzczNSwgMzczNiwgMzczNywgMzczOF2UdWJzLg=="
+ "widget data": "gASVqBQAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTScCjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUWDoUAABbMzAzNSwgMzAzNiwgMzAzNywgMzAzOCwgMzAzOSwgMzA0MCwgMzA0MSwgMzA0MiwgMzA0MywgMzA0NCwgMzA0NSwgMzA0NiwgMzA0NywgMzA0OCwgMzA0OSwgMzA1MCwgMzA1MSwgMzA1MiwgMzA1MywgMzA1NCwgMzA1NSwgMzA1NiwgMzA1NywgMzA1OCwgMzA1OSwgMzA2MCwgMzA2MSwgMzA2MiwgMzA2MywgMzA2NCwgMzA2NSwgMzA2NiwgMzA2NywgMzA2OCwgMzA2OSwgMzA3MCwgMzA3MSwgMzA3MiwgMzA3MywgMzA3NCwgMzA3NSwgMzA3NiwgMzA3NywgMzA3OCwgMzA3OSwgMzA4MCwgMzA4MSwgMzA4MiwgMzA4MywgMzA4NCwgMzA4NSwgMzA4NiwgMzA4NywgMzA4OCwgMzA4OSwgMzA5MCwgMzA5MSwgMzA5MiwgMzA5MywgMzA5NCwgMzA5NSwgMzA5NiwgMzA5NywgMzA5OCwgMzA5OSwgMzEwMCwgMzEwMSwgMzEwMiwgMzEwMywgMzEwNCwgMzEwNSwgMzEwNiwgMzEwNywgMzEwOCwgMzEwOSwgMzExMCwgMzExMSwgMzExMiwgMzExMywgMzExNCwgMzExNSwgMzExNiwgMzExNywgMzExOCwgMzExOSwgMzEyMCwgMzEyMSwgMzEyMiwgMzEyMywgMzEyNCwgMzEyNSwgMzEyNiwgMzEyNywgMzEyOCwgMzEyOSwgMzEzMCwgMzEzMSwgMzEzMiwgMzEzMywgMzEzNCwgMzEzNCwgMzEzNSwgMzEzNiwgMzEzNywgMzEzOCwgMzEzOSwgMzE0MCwgMzE0MSwgMzE0MiwgMzE0MywgMzE0NCwgMzE0NSwgMzE0NiwgMzE0NywgMzE0OCwgMzE0OSwgMzE1MCwgMzE1MSwgMzE1MiwgMzE1MywgMzE1NCwgMzE1NSwgMzE1NiwgMzE1NywgMzE1OCwgMzE1OSwgMzE2MCwgMzE2MSwgMzE2MiwgMzE2MywgMzE2NCwgMzE2NSwgMzE2NiwgMzE2NywgMzE2OCwgMzE2OSwgMzE3MCwgMzE3MSwgMzE3MiwgMzE3MywgMzE3NCwgMzE3NSwgMzE3NiwgMzE3NywgMzE3OCwgMzE3OSwgMzE4MCwgMzE4MSwgMzE4MiwgMzE4MywgMzE4NCwgMzE4NSwgMzE4NiwgMzE4NywgMzE4OCwgMzE4OSwgMzE5MCwgMzE5MSwgMzE5MiwgMzE5MywgMzE5NCwgMzE5NSwgMzE5NiwgMzE5NywgMzE5OCwgMzE5OSwgMzIwMCwgMzIwMSwgMzIwMiwgMzIwMywgMzIwNCwgMzIwNSwgMzIwNiwgMzIwNywgMzIwOCwgMzIwOSwgMzIxMCwgMzIxMSwgMzIxMiwgMzIxMywgMzIxNCwgMzIxNSwgMzIxNiwgMzIxNywgMzIxOCwgMzIxOSwgMzIyMCwgMzIyMSwgMzIyMiwgMzIyMywgMzIyNCwgMzIyNSwgMzIyNiwgMzIyNywgMzIyOCwgMzIyOSwgMzIzMCwgMzIzMSwgMzIzMiwgMzIzMywgMzIzNCwgMzIzNSwgMzIzNiwgMzIzNywgMzIzOCwgMzIzOSwgMzI0MCwgMzI0MSwgMzI0MiwgMzI0MywgMzI0NCwgMzI0NSwgMzI0NiwgMzI0NywgMzI0OCwgMzI0OSwgMzI1MCwgMzI1MSwgMzI1MiwgMzI1MywgMzI1NCwgMzI1NSwgMzI1NiwgMzI1NywgMzI1OCwgMzI1OSwgMzI2MCwgMzI2MSwgMzI2MiwgMzI2MywgMzI2NCwgMzI2NSwgMzI2NiwgMzI2NywgMzI2OCwgMzI2OSwgMzI3MCwgMzI3MSwgMzI3MiwgMzI3MywgMzI3NCwgMzI3NSwgMzI3NiwgMzI3NywgMzI3OCwgMzI3OSwgMzI4MCwgMzI4MSwgMzI4MiwgMzI4MywgMzI4NCwgMzI4NSwgMzI4NiwgMzI4NywgMzI4OCwgMzI4OSwgMzI5MCwgMzI5MSwgMzI5MiwgMzI5MywgMzI5NCwgMzI5NSwgMzI5NiwgMzI5NywgMzI5OCwgMzI5OSwgMzMwMCwgMzMwMSwgMzMwMiwgMzMwMywgMzMwNCwgMzMwNSwgMzMwNiwgMzMwNywgMzMwOCwgMzMwOSwgMzMxMCwgMzMxMSwgMzMxMiwgMzMxMywgMzMxNCwgMzMxNSwgMzMxNiwgMzMxNywgMzMxOCwgMzMxOSwgMzMyMCwgMzMyMSwgMzMyMiwgMzMyMywgMzMyNCwgMzMyNSwgMzMyNiwgMzMyNywgMzMyOCwgMzMyOSwgMzMzMCwgMzMzMSwgMzMzMiwgMzMzMywgMzMzNCwgMzMzNSwgMzMzNiwgMzMzNywgMzMzOCwgMzMzOSwgMzM0MCwgMzM0MSwgMzM0MiwgMzM0MywgMzM0NCwgMzM0NSwgMzM0NiwgMzM0NywgMzM0OCwgMzM0OSwgMzM1MCwgMzM1MSwgMzM1MiwgMzM1MywgMzM1NCwgMzM1NSwgMzM1NiwgMzM1NywgMzM1OCwgMzM1OSwgMzM2MCwgMzM2MSwgMzM2MiwgMzM2MywgMzM2NCwgMzM2NSwgMzM2NiwgMzM2NywgMzM2OCwgMzM2OSwgMzM3MCwgMzM3MSwgMzM3MiwgMzM3MywgMzM3NCwgMzM3NSwgMzM3NiwgMzM3NywgMzM3OCwgMzM3OSwgMzM4MCwgMzM4MSwgMzM4MiwgMzM4MywgMzM4NCwgMzM4NSwgMzM4NiwgMzM4NywgMzM4OCwgMzM4OSwgMzM5MCwgMzM5MSwgMzM5MiwgMzM5MywgMzM5NCwgMzM5NSwgMzM5NiwgMzM5NywgMzM5OCwgMzM5OSwgMzQwMCwgMzQwMSwgMzQwMiwgMzQwMywgMzQwNCwgMzQwNSwgMzQwNiwgMzQwNywgMzQwOCwgMzQwOSwgMzQxMCwgMzQxMSwgMzQxMiwgMzQxMywgMzQxNCwgMzQxNSwgMzQxNiwgMzQxNywgMzQxOCwgMzQxOSwgMzQyMCwgMzQyMSwgMzQyMiwgMzQyMywgMzQyNCwgMzQyNSwgMzQyNiwgMzQyNywgMzQyOCwgMzQyOSwgMzQzMCwgMzQzMSwgMzQzMiwgMzQzMywgMzQzNCwgMzQzNSwgMzQzNiwgMzQzNywgMzQzOCwgMzQzOSwgMzQ0MCwgMzQ0MSwgMzQ0MiwgMzQ0MywgMzQ0NCwgMzQ0NSwgMzQ0NiwgMzQ0NywgMzQ0OCwgMzQ0OSwgMzQ1MCwgMzQ1MSwgMzQ1MiwgMzQ1MywgMzQ1NCwgMzQ1NSwgMzQ1NiwgMzQ1NywgMzQ1OCwgMzQ1OSwgMzQ2MCwgMzQ2MSwgMzQ2MiwgMzQ2MywgMzQ2NCwgMzQ2NSwgMzQ2NiwgMzQ2NywgMzQ2OCwgMzQ2OSwgMzQ3MCwgMzQ3MSwgMzQ3MiwgMzQ3MywgMzQ3NCwgMzQ3NSwgMzQ3NiwgMzQ3NywgMzQ3OCwgMzQ3OSwgMzQ4MCwgMzQ4MSwgMzQ4MiwgMzQ4MywgMzQ4NCwgMzQ4NSwgMzQ4NiwgMzQ4NywgMzQ4OCwgMzQ4OSwgMzQ5MCwgMzQ5MSwgMzQ5MiwgMzQ5MywgMzQ5NCwgMzQ5NSwgMzQ5NiwgMzQ5NywgMzQ5OCwgMzQ5OSwgMzUwMCwgMzUwMSwgMzUwMiwgMzUwMywgMzUwNCwgMzUwNSwgMzUwNiwgMzUwNywgMzUwOCwgMzUwOSwgMzUxMCwgMzUxMSwgMzUxMiwgMzUxMywgMzUxNCwgMzUxNSwgMzUxNiwgMzUxNywgMzUxOCwgMzUxOSwgMzUyMCwgMzUyMSwgMzUyMiwgMzUyMywgMzUyNCwgMzUyNSwgMzUyNiwgMzUyNywgMzUyOCwgMzUyOSwgMzUzMCwgMzUzMSwgMzUzMiwgMzUzMywgMzUzNCwgMzUzNSwgMzUzNiwgMzUzNywgMzUzOCwgMzUzOSwgMzU0MCwgMzU0MSwgMzU0MiwgMzU0MywgMzU0NCwgMzU0NSwgMzU0NiwgMzU0NywgMzU0OCwgMzU0OSwgMzU1MCwgMzU1MSwgMzU1MiwgMzU1MywgMzU1NCwgMzU1NSwgMzU1NiwgMzU1NywgMzU1OCwgMzU1OSwgMzU2MCwgMzU2MSwgMzU2MiwgMzU2MywgMzU2NCwgMzU2NSwgMzU2NiwgMzU2NywgMzU2OCwgMzU2OSwgMzU3MCwgMzU3MSwgMzU3MiwgMzU3MywgMzU3NCwgMzU3NSwgMzU3NiwgMzU3NywgMzU3OCwgMzU3OSwgMzU4MCwgMzU4MSwgMzU4MiwgMzU4MywgMzU4NCwgMzU4NSwgMzU4NiwgMzU4NywgMzU4OCwgMzU4OSwgMzU5MCwgMzU5MSwgMzU5MiwgMzU5MywgMzU5NCwgMzU5NSwgMzU5NiwgMzU5NywgMzU5OCwgMzU5OSwgMzYwMCwgMzYwMSwgMzYwMiwgMzYwMywgMzYwNCwgMzYwNSwgMzYwNiwgMzYwNywgMzYwOCwgMzYwOSwgMzYxMCwgMzYxMSwgMzYxMiwgMzYxMywgMzYxNCwgMzYxNSwgMzYxNiwgMzYxNywgMzYxOCwgMzYxOSwgMzYyMCwgMzYyMSwgMzYyMiwgMzYyMywgMzYyNCwgMzYyNSwgMzYyNiwgMzYyNywgMzYyOCwgMzYyOSwgMzYzMCwgMzYzMSwgMzYzMiwgMzYzMywgMzYzNCwgMzYzNSwgMzYzNiwgMzYzNywgMzYzOCwgMzYzOSwgMzY0MCwgMzY0MSwgMzY0MiwgMzY0MywgMzY0NCwgMzY0NSwgMzY0NiwgMzY0NywgMzY0OCwgMzY0OSwgMzY1MCwgMzY1MSwgMzY1MiwgMzY1MywgMzY1NCwgMzY1NSwgMzY1NiwgMzY1NywgMzY1OCwgMzY1OSwgMzY2MCwgMzY2MSwgMzY2MiwgMzY2MywgMzY2NCwgMzY2NSwgMzY2NiwgMzY2NywgMzY2OCwgMzY2OSwgMzY3MCwgMzY3MSwgMzY3MiwgMzY3MywgMzY3NCwgMzY3NSwgMzY3NiwgMzY3NywgMzY3OCwgMzY3OSwgMzY4MCwgMzY4MSwgMzY4MiwgMzY4MywgMzY4NCwgMzY4NSwgMzY4NiwgMzY4NywgMzY4OCwgMzY4OSwgMzY5MCwgMzY5MSwgMzY5MiwgMzY5MywgMzY5NCwgMzY5NSwgMzY5NiwgMzY5NywgMzY5OCwgMzY5OSwgMzcwMCwgMzcwMSwgMzcwMiwgMzcwMywgMzcwNCwgMzcwNSwgMzcwNiwgMzcwNywgMzcwOCwgMzcwOSwgMzcxMCwgMzcxMSwgMzcxMiwgMzcxMywgMzcxNCwgMzcxNSwgMzcxNiwgMzcxNywgMzcxOCwgMzcxOSwgMzcyMCwgMzcyMSwgMzcyMiwgMzcyMywgMzcyNCwgMzcyNSwgMzcyNiwgMzcyNywgMzcyOCwgMzcyOSwgMzczMCwgMzczMSwgMzczMiwgMzczMywgMzczNCwgMzczNSwgMzczNiwgMzczNywgMzczOCwgMzczOSwgMzczOSwgMzc0MCwgMzc0MCwgMzc0MSwgMzc0MSwgMzc0MiwgMzc0MiwgMzc0MywgMzc0MywgMzc0NCwgMzc0NCwgMzc0NSwgMzc0NSwgMzc0NiwgMzc0NiwgMzc0NywgMzc0NywgMzc0OCwgMzc0OCwgMzc0OSwgMzc0OSwgMzc1MCwgMzc1MCwgMzc1MSwgMzc1MSwgMzc1MiwgMzc1MiwgMzc1MywgMzc1MywgMzc1NCwgMzc1NCwgMzc1NSwgMzc1NSwgMzc1NiwgMzc1NiwgMzc1NywgMzc1NywgMzc1OCwgMzc1OCwgMzc1OSwgMzc1OSwgMzc2MCwgMzc2MCwgMzc2MSwgMzc2MSwgMzc2MiwgMzc2MiwgMzc2MywgMzc2MywgMzc2NCwgMzc2NCwgMzc2NSwgMzc2NSwgMzc2NiwgMzc2NiwgMzc2NywgMzc2NywgMzc2OCwgMzc2OCwgMzc2OSwgMzc2OSwgMzc3MCwgMzc3MCwgMzc3MSwgMzc3MSwgMzc3MiwgMzc3MiwgMzc3MywgMzc3MywgMzc3NCwgMzc3NCwgMzc3NSwgMzc3NSwgMzc3NiwgMzc3NiwgMzc3NywgMzc3NywgMzc3OCwgMzc3OCwgMzc3OSwgMzc3OSwgMzc4MCwgMzc4MCwgMzc4MSwgMzc4MSwgMzc4MiwgMzc4MiwgMzc4MywgMzc4MywgMzc4NCwgMzc4NCwgMzc4NSwgMzc4NSwgMzc4NiwgMzc4NiwgMzc4NywgMzc4NywgMzc4OCwgMzc4OCwgMzc4OSwgMzc4OSwgMzc5MCwgMzc5MCwgMzc5MSwgMzc5MSwgMzc5MiwgMzc5MiwgMzc5MywgMzc5MywgMzc5NCwgMzc5NCwgMzc5NSwgMzc5NSwgMzc5NiwgMzc5NiwgMzc5NywgMzc5NywgMzc5OCwgMzc5OCwgMzc5OSwgMzc5OSwgMzgwMCwgMzgwMCwgMzgwMSwgMzgwMSwgMzgwMiwgMzgwMiwgMzgwMywgMzgwMywgMzgwNCwgMzgwNCwgMzgwNSwgMzgwNSwgMzgwNiwgMzgwNiwgMzgwNywgMzgwNywgMzgwOCwgMzgwOCwgMzgwOSwgMzgwOSwgMzgxMCwgMzgxMCwgMzgxMSwgMzgxMSwgMzgxMiwgMzgxMiwgMzgxMywgMzgxMywgMzgxNCwgMzgxNCwgMzgxNSwgMzgxNSwgMzgxNiwgMzgxNiwgMzgxNywgMzgxN12UdWJzLg=="
}
],
"outputs": [
{
- "GID": 40,
+ "GID": 46,
"type": "data",
"label": "val"
}
@@ -321,33 +326,34 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "set var"
+ "display title": "set var",
+ "inspector widget": {}
},
{
- "GID": 41,
+ "GID": 49,
"version": "v0.2",
"identifier": "built_in.GetVar_Node",
"state data": "gAR9lC4=",
"additional data": {},
"inputs": [
{
- "GID": 44,
+ "GID": 52,
"type": "data",
"label": "",
"default": {
- "GID": 120,
+ "GID": 56,
"identifier": "Data",
"serialized": "gASVBQAAAAAAAACMAWGULg=="
},
"has widget": true,
"widget name": "varname",
"widget pos": "besides",
- "widget data": "gASVbAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTR8SjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAFhlHVicy4="
+ "widget data": "gASVbAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSgCjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAFhlHVicy4="
}
],
"outputs": [
{
- "GID": 45,
+ "GID": 54,
"type": "data",
"label": "val"
}
@@ -375,47 +381,48 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "get var"
+ "display title": "get var",
+ "inspector widget": {}
},
{
- "GID": 46,
- "version": "v0.2",
- "identifier": "std.Clock_Node",
+ "GID": 57,
+ "version": "v0.3",
+ "identifier": "examples.special_nodes.Clock_Node",
"state data": "gAR9lC4=",
"additional data": {},
"inputs": [
{
- "GID": 50,
+ "GID": 61,
"type": "data",
"label": "delay",
"default": {
- "GID": 153,
+ "GID": 67,
"identifier": "Data",
"serialized": "gARLAC4="
},
"has widget": true,
"widget name": "delay",
"widget pos": "besides",
- "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSASjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwB1YnMu"
+ "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSkCjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwB1YnMu"
},
{
- "GID": 51,
+ "GID": 63,
"type": "data",
"label": "iterations",
"default": {
- "GID": 54,
+ "GID": 68,
"identifier": "Data",
"serialized": "gASVBgAAAAAAAABK/////y4="
},
"has widget": true,
"widget name": "iter",
"widget pos": "besides",
- "widget data": "gASVbQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSESjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSv////91YnMu"
+ "widget data": "gASVbQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSoCjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSv////91YnMu"
}
],
"outputs": [
{
- "GID": 52,
+ "GID": 65,
"type": "exec",
"label": ""
}
@@ -448,57 +455,58 @@
"method": "stop"
}
},
- "display title": "clock"
+ "display title": "clock",
+ "inspector widget": {}
},
{
- "GID": 55,
+ "GID": 69,
"version": "v0.1",
"identifier": "built_in.SetVar_Node",
"state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIhzLg==",
"additional data": {},
"inputs": [
{
- "GID": 61,
+ "GID": 75,
"type": "exec",
"label": ""
},
{
- "GID": 62,
+ "GID": 76,
"type": "data",
"label": "var",
"default": {
- "GID": 109,
+ "GID": 82,
"identifier": "Data",
"serialized": "gASVBwAAAAAAAACMA2N0cpQu"
},
"has widget": true,
"widget name": "varname",
"widget pos": "besides",
- "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSISjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANjdHKUdWJzLg=="
+ "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSsCjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANjdHKUdWJzLg=="
},
{
- "GID": 63,
+ "GID": 78,
"type": "data",
"label": "val",
"default": {
- "GID": 4634,
+ "GID": 534,
"identifier": "Data",
- "serialized": "gASVCAAAAAAAAACMBDM3MzmULg=="
+ "serialized": "gASVCAAAAAAAAACMBDM4MTiULg=="
},
"has widget": true,
"widget name": "val",
"widget pos": "besides",
- "widget data": "gASVbwAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSMSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAQzNzM5lHVicy4="
+ "widget data": "gASVbwAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSwCjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAQzODE4lHVicy4="
}
],
"outputs": [
{
- "GID": 64,
+ "GID": 80,
"type": "exec",
"label": ""
},
{
- "GID": 65,
+ "GID": 81,
"type": "data",
"label": "val"
}
@@ -524,33 +532,34 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "set var"
+ "display title": "set var",
+ "inspector widget": {}
},
{
- "GID": 66,
+ "GID": 84,
"version": "v0.2",
"identifier": "built_in.GetVar_Node",
"state data": "gAR9lC4=",
"additional data": {},
"inputs": [
{
- "GID": 69,
+ "GID": 87,
"type": "data",
"label": "",
"default": {
- "GID": 113,
+ "GID": 91,
"identifier": "Data",
"serialized": "gASVBwAAAAAAAACMA2N0cpQu"
},
"has widget": true,
"widget name": "varname",
"widget pos": "besides",
- "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSQSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANjdHKUdWJzLg=="
+ "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTS0CjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANjdHKUdWJzLg=="
}
],
"outputs": [
{
- "GID": 70,
+ "GID": 89,
"type": "data",
"label": "val"
}
@@ -578,47 +587,48 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "get var"
+ "display title": "get var",
+ "inspector widget": {}
},
{
- "GID": 71,
- "version": "v0.2",
- "identifier": "std.Plus_Node",
+ "GID": 92,
+ "version": "v0.3",
+ "identifier": "examples.basic_operators.Plus_Node",
"state data": "gAR9lC4=",
"additional data": {},
"inputs": [
{
- "GID": 75,
+ "GID": 96,
"type": "data",
"label": "",
"default": {
- "GID": 4632,
+ "GID": 532,
"identifier": "Data",
- "serialized": "gASVBAAAAAAAAABNmg4u"
+ "serialized": "gASVBAAAAAAAAABN6Q4u"
},
"has widget": true,
"widget name": "in",
"widget pos": "besides",
- "widget data": "gASVawAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSUSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUTZoOdWJzLg=="
+ "widget data": "gASVawAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTS4CjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUTekOdWJzLg=="
},
{
- "GID": 76,
+ "GID": 98,
"type": "data",
"label": "",
"default": {
- "GID": 110,
+ "GID": 104,
"identifier": "Data",
"serialized": "gARLAS4="
},
"has widget": true,
"widget name": "in",
"widget pos": "besides",
- "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSYSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
+ "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTS8CjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
}
],
"outputs": [
{
- "GID": 77,
+ "GID": 100,
"type": "data",
"label": ""
}
@@ -657,17 +667,18 @@
}
}
},
- "display title": "+"
+ "display title": "+",
+ "inspector widget": {}
},
{
- "GID": 80,
- "version": "v0.2",
- "identifier": "std.LinkIN_Node",
+ "GID": 105,
+ "version": "v0.3",
+ "identifier": "examples.special_nodes.LinkIN_Node",
"state data": "gASVMAAAAAAAAAB9lIwCSUSUjCRjNWVmZjE5MC0yNzllLTQ3M2MtYTQ3Ny1iNjJmMjg0MGUzOTiUcy4=",
"additional data": {},
"inputs": [
{
- "GID": 82,
+ "GID": 107,
"type": "data",
"label": "",
"has widget": false
@@ -702,18 +713,19 @@
"method": "copy_ID"
}
},
- "display title": "link IN"
+ "display title": "link IN",
+ "inspector widget": {}
},
{
- "GID": 103,
- "version": "v0.2",
- "identifier": "std.Button_Node",
+ "GID": 108,
+ "version": "v0.3",
+ "identifier": "examples.special_nodes.Button_Node",
"state data": "gAR9lC4=",
"additional data": {},
"inputs": [],
"outputs": [
{
- "GID": 104,
+ "GID": 110,
"type": "exec",
"label": ""
}
@@ -740,7 +752,8 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "Button"
+ "display title": "Button",
+ "inspector widget": {}
}
],
"connections": [
@@ -808,9 +821,9 @@
"output data": [
{
"data": {
- "GID": 4627,
+ "GID": 536,
"identifier": "Data",
- "serialized": "gASVSAgAAAAAAABdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxNPgxNPwxNQAxNQQxNQgxNQwxNRAxNRQxNRgxNRwxNSAxNSQxNSgxNSwxNTAxNTQxNTgxNTwxNUAxNUQxNUgxNUwxNVAxNVQxNVgxNVwxNWAxNWQxNWgxNWwxNXAxNXQxNXgxNXwxNYAxNYQxNYgxNYwxNZAxNZQxNZgxNZwxNaAxNaQxNagxNawxNbAxNbQxNbgxNbwxNcAxNcQxNcgxNcwxNdAxNdQxNdgxNdwxNeAxNeQxNegxNewxNfAxNfQxNfgxNfwxNgAxNgQxNggxNgwxNhAxNhQxNhgxNhwxNiAxNiQxNigxNiwxNjAxNjQxNjgxNjwxNkAxNkQxNkgxNkwxNlAxNlQxNlgxNlwxNmAxNmQxNmgxNmwxNnAxNnQxNngxNnwxNoAxNoQxNogxNowxNpAxNpQxNpgxNpwxNqAxNqQxNqgxNqwxNrAxNrQxNrgxNrwxNsAxNsQxNsgxNswxNtAxNtQxNtgxNtwxNuAxNuQxNugxNuwxNvAxNvQxNvgxNvwxNwAxNwQxNwgxNwwxNxAxNxQxNxgxNxwxNyAxNyQxNygxNywxNzAxNzQxNzgxNzwxN0AxN0QxN0gxN0wxN1AxN1QxN1gxN1wxN2AxN2QxN2gxN2wxN3AxN3QxN3gxN3wxN4AxN4QxN4gxN4wxN5AxN5QxN5gxN5wxN6AxN6QxN6gxN6wxN7AxN7QxN7gxN7wxN8AxN8QxN8gxN8wxN9AxN9QxN9gxN9wxN+AxN+QxN+gxN+wxN/AxN/QxN/gxN/wxNAA1NAQ1NAg1NAw1NBA1NBQ1NBg1NBw1NCA1NCQ1NCg1NCw1NDA1NDQ1NDg1NDw1NEA1NEQ1NEg1NEw1NFA1NFQ1NFg1NFw1NGA1NGQ1NGg1NGw1NHA1NHQ1NHg1NHw1NIA1NIQ1NIg1NIw1NJA1NJQ1NJg1NJw1NKA1NKQ1NKg1NKw1NLA1NLQ1NLg1NLw1NMA1NMQ1NMg1NMw1NNA1NNQ1NNg1NNw1NOA1NOQ1NOg1NOw1NPA1NPQ1NPg1NPw1NQA1NQQ1NQg1NQw1NRA1NRQ1NRg1NRw1NSA1NSQ1NSg1NSw1NTA1NTQ1NTg1NTw1NUA1NUQ1NUg1NUw1NVA1NVQ1NVg1NVw1NWA1NWQ1NWg1NWw1NXA1NXQ1NXg1NXw1NYA1NYQ1NYg1NYw1NZA1NZQ1NZg1NZw1NaA1NaQ1Nag1Naw1NbA1NbQ1Nbg1Nbw1NcA1NcQ1Ncg1Ncw1NdA1NdQ1Ndg1Ndw1NeA1NeQ1Neg1New1NfA1NfQ1Nfg1Nfw1NgA1NgQ1Ngg1Ngw1NhA1NhQ1Nhg1Nhw1NiA1NiQ1Nig1Niw1NjA1NjQ1Njg1Njw1NkA1NkQ1Nkg1Nkw1NlA1NlQ1Nlg1Nlw1NmA1NmQ1Nmg1Nmw1NnA1NnQ1Nng1Nnw1NoA1NoQ1Nog1Now1NpA1NpQ1Npg1Npw1NqA1NqQ1Nqg1Nqw1NrA1NrQ1Nrg1Nrw1NsA1NsQ1Nsg1Nsw1NtA1NtQ1Ntg1Ntw1NuA1NuQ1Nug1Nuw1NvA1NvQ1Nvg1Nvw1NwA1NwQ1Nwg1Nww1NxA1NxQ1Nxg1Nxw1NyA1NyQ1Nyg1Nyw1NzA1NzQ1Nzg1Nzw1N0A1N0Q1N0g1N0w1N1A1N1Q1N1g1N1w1N2A1N2Q1N2g1N2w1N3A1N3Q1N3g1N3w1N4A1N4Q1N4g1N4w1N5A1N5Q1N5g1N5w1N6A1N6Q1N6g1N6w1N7A1N7Q1N7g1N7w1N8A1N8Q1N8g1N8w1N9A1N9Q1N9g1N9w1N+A1N+Q1N+g1N+w1N/A1N/Q1N/g1N/w1NAA5NAQ5NAg5NAw5NBA5NBQ5NBg5NBw5NCA5NCQ5NCg5NCw5NDA5NDQ5NDg5NDw5NEA5NEQ5NEg5NEw5NFA5NFQ5NFg5NFw5NGA5NGQ5NGg5NGw5NHA5NHQ5NHg5NHw5NIA5NIQ5NIg5NIw5NJA5NJQ5NJg5NJw5NKA5NKQ5NKg5NKw5NLA5NLQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5lLg=="
+ "serialized": "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LQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5Nmw5Nmw5NnA5NnA5NnQ5NnQ5Nng5Nng5Nnw5Nnw5NoA5NoA5NoQ5NoQ5Nog5Nog5Now5Now5NpA5NpA5NpQ5NpQ5Npg5Npg5Npw5Npw5NqA5NqA5NqQ5NqQ5Nqg5Nqg5Nqw5Nqw5NrA5NrA5NrQ5NrQ5Nrg5Nrg5Nrw5Nrw5NsA5NsA5NsQ5NsQ5Nsg5Nsg5Nsw5Nsw5NtA5NtA5NtQ5NtQ5Ntg5Ntg5Ntw5Ntw5NuA5NuA5NuQ5NuQ5Nug5Nug5Nuw5Nuw5NvA5NvA5NvQ5NvQ5Nvg5Nvg5Nvw5Nvw5NwA5NwA5NwQ5NwQ5Nwg5Nwg5Nww5Nww5NxA5NxA5NxQ5NxQ5Nxg5Nxg5Nxw5Nxw5NyA5NyA5NyQ5NyQ5Nyg5Nyg5Nyw5Nyw5NzA5NzA5NzQ5NzQ5Nzg5Nzg5Nzw5Nzw5N0A5N0A5N0Q5N0Q5N0g5N0g5N0w5N0w5N1A5N1A5N1Q5N1Q5N1g5N1g5N1w5N1w5N2A5N2A5N2Q5N2Q5N2g5N2g5N2w5N2w5N3A5N3A5N3Q5N3Q5N3g5N3g5N3w5N3w5N4A5N4A5N4Q5N4Q5N4g5N4g5N4w5N4w5N5A5N5A5N5Q5N5Q5N5g5N5g5N5w5N5w5N6A5N6A5N6Q5N6Q5lLg=="
},
"dependent node outputs": [
0,
@@ -819,9 +832,9 @@
},
{
"data": {
- "GID": 4631,
+ "GID": 542,
"identifier": "Data",
- "serialized": "gASVBAAAAAAAAABNmg4u"
+ "serialized": "gASVBAAAAAAAAABN6Q4u"
},
"dependent node outputs": [
1,
@@ -830,9 +843,9 @@
},
{
"data": {
- "GID": 151,
+ "GID": 547,
"identifier": "Data",
- "serialized": "gASVCgAAAAAAAABHP+haHKwIMScu"
+ "serialized": "gASVCgAAAAAAAABHP+MzMzMzMzMu"
},
"dependent node outputs": [
3,
@@ -841,9 +854,9 @@
},
{
"data": {
- "GID": 4630,
+ "GID": 541,
"identifier": "Data",
- "serialized": "gASVSAgAAAAAAABdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxNPgxNPwxNQAxNQQxNQgxNQwxNRAxNRQxNRgxNRwxNSAxNSQxNSgxNSwxNTAxNTQxNTgxNTwxNUAxNUQxNUgxNUwxNVAxNVQxNVgxNVwxNWAxNWQxNWgxNWwxNXAxNXQxNXgxNXwxNYAxNYQxNYgxNYwxNZAxNZQxNZgxNZwxNaAxNaQxNagxNawxNbAxNbQxNbgxNbwxNcAxNcQxNcgxNcwxNdAxNdQxNdgxNdwxNeAxNeQxNegxNewxNfAxNfQxNfgxNfwxNgAxNgQxNggxNgwxNhAxNhQxNhgxNhwxNiAxNiQxNigxNiwxNjAxNjQxNjgxNjwxNkAxNkQxNkgxNkwxNlAxNlQxNlgxNlwxNmAxNmQxNmgxNmwxNnAxNnQxNngxNnwxNoAxNoQxNogxNowxNpAxNpQxNpgxNpwxNqAxNqQxNqgxNqwxNrAxNrQxNrgxNrwxNsAxNsQxNsgxNswxNtAxNtQxNtgxNtwxNuAxNuQxNugxNuwxNvAxNvQxNvgxNvwxNwAxNwQxNwgxNwwxNxAxNxQxNxgxNxwxNyAxNyQxNygxNywxNzAxNzQxNzgxNzwxN0AxN0QxN0gxN0wxN1AxN1QxN1gxN1wxN2AxN2QxN2gxN2wxN3AxN3QxN3gxN3wxN4AxN4QxN4gxN4wxN5AxN5QxN5gxN5wxN6AxN6QxN6gxN6wxN7AxN7QxN7gxN7wxN8AxN8QxN8gxN8wxN9AxN9QxN9gxN9wxN+AxN+QxN+gxN+wxN/AxN/QxN/gxN/wxNAA1NAQ1NAg1NAw1NBA1NBQ1NBg1NBw1NCA1NCQ1NCg1NCw1NDA1NDQ1NDg1NDw1NEA1NEQ1NEg1NEw1NFA1NFQ1NFg1NFw1NGA1NGQ1NGg1NGw1NHA1NHQ1NHg1NHw1NIA1NIQ1NIg1NIw1NJA1NJQ1NJg1NJw1NKA1NKQ1NKg1NKw1NLA1NLQ1NLg1NLw1NMA1NMQ1NMg1NMw1NNA1NNQ1NNg1NNw1NOA1NOQ1NOg1NOw1NPA1NPQ1NPg1NPw1NQA1NQQ1NQg1NQw1NRA1NRQ1NRg1NRw1NSA1NSQ1NSg1NSw1NTA1NTQ1NTg1NTw1NUA1NUQ1NUg1NUw1NVA1NVQ1NVg1NVw1NWA1NWQ1NWg1NWw1NXA1NXQ1NXg1NXw1NYA1NYQ1NYg1NYw1NZA1NZQ1NZg1NZw1NaA1NaQ1Nag1Naw1NbA1NbQ1Nbg1Nbw1NcA1NcQ1Ncg1Ncw1NdA1NdQ1Ndg1Ndw1NeA1NeQ1Neg1New1NfA1NfQ1Nfg1Nfw1NgA1NgQ1Ngg1Ngw1NhA1NhQ1Nhg1Nhw1NiA1NiQ1Nig1Niw1NjA1NjQ1Njg1Njw1NkA1NkQ1Nkg1Nkw1NlA1NlQ1Nlg1Nlw1NmA1NmQ1Nmg1Nmw1NnA1NnQ1Nng1Nnw1NoA1NoQ1Nog1Now1NpA1NpQ1Npg1Npw1NqA1NqQ1Nqg1Nqw1NrA1NrQ1Nrg1Nrw1NsA1NsQ1Nsg1Nsw1NtA1NtQ1Ntg1Ntw1NuA1NuQ1Nug1Nuw1NvA1NvQ1Nvg1Nvw1NwA1NwQ1Nwg1Nww1NxA1NxQ1Nxg1Nxw1NyA1NyQ1Nyg1Nyw1NzA1NzQ1Nzg1Nzw1N0A1N0Q1N0g1N0w1N1A1N1Q1N1g1N1w1N2A1N2Q1N2g1N2w1N3A1N3Q1N3g1N3w1N4A1N4Q1N4g1N4w1N5A1N5Q1N5g1N5w1N6A1N6Q1N6g1N6w1N7A1N7Q1N7g1N7w1N8A1N8Q1N8g1N8w1N9A1N9Q1N9g1N9w1N+A1N+Q1N+g1N+w1N/A1N/Q1N/g1N/w1NAA5NAQ5NAg5NAw5NBA5NBQ5NBg5NBw5NCA5NCQ5NCg5NCw5NDA5NDQ5NDg5NDw5NEA5NEQ5NEg5NEw5NFA5NFQ5NFg5NFw5NGA5NGQ5NGg5NGw5NHA5NHQ5NHg5NHw5NIA5NIQ5NIg5NIw5NJA5NJQ5NJg5NJw5NKA5NKQ5NKg5NKw5NLA5NLQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5lLg=="
+ "serialized": "gASVIgoAAAAAAABdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxNPgxNPwxNQAxNQQxNQgxNQwxNRAxNRQxNRgxNRwxNSAxNSQxNSgxNSwxNTAxNTQxNTgxNTwxNUAxNUQxNUgxNUwxNVAxNVQxNVgxNVwxNWAxNWQxNWgxNWwxNXAxNXQxNXgxNXwxNYAxNYQxNYgxNYwxNZAxNZQxNZgxNZwxNaAxNaQxNagxNawxNbAxNbQxNbgxNbwxNcAxNcQxNcgxNcwxNdAxNdQxNdgxNdwxNeAxNeQxNegxNewxNfAxNfQxNfgxNfwxNgAxNgQxNggxNgwxNhAxNhQxNhgxNhwxNiAxNiQxNigxNiwxNjAxNjQxNjgxNjwxNkAxNkQxNkgxNkwxNlAxNlQxNlgxNlwxNmAxNmQxNmgxNmwxNnAxNnQxNngxNnwxNoAxNoQxNogxNowxNpAxNpQxNpgxNpwxNqAxNqQxNqgxNqwxNrAxNrQxNrgxNrwxNsAxNsQxNsgxNswxNtAxNtQxNtgxNtwxNuAxNuQxNugxNuwxNvAxNvQxNvgxNvwxNwAxNwQxNwgxNwwxNxAxNxQxNxgxNxwxNyAxNyQxNygxNywxNzAxNzQxNzgxNzwxN0AxN0QxN0gxN0wxN1AxN1QxN1gxN1wxN2AxN2QxN2gxN2wxN3AxN3QxN3gxN3wxN4AxN4QxN4gxN4wxN5AxN5QxN5gxN5wxN6AxN6QxN6gxN6wxN7AxN7QxN7gxN7wxN8AxN8QxN8gxN8wxN9AxN9QxN9gxN9wxN+AxN+QxN+gxN+wxN/AxN/QxN/gxN/wxNAA1NAQ1NAg1NAw1NBA1NBQ1NBg1NBw1NCA1NCQ1NCg1NCw1NDA1NDQ1NDg1NDw1NEA1NEQ1NEg1NEw1NFA1NFQ1NFg1NFw1NGA1NGQ1NGg1NGw1NHA1NHQ1NHg1NHw1NIA1NIQ1NIg1NIw1NJA1NJQ1NJg1NJw1NKA1NKQ1NKg1NKw1NLA1NLQ1NLg1NLw1NMA1NMQ1NMg1NMw1NNA1NNQ1NNg1NNw1NOA1NOQ1NOg1NOw1NPA1NPQ1NPg1NPw1NQA1NQQ1NQg1NQw1NRA1NRQ1NRg1NRw1NSA1NSQ1NSg1NSw1NTA1NTQ1NTg1NTw1NUA1NUQ1NUg1NUw1NVA1NVQ1NVg1NVw1NWA1NWQ1NWg1NWw1NXA1NXQ1NXg1NXw1NYA1NYQ1NYg1NYw1NZA1NZQ1NZg1NZw1NaA1NaQ1Nag1Naw1NbA1NbQ1Nbg1Nbw1NcA1NcQ1Ncg1Ncw1NdA1NdQ1Ndg1Ndw1NeA1NeQ1Neg1New1NfA1NfQ1Nfg1Nfw1NgA1NgQ1Ngg1Ngw1NhA1NhQ1Nhg1Nhw1NiA1NiQ1Nig1Niw1NjA1NjQ1Njg1Njw1NkA1NkQ1Nkg1Nkw1NlA1NlQ1Nlg1Nlw1NmA1NmQ1Nmg1Nmw1NnA1NnQ1Nng1Nnw1NoA1NoQ1Nog1Now1NpA1NpQ1Npg1Npw1NqA1NqQ1Nqg1Nqw1NrA1NrQ1Nrg1Nrw1NsA1NsQ1Nsg1Nsw1NtA1NtQ1Ntg1Ntw1NuA1NuQ1Nug1Nuw1NvA1NvQ1Nvg1Nvw1NwA1NwQ1Nwg1Nww1NxA1NxQ1Nxg1Nxw1NyA1NyQ1Nyg1Nyw1NzA1NzQ1Nzg1Nzw1N0A1N0Q1N0g1N0w1N1A1N1Q1N1g1N1w1N2A1N2Q1N2g1N2w1N3A1N3Q1N3g1N3w1N4A1N4Q1N4g1N4w1N5A1N5Q1N5g1N5w1N6A1N6Q1N6g1N6w1N7A1N7Q1N7g1N7w1N8A1N8Q1N8g1N8w1N9A1N9Q1N9g1N9w1N+A1N+Q1N+g1N+w1N/A1N/Q1N/g1N/w1NAA5NAQ5NAg5NAw5NBA5NBQ5NBg5NBw5NCA5NCQ5NCg5NCw5NDA5NDQ5NDg5NDw5NEA5NEQ5NEg5NEw5NFA5NFQ5NFg5NFw5NGA5NGQ5NGg5NGw5NHA5NHQ5NHg5NHw5NIA5NIQ5NIg5NIw5NJA5NJQ5NJg5NJw5NKA5NKQ5NKg5NKw5NLA5NLQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5Nmw5Nmw5NnA5NnA5NnQ5NnQ5Nng5Nng5Nnw5Nnw5NoA5NoA5NoQ5NoQ5Nog5Nog5Now5Now5NpA5NpA5NpQ5NpQ5Npg5Npg5Npw5Npw5NqA5NqA5NqQ5NqQ5Nqg5Nqg5Nqw5Nqw5NrA5NrA5NrQ5NrQ5Nrg5Nrg5Nrw5Nrw5NsA5NsA5NsQ5NsQ5Nsg5Nsg5Nsw5Nsw5NtA5NtA5NtQ5NtQ5Ntg5Ntg5Ntw5Ntw5NuA5NuA5NuQ5NuQ5Nug5Nug5Nuw5Nuw5NvA5NvA5NvQ5NvQ5Nvg5Nvg5Nvw5Nvw5NwA5NwA5NwQ5NwQ5Nwg5Nwg5Nww5Nww5NxA5NxA5NxQ5NxQ5Nxg5Nxg5Nxw5Nxw5NyA5NyA5NyQ5NyQ5Nyg5Nyg5Nyw5Nyw5NzA5NzA5NzQ5NzQ5Nzg5Nzg5Nzw5Nzw5N0A5N0A5N0Q5N0Q5N0g5N0g5N0w5N0w5N1A5N1A5N1Q5N1Q5N1g5N1g5N1w5N1w5N2A5N2A5N2Q5N2Q5N2g5N2g5N2w5N2w5N3A5N3A5N3Q5N3Q5N3g5N3g5N3w5N3w5N4A5N4A5N4Q5N4Q5N4g5N4g5N4w5N4w5N5A5N5A5N5Q5N5Q5N5g5N5g5N5w5N5w5N6A5N6A5N6Q5N6Q5lLg=="
},
"dependent node outputs": [
6,
@@ -852,9 +865,9 @@
},
{
"data": {
- "GID": 4626,
+ "GID": 535,
"identifier": "Data",
- "serialized": "gASVBAAAAAAAAABNmg4u"
+ "serialized": "gASVBAAAAAAAAABN6Q4u"
},
"dependent node outputs": [
9,
@@ -863,9 +876,9 @@
},
{
"data": {
- "GID": 4633,
+ "GID": 543,
"identifier": "Data",
- "serialized": "gASVBAAAAAAAAABNmw4u"
+ "serialized": "gASVBAAAAAAAAABN6g4u"
},
"dependent node outputs": [
10,
@@ -882,19 +895,19 @@
}
},
"script 2": {
- "GID": 83,
+ "GID": 117,
"algorithm mode": "data",
"nodes": [
{
- "GID": 84,
- "version": "v0.2",
- "identifier": "std.LinkOUT_Node",
+ "GID": 118,
+ "version": "v0.3",
+ "identifier": "examples.special_nodes.LinkOUT_Node",
"state data": "gASVNwAAAAAAAAB9lIwJbGlua2VkIElElIwkYzVlZmYxOTAtMjc5ZS00NzNjLWE0NzctYjYyZjI4NDBlMzk4lHMu",
"additional data": {},
"inputs": [],
"outputs": [
{
- "GID": 85,
+ "GID": 119,
"type": "data",
"label": ""
}
@@ -920,17 +933,18 @@
"method": "link_to_ID"
}
},
- "display title": "link OUT"
+ "display title": "link OUT",
+ "inspector widget": {}
},
{
- "GID": 86,
+ "GID": 120,
"version": "v0.2",
"identifier": "built_in.Result_Node",
"state data": "gAR9lC4=",
"additional data": {},
"inputs": [
{
- "GID": 88,
+ "GID": 122,
"type": "data",
"label": "",
"has widget": false
@@ -956,7 +970,8 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "result"
+ "display title": "result",
+ "inspector widget": {}
}
],
"connections": [
@@ -970,9 +985,9 @@
"output data": [
{
"data": {
- "GID": 151,
+ "GID": 547,
"identifier": "Data",
- "serialized": "gASVCgAAAAAAAABHP+haHKwIMScu"
+ "serialized": "gASVCgAAAAAAAABHP+MzMzMzMzMu"
},
"dependent node outputs": [
0,
@@ -996,17 +1011,17 @@
"custom state": {
"5": {
"a": {
- "GID": 4629,
+ "GID": 538,
"identifier": "Data",
- "serialized": "gASVSAgAAAAAAABdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxNPgxNPwxNQAxNQQxNQgxNQwxNRAxNRQxNRgxNRwxNSAxNSQxNSgxNSwxNTAxNTQxNTgxNTwxNUAxNUQxNUgxNUwxNVAxNVQxNVgxNVwxNWAxNWQxNWgxNWwxNXAxNXQxNXgxNXwxNYAxNYQxNYgxNYwxNZAxNZQxNZgxNZwxNaAxNaQxNagxNawxNbAxNbQxNbgxNbwxNcAxNcQxNcgxNcwxNdAxNdQxNdgxNdwxNeAxNeQxNegxNewxNfAxNfQxNfgxNfwxNgAxNgQxNggxNgwxNhAxNhQxNhgxNhwxNiAxNiQxNigxNiwxNjAxNjQxNjgxNjwxNkAxNkQxNkgxNkwxNlAxNlQxNlgxNlwxNmAxNmQxNmgxNmwxNnAxNnQxNngxNnwxNoAxNoQxNogxNowxNpAxNpQxNpgxNpwxNqAxNqQxNqgxNqwxNrAxNrQxNrgxNrwxNsAxNsQxNsgxNswxNtAxNtQxNtgxNtwxNuAxNuQxNugxNuwxNvAxNvQxNvgxNvwxNwAxNwQxNwgxNwwxNxAxNxQxNxgxNxwxNyAxNyQxNygxNywxNzAxNzQxNzgxNzwxN0AxN0QxN0gxN0wxN1AxN1QxN1gxN1wxN2AxN2QxN2gxN2wxN3AxN3QxN3gxN3wxN4AxN4QxN4gxN4wxN5AxN5QxN5gxN5wxN6AxN6QxN6gxN6wxN7AxN7QxN7gxN7wxN8AxN8QxN8gxN8wxN9AxN9QxN9gxN9wxN+AxN+QxN+gxN+wxN/AxN/QxN/gxN/wxNAA1NAQ1NAg1NAw1NBA1NBQ1NBg1NBw1NCA1NCQ1NCg1NCw1NDA1NDQ1NDg1NDw1NEA1NEQ1NEg1NEw1NFA1NFQ1NFg1NFw1NGA1NGQ1NGg1NGw1NHA1NHQ1NHg1NHw1NIA1NIQ1NIg1NIw1NJA1NJQ1NJg1NJw1NKA1NKQ1NKg1NKw1NLA1NLQ1NLg1NLw1NMA1NMQ1NMg1NMw1NNA1NNQ1NNg1NNw1NOA1NOQ1NOg1NOw1NPA1NPQ1NPg1NPw1NQA1NQQ1NQg1NQw1NRA1NRQ1NRg1NRw1NSA1NSQ1NSg1NSw1NTA1NTQ1NTg1NTw1NUA1NUQ1NUg1NUw1NVA1NVQ1NVg1NVw1NWA1NWQ1NWg1NWw1NXA1NXQ1NXg1NXw1NYA1NYQ1NYg1NYw1NZA1NZQ1NZg1NZw1NaA1NaQ1Nag1Naw1NbA1NbQ1Nbg1Nbw1NcA1NcQ1Ncg1Ncw1NdA1NdQ1Ndg1Ndw1NeA1NeQ1Neg1New1NfA1NfQ1Nfg1Nfw1NgA1NgQ1Ngg1Ngw1NhA1NhQ1Nhg1Nhw1NiA1NiQ1Nig1Niw1NjA1NjQ1Njg1Njw1NkA1NkQ1Nkg1Nkw1NlA1NlQ1Nlg1Nlw1NmA1NmQ1Nmg1Nmw1NnA1NnQ1Nng1Nnw1NoA1NoQ1Nog1Now1NpA1NpQ1Npg1Npw1NqA1NqQ1Nqg1Nqw1NrA1NrQ1Nrg1Nrw1NsA1NsQ1Nsg1Nsw1NtA1NtQ1Ntg1Ntw1NuA1NuQ1Nug1Nuw1NvA1NvQ1Nvg1Nvw1NwA1NwQ1Nwg1Nww1NxA1NxQ1Nxg1Nxw1NyA1NyQ1Nyg1Nyw1NzA1NzQ1Nzg1Nzw1N0A1N0Q1N0g1N0w1N1A1N1Q1N1g1N1w1N2A1N2Q1N2g1N2w1N3A1N3Q1N3g1N3w1N4A1N4Q1N4g1N4w1N5A1N5Q1N5g1N5w1N6A1N6Q1N6g1N6w1N7A1N7Q1N7g1N7w1N8A1N8Q1N8g1N8w1N9A1N9Q1N9g1N9w1N+A1N+Q1N+g1N+w1N/A1N/Q1N/g1N/w1NAA5NAQ5NAg5NAw5NBA5NBQ5NBg5NBw5NCA5NCQ5NCg5NCw5NDA5NDQ5NDg5NDw5NEA5NEQ5NEg5NEw5NFA5NFQ5NFg5NFw5NGA5NGQ5NGg5NGw5NHA5NHQ5NHg5NHw5NIA5NIQ5NIg5NIw5NJA5NJQ5NJg5NJw5NKA5NKQ5NKg5NKw5NLA5NLQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5lLg=="
+ "serialized": "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LQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5Nmw5Nmw5NnA5NnA5NnQ5NnQ5Nng5Nng5Nnw5Nnw5NoA5NoA5NoQ5NoQ5Nog5Nog5Now5Now5NpA5NpA5NpQ5NpQ5Npg5Npg5Npw5Npw5NqA5NqA5NqQ5NqQ5Nqg5Nqg5Nqw5Nqw5NrA5NrA5NrQ5NrQ5Nrg5Nrg5Nrw5Nrw5NsA5NsA5NsQ5NsQ5Nsg5Nsg5Nsw5Nsw5NtA5NtA5NtQ5NtQ5Ntg5Ntg5Ntw5Ntw5NuA5NuA5NuQ5NuQ5Nug5Nug5Nuw5Nuw5NvA5NvA5NvQ5NvQ5Nvg5Nvg5Nvw5Nvw5NwA5NwA5NwQ5NwQ5Nwg5Nwg5Nww5Nww5NxA5NxA5NxQ5NxQ5Nxg5Nxg5Nxw5Nxw5NyA5NyA5NyQ5NyQ5Nyg5Nyg5Nyw5Nyw5NzA5NzA5NzQ5NzQ5Nzg5Nzg5Nzw5Nzw5N0A5N0A5N0Q5N0Q5N0g5N0g5N0w5N0w5N1A5N1A5N1Q5N1Q5N1g5N1g5N1w5N1w5N2A5N2A5N2Q5N2Q5N2g5N2g5N2w5N2w5N3A5N3A5N3Q5N3Q5N3g5N3g5N3w5N3w5N4A5N4A5N4Q5N4Q5N4g5N4g5N4w5N4w5N5A5N5A5N5Q5N5Q5N5g5N5g5N5w5N5w5N6A5N6A5N6Q5N6Q5lLg=="
},
"ctr": {
- "GID": 4625,
+ "GID": 523,
"identifier": "Data",
- "serialized": "gASVBAAAAAAAAABNmg4u"
+ "serialized": "gASVBAAAAAAAAABN6Q4u"
}
},
- "83": {}
+ "117": {}
}
},
"Logging": {
@@ -1014,5 +1029,43 @@
"version": "0.0.1",
"custom state": {}
}
+ },
+ "geometry": "01d9d0cb0003000000000138000000960000071b000003d50000013c000000b200000717000003d1000000000000000008d00000013c000000b200000717000003d1",
+ "state": "000000ff00000000fd000000020000000000000124000001edfc0200000001fc0000001e000001ed0000014b0100001efa000000010200000002fb000000140066006c006f00770073005f0064006f0063006b0100000000ffffffff0000007f00fffffffb00000014006e006f006400650073005f0064006f0063006b0100000000ffffffff0000012c00ffffff00000003000005dc00000101fc0100000001fb000000160063006f006e0073006f006c00650044006f0063006b0100000000000005dc0000005d00ffffff000004a4000001ed00000004000000040000000800000008fc00000000",
+ "flow_uis": {
+ "5": {
+ "geometry": "01d9d0cb0003000000000000000000000000048d000001b70000000000000000ffffffffffffffff000000000000000008d000000000000000000000048d000001b7",
+ "state": "000000ff00000000fd0000000100000001000000d8000001b8fc0200000001fc00000000000001b8000000d10000005bfa000000000200000006fb0000001c0069006e00730070006500630074006f0072005f0064006f0063006b0100000000ffffffff000000b200fffffffb000000220075006e0064006f005f0068006900730074006f00720079005f0064006f0063006b0100000000ffffffff0000005600fffffffb0000001a00730065007400740069006e00670073005f0064006f0063006b0100000000ffffffff0000003c0000003cfb0000001c007600610072006900610062006c00650073005f0064006f0063006b0100000000ffffffff0000007f00fffffffb00000016006c006f0067006700650072005f0064006f0063006b0100000000ffffffff0000004800fffffffb000000160073006f0075007200630065005f0064006f0063006b0100000000ffffffff0000009000ffffff000003a2000001b800000004000000040000000800000008fc00000000",
+ "view": {
+ "m11": 0.9712579279921432,
+ "m12": 0.0,
+ "m13": 0.0,
+ "m21": 0.0,
+ "m22": 0.9712579279921432,
+ "m23": 0.0,
+ "m31": 0.0,
+ "m32": 0.0,
+ "m33": 1.0,
+ "v_scroll": 30,
+ "h_scroll": 6
+ }
+ },
+ "117": {
+ "geometry": "01d9d0cb0003000000000000000000000000048d000001b70000000000000000ffffffffffffffff000000000000000008d000000000000000000000048d000001b7",
+ "state": "000000ff00000000fd0000000100000001000000d8000001b8fc0200000001fc00000000000001b8000000d10000005bfa000000000200000006fb0000001c0069006e00730070006500630074006f0072005f0064006f0063006b0100000000ffffffff000000b200fffffffb000000220075006e0064006f005f0068006900730074006f00720079005f0064006f0063006b0100000000ffffffff0000005600fffffffb0000001a00730065007400740069006e00670073005f0064006f0063006b0100000000ffffffff0000003c0000003cfb0000001c007600610072006900610062006c00650073005f0064006f0063006b0100000000ffffffff0000007f00fffffffb00000016006c006f0067006700650072005f0064006f0063006b0100000000ffffffff0000004800fffffffb000000160073006f0075007200630065005f0064006f0063006b0100000000ffffffff0000009000ffffff000003a2000001b800000004000000040000000800000008fc00000000",
+ "view": {
+ "m11": 1.250765972982705,
+ "m12": 0.0,
+ "m13": 0.0,
+ "m21": 0.0,
+ "m22": 1.250765972982705,
+ "m23": 0.0,
+ "m31": 0.0,
+ "m32": 0.0,
+ "m33": 1.0,
+ "v_scroll": 207,
+ "h_scroll": 139
+ }
+ }
}
}
\ No newline at end of file
diff --git a/ryven-editor/ryven/example_projects/matrices.json b/ryven-editor/ryven/example_projects/matrices.json
index 05f5eda8..78ac6ba6 100644
--- a/ryven-editor/ryven/example_projects/matrices.json
+++ b/ryven-editor/ryven/example_projects/matrices.json
@@ -1,20 +1,20 @@
{
"general info": {
"type": "Ryven project file",
- "ryven version": "3.3.0a1"
+ "ryven version": "3.4.3"
},
"required packages": [
{
- "name": "linalg",
- "dir": "/home/leon/projects/ryven_projects/Ryven/ryven/example_nodes/linalg"
+ "name": "examples",
+ "dir": "/home/leon/projects/ryven_projects/Ryven/ryven-editor/ryven/example_nodes/examples"
},
{
- "name": "std",
- "dir": "/home/leon/projects/ryven_projects/Ryven/ryven/example_nodes/std"
+ "name": "linalg",
+ "dir": "/home/leon/projects/ryven_projects/Ryven/ryven-editor/ryven/example_nodes/linalg"
}
],
"GID": 2,
- "version": "0.4.0a12",
+ "version": "0.4.0",
"flows": {
"hello world": {
"GID": 5,
@@ -61,11 +61,12 @@
"method": "ac_hide_mw"
}
},
- "display title": "Matrix"
+ "display title": "Matrix",
+ "inspector widget": {}
},
{
"GID": 12,
- "version": "v0.2",
+ "version": "v0.3",
"identifier": "linalg.ShowMatrix",
"state data": "gAR9lC4=",
"additional data": {},
@@ -89,7 +90,7 @@
},
"pos x": 469.009788256565,
"pos y": 289.198189623965,
- "main widget data": "gASVQwAAAAAAAAB9lCiMBHRleHSUjCsgICAgICAgIDFqICAgICAoMSswaikKICAgICAgIC0xaiAoMC43NzIrMGoplIwFc2hvd26UiHUu",
+ "main widget data": "gASVQwAAAAAAAAB9lCiMBHRleHSUjCsgICAgICAgIDFqICAgICAoMSswaikKICAgICAgIC0xaiAoMC43NzgrMGoplIwFc2hvd26UiHUu",
"unconnected ports hidden": false,
"collapsed": false,
"actions": {
@@ -109,7 +110,8 @@
"method": "ac_hide_mw"
}
},
- "display title": "Show Matrix"
+ "display title": "Show Matrix",
+ "inspector widget": {}
},
{
"GID": 17,
@@ -130,21 +132,21 @@
"has widget": true,
"widget name": "varname",
"widget pos": "besides",
- "widget data": "gASVbAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVAZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAFhlHVicy4="
+ "widget data": "gASVbAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTTIDjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAFhlHVicy4="
},
{
"GID": 25,
"type": "data",
"label": "val",
"default": {
- "GID": 6431,
+ "GID": 771,
"identifier": "Data",
- "serialized": "gASVCQAAAAAAAACMBTAuNzcylC4="
+ "serialized": "gASVCQAAAAAAAACMBTAuNzc4lC4="
},
"has widget": true,
"widget name": "val",
"widget pos": "besides",
- "widget data": "gASVcAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVEZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAUwLjc3MpR1YnMu"
+ "widget data": "gASVcAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTTMDjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAUwLjc3OJR1YnMu"
}
],
"outputs": [
@@ -175,13 +177,14 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "set var"
+ "display title": "set var",
+ "inspector widget": {}
},
{
"GID": 30,
- "version": "v0.2",
- "identifier": "std.Slider_Node",
- "state data": "gASVEwAAAAAAAAB9lIwDdmFslEc/6LQ5WBBiTnMu",
+ "version": "v0.3",
+ "identifier": "examples.special_nodes.Slider_Node",
+ "state data": "gASVEwAAAAAAAAB9lIwDdmFslEc/6OVgQYk3THMu",
"additional data": {},
"inputs": [
{
@@ -196,7 +199,7 @@
"has widget": true,
"widget name": "scale",
"widget pos": "besides",
- "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVIZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
+ "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTTQDjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
},
{
"GID": 36,
@@ -210,7 +213,7 @@
"has widget": true,
"widget name": "round",
"widget pos": "besides",
- "widget data": "gASVaQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVMZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUiXVicy4="
+ "widget data": "gASVaQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTTUDjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUiXVicy4="
}
],
"outputs": [
@@ -242,12 +245,13 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "slider"
+ "display title": "slider",
+ "inspector widget": {}
},
{
"GID": 43,
- "version": "v0.2",
- "identifier": "std.Multiply_Node",
+ "version": "v0.3",
+ "identifier": "examples.basic_operators.Multiply_Node",
"state data": "gAR9lC4=",
"additional data": {},
"inputs": [
@@ -256,14 +260,14 @@
"type": "data",
"label": "",
"default": {
- "GID": 6429,
+ "GID": 769,
"identifier": "Data",
- "serialized": "gASVCgAAAAAAAABHP+i0OVgQYk4u"
+ "serialized": "gASVCgAAAAAAAABHP+jlYEGJN0wu"
},
"has widget": true,
"widget name": "in",
"widget pos": "besides",
- "widget data": "gASVcQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVQZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSURz/otDlYEGJOdWJzLg=="
+ "widget data": "gASVcQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTTYDjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSURz/o5WBBiTdMdWJzLg=="
},
{
"GID": 49,
@@ -277,7 +281,7 @@
"has widget": true,
"widget name": "in",
"widget pos": "besides",
- "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVUZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
+ "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTTcDjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
}
],
"outputs": [
@@ -321,7 +325,8 @@
}
}
},
- "display title": "*"
+ "display title": "*",
+ "inspector widget": {}
},
{
"GID": 56,
@@ -342,21 +347,21 @@
"has widget": true,
"widget name": "varname",
"widget pos": "besides",
- "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVYZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANtYXSUdWJzLg=="
+ "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTTgDjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANtYXSUdWJzLg=="
},
{
"GID": 64,
"type": "data",
"label": "val",
"default": {
- "GID": 6435,
+ "GID": 775,
"identifier": "Data",
- "serialized": "gASVMgAAAAAAAACMLltbMC4gICArMS5qIDEuICAgKzAual0KIFswLiAgIC0xLmogMC43NzIrMC5qXV2ULg=="
+ "serialized": "gASVMgAAAAAAAACMLltbMC4gICArMS5qIDEuICAgKzAual0KIFswLiAgIC0xLmogMC43NzgrMC5qXV2ULg=="
},
"has widget": true,
"widget name": "val",
"widget pos": "besides",
- "widget data": "gASVmQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVcZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjC5bWzAuICAgKzEuaiAxLiAgICswLmpdCiBbMC4gICAtMS5qIDAuNzcyKzAual1dlHVicy4="
+ "widget data": "gASVmQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTTkDjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjC5bWzAuICAgKzEuaiAxLiAgICswLmpdCiBbMC4gICAtMS5qIDAuNzc4KzAual1dlHVicy4="
}
],
"outputs": [
@@ -387,7 +392,8 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "set var"
+ "display title": "set var",
+ "inspector widget": {}
},
{
"GID": 69,
@@ -408,7 +414,7 @@
"has widget": true,
"widget name": "varname",
"widget pos": "besides",
- "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVgZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANtYXSUdWJzLg=="
+ "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTToDjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANtYXSUdWJzLg=="
}
],
"outputs": [
@@ -441,11 +447,12 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "get var"
+ "display title": "get var",
+ "inspector widget": {}
},
{
"GID": 77,
- "version": "v0.2",
+ "version": "v0.3",
"identifier": "linalg.InverseMatrix",
"state data": "gAR9lC4=",
"additional data": {},
@@ -469,7 +476,7 @@
},
"pos x": 175.96624820900752,
"pos y": 548.4374023593136,
- "main widget data": "gASVRwAAAAAAAAB9lCiMBHRleHSUjC8gICAtMC40MzU3aiAgICAgMC41NjQzagooMC41NjQzKzBqKSAoMC41NjQzKzBqKZSMBXNob3dulIh1Lg==",
+ "main widget data": "gASVRwAAAAAAAAB9lCiMBHRleHSUjC8gICAtMC40Mzc2aiAgICAgMC41NjI0agooMC41NjI0KzBqKSAoMC41NjI0KzBqKZSMBXNob3dulIh1Lg==",
"unconnected ports hidden": false,
"collapsed": false,
"actions": {
@@ -489,11 +496,12 @@
"method": "ac_hide_mw"
}
},
- "display title": "Inverse"
+ "display title": "Inverse",
+ "inspector widget": {}
},
{
"GID": 82,
- "version": "v0.2",
+ "version": "v0.3",
"identifier": "linalg.DetOfMatrix",
"state data": "gAR9lC4=",
"additional data": {},
@@ -517,7 +525,7 @@
},
"pos x": 611.0383971650748,
"pos y": 548.1962980663284,
- "main widget data": "gASVLAAAAAAAAAB9lCiMBHRleHSUjBQtMC41NjQzMzQwODU3Nzg3ODExapSMBXNob3dulIh1Lg==",
+ "main widget data": "gASVKwAAAAAAAAB9lCiMBHRleHSUjBMtMC41NjI0Mjk2OTYyODc5NjRqlIwFc2hvd26UiHUu",
"unconnected ports hidden": false,
"collapsed": false,
"actions": {
@@ -537,11 +545,12 @@
"method": "ac_hide_mw"
}
},
- "display title": "Determinant"
+ "display title": "Determinant",
+ "inspector widget": {}
},
{
"GID": 87,
- "version": "v0.2",
+ "version": "v0.3",
"identifier": "linalg.SolveLEq",
"state data": "gAR9lC4=",
"additional data": {},
@@ -571,7 +580,7 @@
},
"pos x": 885.1839459089059,
"pos y": 322.570155217133,
- "main widget data": "gASVLwAAAAAAAAB9lCiMBHRleHSUjBcgICAtMC40MzU3aiAoMC41NjQzKzBqKZSMBXNob3dulIh1Lg==",
+ "main widget data": "gASVLwAAAAAAAAB9lCiMBHRleHSUjBcgICAtMC40Mzc2aiAoMC41NjI0KzBqKZSMBXNob3dulIh1Lg==",
"unconnected ports hidden": false,
"collapsed": false,
"actions": {
@@ -591,11 +600,12 @@
"method": "ac_hide_mw"
}
},
- "display title": "Solve"
+ "display title": "Solve",
+ "inspector widget": {}
},
{
"GID": 94,
- "version": "v0.2",
+ "version": "v0.3",
"identifier": "linalg.MatMul",
"state data": "gAR9lC4=",
"additional data": {},
@@ -645,12 +655,13 @@
"method": "ac_hide_mw"
}
},
- "display title": "Mult"
+ "display title": "Mult",
+ "inspector widget": {}
},
{
"GID": 101,
- "version": "v0.2",
- "identifier": "std.Eval_Node",
+ "version": "v0.3",
+ "identifier": "examples.special_nodes.Eval_Node",
"state data": "gASVPQAAAAAAAAB9lCiMEG51bSBwYXJhbSBpbnB1dHOUSwGMD2V4cHJlc3Npb24gY29kZZSMDihpbnBbMF0pWzBdWzpdlHUu",
"additional data": {},
"inputs": [
@@ -694,7 +705,8 @@
},
"remove input": {}
},
- "display title": "eval"
+ "display title": "eval",
+ "inspector widget": {}
},
{
"GID": 105,
@@ -733,7 +745,8 @@
"method": "console_ref_monkeypatch"
}
},
- "display title": "result"
+ "display title": "result",
+ "inspector widget": {}
}
],
"connections": [
@@ -813,9 +826,9 @@
"output data": [
{
"data": {
- "GID": 6456,
+ "GID": 796,
"identifier": "MatrixData",
- "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv05iEFg5tOg/AAAAAAAAAACUdJRiLg=="
+ "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv0w3iUFg5eg/AAAAAAAAAACUdJRiLg=="
},
"dependent node outputs": [
0,
@@ -824,9 +837,9 @@
},
{
"data": {
- "GID": 6457,
+ "GID": 797,
"identifier": "MatrixData",
- "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv05iEFg5tOg/AAAAAAAAAACUdJRiLg=="
+ "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv0w3iUFg5eg/AAAAAAAAAACUdJRiLg=="
},
"dependent node outputs": [
1,
@@ -835,9 +848,9 @@
},
{
"data": {
- "GID": 6428,
+ "GID": 768,
"identifier": "Data",
- "serialized": "gASVCgAAAAAAAABHP+i0OVgQYk4u"
+ "serialized": "gASVCgAAAAAAAABHP+jlYEGJN0wu"
},
"dependent node outputs": [
3,
@@ -846,9 +859,9 @@
},
{
"data": {
- "GID": 6430,
+ "GID": 770,
"identifier": "Data",
- "serialized": "gASVCgAAAAAAAABHP+i0OVgQYk4u"
+ "serialized": "gASVCgAAAAAAAABHP+jlYEGJN0wu"
},
"dependent node outputs": [
4,
@@ -857,9 +870,9 @@
},
{
"data": {
- "GID": 6468,
+ "GID": 808,
"identifier": "Data",
- "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv05iEFg5tOg/AAAAAAAAAACUdJRiLg=="
+ "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv0w3iUFg5eg/AAAAAAAAAACUdJRiLg=="
},
"dependent node outputs": [
6,
@@ -868,9 +881,9 @@
},
{
"data": {
- "GID": 6472,
+ "GID": 812,
"identifier": "MatrixData",
- "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAEPljSfPh278AAAAAAAAAAHgDTlsGD+I/eANOWwYP4j8AAAAAAAAAAHgDTlsGD+I/AAAAAAAAAACUdJRiLg=="
+ "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAADcJ4CYB3L8AAAAAAAAAAIBk+49s/+E/gGT7j2z/4T8AAAAAAAAAAIBk+49s/+E/AAAAAAAAAACUdJRiLg=="
},
"dependent node outputs": [
7,
@@ -879,9 +892,9 @@
},
{
"data": {
- "GID": 6473,
+ "GID": 813,
"identifier": "MatrixData",
- "serialized": "gASVcgAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMBnNjYWxhcpSTlIwFbnVtcHmUjAVkdHlwZZSTlIwDYzE2lImIh5RSlChLA4wBPJROTk5K/////0r/////SwB0lGJDEAAAAAAAAAAAeANOWwYP4r+UhpRSlC4="
+ "serialized": "gASVcgAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMBnNjYWxhcpSTlIwFbnVtcHmUjAVkdHlwZZSTlIwDYzE2lImIh5RSlChLA4wBPJROTk5K/////0r/////SwB0lGJDEAAAAAAAAAAAgGT7j2z/4b+UhpRSlC4="
},
"dependent node outputs": [
8,
@@ -890,9 +903,9 @@
},
{
"data": {
- "GID": 6479,
+ "GID": 817,
"identifier": "MatrixData",
- "serialized": "gASVqQAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwKFlGgDjAVkdHlwZZSTlIwDYzE2lImIh5RSlChLA4wBPJROTk5K/////0r/////SwB0lGKJQyAAAAAAAAAAABD5Y0nz4du/eANOWwYP4j8AAAAAAAAAAJR0lGIu"
+ "serialized": "gASVqQAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwKFlGgDjAVkdHlwZZSTlIwDYzE2lImIh5RSlChLA4wBPJROTk5K/////0r/////SwB0lGKJQyAAAAAAAAAAAAA3CeAmAdy/gGT7j2z/4T8AAAAAAAAAAJR0lGIu"
},
"dependent node outputs": [
9,
@@ -901,9 +914,9 @@
},
{
"data": {
- "GID": 6474,
+ "GID": 814,
"identifier": "MatrixData",
- "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkP6YJq2umDwAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAACUdJRiLg=="
+ "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKxyE25yiDwAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAACUdJRiLg=="
},
"dependent node outputs": [
10,
@@ -912,7 +925,7 @@
},
{
"data": {
- "GID": 6478,
+ "GID": 815,
"identifier": "Data",
"serialized": "gASVqQAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwKFlGgDjAVkdHlwZZSTlIwDYzE2lImIh5RSlChLA4wBPJROTk5K/////0r/////SwB0lGKJQyAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJR0lGIu"
},
@@ -938,14 +951,14 @@
"custom state": {
"5": {
"a": {
- "GID": 6432,
+ "GID": 772,
"identifier": "Data",
- "serialized": "gASVCgAAAAAAAABHP+i0OVgQYk4u"
+ "serialized": "gASVCgAAAAAAAABHP+jlYEGJN0wu"
},
"mat": {
- "GID": 6458,
+ "GID": 798,
"identifier": "Data",
- "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv05iEFg5tOg/AAAAAAAAAACUdJRiLg=="
+ "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv0w3iUFg5eg/AAAAAAAAAACUdJRiLg=="
}
}
}
@@ -955,5 +968,26 @@
"version": "0.0.1",
"custom state": {}
}
+ },
+ "geometry": "01d9d0cb00030000000001270000009b000007590000047f0000012b000000b7000007550000047b000000000000000008d00000012b000000b7000007550000047b",
+ "state": "000000ff00000000fd000000020000000000000124000002b7fc0200000001fc0000001e000002b70000014b0100001efa000000010200000002fb000000140066006c006f00770073005f0064006f0063006b0100000000ffffffff0000007f00fffffffb00000014006e006f006400650073005f0064006f0063006b0100000000ffffffff0000012c00ffffff000000030000062b000000dcfc0100000001fb000000160063006f006e0073006f006c00650044006f0063006b01000000000000062b0000005d00ffffff000004f3000002b700000004000000040000000800000008fc00000000",
+ "flow_uis": {
+ "5": {
+ "geometry": "01d9d0cb000300000000000000000000000004dc000002810000000000000000ffffffffffffffff000000000000000008d00000000000000000000004dc00000281",
+ "state": "000000ff00000000fd0000000100000001000000d800000282fc0200000001fc0000000000000282000000d10000005bfa000000000200000006fb0000001c0069006e00730070006500630074006f0072005f0064006f0063006b0100000000ffffffff000000b200fffffffb000000220075006e0064006f005f0068006900730074006f00720079005f0064006f0063006b0100000000ffffffff0000005600fffffffb0000001a00730065007400740069006e00670073005f0064006f0063006b0100000000ffffffff0000003c0000003cfb0000001c007600610072006900610062006c00650073005f0064006f0063006b0100000000ffffffff0000007f00fffffffb00000016006c006f0067006700650072005f0064006f0063006b0100000000ffffffff0000004800fffffffb000000160073006f0075007200630065005f0064006f0063006b0100000000ffffffff0000009000ffffff000003f10000028200000004000000040000000800000008fc00000000",
+ "view": {
+ "m11": 0.975210518583877,
+ "m12": 0.0,
+ "m13": 0.0,
+ "m21": 0.0,
+ "m22": 0.975210518583877,
+ "m23": 0.0,
+ "m31": 0.0,
+ "m32": 0.0,
+ "m33": 1.0,
+ "v_scroll": 51,
+ "h_scroll": 0
+ }
+ }
}
}
\ No newline at end of file
diff --git a/ryven-editor/ryven/gui/code_editor/CodePreviewWidget.py b/ryven-editor/ryven/gui/code_editor/CodePreviewWidget.py
index 8f412151..6960de82 100644
--- a/ryven-editor/ryven/gui/code_editor/CodePreviewWidget.py
+++ b/ryven-editor/ryven/gui/code_editor/CodePreviewWidget.py
@@ -1,5 +1,11 @@
+# prevent circular imports
+from __future__ import annotations
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+ from ryven.gui.main_window import MainWindow
+
from dataclasses import dataclass
-from typing import Type, Optional
+from typing import Type, Optional, List
from qtpy.QtCore import Qt
from qtpy.QtWidgets import (
@@ -12,7 +18,6 @@
QGridLayout,
QPushButton
)
-from ryvencore import Node
from ryven.gui.code_editor.EditSrcCodeInfoDialog import EditSrcCodeInfoDialog
from ryven.gui.code_editor.CodeEditorWidget import CodeEditorWidget
@@ -29,6 +34,9 @@
load_src_code,
)
+from ryvencore import Node
+from ryvencore_qt.src.flows.FlowView import FlowView
+
class LoadSrcCodeButton(QPushButton):
def __init__(self):
@@ -53,14 +61,14 @@ def __init__(self, name, obj: Inspectable):
class CodePreviewWidget(QWidget):
- def __init__(self, main_window, flow_view):
+ def __init__(self, main_window: MainWindow, flow_view: FlowView):
super().__init__()
self.edits_enabled = main_window.config.src_code_edits_enabled
self.current_insp: Optional[Inspectable] = None
# widgets
- self.radio_buttons = []
+ self.radio_buttons: List[QRadioButton] = []
self.text_edit = CodeEditorWidget(main_window.theme)
self.setup_ui()
@@ -146,7 +154,9 @@ def _set_node(self, node: Optional[Node]):
def _process_node_src(self, node: Node):
self._rebuild_class_selection(node)
- code = class_codes[node.__class__].node_cls
+ codes = class_codes[node.__class__]
+ assert codes is not None
+ code = codes.node_cls
if self.edits_enabled and node in modif_codes:
code = modif_codes[node]
self._update_code(NodeInspectable(node, code))
@@ -165,11 +175,14 @@ def _update_code(self, insp: Inspectable):
def _rebuild_class_selection(self, node: Node):
+ assert hasattr(node, 'gui')
+
self.load_code_button.hide()
self._clear_class_layout()
self.radio_buttons.clear()
- codes: NodeTypeCodes = class_codes[node.__class__]
+ codes = class_codes[node.__class__]
+ assert codes is not None
def register_rb(rb: QRadioButton):
rb.toggled.connect(self._class_rb_toggled)
@@ -213,7 +226,7 @@ def _clear_class_layout(self):
widget.hide()
self.class_selection_layout.removeItem(item)
- def _load_code_button_clicked(self):
+ def _load_code_button_clicked(self) -> None:
node: Node = self.sender().node
load_src_code(node.__class__)
self.load_code_button.hide()
@@ -234,7 +247,7 @@ def _update_radio_buttons_edit_status(self):
f.setBold(False)
br.setFont(f)
- def _class_rb_toggled(self, checked):
+ def _class_rb_toggled(self, checked: bool) -> None:
if checked:
rb: LinkedRadioButton = self.sender()
self._update_code(rb.representing)
diff --git a/ryven-editor/ryven/gui/code_editor/SourceCodeUpdater.py b/ryven-editor/ryven/gui/code_editor/SourceCodeUpdater.py
index a426fe30..b8020fa8 100644
--- a/ryven-editor/ryven/gui/code_editor/SourceCodeUpdater.py
+++ b/ryven-editor/ryven/gui/code_editor/SourceCodeUpdater.py
@@ -1,5 +1,5 @@
import types
-from typing import Union
+from typing import Union, List
def get_method_funcs(cls_def_str: str, obj):
@@ -16,15 +16,15 @@ def get_method_funcs(cls_def_str: str, obj):
import ast
# extract functions
- ast_funcs: [ast.FunctionDef] = [
+ ast_funcs: List[ast.FunctionDef] = [
f
- for f in ast.parse(cls_def_str).body[0].body
+ for f in ast.parse(cls_def_str).body[0].body # type: ignore
if type(f) == ast.FunctionDef
]
funcs = {}
for astf in ast_funcs:
- d = __builtins__.copy() # important: provide builtins when parsing the function
+ d = __builtins__.__dict__.copy() # important: provide builtins when parsing the function
exec(ast.unparse(astf), d)
f = d[astf.name]
# # add locals scope of the object to the function
@@ -47,6 +47,7 @@ def override_code(obj: object, new_class_src) -> Union[None, Exception]:
for name, f in funcs.items(): # override all methods
setattr(obj, name, types.MethodType(f, obj))
# types.MethodType() creates a method bound to obj, from the function f
+ return None
except Exception as e:
return e
diff --git a/ryven-editor/ryven/gui/code_editor/codes_storage.py b/ryven-editor/ryven/gui/code_editor/codes_storage.py
index 073ec8a9..b068418a 100644
--- a/ryven-editor/ryven/gui/code_editor/codes_storage.py
+++ b/ryven-editor/ryven/gui/code_editor/codes_storage.py
@@ -1,6 +1,6 @@
# statically stores source codes of nodes and their widgets
from dataclasses import dataclass
-from typing import Type, Optional
+from typing import Type, Optional, Dict, no_type_check
import inspect
from ryvencore import Node
@@ -13,16 +13,20 @@ def register_node_type(n: Type[Node]):
source code loading is disabled.
"""
+ assert instance is not None, 'Ryven instance not initialized.'
+
if not instance.defer_code_loading:
load_src_code(n)
else:
class_codes[n] = None
+@no_type_check
def load_src_code(n: Type[Node]):
- has_gui = hasattr(n, 'GUI') # check if node type has custom gui
+ # check for custom GUI and main widget
+ has_gui = hasattr(n, 'GUI')
has_mw = has_gui and n.GUI.main_widget_class is not None
-
+
src = inspect.getsource(n)
mw_src = inspect.getsource(n.GUI.main_widget_class) if has_mw else None
inp_src = {
@@ -50,7 +54,7 @@ def load_src_code(n: Type[Node]):
class NodeTypeCodes:
node_cls: str
main_widget_cls: Optional[str]
- custom_input_widget_clss: {str: str}
+ custom_input_widget_clss: Dict[str, str]
class Inspectable:
@@ -79,7 +83,7 @@ class CustomInputWidgetInspectable(Inspectable):
pass
-class_codes: {Type[Node]: {}} = {}
+class_codes: Dict[Type[Node], Optional[NodeTypeCodes]] = {}
# {
# Type[Node]: NodeTypeCodeInfo
# }
@@ -91,7 +95,7 @@ class CustomInputWidgetInspectable(Inspectable):
# maps node- or widget classes to their full module source code
-mod_codes: {Type: str} = {}
+mod_codes: Dict[Type, str] = {}
# maps node- or widget objects to their modified source code
-modif_codes: {object: str} = {}
+modif_codes: Dict[object, str] = {}
diff --git a/ryven-editor/ryven/gui/flow_ui.py b/ryven-editor/ryven/gui/flow_ui.py
index 70f36372..d7aeedab 100644
--- a/ryven-editor/ryven/gui/flow_ui.py
+++ b/ryven-editor/ryven/gui/flow_ui.py
@@ -103,7 +103,7 @@ def __init__(self, main_window, flow: Flow, flow_view: FlowView):
self.ui.inspector_dock.setWidget(self.inspector_widget)
#undo history widget
- self.undo_widget = QUndoView(self.flow_view._undo_stack)
+ self.undo_widget = QUndoView(stack=self.flow_view._undo_stack) # type: ignore
self.ui.undo_history_dock.setWidget(self.undo_widget)
# logs
self.ui.logs_scrollArea.setWidget(self.create_loggers_widget())
diff --git a/ryven-editor/ryven/gui/main_console.py b/ryven-editor/ryven/gui/main_console.py
index 0602ab82..4b4a2ad8 100644
--- a/ryven-editor/ryven/gui/main_console.py
+++ b/ryven-editor/ryven/gui/main_console.py
@@ -1,3 +1,4 @@
+from typing import Optional, List
import code
import re
import os
@@ -151,7 +152,9 @@ def push(self, commands: str) -> None:
self.prompt_label.show()
# add leading space for next input
- leading_space = re.match(r"\s*", self.buffer[-1]).group()
+ m = re.match(r"\s*", self.buffer[-1])
+ assert m is not None
+ leading_space = m.group()
self.inpedit.next_line = leading_space
else: # no more input required
@@ -167,9 +170,9 @@ def errorwrite(self, line: str) -> None:
"""capture stderr and print to outdisplay"""
self.writeoutput(line, self.errfmt)
- def writeoutput(self, line: str, fmt: QTextCharFormat = None) -> None:
+ def writeoutput(self, line: str, fmt: Optional[QTextCharFormat] = None) -> None:
"""prints to outdisplay"""
- if fmt:
+ if fmt is not None:
self.out_display.setCurrentCharFormat(fmt)
self.out_display.appendPlainText(line.rstrip())
self.out_display.setCurrentCharFormat(self.outfmt)
@@ -188,7 +191,7 @@ def __init__(self, code_text_edit, max_history: int = 100):
self.code_text_edit.returned.connect(self.code_text_edit_returned)
self.max_hist = max_history
self.hist_index = 0
- self.hist_list = []
+ self.hist_list: List[str] = []
self.next_line = '' # can be set by console
self.prompt_pattern = re.compile('^[>\.]')
diff --git a/ryven-editor/ryven/gui/main_window.py b/ryven-editor/ryven/gui/main_window.py
index a01d2e78..39f494a3 100644
--- a/ryven-editor/ryven/gui/main_window.py
+++ b/ryven-editor/ryven/gui/main_window.py
@@ -1,3 +1,5 @@
+from typing import Set, Dict, List, Optional, Type, Union
+
import sys
import os
import os.path
@@ -34,7 +36,7 @@
import ryvencore_qt as rc
import ryvencore_qt.src.widgets as rc_GUI
-from ryvencore import InfoMsgs, Flow
+from ryvencore import InfoMsgs, Flow, Session as CoreSession
class MainWindow(QMainWindow):
@@ -42,20 +44,21 @@ class MainWindow(QMainWindow):
def __init__(
self,
config: Config,
- requested_packages: set = (),
- required_packages: set = None, # only valid when project_content is provided
- project_content: dict = None,
+ requested_packages: Optional[Set] = None,
+ required_packages: Optional[Set] = None, # only valid when project_content is provided
+ project_content: Optional[Dict] = None,
parent=None,
):
super().__init__(parent)
self.config = config
- self.session_gui, self.core_session = None, None
+ self.session_gui: rc.SessionGUI
+ self.core_session: CoreSession
self.theme = config.window_theme
- self.node_packages = {} # {Node: str}
- self.flow_UIs = {} # Should be dict[Flow, FlowUI] in 3.9+
- self.flow_ui_template = None # Should be dict[str, QByteArray | dict] in 3.9+
- self._project_content = None
+ self.node_packages: Dict[Type[rc.Node], NodesPackage] = {}
+ self.flow_UIs: Dict[Flow, FlowUI] = {}
+ self.flow_ui_template: Optional[Dict[str, Union[QByteArray, Dict]]] = None
+ self._project_content: Optional[Dict] = None
# Init Session GUI
@@ -80,7 +83,7 @@ def __init__(
self.setup_ui()
- self.flow_view_theme_actions = []
+ self.flow_view_theme_actions: List[QAction] = []
self.setup_menu_actions()
self.setWindowTitle(self.config.window_title)
@@ -95,7 +98,7 @@ def __init__(
import_nodes_shortcut.activated.connect(self.on_import_nodes_triggered)
# Setup Main Console
-
+ assert MainConsole.instance is not None, 'MainConsole not initialized'
MainConsole.instance.session = self.session_gui
MainConsole.instance.reset_interpreter()
@@ -105,7 +108,7 @@ def console_ref_monkeypatch(self):
NodeGUI.console_ref_monkeypatch = console_ref_monkeypatch
old_ac_init = NodeGUI._init_default_actions
- NodeGUI._init_default_actions = lambda self: {
+ NodeGUI._init_default_actions = lambda self: { # type: ignore
**old_ac_init(self),
'console ref': {'method': self.console_ref_monkeypatch},
}
@@ -126,8 +129,11 @@ def console_ref_monkeypatch(self):
# Requested packages take precedence over other packages
print('importing requested packages...')
+ if requested_packages is None:
+ requested_packages = set()
self.import_packages(requested_packages)
if project_content is not None:
+ assert required_packages is not None, 'required_packages must be provided when loading a project'
self._project_content = project_content
print('importing required packages...')
self.import_packages(required_packages)
@@ -164,14 +170,11 @@ def print_info(self):
# UI
- def setup_ui(self):
+ def setup_ui(self) -> None:
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.statusBar.hide()
- # actions for setting / unsetting a flow ui template
- self.focused_flow: FlowUI = None
-
def unset_flow_template():
self.set_flow_ui_template(None)
@@ -400,7 +403,7 @@ def on_save_scene_pic_viewport_triggered(self):
def on_save_scene_pic_whole_triggered(self):
"""Saves a picture of the whole currently visible scene."""
- if len(self.session_gui.flows) == 0:
+ if len(self.core_session.flows) == 0:
return
file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0]
@@ -484,14 +487,15 @@ def get_current_flow(self):
def focus_on_flow(self, flow):
self.ui.flows_tab_widget.setCurrentWidget(self.flow_UIs[flow])
- def import_packages(self, packages_list: [NodesPackage]):
+ def import_packages(self, packages_list: List[NodesPackage]):
for p in packages_list:
self.import_nodes(p)
- def import_nodes(self, package: NodesPackage = None, path: str = None):
+ def import_nodes(self, package: Optional[NodesPackage] = None, path: Optional[str] = None):
if package is not None:
p = package
else:
+ assert path is not None, 'either package or path must be provided'
p = NodesPackage(path)
if p in self.node_packages.values():
@@ -511,7 +515,7 @@ def import_nodes(self, package: NodesPackage = None, path: str = None):
self,
)
msg_box.exec_()
- sys.exit(e)
+ sys.exit(str(e))
self.core_session.register_data_types(data_types)
self.core_session.register_node_types(nodes)
@@ -559,7 +563,7 @@ def load_flow_ui(self, flow_ui: FlowUI):
# print(f'Could not load previous UI state for flow with previous id: {flow_ui.flow.prev_global_id}')
pass
- def save_project(self, file_name):
+ def save_project(self, file_name: str) -> None:
import json
file = None
@@ -594,7 +598,7 @@ def save_project(self, file_name):
# Serialization of the flow views
# should be dict[str, dict[str, str | dict]] in 3.9+
- flow_uis_ser: dict = {}
+ flow_uis_ser: Dict[str, Dict] = {}
for flow, flow_ui in self.flow_UIs.items():
flow_uis_ser[str(flow.global_id)] = flow_ui.save_state()
@@ -610,8 +614,8 @@ def save_project(self, file_name):
# flow ui template
if self.flow_ui_template:
whole_project_dict['flow_ui_template'] = {
- 'geometry': QByteArray(self.flow_ui_template['geometry']).toHex().data().decode(),
- 'state': QByteArray(self.flow_ui_template['state']).toHex().data().decode(),
+ 'geometry': QByteArray(self.flow_ui_template['geometry']).toHex().data().decode(), # type: ignore
+ 'state': QByteArray(self.flow_ui_template['state']).toHex().data().decode(), # type: ignore
'view': self.flow_ui_template['view']
}
diff --git a/ryven-editor/ryven/gui/startup_dialog/StartupDialog.py b/ryven-editor/ryven/gui/startup_dialog/StartupDialog.py
index bed2b94d..7e9e089a 100644
--- a/ryven-editor/ryven/gui/startup_dialog/StartupDialog.py
+++ b/ryven-editor/ryven/gui/startup_dialog/StartupDialog.py
@@ -71,7 +71,8 @@ def minimumSizeHint(self):
def sizeHint(self):
hint = self.fontMetrics().boundingRect(self.text()).size()
- l, t, r, b = self.getContentsMargins()
+ c_margins = self.contentsMargins()
+ l, t, r, b = c_margins.left(), c_margins.top(), c_margins.right(), c_margins.bottom()
margin = self.margin() * 2
return QSize(
min(100, hint.width()) + l + r + margin,
@@ -83,7 +84,8 @@ def paintEvent(self, event):
opt = QStyleOptionFrame()
self.initStyleOption(opt)
self.style().drawControl(QStyle.CE_ShapedFrame, opt, qp, self)
- l, t, r, b = self.getContentsMargins()
+ c_margins = self.contentsMargins()
+ l, t, r, b = c_margins.left(), c_margins.top(), c_margins.right(), c_margins.bottom()
margin = self.margin()
try:
# since Qt >= 5.11
@@ -481,7 +483,7 @@ def on_load_example_project_button_clicked(self):
"""Call-back method, whenever the 'Example' button was clicked."""
# Load an example project, starting in the ryven's example directory
project_path = self.get_project(
- abs_path_from_package_dir('examples_projects'), title='Select Ryven example'
+ abs_path_from_package_dir('example_projects'), title='Select Ryven example'
)
if project_path is not None:
@@ -660,7 +662,7 @@ def load_project(self, project_path: Optional[pathlib.Path]):
else:
self.conf.project = project_path
- self.project_name.setText(project_path)
+ self.project_name.setText(str(project_path))
self.create_project_button.setEnabled(True)
required_nodes, missing_nodes, _ = process_nodes_packages(project_path)
diff --git a/ryven-editor/ryven/gui/std_input_widgets.py b/ryven-editor/ryven/gui/std_input_widgets.py
index 992ef0ec..903bee5a 100644
--- a/ryven-editor/ryven/gui/std_input_widgets.py
+++ b/ryven-editor/ryven/gui/std_input_widgets.py
@@ -118,7 +118,7 @@ def text_changed(self, new_text):
self.on_widget_val_changed(self.val)
@property
- def val(self) -> data_type:
+ def val(self) -> Data:
try:
return data_type(eval(self.text()))
except:
@@ -197,7 +197,7 @@ def text_changed(self, new_text):
self.on_widget_val_changed(self.val)
@property
- def val(self) -> data_type:
+ def val(self) -> Data:
return data_type(self.text())
def load_from(self, val: Data):
@@ -248,7 +248,7 @@ def __init__(self, params):
@property
- def val(self) -> data_type:
+ def val(self) -> Data:
return data_type(self.value())
def load_from(self, val: Data):
@@ -292,7 +292,7 @@ def __init__(self, params):
self.setValue(init)
@property
- def val(self) -> data_type:
+ def val(self) -> Data:
return data_type(self.value())
def load_from(self, val: Data):
@@ -333,7 +333,7 @@ def __init__(self, params):
self.setChecked(init)
@property
- def val(self) -> data_type:
+ def val(self) -> Data:
return data_type(self.isChecked())
def load_from(self, val: Data):
diff --git a/ryven-editor/ryven/gui/styling/window_theme.py b/ryven-editor/ryven/gui/styling/window_theme.py
index 944801f0..e7f3e29d 100644
--- a/ryven-editor/ryven/gui/styling/window_theme.py
+++ b/ryven-editor/ryven/gui/styling/window_theme.py
@@ -1,3 +1,5 @@
+from typing import Dict, Optional
+
from ryven.main.utils import abs_path_from_package_dir
@@ -11,8 +13,8 @@ def hex_to_rgb(hex: str):
class WindowTheme:
name = ''
- colors = {}
- rules = {}
+ colors: Dict[str, Optional[str]] = {}
+ rules: Dict[str, Optional[str]] = {}
def __init__(self):
self.init_rules()
@@ -81,7 +83,8 @@ class WindowTheme_Plain(WindowTheme):
}
-def apply_stylesheet(style: str):
+def apply_stylesheet(style: str) -> WindowTheme:
+
from qtpy.QtWidgets import QApplication
# set to None if not used
@@ -91,6 +94,8 @@ def apply_stylesheet(style: str):
d = QDir()
d.setSearchPaths('icon', [icons_dir])
+ window_theme: WindowTheme
+
if style in (None, 'plain'):
window_theme = WindowTheme_Plain()
stylesheet = None
diff --git a/ryven-editor/ryven/gui/uic/README.md b/ryven-editor/ryven/gui/uic/README.md
new file mode 100644
index 00000000..e1f844e0
--- /dev/null
+++ b/ryven-editor/ryven/gui/uic/README.md
@@ -0,0 +1 @@
+The Python files here are generated from the Qt Designer UI files using something like `pyside2-tools uic`.
\ No newline at end of file
diff --git a/ryven-editor/ryven/main/Ryven.py b/ryven-editor/ryven/main/Ryven.py
index 65d89f3e..30ba786c 100644
--- a/ryven-editor/ryven/main/Ryven.py
+++ b/ryven-editor/ryven/main/Ryven.py
@@ -7,8 +7,39 @@
from ryven.main.args_parser import process_args
+def check_pyside_available(qt_api: str):
+ if qt_api == 'pyside2':
+ if sys.version_info >= (3, 11):
+ sys.exit(
+ 'You are trying to use PySide2 as the Qt API, but it is not available for Python 3.11 and later. '
+ 'Please use PySide6 instead (run `ryven -q pyside6`), or use Python 3.10 or earlier. '
+ )
+ try:
+ import PySide2
+ except ImportError:
+ sys.exit(
+ 'You are trying to use PySide2 as the Qt API, but it is not available. '
+ 'Please install it, or use pyside6 as the Qt API. Either of those can '
+ 'installed through pip, e.g. `pip install pyside2` or `pip install \'pyside6<6.7\'`. '
+ )
+ elif qt_api == 'pyside6':
+ try:
+ import PySide6
+ except ImportError:
+ sys.exit(
+ 'You are trying to use PySide6 as the Qt API, but it is not available. '
+ 'please install it, or use pyside2 as the Qt API. Either of those can '
+ 'installed through pip, e.g. `pip install pyside2` or `pip install \'pyside6<6.7\'`. '
+ )
+ else:
+ sys.exit(
+ f'Error: Illegal Qt API: "{qt_api}". '
+ f'Use either "pyside2" or "pyside6". '
+ )
+
+
def run(*args_,
- qt_app=None, gui_parent=None, use_sysargs=True,
+ qt_app=None, gui_parent=None, use_sysargs: bool = True,
**kwargs):
"""Start the Ryven window.
@@ -72,6 +103,7 @@ def run(*args_,
#
# Init environment
+ check_pyside_available(conf.qt_api)
os.environ['RYVEN_MODE'] = 'gui'
os.environ['QT_API'] = conf.qt_api
from ryven.node_env import init_node_env
@@ -124,7 +156,7 @@ def run(*args_,
sys.exit('Start-up screen dismissed')
# Replace node directories with `NodePackage` instances
- if conf.nodes:
+ if len(conf.nodes) > 0:
conf.nodes, pkgs_not_found, _ = ryven.main.packages.nodes_package.process_nodes_packages(list(conf.nodes))
if pkgs_not_found:
sys.exit(
@@ -133,6 +165,7 @@ def run(*args_,
# editor_config['requested packages'] = conf.nodes
# Store WindowTheme object
+ assert isinstance(conf.window_theme, str)
conf.window_theme = apply_stylesheet(conf.window_theme)
# Adjust flow theme if not set
@@ -150,7 +183,9 @@ def run(*args_,
# Get packages required by the project
if conf.project:
pkgs, pkgs_not_found, project_dict = ryven.main.packages.nodes_package.process_nodes_packages(
- conf.project, requested_packages=list(conf.nodes))
+ conf.project,
+ requested_packages=list(conf.nodes) # type: ignore
+ )
if pkgs_not_found:
str_missing_pkgs = ', '.join([str(p.name) for p in pkgs_not_found])
diff --git a/ryven-editor/ryven/main/RyvenConsole.py b/ryven-editor/ryven/main/RyvenConsole.py
index f3aa935a..2aefe9d7 100644
--- a/ryven-editor/ryven/main/RyvenConsole.py
+++ b/ryven-editor/ryven/main/RyvenConsole.py
@@ -1,3 +1,5 @@
+# TODO this could be improved
+
"""
This module includes the whole Ryven Console application.
It simply deploys a session with the project provided and implements a REPL.
diff --git a/ryven-editor/ryven/main/args_parser.py b/ryven-editor/ryven/main/args_parser.py
index c967569c..7b1291cf 100644
--- a/ryven-editor/ryven/main/args_parser.py
+++ b/ryven-editor/ryven/main/args_parser.py
@@ -146,7 +146,7 @@ def parse_sys_args(just_defaults=False) -> Config:
"""
# Get available examples
- exampledir = utils.abs_path_from_package_dir('examples_projects')
+ exampledir = utils.abs_path_from_package_dir('example_projects')
examples = [e.stem for e in pathlib.Path(exampledir).glob('*.json')]
#
@@ -365,16 +365,15 @@ def parse_sys_args(just_defaults=False) -> Config:
# Check, if project file exists
if args.project:
if args.project == '-':
- args.project = sys.stdin
- else:
- project = utils.find_project(args.project)
- if project is None:
- parser.error(
- 'project file does not exist')
- args.project = project
+ args.project = pathlib.Path(str(sys.stdin))
+ project = utils.find_project(args.project)
+ if project is None:
+ parser.error(
+ 'project file does not exist')
+ args.project = project
# Make a `set` of paths to node packages
- args.nodes = set([pathlib.Path(nodes_pkg) for nodes_pkg in args.nodes])
+ args.nodes = set([pathlib.Path(nodes_pkg) for nodes_pkg in args.nodes]) # type: ignore
# Put example into 'project' argument
if args.example:
diff --git a/ryven-editor/ryven/main/config.py b/ryven-editor/ryven/main/config.py
index 036e1687..17330685 100644
--- a/ryven-editor/ryven/main/config.py
+++ b/ryven-editor/ryven/main/config.py
@@ -29,7 +29,7 @@ class Config:
project: Optional[pathlib.Path] = None
show_dialog: bool = True
verbose: bool = False
- nodes: Union[Set[pathlib.Path], Set[NodesPackage]] = []
+ nodes: Union[Set[pathlib.Path], Set[NodesPackage]] = set()
example: Optional[str] = None
window_theme: Union[str, WindowTheme] = 'dark'
flow_theme: Optional[str] = None # None means it depends on window_theme
diff --git a/ryven-editor/ryven/main/packages/built_in/nodes.py b/ryven-editor/ryven/main/packages/built_in/nodes.py
index c20449c4..11fb95ae 100644
--- a/ryven-editor/ryven/main/packages/built_in/nodes.py
+++ b/ryven-editor/ryven/main/packages/built_in/nodes.py
@@ -86,7 +86,7 @@ class Val_Node(NodeBase):
# NodeInputType(default=Data()),
]
init_outputs = [
- NodeInputType(type_='data'),
+ NodeOutputType(type_='data'),
]
def __init__(self, params):
diff --git a/ryven-editor/ryven/main/packages/gui_env.py b/ryven-editor/ryven/main/packages/gui_env.py
index db107940..1dbbf946 100644
--- a/ryven-editor/ryven/main/packages/gui_env.py
+++ b/ryven-editor/ryven/main/packages/gui_env.py
@@ -1,46 +1,25 @@
-"""
-This module automatically imports all requirements for Gui definitions of a nodes package.
-"""
-
-from typing import Type
+from typing import Type, List, Set
from ryvencore import Data, Node, serialize, deserialize
from ryvencore.InfoMsgs import InfoMsgs
from ryvencore_qt import NodeInputWidget, NodeMainWidget, NodeGUI, NodeInspectorWidget
-import ryven.gui.std_input_widgets as inp_widgets
+from ryven.gui import std_input_widgets as inp_widgets
from ryven.main.utils import in_gui_mode
-__explicit_nodes: set = set() # for protection against setting the gui twice on the same node
+__explicit_nodes: Set = set() # for protection against setting the gui twice on the same node
def init_node_guis_env():
pass
-
-class GuiClassesRegistry:
- """
- Used for statically keeping the gui classes specified in export_guis to access them through node_env.import_guis().
- """
-
- exported_guis = []
-
-
-class GuiClassesContainer:
- pass
-
-
-def export_guis(guis: [Type[NodeGUI]]):
- """
- Exports/exposes the specified node gui classes to the nodes file importing them via import_guis().
- Returns an object with all exported gui classes as attributes for direct access.
- """
-
- gcc = GuiClassesContainer()
- for w in guis:
- setattr(gcc, w.__name__, w)
- GuiClassesRegistry.exported_guis.append(gcc)
+def export_guis(guis: List[Type[NodeGUI]]):
+ raise Exception(
+ 'The function export_guis is deprecated and should not be used anymore. '
+ 'Please use the node_gui decorator instead. '
+ 'See the example nodes packages that Ryven comes with for reference. '
+ )
def node_gui(node_cls: Type[Node]):
@@ -53,10 +32,10 @@ def node_gui(node_cls: Type[Node]):
def register_gui(gui_cls: Type[NodeGUI]):
if node_cls in __explicit_nodes:
+ assert hasattr(node_cls, 'GUI')
InfoMsgs.write(f'{node_cls.__name__} has defined an explicit gui {node_cls.GUI.__name__}')
return
-
- node_cls.GUI = gui_cls
+ node_cls.GUI = gui_cls # type: ignore
__explicit_nodes.add(node_cls)
InfoMsgs.write(f"Registered node gui: {gui_cls} for {node_cls}")
return gui_cls
diff --git a/ryven-editor/ryven/main/packages/node_env.py b/ryven-editor/ryven/main/packages/node_env.py
index cdf5721c..7105397a 100644
--- a/ryven-editor/ryven/main/packages/node_env.py
+++ b/ryven-editor/ryven/main/packages/node_env.py
@@ -1,10 +1,12 @@
-"""
-This module automatically imports all requirements for custom nodes.
-"""
+# prevent circular imports
+from __future__ import annotations
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+ from ryven.main.packages.nodes_package import NodesPackage
import os
from os.path import basename, normpath
-from typing import Type, Tuple, List
+from typing import Type, Tuple, List, Optional, Dict
from ryven.main.utils import in_gui_mode, load_from_file
@@ -32,39 +34,12 @@ def init_node_env():
import ryvencore_qt
-# LEAVING THIS HERE FOR LEGACY PURPOSES
def import_guis(origin_file: str, gui_file_name='gui.py'):
- """
- Import all exported GUI classes from gui_file_name with respect to the origin_file location.
- Returns an object with all exported gui classes as attributes for direct access.
- """
-
- caller_location = os.path.dirname(origin_file)
-
- # alternative solution without __file__ argument; does not work with debugging, so it's not the best idea
- # caller_location = os.path.dirname(stack()[1].filename) # getting caller file path from stack frame
-
- abs_path = os.path.join(caller_location, gui_file_name)
-
- if os.environ['RYVEN_MODE'] == 'gui':
- # import the gui module
- load_from_file(abs_path)
-
- # in GUI mode, import the gui classes container from gui_env containing all the exported gui classes
- from ryven import gui_env
-
- gui_classes_container = gui_env.GuiClassesRegistry.exported_guis[-1]
-
- else:
- # in non-gui mode, return an object that just returns None for all accessed attributes
- # so guis.MyGUI in the nodes file just returns None then
- class PlaceholderGuisContainer:
- def __getattr__(self, item):
- return None
-
- gui_classes_container = PlaceholderGuisContainer()
-
- return gui_classes_container
+ raise Exception(
+ 'The function import_guis is deprecated and should not be used anymore. '
+ 'Please use the on_gui_load decorator instead. '
+ 'See the example nodes packages that Ryven comes with for reference. '
+ )
class NodesEnvRegistry:
@@ -77,22 +52,20 @@ class NodesEnvRegistry:
"""
# stores, for each nodes package or subpackage a tuple of exported node types and data
- # should be dict[str, tuple[list[type[Node]], list[type[Data]]]] in future versions
- exported_package_metadata: dict = {}
+ exported_package_metadata: Dict[str, Tuple[List[Type[Node]], List[Type[Data]]]] = {}
# the last exported package to be consumed for loading
- # should be list[tuple[list[type[Node]], list[type[Data]]]]
- last_exported_package: list = []
+ last_exported_package: List[Tuple[List[Type[Node]], List[Type[Data]]]] = []
# stores, for each nodes package separately, a list of exported node types
- exported_nodes_legacy: [[Type[Node]]] = []
+ exported_nodes_legacy: List[List[Type[Node]]] = []
# stores, for each nodes package separately, a list of exported data types
- exported_data_types_legacy: [[Type[Data]]] = []
+ exported_data_types_legacy: List[List[Type[Data]]] = []
# stores the package that is currently being imported; set by the nodes package
# loader ryven.main.packages.nodes_package.import_nodes_package;
# needed for extending the identifiers of node types to include the package name
- current_package = None # type NodesPackage (not imported to avoid circular imports)
+ current_package: Optional[NodesPackage] = None
@classmethod
def current_package_id(cls):
@@ -104,10 +77,10 @@ def current_package_id(cls):
return cls.current_package.name
@classmethod
- # should be -> tuple[list[type[Node]], list[type[Data]] in 3.9+
- # should be result: tuple[list[type[Node]], list[type[Data]]] in 3.9+
def consume_last_exported_package(cls) -> Tuple[List[Type[Node]], List[Type[Data]]]:
- """Consumes the last exported package"""
+ """
+ Returns and forgets the content of the last call to `export_nodes`.
+ """
result: Tuple[List[Type[Node]], List[Type[Data]]] = ([], [])
node_types, data_types = result
for nodes, data in cls.last_exported_package:
@@ -118,9 +91,9 @@ def consume_last_exported_package(cls) -> Tuple[List[Type[Node]], List[Type[Data
def export_nodes(
- node_types: [Type[Node]],
- data_types: [Type[Data]] = None,
- sub_pkg_name: str = None
+ node_types: List[Type[Node]],
+ data_types: Optional[List[Type[Data]]] = None,
+ sub_pkg_name: Optional[str] = None
):
"""
Exports/exposes the specified nodes to Ryven for use in flows. Nodes will have the same identifier, since they come as a package.
@@ -130,32 +103,37 @@ def export_nodes(
if data_types is None:
data_types = []
- pkg_name = NodesEnvRegistry.current_package_id()
+ pkg_name: str = NodesEnvRegistry.current_package_id()
if sub_pkg_name is not None:
- pkg_name = f"{pkg_name}.{sub_pkg_name}"
+ full_pkg_name = f"{pkg_name}.{sub_pkg_name}"
+ else:
+ full_pkg_name = pkg_name
# extend identifiers of node types to include the package name
for n_cls in node_types:
- # store the package id as identifier prefix, which will be added
- # by ryvencore when registering the node type
- n_cls.identifier_prefix = pkg_name
+ if not hasattr(n_cls, 'identifier') or n_cls.identifier is None:
+ n_cls._build_identifier()
- # also add the identifier without the prefix as fallback for older versions
+ # fallbacks for older versions
n_cls.legacy_identifiers = [
*n_cls.legacy_identifiers,
- n_cls.identifier if n_cls.identifier else n_cls.__name__,
+ n_cls.identifier,
+ f"{pkg_name}.{n_cls.identifier}",
]
+ # store the package id as identifier prefix, which will be added
+ # by ryvencore when registering the node type
+ n_cls.identifier_prefix = full_pkg_name
+
# same for data types
for d_cls in data_types:
- d_cls.identifier = f'{pkg_name}.{d_cls.identifier}'
+ d_cls.identifier = f'{full_pkg_name}.{d_cls.identifier}'
NodesEnvRegistry.exported_nodes_legacy.append(node_types)
NodesEnvRegistry.exported_data_types_legacy.append(data_types)
- metadata = NodesEnvRegistry.exported_package_metadata
nodes_datas = (node_types, data_types)
- metadata[pkg_name] = nodes_datas
+ NodesEnvRegistry.exported_package_metadata[full_pkg_name] = nodes_datas
NodesEnvRegistry.last_exported_package.append(nodes_datas)
@@ -170,8 +148,7 @@ def on_gui_load(func):
When Ryven is running in headless mode, this function will not
be called, and your nodes package should function without any.
- Example:
- `nodes.py`:
+ Example `nodes.py`:
```
from ryven.node_env import *
diff --git a/ryven-editor/ryven/main/packages/nodes_package.py b/ryven-editor/ryven/main/packages/nodes_package.py
index a4f32c8b..6bcdf83a 100644
--- a/ryven-editor/ryven/main/packages/nodes_package.py
+++ b/ryven-editor/ryven/main/packages/nodes_package.py
@@ -7,7 +7,7 @@
import os, sys
import pathlib
from os.path import basename, dirname, splitext, normpath, join
-from typing import Tuple, List, Type, Union, Set, Optional
+from typing import Tuple, List, Type, Union, Set, Optional, Dict
import pkgutil
from ryvencore import Node, Data
@@ -56,7 +56,7 @@ def config_data(self):
# should be Tuple[list[Type[Node]], list[Type[Data]] in 3.9+
def import_nodes_package(
- package: NodesPackage = None, directory: str = None
+ package: Optional[NodesPackage] = None, directory: Optional[str] = None
) -> Tuple[List[Type[Node]], List[Type[Data]]]:
"""Loads node and data classes from a Ryven nodes package and returns both in separate lists.
@@ -69,6 +69,7 @@ def import_nodes_package(
"""
if package is None:
+ assert directory is not None, 'Either package or directory must be specified.'
package = NodesPackage(directory)
if 'RYVEN_MODE' not in os.environ:
@@ -84,16 +85,13 @@ def import_nodes_package(
node_env.NodesEnvRegistry.current_package = package
load_from_file(package.file_path)
- # obsolete node_types = node_env.NodesEnvRegistry.exported_nodes[-1]
- # obsolote data_types = node_env.NodesEnvRegistry.exported_data_types[-1]
-
- node_types, data_types = node_env.NodesEnvRegistry.consume_last_exported_package()
-
# load guis
if in_gui_mode():
load_current_guis()
- # load soruce codes
+ node_types, data_types = node_env.NodesEnvRegistry.consume_last_exported_package()
+
+ # load source codes
if in_gui_mode():
from ryven.gui.code_editor.codes_storage import register_node_type
for node_type in node_types:
@@ -106,8 +104,8 @@ def process_nodes_packages(
Union[str, pathlib.Path], # path to Ryven project
List[Union[str, pathlib.Path, NodesPackage]], # list of node packages
],
- requested_packages: List[NodesPackage] = None,
-) -> Tuple[Set[NodesPackage], List[pathlib.Path], Optional[dict]]:
+ requested_packages: Optional[List[NodesPackage]] = None,
+) -> Tuple[Set[NodesPackage], Set[pathlib.Path], Optional[Dict]]:
"""Takes a project or list of node packages and additionally requested node
packages and checks whether the node packages are valid.
@@ -140,20 +138,18 @@ def process_nodes_packages(
if requested_packages is None:
requested_packages = []
+ pkgs: Set[NodesPackage] = set()
+ pkgs_not_found: Set[pathlib.Path] = set()
+ project_dict: Optional[Dict]
+
# Find nodes in the project file
- try:
+ if isinstance(project_or_nodes, (str, pathlib.Path)):
project_dict = read_project(project_or_nodes)
node_pkg_paths = [p['dir'] for p in project_dict['required packages']]
- except TypeError:
+ else:
project_dict = None
node_pkg_paths = project_or_nodes
- except KeyError:
- # No required packages found
- project_dict = None
- node_pkg_paths = []
- pkgs = set()
- pkgs_not_found = set()
for pkg in node_pkg_paths:
if isinstance(pkg, NodesPackage):
pkgs.add(pkg)
@@ -189,8 +185,8 @@ def process_nodes_packages(
# `requested_nodes`.
# This check is done by comparing the path name to the nodes' names
args_pkgs_names = [pkg.name for pkg in requested_packages]
- pkgs_not_found = [
+ pkgs_not_found = set(
pkg_path for pkg_path in pkgs_not_found if pkg_path.name not in args_pkgs_names
- ]
+ )
return pkgs, pkgs_not_found, project_dict
diff --git a/ryven-editor/ryven/main/utils.py b/ryven-editor/ryven/main/utils.py
index 98fece78..0a1202f5 100644
--- a/ryven-editor/ryven/main/utils.py
+++ b/ryven-editor/ryven/main/utils.py
@@ -8,7 +8,7 @@
from os.path import normpath, join, dirname, abspath, expanduser
import pathlib
import importlib
-from typing import Union, Optional, Tuple
+from typing import Union, Optional, Tuple, List, Dict
from packaging.version import Version
from ryvencore import InfoMsgs
@@ -17,7 +17,7 @@ def in_gui_mode() -> bool:
return environ['RYVEN_MODE'] == 'gui'
-def load_from_file(file: str = None, components_list: [str] = None) -> Tuple:
+def load_from_file(file: str, components_list: Optional[List[str]] = None) -> Optional[Tuple]:
"""
Imports specified components from a python module with given file path.
The directory of the file is added to sys.path if not already present.
@@ -32,7 +32,7 @@ def load_from_file(file: str = None, components_list: [str] = None) -> Tuple:
# protection from re-loading for no reason
if name in sys.modules:
- return
+ return None
if parent_dirpath not in sys.path:
sys.path.append(parent_dirpath)
@@ -49,9 +49,10 @@ def load_from_file(file: str = None, components_list: [str] = None) -> Tuple:
f'or your package name conflicts with another python package name '
f'that the interpreter knows about (e.g. math).\n\n'
)
+ raise e
-def read_project(project_path: Union[str, pathlib.Path]) -> dict:
+def read_project(project_path: Union[str, pathlib.Path]) -> Dict:
"""Read the project file and return its dictionary.
:param project_path: The path to the project file.
@@ -78,19 +79,20 @@ def read_project(project_path: Union[str, pathlib.Path]) -> dict:
)
project_dict = translate_project_v3_2_0(project_dict)
+ assert isinstance(project_dict, Dict), 'Project file seems corrupted.'
return project_dict
-def translate_project_v3_2_0(p: dict):
- def max_gid(d: dict) -> int:
+def translate_project_v3_2_0(p: Dict):
+ def max_gid(d: Dict) -> int:
"""Recursively find the maximum GID used in the project.."""
n = 0
for k, v in d.items():
- if isinstance(v, dict):
+ if isinstance(v, Dict):
n = max(n, max_gid(v))
elif isinstance(v, list):
for e in v:
- if isinstance(e, dict):
+ if isinstance(e, Dict):
n = max(n, max_gid(e))
elif k == 'GID':
n = max(n, v)
@@ -105,7 +107,7 @@ def get_gid():
def replace_item(obj, key, replace_value):
# https://stackoverflow.com/questions/45335445/how-to-recursively-replace-dictionary-values-with-a-matching-key
for k, v in obj.items():
- if isinstance(v, dict):
+ if isinstance(v, Dict):
obj[k] = replace_item(v, key, replace_value)
if key in obj:
obj[key] = replace_value
diff --git a/ryven-editor/setup.cfg b/ryven-editor/setup.cfg
index c60d450f..0df4fa06 100644
--- a/ryven-editor/setup.cfg
+++ b/ryven-editor/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = ryven
-version = 3.4.3
+version = 3.5.0
author = Leon Thomm
author_email = l.thomm@mailbox.org
description = Flow-based visual scripting for Python
@@ -19,15 +19,21 @@ classifiers =
[options]
packages = find:
include_package_data = True
-python_requires = >=3.6, <3.11
+python_requires = >=3.6, <3.13
install_requires =
- ryvencore-qt ==0.4.*
- ryvencore ==0.4.*
+ ryvencore-qt ==0.5.*
+ ryvencore ==0.5.*
Jinja2
Pygments
textdistance
packaging
+[optionas.extras_require]
+PySide2 =
+ PySide2
+PySide6 =
+ PySide6 = <6.7
+
[options.entry_points]
console_scripts =
ryven = ryven:run_ryven
diff --git a/ryvencore-qt/pyproject.toml b/ryvencore-qt/pyproject.toml
deleted file mode 100644
index f8d89757..00000000
--- a/ryvencore-qt/pyproject.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-[build-system]
-requires = [
- "setuptools",
- "wheel"
-]
-build-backend = "setuptools.build_meta"
\ No newline at end of file
diff --git a/ryvencore-qt/ryvencore_qt/py.typed b/ryvencore-qt/ryvencore_qt/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/ryvencore-qt/ryvencore_qt/src/Design.py b/ryvencore-qt/ryvencore_qt/src/Design.py
index f2d10dcc..1eea9fcc 100644
--- a/ryvencore-qt/ryvencore_qt/src/Design.py
+++ b/ryvencore-qt/ryvencore_qt/src/Design.py
@@ -1,4 +1,5 @@
import json
+from typing import Optional, List, Tuple
from qtpy.QtCore import QObject, Signal
from qtpy.QtGui import QFontDatabase
@@ -17,22 +18,22 @@ class Design(QObject):
flow_theme_changed = Signal(str)
performance_mode_changed = Signal(str)
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
- self.flow_themes = flow_themes
- self.flow_theme: FlowTheme = None
- self.default_flow_size = None
- self.performance_mode: str = None
- self.node_item_shadows_enabled: bool = None
- self.animations_enabled: bool = None
- self.node_selection_stylesheet: str = None
+ self.flow_themes: List[FlowTheme] = flow_themes
+ self.flow_theme: FlowTheme
+ self.default_flow_size: Tuple[int, int]
+ self.performance_mode: str
+ self.node_item_shadows_enabled: bool
+ self.animations_enabled: bool
+ self.node_selection_stylesheet: str
# load standard default values
self._default_flow_theme = self.flow_themes[-1]
self.set_performance_mode('pretty')
self.set_animations_enabled(True)
- self.default_flow_size = [1000, 700]
+ self.default_flow_size = (1000, 700)
self.set_flow_theme(self._default_flow_theme)
@staticmethod
@@ -77,13 +78,13 @@ def flow_theme_by_name(self, name: str) -> FlowTheme:
for theme in self.flow_themes:
if theme.name.casefold() == name.casefold():
return theme
- return None
+ raise ValueError(f'Flow theme with name "{name}" not found')
- def set_flow_theme(self, theme: FlowTheme = None, name: str = ''):
+ def set_flow_theme(self, theme: Optional[FlowTheme] = None, name: Optional[str] = None):
"""You can either specify the theme by name, or directly provide a FlowTheme object"""
- if theme:
+ if theme is not None:
self.flow_theme = theme
- elif name and name != '':
+ elif name is not None and name != '':
self.flow_theme = self.flow_theme_by_name(name)
else:
return
@@ -93,9 +94,6 @@ def set_flow_theme(self, theme: FlowTheme = None, name: str = ''):
self.flow_theme_changed.emit(self.flow_theme.name)
def set_performance_mode(self, new_mode: str):
- if self.performance_mode == new_mode:
- return
-
if new_mode == 'fast':
self.node_item_shadows_enabled = False
else:
diff --git a/ryvencore-qt/ryvencore_qt/src/GUIBase.py b/ryvencore-qt/ryvencore_qt/src/GUIBase.py
index 0adbcf84..859f2446 100644
--- a/ryvencore-qt/ryvencore_qt/src/GUIBase.py
+++ b/ryvencore-qt/ryvencore_qt/src/GUIBase.py
@@ -1,5 +1,8 @@
+from typing import Any, Dict, Optional
+
from ryvencore.Base import Base
from qtpy.QtWidgets import QGraphicsObject, QGraphicsItem
+from qtpy.QtCore import QObject
class GUIBase:
@@ -8,7 +11,7 @@ class GUIBase:
# every frontend GUI object that represents some specific component from the core
# is stored there under the the global id of the represented component.
# used for completing data (serialization)
- FRONTEND_COMPONENT_ASSIGNMENTS = {} # component global id : GUI object
+ FRONTEND_COMPONENT_ASSIGNMENTS: Dict[int, Any] = {} # component global id : GUI object
@staticmethod
def get_complete_data_function(session):
@@ -43,7 +46,7 @@ def analyze(obj):
return analyze
- def __init__(self, representing_component: Base = None):
+ def __init__(self, representing_component: Optional[Base] = None):
"""parameter `representing` indicates representation of a specific core component"""
if representing_component is not None:
GUIBase.FRONTEND_COMPONENT_ASSIGNMENTS[representing_component.global_id] = self
diff --git a/ryvencore-qt/ryvencore_qt/src/GlobalAttributes.py b/ryvencore-qt/ryvencore_qt/src/GlobalAttributes.py
index 8df020f9..17b36b0d 100644
--- a/ryvencore-qt/ryvencore_qt/src/GlobalAttributes.py
+++ b/ryvencore-qt/ryvencore_qt/src/GlobalAttributes.py
@@ -1,2 +1,2 @@
class Location:
- PACKAGE_PATH = None
+ PACKAGE_PATH: str
diff --git a/ryvencore-qt/ryvencore_qt/src/SessionGUI.py b/ryvencore-qt/ryvencore_qt/src/SessionGUI.py
index 80f080ef..cfa3180a 100644
--- a/ryvencore-qt/ryvencore_qt/src/SessionGUI.py
+++ b/ryvencore-qt/ryvencore_qt/src/SessionGUI.py
@@ -1,4 +1,4 @@
-from typing import List
+from typing import List, Dict
from qtpy.QtCore import QObject, Signal, Qt
from qtpy.QtWidgets import QWidget, QApplication
@@ -36,7 +36,7 @@ def __init__(self, gui_parent: QWidget):
self.gui_parent = gui_parent
# flow views
- self.flow_views = {} # {Flow : FlowView}
+ self.flow_views: Dict[ryvencore.Flow, FlowView] = {}
# register complete_data function
ryvencore.set_complete_data_func(self.get_complete_data_function(self))
diff --git a/ryvencore-qt/ryvencore_qt/src/flows/FlowCommands.py b/ryvencore-qt/ryvencore_qt/src/flows/FlowCommands.py
index c68171cb..1b752250 100644
--- a/ryvencore-qt/ryvencore_qt/src/flows/FlowCommands.py
+++ b/ryvencore-qt/ryvencore_qt/src/flows/FlowCommands.py
@@ -2,6 +2,12 @@
This file contains the implementations of undoable actions for FlowView.
"""
+# prevent circular imports
+from __future__ import annotations
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+ from .FlowView import FlowView
+
from typing import Tuple
from qtpy.QtCore import QObject, QPointF
@@ -35,10 +41,10 @@ class FlowUndoCommand(QObject, QUndoCommand):
# prevent recursive calls of redo() and undo()
_any_cmd_active = False
- def __init__(self, flow_view):
- self.flow_view = flow_view
+ def __init__(self, flow_view: FlowView) -> None:
+ self.flow_view: FlowView = flow_view
self.flow: Flow = flow_view.flow
- self._activated = False
+ self._activated: bool = False
QObject.__init__(self)
QUndoCommand.__init__(self)
diff --git a/ryvencore-qt/ryvencore_qt/src/flows/FlowTheme.py b/ryvencore-qt/ryvencore_qt/src/flows/FlowTheme.py
index db7076c4..f5c1f57f 100644
--- a/ryvencore-qt/ryvencore_qt/src/flows/FlowTheme.py
+++ b/ryvencore-qt/ryvencore_qt/src/flows/FlowTheme.py
@@ -1,3 +1,5 @@
+from typing import Optional, Tuple, List
+
from qtpy.QtCore import Qt, QPointF, QPoint, QRectF, QMargins, QMarginsF
from qtpy.QtGui import QColor, QPainter, QBrush, QRadialGradient, QLinearGradient, QPen, QPainterPath, QFont, QPolygon
from qtpy.QtWidgets import QStyle, QStyleOption
@@ -38,12 +40,12 @@ class FlowTheme:
data_conn_pen_style = Qt.DashLine
flow_background_brush = QBrush(QColor('#333333'))
- flow_background_grid = None
+ flow_background_grid: Optional[Tuple[str, QColor, int, int, int]] = None
flow_highlight_pen_color = QColor('#245d75')
node_item_shadow_color = QColor('#2b2b2b')
- EXPORT = []
+ EXPORT: List[str] = []
def __init__(self):
pass
@@ -131,7 +133,7 @@ def paint_NI_selection_border(self, ni, painter: QPainter, color: QColor, w, h,
def paint_NI_title_label_default(painter: QPainter, node_style: str, title: str, color: QColor, pen_w: float,
font: QFont, node_item_bounding_rect):
pen = QPen(color)
- pen.setWidth(pen_w)
+ pen.setWidth(pen_w) # type: ignore
painter.setPen(pen)
painter.setFont(font)
@@ -198,7 +200,7 @@ def hex_to_col(hex_str: str) -> QColor:
)
return QColor(r, g, b, a)
- return None
+ raise ValueError(f'Invalid hex color string: {hex_str}')
@staticmethod
def col(c: QColor, alpha=255):
@@ -279,7 +281,11 @@ def draw_NI_normal(self, node_gui, selected: bool, hovered: bool,
painter: QPainter, c: QColor, w, h, bounding_rect, title_rect):
# main rect
- header_color = QColor(c.red() / 10 + 100, c.green() / 10 + 100, c.blue() / 10 + 100)
+ header_color = QColor(
+ int(c.red() / 10 + 100),
+ int(c.green() / 10 + 100),
+ int(c.blue() / 10 + 100),
+ )
if selected:
header_color = header_color.lighter()
body_gradient = QRadialGradient(bounding_rect.topLeft(), pythagoras(h, w))
@@ -1613,7 +1619,7 @@ def draw_NI_small(self, node_gui, selected: bool, hovered: bool,
painter.drawRoundedRect(bounding_rect, 4, 4)
-flow_themes = [
+flow_themes: List[FlowTheme] = [
FlowTheme_Toy(),
FlowTheme_DarkTron(),
FlowTheme_Ghost(),
diff --git a/ryvencore-qt/ryvencore_qt/src/flows/FlowView.py b/ryvencore-qt/ryvencore_qt/src/flows/FlowView.py
index 317f72cb..d2923f03 100644
--- a/ryvencore-qt/ryvencore_qt/src/flows/FlowView.py
+++ b/ryvencore-qt/ryvencore_qt/src/flows/FlowView.py
@@ -1,6 +1,12 @@
+# prevent circular imports
+from __future__ import annotations
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+ from ..SessionGUI import SessionGUI
+
import json
-from typing import Tuple
+from typing import Tuple, Optional
from qtpy.QtCore import (
Qt,
@@ -19,6 +25,8 @@
QColor,
QKeySequence,
QTabletEvent,
+ QDropEvent,
+ QContextMenuEvent,
QImage,
QGuiApplication,
QFont,
@@ -31,12 +39,17 @@
QShortcut,
QMenu,
QGraphicsItem,
- QUndoStack,
QPushButton,
QHBoxLayout,
QWidget,
)
+# for compatibility between qt5 and qt6
+try:
+ from qtpy.QtGui import QUndoStack
+except ImportError:
+ from qtpy.QtWidgets import QUndoStack # type: ignore
+
from ryvencore.Flow import Flow
from ryvencore.Node import Node
from ryvencore.NodePort import NodePort, NodeInput, NodeOutput
@@ -106,7 +119,7 @@ class FlowView(GUIBase, QGraphicsView):
viewport_update_mode_changed = Signal(str)
- def __init__(self, session_gui, flow, parent=None):
+ def __init__(self, session_gui: SessionGUI, flow: Flow, parent=None) -> None:
GUIBase.__init__(self, representing_component=flow)
QGraphicsView.__init__(self, parent=parent)
@@ -125,31 +138,31 @@ def __init__(self, session_gui, flow, parent=None):
self.design: Design = session_gui.design # type hinting and quicker access
self.flow: Flow = flow
- self.node_items: dict = {} # {Node: NodeItem}
- self.node_items__cache: dict = {}
- self.connection_items: dict = {} # {Connection: ConnectionItem}
- self.connection_items__cache: dict = {}
+ self.node_items: Dict = {} # {Node: NodeItem}
+ self.node_items__cache: Dict = {}
+ self.connection_items: Dict = {} # {Connection: ConnectionItem}
+ self.connection_items__cache: Dict = {}
self.selection_mode: _SelectionMode = _SelectionMode.UNDOABLE_CLICK
# PRIVATE FIELDS
- self._loaded_state = None # h and v scrollbars are changed on import, so we need to defer
+ self._loaded_state: Optional[Dict] = None # h and v scrollbars are changed on import, so we need to defer
self._tmp_data = None
- self._selected_pin: PortItemPin = None
+ self._selected_pin: Optional[PortItemPin] = None
self._dragging_connection = False
- self._temp_connection_ports = None
+ self._temp_connection_ports: Optional[Tuple[NodeOutput, NodeInput]] = None
self._waiting_for_connection_request: bool = False
self.mouse_event_taken = False # for stylus - see tablet event
- self._last_mouse_move_pos: QPointF = None
+ self._last_mouse_move_pos: Optional[QPointF] = None
self._node_place_pos = QPointF()
self._left_mouse_pressed_in_flow = False
self._right_mouse_pressed_in_flow = False
- self._mouse_press_pos: QPointF = None
+ self._mouse_press_pos: Optional[QPointF] = None
self._multi_selection = False
- self._current_selected = []
- self._auto_connection_pin = None # stores the gate that we may try to auto connect to a newly placed NI
+ self._current_selected: List[QGraphicsItem] = []
+ self._auto_connection_pin: Optional[PortItemPin] = None # stores the gate that we may try to auto connect to a newly placed NI
self._panning = False
- self._pan_last_x = None
- self._pan_last_y = None
+ self._pan_last_x: Optional[int] = None
+ self._pan_last_y: Optional[int] = None
self._current_scale = 1
self._total_scale_div = 1
self._zoom_data = {
@@ -234,7 +247,7 @@ def init_proxy_widget(widget: QWidget, proxy: FlowViewProxyWidget):
self.stylus_mode = ''
self._current_drawing = None
self._drawing = False
- self.drawings = []
+ self.drawings: List[DrawingObject] = []
self._stylus_modes_widget = FlowViewStylusModesWidget(self)
self._stylus_modes_proxy = init_proxy_widget(
self._stylus_modes_widget, FlowViewProxyWidget(self)
@@ -254,13 +267,11 @@ def init_proxy_widget(widget: QWidget, proxy: FlowViewProxyWidget):
menu_layout_widget.layout().addWidget(menu_button)
def menu_button_clicked():
- point = self._menu_layout_proxy.scenePos()
- view_pos = self.mapFromScene(point.toPoint())
- # apply offset after
- global_pos = self.viewport().mapToGlobal(
- view_pos) + QPoint(8, self._menu_layout_proxy.widget().height()
- )
- self._menu.exec_(global_pos)
+ # prob not entirely correct, since menu is part of a layout
+ # but since it's the first item, it's the same
+ menu_pos = self._menu_button.pos()
+ menu_pos = self.mapToGlobal(menu_pos) + QPoint(8, self._menu_button.height() + 10)
+ self._menu.exec_(menu_pos)
menu_button.clicked.connect(menu_button_clicked)
@@ -506,13 +517,15 @@ def wheelEvent(self, event):
if event.modifiers() & Qt.ControlModifier:
event.accept()
- self._zoom_data['viewport pos'] = event.posF()
- self._zoom_data['scene pos'] = pointF_mapped(self.mapToScene(event.pos()), event.posF())
+ view_pos = event.position()
+ self._zoom_data['viewport pos'] = view_pos
+ self._zoom_data['scene pos'] = self.mapToScene(view_pos.toPoint())
- self._zoom_data['delta'] += event.delta()
+ y_delta = event.angleDelta().y()
+ self._zoom_data['delta'] += y_delta
- if self._zoom_data['delta'] * event.delta() < 0:
- self._zoom_data['delta'] = event.delta()
+ if self._zoom_data['delta'] * y_delta < 0:
+ self._zoom_data['delta'] = y_delta
anim = QTimeLine(100, self)
anim.setUpdateInterval(10)
@@ -538,7 +551,6 @@ def viewportEvent(self, event: QEvent) -> bool:
return True
elif event.type() == QEvent.TouchUpdate:
- event: QTouchEvent
if len(event.touchPoints()) == 2:
tp0, tp1 = event.touchPoints()[0], event.touchPoints()[1]
@@ -569,7 +581,7 @@ def viewportEvent(self, event: QEvent) -> bool:
else:
return super().viewportEvent(event)
- def tabletEvent(self, event):
+ def tabletEvent(self, event: QTabletEvent) -> None:
"""tabletEvent gets called by stylus operations.
LeftButton: std, no button pressed
RightButton: upper button pressed"""
@@ -582,7 +594,9 @@ def tabletEvent(self, event):
):
return # let the mousePress/Move/Release-Events handle it
- scaled_event_pos: QPointF = event.posF() / self._current_scale
+ scaled_event_pos: QPointF = event.posF()
+ scaled_event_pos /= self._current_scale # type: ignore
+ # mypy thinks the above results in a float, but it doesn't
if event.type() == QTabletEvent.TabletPress:
self.mouse_event_taken = True
@@ -616,6 +630,7 @@ def tabletEvent(self, event):
self.remove_drawing(i)
break
elif self.stylus_mode == 'comment' and self._drawing:
+ assert self._current_drawing is not None
if self._current_drawing.append_point(scaled_event_pos):
self._current_drawing.stroke_weights.append(
event.pressure() * self._stylus_modes_widget.pen_width()
@@ -627,6 +642,7 @@ def tabletEvent(self, event):
if self._panning:
self._panning = False
if self.stylus_mode == 'comment' and self._drawing:
+ assert self._current_drawing is not None
self._current_drawing.finish()
InfoMsgs.write('drawing finished')
self._current_drawing = None
@@ -656,16 +672,17 @@ def dragMoveEvent(self, event):
if event.mimeData().hasFormat('application/json'):
event.acceptProposedAction()
- def dropEvent(self, event):
+ def dropEvent(self, event: QDropEvent):
try:
text = str(event.mimeData().data('application/json'), 'utf-8')
- data: dict = json.loads(text)
+ data: Dict = json.loads(text)
if data['type'] == 'node':
self._node_place_pos = self.mapToScene(event.pos())
self.create_node__cmd(
node_from_identifier(
- data['node identifier'], self.session_gui.core_session.nodes
+ data['node identifier'],
+ list(self.session_gui.core_session.nodes)
)
)
# without this keyPressed function isn't called if we don't click in the view
@@ -673,7 +690,7 @@ def dropEvent(self, event):
except Exception:
pass
- def contextMenuEvent(self, event):
+ def contextMenuEvent(self, event: QContextMenuEvent):
QGraphicsView.contextMenuEvent(self, event)
# in the case of the menu already being shown by a widget under the mouse, the event is accepted here
if event.isAccepted():
@@ -771,9 +788,11 @@ def get_viewport_img(self) -> QImage:
self.hide_proxies()
img = QImage(
- self.viewport().rect().width(),
- self.viewport().height(),
- QImage.Format_ARGB32,
+ size=QSizeF(
+ self.viewport().rect().width(),
+ self.viewport().height(),
+ ),
+ format=QImage.Format_ARGB32,
)
img.fill(Qt.transparent)
@@ -790,9 +809,11 @@ def get_whole_scene_img(self) -> QImage:
self.hide_proxies()
img = QImage(
- self.sceneRect().width() / self._total_scale_div,
- self.sceneRect().height() / self._total_scale_div,
- QImage.Format_RGB32,
+ size=QSizeF(
+ self.sceneRect().width() / self._total_scale_div,
+ self.sceneRect().height() / self._total_scale_div,
+ ),
+ format=QImage.Format_RGB32,
)
img.fill(Qt.transparent)
@@ -963,9 +984,9 @@ def zoom(self, p_abs, p_mapped, angle):
def create_node__cmd(self, node_class):
self.push_undo(PlaceNode_Command(self, node_class, self._node_place_pos))
- def add_node(self, node):
+ def add_node(self, node: Node):
# create item
- item: NodeItem = None
+ item: NodeItem
if node in self.node_items__cache.keys(): # load from cache
# print('using a cached item')
@@ -1042,16 +1063,20 @@ def connection_request_valid(self, valid: bool):
Triggered after the abstract flow evaluated validity of pending connect request.
This can also lead to a disconnect!
"""
+
+ # TODO: this stuff is too complicated, simplify
if self._waiting_for_connection_request:
self._waiting_for_connection_request = False
else:
return
+ assert self._temp_connection_ports is not None
+
if valid:
+ out: NodeOutput
+ inp: NodeInput
out, inp = self._temp_connection_ports
- if out.io_pos == PortObjPos.INPUT:
- out, inp = inp, out
if self.flow.graph_adj_rev[inp] not in (None, out): # out connected to something else
# remove existing connection
@@ -1071,10 +1096,9 @@ def add_connection(self, c: Tuple[NodeOutput, NodeInput]):
out, inp = c
# TODO: need to verify that connection_items_cache still works fine with new connection object
- item: ConnectionItem = None
+ item: ConnectionItem
if c in self.connection_items__cache.keys():
item = self.connection_items__cache[c]
-
else:
if inp.type_ == 'data':
# item = self.CLASSES['data conn item'](c, self.session.design)
@@ -1141,7 +1165,7 @@ def create_drawing(self, data=None) -> DrawingObject:
new_drawing = DrawingObject(self, data)
return new_drawing
- def add_drawing(self, drawing_obj, posF=None):
+ def add_drawing(self, drawing_obj: DrawingObject, posF=None):
"""Adds a DrawingObject to the scene."""
self._set_selection_mode(_SelectionMode.INSTANT)
@@ -1191,7 +1215,7 @@ def add_component(self, e: QGraphicsItem):
elif isinstance(e, DrawingObject):
self.add_drawing(e)
- def remove_components(self, comps: [QGraphicsItem]):
+ def remove_components(self, comps: List[QGraphicsItem]):
for c in comps:
self.remove_component(c)
@@ -1285,18 +1309,18 @@ def _select__cmd(self):
SelectComponents_Command(self, items, self._current_selected)
)
- def selected_node_items(self, item_list: list = None) -> [NodeItem]:
+ def selected_node_items(self, item_list: Optional[List[NodeItem]] = None) -> List[NodeItem]:
"""Returns a list of the currently selected NodeItems."""
- search_list = item_list if item_list else self.scene().selectedItems()
+ search_list = item_list if item_list is not None else self.scene().selectedItems()
return [node_item for node_item in search_list if isinstance(node_item, NodeItem)]
- def selected_nodes(self, item_list: list = None) -> [Node]:
+ def selected_nodes(self, item_list: Optional[List[NodeItem]] = None) -> List[Node]:
"""Returns a list of the currently selected nodes."""
return [node_item.node for node_item in self.selected_node_items(item_list)]
- def selected_drawings(self) -> [DrawingObject]:
+ def selected_drawings(self) -> List[DrawingObject]:
"""Returns a list of the currently selected drawings."""
return [
@@ -1398,7 +1422,7 @@ def _paste(self): # ctrl+v
self.push_undo(Paste_Command(self, data, offset_for_middle_pos))
# DATA
- def complete_data(self, data: dict):
+ def complete_data(self, data: Dict):
data['flow view'] = {
'drawings': self._get_drawings_data(self.drawings),
'view size': [
@@ -1446,7 +1470,7 @@ def save_state(self) -> dict:
'h_scroll': self.verticalScrollBar().value(),
}
- def load(self, state: dict):
+ def load(self, state: Dict):
"""Load the state of the view"""
transform = QTransform(
state['m11'], state['m12'], state['m13'],
@@ -1461,6 +1485,6 @@ def load(self, state: dict):
self._loaded_state = state
def reload(self):
- if self._loaded_state:
+ if self._loaded_state is not None:
self.load(self._loaded_state)
\ No newline at end of file
diff --git a/ryvencore-qt/ryvencore_qt/src/flows/FlowViewProxyWidget.py b/ryvencore-qt/ryvencore_qt/src/flows/FlowViewProxyWidget.py
index 7c5fa4a9..1c1f6c68 100644
--- a/ryvencore-qt/ryvencore_qt/src/flows/FlowViewProxyWidget.py
+++ b/ryvencore-qt/ryvencore_qt/src/flows/FlowViewProxyWidget.py
@@ -1,3 +1,9 @@
+# prevent circular imports
+from __future__ import annotations
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+ from .FlowView import FlowView
+
from qtpy.QtWidgets import QGraphicsProxyWidget, QGraphicsSceneHoverEvent
@@ -30,9 +36,9 @@ def keyPressEvent(self, arg__1):
class FlowViewProxyHoverWidget(FlowViewProxyWidget):
"""Additional hover controls for QProxyWidgets in the flow."""
- def __init__(self, flow_view, parent=None):
+ def __init__(self, flow_view: FlowView, parent=None):
super(FlowViewProxyHoverWidget, self).__init__(flow_view, parent)
- self._is_hovered = False
+ self._is_hovered: bool = False
def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
super().hoverEnterEvent(event)
diff --git a/ryvencore-qt/ryvencore_qt/src/flows/connections/ConnectionAnimation.py b/ryvencore-qt/ryvencore_qt/src/flows/connections/ConnectionAnimation.py
index 2d9c7047..bf26f1c7 100644
--- a/ryvencore-qt/ryvencore_qt/src/flows/connections/ConnectionAnimation.py
+++ b/ryvencore-qt/ryvencore_qt/src/flows/connections/ConnectionAnimation.py
@@ -1,10 +1,11 @@
-from typing import List
+from typing import List, Tuple
from enum import Enum
from qtpy.QtCore import (
QTimeLine,
QPropertyAnimation,
- QParallelAnimationGroup,
+ QParallelAnimationGroup,
+ QEasingCurve,
)
from qtpy.QtGui import (
QPen,
@@ -42,13 +43,14 @@ def __init__(
self.connection = connection
self.between = between
self.speed = speed
- self.visible_items = []
+ self.visible_items: List[Tuple[QGraphicsItemAnimated, float]] = []
+ self.__visible_flag = True
self.setParentItem(self.connection)
self.timeline = QTimeLine()
self.timeline.setFrameRange(0, frames)
- self.timeline.setCurveShape(QTimeLine.LinearCurve)
+ self.timeline.setEasingCurve(QEasingCurve(QEasingCurve.Type.Linear))
self.timeline.valueChanged.connect(self._update_items)
self.timeline.setLoopCount(0)
@@ -88,11 +90,16 @@ def stop(self, recompute: bool = True):
self.recompute()
def recompute(self):
- for item in self.items:
- item.setVisible(False)
+
+ if self.__visible_flag:
+ for item in self.items:
+ item.setVisible(False)
+ self.__visible_flag = False
if self.timeline.state() == QTimeLine.State.NotRunning:
return
+
+ self.__visible_flag = True
path_len = self.connection.path().length()
num_points = max(3, min(self.connection.num_dots, int(path_len / self.between)))
@@ -152,17 +159,27 @@ def __init__(
for item in self.con_items_anim.items:
item.setScale(0)
# to scaler
- to_scalar_anim = QPropertyAnimation(item, b'scale')
+ to_scalar_anim = QPropertyAnimation(item, b'scale') # type: ignore
to_scalar_anim.setDuration(self.duration)
self.to_scalar_group.addAnimation(to_scalar_anim)
# to zero
- to_zero_anim = QPropertyAnimation(item, b'scale')
+ to_zero_anim = QPropertyAnimation(item, b'scale') # type: ignore
to_zero_anim.setDuration(self.duration)
self.to_zero_group.addAnimation(to_zero_anim)
self.to_scalar_group.finished.connect(self._on_scalar_ended)
self.to_zero_group.finished.connect(self._on_zero_ended)
-
+
+ def start(self):
+ if (self.state == ConnPathItemsAnimationScaled.State.NOT_RUNNING or
+ self.state == ConnPathItemsAnimationScaled.State.TO_ZERO):
+ self.toggle()
+
+ def stop(self):
+ if (self.state == ConnPathItemsAnimationScaled.State.RUNNING or
+ self.state == ConnPathItemsAnimationScaled.State.TO_SCALE):
+ self.toggle()
+
def toggle(self):
if self.state == ConnPathItemsAnimationScaled.State.NOT_RUNNING:
self._run_scalar()
@@ -180,7 +197,7 @@ def toggle(self):
self._run_scalar()
self.state = ConnPathItemsAnimationScaled.State.TO_SCALE
- def _stop(self):
+ def force_stop(self):
self.to_zero_group.stop()
self.to_scalar_group.stop()
@@ -203,4 +220,4 @@ def _on_scalar_ended(self):
def _on_zero_ended(self):
if self.state == ConnPathItemsAnimationScaled.State.TO_ZERO:
self.con_items_anim.stop()
- self.state = ConnPathItemsAnimationScaled.State.NOT_RUNNING
+ self.state = ConnPathItemsAnimationScaled.State.NOT_RUNNING
\ No newline at end of file
diff --git a/ryvencore-qt/ryvencore_qt/src/flows/connections/ConnectionItem.py b/ryvencore-qt/ryvencore_qt/src/flows/connections/ConnectionItem.py
index 57927d85..8aba449e 100644
--- a/ryvencore-qt/ryvencore_qt/src/flows/connections/ConnectionItem.py
+++ b/ryvencore-qt/ryvencore_qt/src/flows/connections/ConnectionItem.py
@@ -1,5 +1,5 @@
# import math
-from typing import List
+from typing import List, Any, Tuple
from qtpy.QtCore import QPointF, Qt
from qtpy.QtGui import QPainter, QColor, QRadialGradient, QPainterPath, QPen
from qtpy.QtWidgets import (
@@ -15,13 +15,16 @@
from ...flows.nodes.PortItem import PortItem
from .ConnectionAnimation import ConnPathItemsAnimation, ConnPathItemsAnimationScaled
+from ryvencore_qt.src.Design import Design
+from ryvencore.NodePort import NodeInput, NodeOutput
+
class ConnectionItem(GUIBase, QGraphicsPathItem):
"""The GUI representative for a connection. The classes ExecConnectionItem and DataConnectionItem will be ready
for reimplementation later, so users can add GUI for the enhancements of DataConnection and ExecConnection,
like input fields for weights."""
- def __init__(self, connection, session_design):
+ def __init__(self, connection: Tuple[NodeOutput, NodeInput], session_design: Design):
QGraphicsPathItem.__init__(self)
self.setAcceptHoverEvents(True)
@@ -31,6 +34,7 @@ def __init__(self, connection, session_design):
out_port_index = out.node.outputs.index(out)
inp_port_index = inp.node.inputs.index(inp)
+ assert hasattr(out.node, 'gui') and hasattr(inp.node, 'gui')
self.out_item: PortItem = out.node.gui.item.outputs[out_port_index]
self.inp_item: PortItem = inp.node.gui.item.inputs[inp_port_index]
@@ -66,14 +70,14 @@ def __str__(self):
node_out_index = out.node.outputs.index(out)
return f'{node_out_index}->{node_in_index} ({node_out_name}, {node_in_name})'
- def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget):
+ def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget = None):
# draw path
painter.setBrush(self.brush())
painter.setPen(self.pen())
painter.drawPath(self.path())
# return super().paint(painter, option, widget)
- def recompute(self):
+ def recompute(self) -> None:
"""Updates scene position and recomputes path, pen, gradient and dots"""
# dots
@@ -83,12 +87,12 @@ def recompute(self):
self.setPos(self.out_pos())
# path
- p1 = QPointF(self.out_item.pin.width_no_padding() * 0.5, 0)
- p2 = self.inp_pos() - self.scenePos() - QPointF(self.inp_item.pin.width_no_padding() * 0.5, 0)
+ p1: QPointF = QPointF(self.out_item.pin.width_no_padding() * 0.5, 0)
+ p2: QPointF = self.inp_pos() - self.scenePos() - QPointF(self.inp_item.pin.width_no_padding() * 0.5, 0)
self.setPath(self.connection_path(p1, p2))
# pen
- pen = self.get_pen()
+ pen: QPen = self.get_pen()
# brush
self.setBrush(Qt.NoBrush)
@@ -103,9 +107,9 @@ def recompute(self):
pythagoras(w, h) / 2
)
- c_r = c.red()
- c_g = c.green()
- c_b = c.blue()
+ c_r: int = c.red()
+ c_g: int = c.green()
+ c_b: int = c.blue()
# this offset will be 1 if inp.x >> out.x and 0 if inp.x < out.x
# hence, no fade for the gradient if the connection goes backwards
@@ -163,13 +167,13 @@ def set_highlighted(self, b: bool):
self.setPen(pen)
def get_pen(self) -> QPen:
- pass
+ raise NotImplementedError
def pen_width(self) -> int:
- pass
+ raise NotImplementedError
def get_style_color(self):
- pass
+ raise NotImplementedError
def flow_theme(self):
return self.session_design.flow_theme
@@ -233,7 +237,7 @@ def get_style_color(self):
return self.flow_theme().data_conn_color
-def default_cubic_connection_path(p1: QPointF, p2: QPointF):
+def default_cubic_connection_path(p1: QPointF, p2: QPointF) -> QPainterPath:
"""Returns the nice looking QPainterPath from p1 to p2"""
path = QPainterPath()
diff --git a/ryvencore-qt/ryvencore_qt/src/flows/drawings/DrawingObject.py b/ryvencore-qt/ryvencore_qt/src/flows/drawings/DrawingObject.py
index 46b26af6..2b623529 100644
--- a/ryvencore-qt/ryvencore_qt/src/flows/drawings/DrawingObject.py
+++ b/ryvencore-qt/ryvencore_qt/src/flows/drawings/DrawingObject.py
@@ -1,3 +1,11 @@
+# prevent circular imports
+from __future__ import annotations
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+ from ..FlowView import FlowView
+
+from typing import Dict, Optional
+
from qtpy.QtWidgets import QGraphicsItem
from qtpy.QtGui import QPen, QPainter, QColor, QPainterPath
from qtpy.QtCore import Qt, QRectF, QPointF, QLineF
@@ -6,7 +14,7 @@
class DrawingObject(QGraphicsItem):
"""GUI implementation for 'drawing objects' in the scene, written by hand using a stylus pen"""
- def __init__(self, flow_view, load_data=None):
+ def __init__(self, flow_view: FlowView, load_data: Optional[Dict] = None):
super(DrawingObject, self).__init__()
self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable |
@@ -17,42 +25,46 @@ def __init__(self, flow_view, load_data=None):
self.flow_view = flow_view
self.color = None
self.base_stroke_weight = None
- self.type = 'pen' # so far the only available, but I already save it so I could add more types in the future
+ self.type_ = 'pen' # so far the only available, but I already save it so I could add more types in the future
self.points = []
self.stroke_weights = []
self.pen_stroke_weight = 0 # approx. avg of self.stroke_weights
self.rect = None
- self.path: QPainterPath = None
+ self.path: Optional[QPainterPath] = None
self.width = -1
self.height = -1
self.finished = False
# viewport_pos enables global floating points for precise pen positions
- self.viewport_pos: QPointF = load_data['viewport pos'] if 'viewport pos' in load_data else None
+ self.viewport_pos: Optional[QPointF] = None
# if the drawing gets loaded, its correct global floating pos is already correct (gets set by flow then)
self.movement_state = None # ugly - should get replaced later, see NodeItem, same issue
self.movement_pos_from = None
- if 'points' in load_data:
- p_c = load_data['points']
- for p in p_c:
- if type(p) == list:
- x = p[0]
- y = p[1]
- w = p[2]
- self.points.append(QPointF(x, y))
- self.stroke_weights.append(w)
- elif type(p) == dict: # backwards compatibility
- x = p['x']
- y = p['y']
- w = p['w']
- self.points.append(QPointF(x, y))
- self.stroke_weights.append(w)
- self.finished = True
-
- self.color = QColor(load_data['color'])
- self.base_stroke_weight = load_data['base stroke weight']
+ if load_data is not None:
+ if 'viewport pos' in load_data:
+ self.viewport_pos = load_data['viewport pos']
+
+ if 'points' in load_data:
+ p_c = load_data['points']
+ for p in p_c:
+ if type(p) == list:
+ x = p[0]
+ y = p[1]
+ w = p[2]
+ self.points.append(QPointF(x, y))
+ self.stroke_weights.append(w)
+ elif type(p) == dict: # backwards compatibility
+ x = p['x']
+ y = p['y']
+ w = p['w']
+ self.points.append(QPointF(x, y))
+ self.stroke_weights.append(w)
+ self.finished = True
+
+ self.color = QColor(load_data['color'])
+ self.base_stroke_weight = load_data['base stroke weight']
def __str__(self):
return generate_name(self, 'Drawing')
@@ -188,7 +200,7 @@ def data_(self):
'pos x': self.pos().x(),
'pos y': self.pos().y(),
'color': self.color.name(),
- 'type': self.type,
+ 'type': self.type_,
'base stroke weight': self.base_stroke_weight
}
points_list = []
diff --git a/ryvencore-qt/ryvencore_qt/src/flows/node_list_widget/NodeListWidget.py b/ryvencore-qt/ryvencore_qt/src/flows/node_list_widget/NodeListWidget.py
index 0d573fb1..35a41367 100644
--- a/ryvencore-qt/ryvencore_qt/src/flows/node_list_widget/NodeListWidget.py
+++ b/ryvencore-qt/ryvencore_qt/src/flows/node_list_widget/NodeListWidget.py
@@ -17,8 +17,8 @@
from .utils import search, sort_nodes, inc, dec
from ..node_list_widget.NodeWidget import NodeWidget
from statistics import median
-from typing import List
from re import escape
+from typing import Dict, Type, List
# from ryven import NodesPackage
@@ -48,13 +48,13 @@ def __init__(self, session, show_packages: bool = False):
super().__init__()
self.session = session
- self.nodes: list = [] # should be list[type[Node]] in 3.9+
- self.package_nodes: list = [] # should be list[type[Node]] in 3.9+
+ self.nodes: List[Type[Node]] = []
+ self.package_nodes: List[Type[Node]] = []
- self.current_nodes = [] # currently selectable nodes
+ self.current_nodes: List[Node] = [] # currently selectable nodes
self.active_node_widget_index = -1 # index of focused node widget
self.active_node_widget = None # focused node widget
- self.node_widgets = {} # Node-NodeWidget assignments
+ self.node_widgets: Dict[Node, NodeWidget] = {} # Node-NodeWidget assignments
self._node_widget_index_counter = 0
# holds the path to the tree item
@@ -63,7 +63,7 @@ def __init__(self, session, show_packages: bool = False):
self.show_packages: bool = show_packages
self._setup_UI()
- def _setup_UI(self):
+ def _setup_UI(self) -> None:
self.main_layout = QVBoxLayout(self)
self.main_layout.setAlignment(Qt.AlignTop)
self.setLayout(self.main_layout)
@@ -151,10 +151,10 @@ def search_pkg_tree(self, search: str):
# removes whitespace and escapes all special regex chars
new_search = escape(search.strip())
# regex that enforces the text starts with
- self.pack_proxy_model.setFilterRegExp(f'^{new_search}')
+ self.pack_proxy_model.setFilterRegularExpression(f'^{new_search}')
self.pack_tree.expandAll()
else:
- self.pack_proxy_model.setFilterRegExp('')
+ self.pack_proxy_model.setFilterRegularExpression('')
self.pack_tree.collapseAll()
def make_nodes_current(self, pack_nodes, pkg_name: str):
@@ -167,7 +167,7 @@ def select_nodes():
return select_nodes
- def make_pack_hier(self):
+ def make_pack_hier(self) -> None:
"""
Creates a hierarchical view of the packages based on the nodes' identifier.
"""
@@ -201,7 +201,7 @@ def make_pack_hier(self):
item.setDragEnabled(False)
self.tree_items.append(item)
item.setEditable(False)
- node_list = []
+ node_list: List[Type[Node]] = []
h_dict[current_path] = (item, node_list)
item.setData(self.make_nodes_current(node_list, current_path), Qt.UserRole + 1)
current_root.appendRow(item)
diff --git a/ryvencore-qt/ryvencore_qt/src/flows/node_list_widget/NodeWidget.py b/ryvencore-qt/ryvencore_qt/src/flows/node_list_widget/NodeWidget.py
index dea94391..365c77d0 100644
--- a/ryvencore-qt/ryvencore_qt/src/flows/node_list_widget/NodeWidget.py
+++ b/ryvencore-qt/ryvencore_qt/src/flows/node_list_widget/NodeWidget.py
@@ -2,7 +2,7 @@
from qtpy.QtWidgets import QLineEdit, QWidget, QLabel, QGridLayout, QHBoxLayout, QSpacerItem, QSizePolicy, QStyleOption, QStyle
from qtpy.QtGui import QFont, QPainter, QColor, QDrag
-from qtpy.QtCore import Signal, Qt, QMimeData
+from qtpy.QtCore import Signal, Qt, QMimeData, QByteArray
class NodeWidget(QWidget):
@@ -13,12 +13,12 @@ class NodeWidget(QWidget):
@staticmethod
def _create_mime_data(node) -> QMimeData:
mime_data = QMimeData()
- mime_data.setData('application/json', bytes(json.dumps(
+ mime_data.setData('application/json', QByteArray(bytes(json.dumps(
{
'type': 'node',
'node identifier': node.identifier,
}
- ), encoding='utf-8'))
+ ), encoding='utf-8')))
return mime_data
def __init__(self, parent, node):
diff --git a/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeGUI.py b/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeGUI.py
index 94a51ef5..04c30bc8 100644
--- a/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeGUI.py
+++ b/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeGUI.py
@@ -1,5 +1,5 @@
from queue import Queue
-from typing import List, Dict, Tuple, Optional, Union
+from typing import List, Dict, Tuple, Optional, Union, Type
from qtpy.QtCore import QObject, Signal
@@ -13,17 +13,17 @@ class NodeGUI(QObject):
"""
# customizable gui attributes
- description_html: str = None
- main_widget_class: Optional[NodeMainWidget] = None
+ description_html: Optional[str] = None
+ main_widget_class: Optional[Type[NodeMainWidget]] = None
main_widget_pos: str = 'below ports'
- input_widget_classes: Dict[str, NodeInputWidget] = {}
- inspector_widget_class: NodeInspectorWidget = NodeInspectorDefaultWidget
+ input_widget_classes: Dict[str, Type[NodeInputWidget]] = {}
+ inspector_widget_class: Type[NodeInspectorWidget] = NodeInspectorDefaultWidget
wrap_inspector_in_default: bool = False
init_input_widgets: dict = {}
style: str = 'normal'
color: str = '#c69a15'
- display_title: str = None
- icon: str = None
+ display_title: Optional[str] = None
+ icon: Optional[str] = None
# qt signals
updating = Signal()
diff --git a/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeInspector.py b/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeInspector.py
index dafa9764..2b9b50db 100644
--- a/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeInspector.py
+++ b/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeInspector.py
@@ -1,12 +1,14 @@
-from typing import Union, Type, List, Optional, Tuple
+from typing import Union, Type, List, Optional, Tuple, TypeVar
from qtpy.QtWidgets import (
QWidget,
QVBoxLayout,
QLabel,
- QTextEdit,
+ QTextEdit,
+ QSplitter,
)
+from qtpy.QtCore import Qt
from ryvencore import Node
from .WidgetBaseClasses import NodeInspectorWidget
@@ -17,10 +19,10 @@ class InspectorView(QWidget):
A widget that can display the inspector of the currently selected node.
"""
- def __init__(self, flow_view, parent: QWidget = None):
+ def __init__(self, flow_view, parent: Optional[QWidget] = None):
super().__init__(parent=parent)
- self.node: Node = None
- self.inspector_widget: NodeInspectorWidget = None
+ self.node: Optional[Node] = None
+ self.inspector_widget: Optional[NodeInspectorWidget] = None
self.flow_view = flow_view
self.setup_ui()
@@ -35,26 +37,29 @@ def set_selected_nodes(self, nodes: List[Node]):
else:
self.set_node(nodes[-1])
- def set_node(self, node: Node):
+ def set_node(self, node: Optional[Node]):
"""Sets a node for inspection, if it exists. Otherwise clears the inspector"""
if self.node == node:
return
- if self.inspector_widget:
- self.inspector_widget.unload()
+ if self.inspector_widget is not None:
+ assert isinstance(self.inspector_widget, QWidget)
self.inspector_widget.setVisible(False)
- self.inspector_widget.setParent(None)
+ self.inspector_widget.setParent(None) # type: ignore
+ self.inspector_widget.unload()
self.node = None
self.inspector_widget = None
if node is not None:
self.node = node
+ assert hasattr(self.node, 'gui')
self.inspector_widget = self.node.gui.inspector_widget
+ assert isinstance(self.inspector_widget, QWidget)
self.layout().addWidget(self.inspector_widget)
- self.inspector_widget.setVisible(True)
self.inspector_widget.load()
+ self.inspector_widget.setVisible(True)
class NodeInspectorDefaultWidget(NodeInspectorWidget, QWidget):
@@ -71,6 +76,7 @@ def __init__(self, params, child: Optional[NodeInspectorWidget] = None):
QWidget.__init__(self)
NodeInspectorWidget.__init__(self, params)
+ self.child = child
self.setLayout(QVBoxLayout())
self.title_label: QLabel = QLabel()
@@ -78,10 +84,17 @@ def __init__(self, params, child: Optional[NodeInspectorWidget] = None):
f'{self.node.title}
'
f'id: {self.node.global_id}, pyid: {id(self.node)}
'
)
+ # title
self.layout().addWidget(self.title_label)
-
- if child:
- self.layout().addWidget(child)
+
+ # content splitter
+ self.content_splitter = QSplitter()
+ self.content_splitter.setOrientation(Qt.Orientation.Vertical)
+ self.layout().addWidget(self.content_splitter)
+
+ if child is not None:
+ assert isinstance(child, QWidget)
+ self.content_splitter.addWidget(child)
desc = self.node.__doc__ if self.node.__doc__ and self.node.__doc__ != "" else "No description given"
bbt = NodeInspectorDefaultWidget._big_bold_text
@@ -99,4 +112,14 @@ def __init__(self, params, child: Optional[NodeInspectorWidget] = None):