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": "gASVLAoAAAAAAAB9lIwEZGF0YZRdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxNPgxNPwxNQAxNQQxNQgxNQwxNRAxNRQxNRgxNRwxNSAxNSQxNSgxNSwxNTAxNTQxNTgxNTwxNUAxNUQxNUgxNUwxNVAxNVQxNVgxNVwxNWAxNWQxNWgxNWwxNXAxNXQxNXgxNXwxNYAxNYQxNYgxNYwxNZAxNZQxNZgxNZwxNaAxNaQxNagxNawxNbAxNbQxNbgxNbwxNcAxNcQxNcgxNcwxNdAxNdQxNdgxNdwxNeAxNeQxNegxNewxNfAxNfQxNfgxNfwxNgAxNgQxNggxNgwxNhAxNhQxNhgxNhwxNiAxNiQxNigxNiwxNjAxNjQxNjgxNjwxNkAxNkQxNkgxNkwxNlAxNlQxNlgxNlwxNmAxNmQxNmgxNmwxNnAxNnQxNngxNnwxNoAxNoQxNogxNowxNpAxNpQxNpgxNpwxNqAxNqQxNqgxNqwxNrAxNrQxNrgxNrwxNsAxNsQxNsgxNswxNtAxNtQxNtgxNtwxNuAxNuQxNugxNuwxNvAxNvQxNvgxNvwxNwAxNwQxNwgxNwwxNxAxNxQxNxgxNxwxNyAxNyQxNygxNywxNzAxNzQxNzgxNzwxN0AxN0QxN0gxN0wxN1AxN1QxN1gxN1wxN2AxN2QxN2gxN2wxN3AxN3QxN3gxN3wxN4AxN4QxN4gxN4wxN5AxN5QxN5gxN5wxN6AxN6QxN6gxN6wxN7AxN7QxN7gxN7wxN8AxN8QxN8gxN8wxN9AxN9QxN9gxN9wxN+AxN+QxN+gxN+wxN/AxN/QxN/gxN/wxNAA1NAQ1NAg1NAw1NBA1NBQ1NBg1NBw1NCA1NCQ1NCg1NCw1NDA1NDQ1NDg1NDw1NEA1NEQ1NEg1NEw1NFA1NFQ1NFg1NFw1NGA1NGQ1NGg1NGw1NHA1NHQ1NHg1NHw1NIA1NIQ1NIg1NIw1NJA1NJQ1NJg1NJw1NKA1NKQ1NKg1NKw1NLA1NLQ1NLg1NLw1NMA1NMQ1NMg1NMw1NNA1NNQ1NNg1NNw1NOA1NOQ1NOg1NOw1NPA1NPQ1NPg1NPw1NQA1NQQ1NQg1NQw1NRA1NRQ1NRg1NRw1NSA1NSQ1NSg1NSw1NTA1NTQ1NTg1NTw1NUA1NUQ1NUg1NUw1NVA1NVQ1NVg1NVw1NWA1NWQ1NWg1NWw1NXA1NXQ1NXg1NXw1NYA1NYQ1NYg1NYw1NZA1NZQ1NZg1NZw1NaA1NaQ1Nag1Naw1NbA1NbQ1Nbg1Nbw1NcA1NcQ1Ncg1Ncw1NdA1NdQ1Ndg1Ndw1NeA1NeQ1Neg1New1NfA1NfQ1Nfg1Nfw1NgA1NgQ1Ngg1Ngw1NhA1NhQ1Nhg1Nhw1NiA1NiQ1Nig1Niw1NjA1NjQ1Njg1Njw1NkA1NkQ1Nkg1Nkw1NlA1NlQ1Nlg1Nlw1NmA1NmQ1Nmg1Nmw1NnA1NnQ1Nng1Nnw1NoA1NoQ1Nog1Now1NpA1NpQ1Npg1Npw1NqA1NqQ1Nqg1Nqw1NrA1NrQ1Nrg1Nrw1NsA1NsQ1Nsg1Nsw1NtA1NtQ1Ntg1Ntw1NuA1NuQ1Nug1Nuw1NvA1NvQ1Nvg1Nvw1NwA1NwQ1Nwg1Nww1NxA1NxQ1Nxg1Nxw1NyA1NyQ1Nyg1Nyw1NzA1NzQ1Nzg1Nzw1N0A1N0Q1N0g1N0w1N1A1N1Q1N1g1N1w1N2A1N2Q1N2g1N2w1N3A1N3Q1N3g1N3w1N4A1N4Q1N4g1N4w1N5A1N5Q1N5g1N5w1N6A1N6Q1N6g1N6w1N7A1N7Q1N7g1N7w1N8A1N8Q1N8g1N8w1N9A1N9Q1N9g1N9w1N+A1N+Q1N+g1N+w1N/A1N/Q1N/g1N/w1NAA5NAQ5NAg5NAw5NBA5NBQ5NBg5NBw5NCA5NCQ5NCg5NCw5NDA5NDQ5NDg5NDw5NEA5NEQ5NEg5NEw5NFA5NFQ5NFg5NFw5NGA5NGQ5NGg5NGw5NHA5NHQ5NHg5NHw5NIA5NIQ5NIg5NIw5NJA5NJQ5NJg5NJw5NKA5NKQ5NKg5NKw5NLA5NLQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5Nmw5Nmw5NnA5NnA5NnQ5NnQ5Nng5Nng5Nnw5Nnw5NoA5NoA5NoQ5NoQ5Nog5Nog5Now5Now5NpA5NpA5NpQ5NpQ5Npg5Npg5Npw5Npw5NqA5NqA5NqQ5NqQ5Nqg5Nqg5Nqw5Nqw5NrA5NrA5NrQ5NrQ5Nrg5Nrg5Nrw5Nrw5NsA5NsA5NsQ5NsQ5Nsg5Nsg5Nsw5Nsw5NtA5NtA5NtQ5NtQ5Ntg5Ntg5Ntw5Ntw5NuA5NuA5NuQ5NuQ5Nug5Nug5Nuw5Nuw5NvA5NvA5NvQ5NvQ5Nvg5Nvg5Nvw5Nvw5NwA5NwA5NwQ5NwQ5Nwg5Nwg5Nww5Nww5NxA5NxA5NxQ5NxQ5Nxg5Nxg5Nxw5Nxw5NyA5NyA5NyQ5NyQ5Nyg5Nyg5Nyw5Nyw5NzA5NzA5NzQ5NzQ5Nzg5Nzg5Nzw5Nzw5N0A5N0A5N0Q5N0Q5N0g5N0g5N0w5N0w5N1A5N1A5N1Q5N1Q5N1g5N1g5N1w5N1w5N2A5N2A5N2Q5N2Q5N2g5N2g5N2w5N2w5N3A5N3A5N3Q5N3Q5N3g5N3g5N3w5N3w5N4A5N4A5N4Q5N4Q5N4g5N4g5N4w5N4w5N5A5N5A5N5Q5N5Q5N5g5N5g5N5w5N5w5N6A5N6A5N6Q5N6Q5lcy4=", "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": "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": [ 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": "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==" }, "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): """) - self.layout().addWidget(self.description_area) + self.content_splitter.addWidget(self.description_area) + + def load(self): + super().load() + if self.child: + self.child.load() + + def unload(self): + if self.child: + self.child.unload() + super().unload() diff --git a/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItem.py b/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItem.py index cbd48a33..c3cc57be 100644 --- a/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItem.py +++ b/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItem.py @@ -1,5 +1,11 @@ +# prevent circular imports +from __future__ import annotations +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ..FlowView import FlowView + import traceback -from typing import Optional, Tuple +from typing import Optional, Tuple, List from qtpy.QtWidgets import QGraphicsItem, QGraphicsObject, QMenu, QGraphicsDropShadowEffect from qtpy.QtCore import Qt, QRectF, QObject, QPointF @@ -8,19 +14,23 @@ from .NodeErrorIndicator import NodeErrorIndicator from .NodeGUI import NodeGUI from ...GUIBase import GUIBase -from ryvencore.NodePort import NodeInput, NodeOutput from .NodeItemAction import NodeItemAction from .NodeItemAnimator import NodeItemAnimator from .NodeItemWidget import NodeItemWidget from .PortItem import InputPortItem, OutputPortItem from ...utils import serialize, deserialize, MovementEnum, generate_name +from ryvencore_qt.src.Design import Design + +from ryvencore import Node +from ryvencore.NodePort import NodeInput, NodeOutput + class NodeItem(GUIBase, QGraphicsObject): # QGraphicsItem, QObject): """The GUI representative for nodes. Unlike the Node class, this class is not subclassed individually and works the same for every node.""" - def __init__(self, node, node_gui, flow_view, design): + def __init__(self, node: Node, node_gui: NodeGUI, flow_view: FlowView, design: Design): GUIBase.__init__(self, representing_component=node) QGraphicsObject.__init__(self) @@ -28,12 +38,12 @@ def __init__(self, node, node_gui, flow_view, design): self.node_gui = node_gui self.node_gui.item = self self.flow_view = flow_view - self.session_design = design - self.movement_state = None - self.movement_pos_from = None - self.painted_once = False - self.inputs = [] - self.outputs = [] + self.session_design: Design = design + self.movement_state: Optional[MovementEnum] = None + self.movement_pos_from: Optional[QPointF] = None + self.painted_once: bool = False + self.inputs: List[InputPortItem] = [] + self.outputs: List[OutputPortItem] = [] self.color = QColor(self.node_gui.color) # manipulated by self.animator self.collapsed = False @@ -41,8 +51,6 @@ def __init__(self, node, node_gui, flow_view, design): self.hiding_unconnected_ports = False self.displaying_error = False - self.personal_logs = [] - # 'initializing' will be set to False below. It's needed for the ports setup, to prevent shape updating stuff self.initializing = True @@ -176,7 +184,7 @@ def on_node_input_added(self, index, inp: NodeInput): insert = index if index == len(self.node.inputs) - 1 else None self.add_new_input(inp, insert) - def add_new_input(self, inp: NodeInput, insert: int = None): + def add_new_input(self, inp: NodeInput, insert: Optional[int] = None): if inp in self.node_gui.input_widgets: widget_name = self.node_gui.input_widgets[inp]['name'] @@ -204,11 +212,13 @@ def on_node_input_removed(self, index, inp: NodeInput): self.remove_input(inp) def remove_input(self, inp: NodeInput): - item = None + item: InputPortItem for inp_item in self.inputs: if inp_item.port == inp: item = inp_item break + else: + return # index = self.node.inputs.index(inp) # item = self.inputs[index] @@ -231,7 +241,7 @@ def on_node_output_added(self, index, out: NodeOutput): insert = index if index == len(self.node.outputs) - 1 else None self.add_new_output(out, insert) - def add_new_output(self, out: NodeOutput, insert: int = None): + def add_new_output(self, out: NodeOutput, insert: Optional[int] = None): # create item # out.item = OutputPortItem(out.node, self, out) @@ -252,11 +262,13 @@ def on_node_output_removed(self, index, out: NodeOutput): self.remove_output(out) def remove_output(self, out: NodeOutput): - item = None + item: OutputPortItem for out_item in self.outputs: if out_item.port == out: item = out_item break + else: + return # index = self.node.outputs.index(out) # item = self.outputs[index] @@ -306,7 +318,8 @@ def boundingRect(self): rect.setHeight(h) return rect - def get_left_body_header_vertex_scene_pos(self): + def get_left_body_header_vertex_scene_pos(self) -> QPointF: + assert self.widget.header_widget is not None return self.mapToScene( QPointF( -self.boundingRect().width() / 2, @@ -314,7 +327,8 @@ def get_left_body_header_vertex_scene_pos(self): ) ) - def get_right_body_header_vertex_scene_pos(self): + def get_right_body_header_vertex_scene_pos(self) -> QPointF: + assert self.widget.header_widget is not None return self.mapToScene( QPointF( +self.boundingRect().width() / 2, diff --git a/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItemAnimator.py b/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItemAnimator.py index ee1b64cb..b9ad8621 100644 --- a/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItemAnimator.py +++ b/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItemAnimator.py @@ -1,23 +1,29 @@ +# prevent circular imports +from __future__ import annotations +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .NodeItem import NodeItem + from qtpy.QtCore import QObject, QPropertyAnimation, Property, QParallelAnimationGroup, QTimeLine from qtpy.QtGui import QColor from qtpy.QtWidgets import QGraphicsItem class NodeItemAnimator(QObject): - def __init__(self, node_item): + def __init__(self, node_item: NodeItem): super(NodeItemAnimator, self).__init__() self.node_item: QGraphicsItem = node_item self.animation_running = False # title color - self.title_activation_animation = QPropertyAnimation(self, b"p_title_color") + self.title_activation_animation = QPropertyAnimation(self, b"p_title_color") # type: ignore self.title_activation_animation.setDuration(700) # body color - self.body_activation_animation = QPropertyAnimation(self, b"p_body_color") + self.body_activation_animation = QPropertyAnimation(self, b"p_body_color") # type: ignore self.body_activation_animation.setDuration(700) # transform - self.scale_animation = QPropertyAnimation(self.node_item, b'scale') + self.scale_animation = QPropertyAnimation(self.node_item, b'scale') # type: ignore self.scale_animation.setDuration(700) self.scalar = 1.04 diff --git a/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItemWidget.py b/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItemWidget.py index f54e35c3..68857c8a 100644 --- a/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItemWidget.py +++ b/ryvencore-qt/ryvencore_qt/src/flows/nodes/NodeItemWidget.py @@ -1,5 +1,15 @@ +# prevent circular imports +from __future__ import annotations +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .NodeGUI import NodeGUI + from .NodeItem import NodeItem + from ..FlowView import FlowView + from qtpy.QtCore import QPointF, QRectF, Qt, QSizeF -from qtpy.QtWidgets import QGraphicsWidget, QGraphicsLinearLayout, QSizePolicy +from qtpy.QtWidgets import QGraphicsWidget, QGraphicsLinearLayout, QSizePolicy, QWidget + +from typing import Optional from .NodeItem_CollapseButton import NodeItem_CollapseButton from ..FlowViewProxyWidget import FlowViewProxyWidget @@ -8,34 +18,37 @@ from .NodeItem_TitleLabel import TitleLabel from .PortItem import InputPortItem, OutputPortItem +from ryvencore import Flow + class NodeItemWidget(QGraphicsWidget): """The QGraphicsWidget managing all GUI components of a NodeItem in widgets and layouts.""" - def __init__(self, node_gui, node_item): + def __init__(self, node_gui: NodeGUI, node_item: NodeItem) -> None: super().__init__(parent=node_item) - self.node_gui = node_gui - self.node_item = node_item - self.flow_view = self.node_item.flow_view - self.flow = self.flow_view.flow + self.node_gui: NodeGUI = node_gui + self.node_item: NodeItem = node_item + self.flow_view: FlowView = self.node_item.flow_view + self.flow: Flow = self.flow_view.flow self.body_padding = 6 self.header_padding = (0, 0, 0, 0) # theme dependent and hence updated in setup_layout()! self.icon = NodeItem_Icon(node_gui, node_item) if node_gui.icon else None - self.collapse_button = NodeItem_CollapseButton(node_gui, node_item) if node_gui.style == 'normal' else None self.title_label = TitleLabel(node_gui, node_item) - self.main_widget_proxy: FlowViewProxyWidget = None + self.main_widget_proxy: Optional[FlowViewProxyWidget] = None if self.node_item.main_widget: + assert isinstance(self.node_item.main_widget, QWidget) self.main_widget_proxy = FlowViewProxyWidget(self.flow_view) self.main_widget_proxy.setWidget(self.node_item.main_widget) - self.header_layout: QGraphicsWidget = None - self.header_widget: QGraphicsWidget = None - self.body_layout: QGraphicsLinearLayout = None - self.body_widget: QGraphicsWidget = None - self.inputs_layout: QGraphicsLinearLayout = None - self.outputs_layout: QGraphicsLinearLayout = None + self.header_layout: Optional[QGraphicsWidget] + self.header_widget: Optional[QGraphicsWidget] + self.collapse_button: Optional[NodeItem_CollapseButton] + self.body_layout: QGraphicsLinearLayout + self.body_widget: QGraphicsWidget + self.inputs_layout: QGraphicsLinearLayout + self.outputs_layout: QGraphicsLinearLayout self.setLayout(self.setup_layout()) def setup_layout(self) -> QGraphicsLinearLayout: @@ -48,6 +61,8 @@ def setup_layout(self) -> QGraphicsLinearLayout: layout.setSpacing(0) if self.node_gui.style == 'normal': + self.collapse_button = NodeItem_CollapseButton(self.node_gui, self.node_item) + self.header_widget = QGraphicsWidget() # self.header_widget.setContentsMargins(0, 0, 0, 0) self.header_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) @@ -70,6 +85,9 @@ def setup_layout(self) -> QGraphicsLinearLayout: layout.addItem(self.header_widget) # layout.setAlignment(self.title_label, Qt.AlignTop) else: + self.collapse_button = None + self.header_widget = None + self.header_layout = None self.setZValue(self.title_label.zValue() + 1) # inputs @@ -146,6 +164,8 @@ def update_shape(self): mw = self.node_item.main_widget if mw is not None: # maybe the main_widget got resized + assert self.main_widget_proxy is not None + # self.main_widget_proxy.setMaximumSize(mw.size()) # self.main_widget_proxy.setMaximumSize(mw.maximumSize()) @@ -191,6 +211,7 @@ def update_shape(self): def add_main_widget_to_layout(self): + assert self.main_widget_proxy is not None if self.node_gui.main_widget_pos == 'between ports': self.body_layout.insertItem(1, self.main_widget_proxy) self.body_layout.insertStretch(2) @@ -239,12 +260,12 @@ def remove_output_from_layout(self, out: OutputPortItem): def collapse(self): self.body_widget.hide() - if self.main_widget_proxy: + if self.main_widget_proxy is not None: self.main_widget_proxy.hide() def expand(self): self.body_widget.show() - if self.main_widget_proxy: + if self.main_widget_proxy is not None: self.main_widget_proxy.show() def hide_unconnected_ports(self): diff --git a/ryvencore-qt/ryvencore_qt/src/flows/nodes/PortItem.py b/ryvencore-qt/ryvencore_qt/src/flows/nodes/PortItem.py index 085a2ca0..89146d93 100644 --- a/ryvencore-qt/ryvencore_qt/src/flows/nodes/PortItem.py +++ b/ryvencore-qt/ryvencore_qt/src/flows/nodes/PortItem.py @@ -1,4 +1,12 @@ -from typing import Tuple +# prevent circular imports +from __future__ import annotations +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .NodeGUI import NodeGUI + from .NodeItem import NodeItem + from ..FlowView import FlowView + +from typing import Tuple, Optional from qtpy.QtWidgets import QGraphicsGridLayout, QGraphicsWidget, QGraphicsLayoutItem from qtpy.QtCore import Qt, QRectF, QPointF, QSizeF @@ -11,7 +19,7 @@ from ...GUIBase import GUIBase from ...utils import get_longest_line, shorten from ..FlowViewProxyWidget import FlowViewProxyWidget - +from .WidgetBaseClasses import NodeInputWidget # utils @@ -52,16 +60,16 @@ def connections(port): class PortItem(GUIBase, QGraphicsWidget): """The GUI representative for ports of nodes, also handling mouse events for connections.""" - def __init__(self, node_gui, node_item, port, flow_view): + def __init__(self, node_gui: NodeGUI, node_item: NodeItem, port: NodePort, flow_view: FlowView): GUIBase.__init__(self, representing_component=port) QGraphicsWidget.__init__(self) self.setGraphicsItem(self) - self.node_gui = node_gui - self.node_item = node_item + self.node_gui: NodeGUI = node_gui + self.node_item: NodeItem = node_item self.port: NodePort = port - self.flow_view = flow_view + self.flow_view: FlowView = flow_view # self.port.has_been_connected.connect(self.port_connected) # self.port.has_been_disconnected.connect(self.port_disconnected) @@ -96,11 +104,12 @@ def port_disconnected(self): class InputPortItem(PortItem): - def __init__(self, node_gui, node_item, port, input_widget: Tuple[type, str] = None): + def __init__(self, node_gui, node_item, port, input_widget: Optional[Tuple[type, str]] = None): + self.port: NodeInput super().__init__(node_gui, node_item, port, node_gui.flow_view()) self.proxy = None # widget proxy - self.widget = None # widget + self.widget: Optional[NodeInputWidget] = None # widget if input_widget is not None: self.create_widget(input_widget[0], input_widget[1]) @@ -119,6 +128,7 @@ def __init__(self, node_gui, node_item, port, input_widget: Tuple[type, str] = N ): c_d = self.port.load_data['widget data'] if c_d is not None: + assert self.widget is not None self.widget.set_state(deserialize(c_d)) else: # this is a little feature that lets us prevent loading of input widgets @@ -312,17 +322,19 @@ def height_no_padding(self): """The height without the padding""" return self.height - 2 * self.padding - def get_scene_center_pos(self): + def get_scene_center_pos(self) -> QPointF: if not self.node_item.collapsed: return QPointF( self.scenePos().x() + self.boundingRect().width() / 2, self.scenePos().y() + self.boundingRect().height() / 2, ) else: + # mypy seems buggy here, it things below methods return Any, + # but they are annotated to return QPointF if isinstance(self.port_item, InputPortItem): - return self.node_item.get_left_body_header_vertex_scene_pos() + return self.node_item.get_left_body_header_vertex_scene_pos() # type: ignore else: - return self.node_item.get_right_body_header_vertex_scene_pos() + return self.node_item.get_right_body_header_vertex_scene_pos() # type: ignore class PortItemLabel(QGraphicsWidget): diff --git a/ryvencore-qt/ryvencore_qt/src/flows/nodes/PortItemInputWidgets.py b/ryvencore-qt/ryvencore_qt/src/flows/nodes/PortItemInputWidgets.py index 64bed37c..12876839 100644 --- a/ryvencore-qt/ryvencore_qt/src/flows/nodes/PortItemInputWidgets.py +++ b/ryvencore-qt/ryvencore_qt/src/flows/nodes/PortItemInputWidgets.py @@ -23,7 +23,7 @@ def __str__(self): class Data_IW(DType_IW_Base, QLineEdit): # virtual - base_width = None # specified by subclasses + base_width: int # specified by subclasses def __init__(self, params): DType_IW_Base.__init__(self, params) @@ -110,7 +110,7 @@ class Data_IW_L(Data_IW): class String_IW(DType_IW_Base, QLineEdit): # virtual - width = None # specified by subclasses + width_: int # specified by subclasses def __init__(self, params): DType_IW_Base.__init__(self, params) @@ -121,7 +121,7 @@ def __init__(self, params): self.setFont(QFont('source code pro', 10)) self.setText(self.dtype.val) - self.setFixedWidth(self.width) + self.setFixedWidth(self.width_) self.setToolTip(self.dtype.doc) self.editingFinished.connect(self.editing_finished) @@ -155,15 +155,15 @@ def set_state(self, data: dict): # custom sized classes for qss access: class String_IW_S(String_IW): - width = 30 + width_ = 30 class String_IW_M(String_IW): - width = 70 + width_ = 70 class String_IW_L(String_IW): - width = 150 + width_ = 150 # ----------------------------------- diff --git a/ryvencore-qt/ryvencore_qt/src/flows/nodes/WidgetBaseClasses.py b/ryvencore-qt/ryvencore_qt/src/flows/nodes/WidgetBaseClasses.py index 8c309424..8b93f796 100644 --- a/ryvencore-qt/ryvencore_qt/src/flows/nodes/WidgetBaseClasses.py +++ b/ryvencore-qt/ryvencore_qt/src/flows/nodes/WidgetBaseClasses.py @@ -1,4 +1,5 @@ """The base classes for node custom widgets for nodes.""" +from typing import Dict from ryvencore import Data from ..FlowCommands import Delegate_Command @@ -10,16 +11,16 @@ class NodeMainWidget: def __init__(self, params): self.node, self.node_item, self.node_gui = params - def get_state(self) -> dict: + def get_state(self) -> Dict: """ *VIRTUAL* Return the state of the widget, in a (pickle) serializable format. """ - data = {} + data: Dict = {} return data - def set_state(self, data: dict): + def set_state(self, data: Dict): """ *VIRTUAL* @@ -42,16 +43,16 @@ def __init__(self, params): self.input, self.input_item, self.node, self.node_gui, self.position = \ params - def get_state(self) -> dict: + def get_state(self) -> Dict: """ *VIRTUAL* Return the state of the widget, in a (pickle) serializable format. """ - data = {} + data: Dict = {} return data - def set_state(self, data: dict): + def set_state(self, data: Dict): """ *VIRTUAL* @@ -93,16 +94,16 @@ class NodeInspectorWidget: def __init__(self, params): self.node, self.node_gui = params - def get_state(self) -> dict: + def get_state(self) -> Dict: """ *VIRTUAL* Return the state of the widget, in a (pickle) serializable format. """ - data = {} + data: Dict = {} return data - def set_state(self, data: dict): + def set_state(self, data: Dict): """ *VIRTUAL* diff --git a/ryvencore-qt/ryvencore_qt/src/widgets/FlowsList_FlowWidget.py b/ryvencore-qt/ryvencore_qt/src/widgets/FlowsList_FlowWidget.py index efc52167..ff75b49a 100644 --- a/ryvencore-qt/ryvencore_qt/src/widgets/FlowsList_FlowWidget.py +++ b/ryvencore-qt/ryvencore_qt/src/widgets/FlowsList_FlowWidget.py @@ -1,6 +1,6 @@ from qtpy.QtWidgets import QWidget, QHBoxLayout, QLabel, QMenu, QAction -from qtpy.QtGui import QIcon, QImage -from qtpy.QtCore import Qt, QEvent, QBuffer, QByteArray +from qtpy.QtGui import QIcon, QImage, QMouseEvent +from qtpy.QtCore import Qt, QEvent, QBuffer, QByteArray, QIODevice from ..GlobalAttributes import Location from .ListWidget_NameLineEdit import ListWidget_NameLineEdit @@ -50,14 +50,14 @@ def __init__(self, flows_list_widget, session_gui, flow): - def mouseDoubleClickEvent(self, event): + def mouseDoubleClickEvent(self, event: QMouseEvent): if event.button() == Qt.LeftButton: if self.title_line_edit.geometry().contains(event.pos()): self.title_line_edit_double_clicked() return - def event(self, event): + def event(self, event: QEvent): if event.type() == QEvent.ToolTip: # generate preview img as QImage @@ -65,7 +65,8 @@ def event(self, event): # store the img data in QBuffer to load it directly from memory buffer = QBuffer() - img.save(buffer, 'PNG') + img.save(device=buffer, format='PNG') # type: ignore + # QBuffer inherits QIODevice, but mypy doesn't know that # generate html from data in memory html = f"" @@ -76,7 +77,7 @@ def event(self, event): return QWidget.event(self, event) - def contextMenuEvent(self, event): + def contextMenuEvent(self, event: QEvent): menu: QMenu = QMenu(self) delete_action = QAction('delete') diff --git a/ryvencore-qt/ryvencore_qt/src/widgets/VarsList_VarWidget.py b/ryvencore-qt/ryvencore_qt/src/widgets/VarsList_VarWidget.py index 315634ea..0faede9d 100644 --- a/ryvencore-qt/ryvencore_qt/src/widgets/VarsList_VarWidget.py +++ b/ryvencore-qt/ryvencore_qt/src/widgets/VarsList_VarWidget.py @@ -1,5 +1,5 @@ from qtpy.QtWidgets import QWidget, QHBoxLayout, QLabel, QMenu, QAction -from qtpy.QtGui import QIcon, QDrag +from qtpy.QtGui import QIcon, QDrag, QMouseEvent from qtpy.QtCore import QMimeData, Qt, QEvent, QByteArray import json @@ -54,14 +54,14 @@ def __init__(self, vars_list_widget, vars_addon: VarsAddon, flow, var): - def mouseDoubleClickEvent(self, event): + def mouseDoubleClickEvent(self, event: QMouseEvent): if event.button() == Qt.LeftButton: if self.name_line_edit.geometry().contains(event.pos()): self.name_line_edit_double_clicked() return - def mousePressEvent(self, event): + def mousePressEvent(self, event: QMouseEvent): if event.button() == Qt.LeftButton: drag = QDrag(self) mime_data = QMimeData() @@ -73,7 +73,7 @@ def mousePressEvent(self, event): return - def event(self, event): + def event(self, event: QEvent): if event.type() == QEvent.ToolTip: val_str = '' try: @@ -85,7 +85,7 @@ def event(self, event): return QWidget.event(self, event) - def contextMenuEvent(self, event): + def contextMenuEvent(self, event: QEvent): menu: QMenu = QMenu(self) delete_action = QAction('delete') diff --git a/ryvencore-qt/setup.cfg b/ryvencore-qt/setup.cfg index b41ee170..5e12e1f7 100644 --- a/ryvencore-qt/setup.cfg +++ b/ryvencore-qt/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = ryvencore-qt -version = v0.4.3 +version = v0.5.0 author = Leon Thomm author_email = l.thomm@mailbox.org description = Qt frontend for ryvencore; Library for building Visual Node Editors @@ -18,10 +18,9 @@ classifiers = [options] packages = find: include_package_data = True -python_requires = >=3.6, <3.11 +python_requires = >=3.6, <3.13 install_requires = - ryvencore ==0.4.* - PySide2 + ryvencore ==0.5.* QtPy waiting textdistance