diff --git a/ryven/ironflow/__init__.py b/ryven/ironflow/__init__.py
index 490564e3..873a4993 100644
--- a/ryven/ironflow/__init__.py
+++ b/ryven/ironflow/__init__.py
@@ -1,4 +1,4 @@
-__all__ = ['gui', 'flow_canvas', 'canvas_widgets', 'node_interface.py']
+__all__ = ['gui', 'flow_canvas', 'canvas_widgets', 'layouts', 'models', 'node_interface']
-from .gui import GUI
+from ryven.ironflow.gui import GUI
from ryven.NENV import Node, NodeInputBP, NodeOutputBP, dtypes
diff --git a/ryven/ironflow/canvas_widgets.py b/ryven/ironflow/canvas_widgets.py
index 257d65ce..4336c3ef 100644
--- a/ryven/ironflow/canvas_widgets.py
+++ b/ryven/ironflow/canvas_widgets.py
@@ -6,13 +6,13 @@
import numpy as np
from IPython.display import display
-from .layouts import Layout, NodeLayout, PortLayout, DataPortLayout, ExecPortLayout, ButtonLayout
+from ryven.ironflow.layouts import Layout, NodeLayout, PortLayout, DataPortLayout, ExecPortLayout, ButtonLayout
from abc import ABC, abstractmethod
from ryvencore.NodePort import NodeInput, NodeOutput
-from typing import TYPE_CHECKING, Optional, Union, List, Callable
+from typing import TYPE_CHECKING, Optional, Union
if TYPE_CHECKING:
- from .flow_canvas import FlowCanvas
+ from ryven.ironflow.flow_canvas import FlowCanvas
from ryven.ironflow.gui import GUI
from ipycanvas import Canvas
from ryven.NENV import Node, NodeInputBP, NodeOutputBP
@@ -21,7 +21,7 @@
from ryvencore.Flow import Flow
-__author__ = "Joerg Neugebauer"
+__author__ = "Joerg Neugebauer, Liam Huber"
__copyright__ = (
"Copyright 2020, Max-Planck-Institut für Eisenforschung GmbH - "
"Computational Materials Design (CM) Department"
@@ -42,7 +42,7 @@ def __init__(
self,
x: Number,
y: Number,
- parent: Union[FlowCanvas, CanvasWidget],
+ parent: FlowCanvas | CanvasWidget,
layout: Layout,
selected: bool = False,
title: Optional[str] = None,
@@ -60,15 +60,12 @@ def __init__(
self._height = self.layout.height
@abstractmethod
- def on_click(self, last_selected_object: Optional[CanvasWidget]) -> Optional[CanvasWidget]:
+ def on_click(self, last_selected_object: Optional[CanvasWidget]) -> CanvasWidget | None:
pass
- def on_double_click(self) -> Optional[CanvasWidget]:
+ def on_double_click(self) -> CanvasWidget | None:
return self
- def _init_after_parent_assignment(self):
- pass
-
@property
def width(self) -> int:
return self.layout.width
@@ -79,11 +76,11 @@ def height(self) -> int:
@property
def x(self) -> Number:
- return self.parent.x + self._x # - self.parent.width//2
+ return self.parent.x + self._x
@property
def y(self) -> Number:
- return self.parent.y + self._y # - self.parent.height//2
+ return self.parent.y + self._y
@property
def canvas(self) -> Canvas:
@@ -114,8 +111,8 @@ def add_x_y(self, dx_in: Number, dy_in: Number) -> None:
def draw_shape(self) -> None:
self.canvas.fill_style = self.layout.selected_color if self.selected else self.layout.background_color
self.canvas.fill_rect(
- self.x, # - (self.width * 0.5),
- self.y, # - (self.height * 0.5),
+ self.x,
+ self.y,
self.width,
self.height,
)
@@ -132,11 +129,11 @@ def draw(self) -> None:
o.draw()
def _is_at_xy(self, x_in: Number, y_in: Number) -> bool:
- x_coord = self.x # - (self.width * 0.5)
- y_coord = self.y # - (self.height * 0.5)
+ x_coord = self.x
+ y_coord = self.y
return x_coord < x_in < (x_coord + self.width) and y_coord < y_in < (y_coord + self.height)
- def get_element_at_xy(self, x_in: Number, y_in: Number) -> Union[CanvasWidget, None]:
+ def get_element_at_xy(self, x_in: Number, y_in: Number) -> CanvasWidget | None:
if self.is_here(x_in, y_in):
for o in self.objects_to_draw:
if o.is_here(x_in, y_in):
@@ -165,7 +162,7 @@ def __init__(
self,
x: Number,
y: Number,
- parent: Union[FlowCanvas, CanvasWidget],
+ parent: FlowCanvas | CanvasWidget,
layout: Layout,
selected: bool = False,
title: Optional[str] = None,
@@ -222,7 +219,7 @@ def __init__(
self,
x: Number,
y: Number,
- parent: Union[FlowCanvas, CanvasWidget],
+ parent: FlowCanvas | CanvasWidget,
layout: PortLayout,
port: NodePort,
selected: bool = False,
@@ -245,7 +242,7 @@ def __init__(
self.radius = radius
self.port = port
- def on_click(self, last_selected_object: Optional[CanvasWidget]) -> Optional[CanvasWidget]:
+ def on_click(self, last_selected_object: Optional[CanvasWidget]) -> PortWidget | None:
if last_selected_object == self:
self.deselect()
return None
@@ -277,7 +274,7 @@ def __init__(
self,
x: Number,
y: Number,
- parent: Union[FlowCanvas, CanvasWidget],
+ parent: FlowCanvas | CanvasWidget,
layout: NodeLayout,
node: Node,
selected: bool = False,
@@ -303,26 +300,25 @@ def __init__(
'exec': ExecPortLayout()
}
- self._title_box_height = 30
-
- n_ports_max = max(len(self.node.inputs), len(self.node.outputs))
- n_ports_min = len([p for p in self.node.inputs if p.type_ == "exec"])
+ n_ports_max = max(len(self.node.inputs), len(self.node.outputs)) + 1 # Includes the expand/collapse button
+ exec_port_i = np.where([p.type_ == "exec" for p in self.node.inputs])[0]
+ n_ports_min = exec_port_i[-1] + 1 if len(exec_port_i) > 0 else 1
subwidget_size_and_buffer = 1.33 * 2 * self.port_radius
- self._io_height = subwidget_size_and_buffer * n_ports_max
- self._exec_height = subwidget_size_and_buffer * n_ports_min
- # TODO: Right now we're hard-coding in that all the exec ports (which come with buttons) appear first in input
- # This isn't necessarily so, nor checked for anywhere. Do better.
- self._expand_collapse_height = subwidget_size_and_buffer
+ self._title_box_height = self.layout.title_box_height
+ self._max_body_height = subwidget_size_and_buffer * n_ports_max
+ self._min_body_height = subwidget_size_and_buffer * n_ports_min
+ self._expanded_height = self._title_box_height + self._max_body_height
+ self._collapsed_height = self._title_box_height + self._min_body_height
self._height = self._expanded_height
- y_step = (self._io_height + self._expand_collapse_height) / (n_ports_max + 1)
- self._port_y_locs = (np.arange(n_ports_max + 1) + 0.5) * y_step + self._title_box_height
+ y_step = self._max_body_height / n_ports_max
+ self._subwidget_y_locs = (np.arange(n_ports_max) + 0.5) * y_step + self._title_box_height
self.add_inputs()
self.add_outputs()
self.expand_button = ExpandButtonWidget(
x=0.5 * self.width - self.port_radius,
- y=self._port_y_locs[0] - self.port_radius,
+ y=self._subwidget_y_locs[0] - self.port_radius,
parent=self,
layout=ButtonLayout(),
pressed=True,
@@ -332,7 +328,7 @@ def __init__(
self.add_widget(self.expand_button)
self.collapse_button = CollapseButtonWidget(
x=0.5 * self.width - self.port_radius,
- y=self._port_y_locs[-1] - self.port_radius,
+ y=self._subwidget_y_locs[-1] - self.port_radius,
parent=self,
layout=ButtonLayout(),
pressed=False,
@@ -341,7 +337,7 @@ def __init__(
)
self.add_widget(self.collapse_button)
- def on_click(self, last_selected_object: Optional[CanvasWidget]) -> Optional[CanvasWidget]:
+ def on_click(self, last_selected_object: Optional[CanvasWidget]) -> NodeWidget | None:
if last_selected_object == self:
return self
else:
@@ -357,7 +353,7 @@ def on_click(self, last_selected_object: Optional[CanvasWidget]) -> Optional[Can
self.deselect()
return None
- def on_double_click(self) -> Optional[CanvasWidget]:
+ def on_double_click(self) -> None:
self.delete()
return None
@@ -373,8 +369,8 @@ def draw_title(self) -> None:
def _add_ports(
self,
radius: Number,
- inputs: Optional[List[NodeInputBP]] = None,
- outputs: Optional[List[NodeOutputBP]] = None,
+ inputs: Optional[list[NodeInputBP]] = None,
+ outputs: Optional[list[NodeOutputBP]] = None,
border: Number = 1.4,
) -> None:
if inputs is not None:
@@ -393,12 +389,12 @@ def _add_ports(
self.add_widget(
PortWidget(
x=x,
- y=self._port_y_locs[i_port],
+ y=self._subwidget_y_locs[i_port],
parent=self,
layout=self.port_layouts[data_or_exec],
port=port,
hidden_x=x,
- hidden_y=self._port_y_locs[0],
+ hidden_y=self._subwidget_y_locs[0],
radius=radius,
)
)
@@ -406,8 +402,8 @@ def _add_ports(
button_layout = ButtonLayout()
self.add_widget(
ExecButtonWidget(
- x=x + 0.3 * button_layout.width,
- y=self._port_y_locs[i_port] - 0.5 * button_layout.height,
+ x=x + radius,
+ y=self._subwidget_y_locs[i_port] - 0.5 * button_layout.height,
parent=self,
layout=button_layout,
port=port
@@ -434,29 +430,17 @@ def delete(self) -> None:
def port_widgets(self) -> list[PortWidget]:
return [o for o in self.objects_to_draw if isinstance(o, PortWidget)]
- @property
- def _expanded_height(self) -> Number:
- return self._title_box_height + self._io_height + self._expand_collapse_height
-
- @property
- def _collapsed_height(self) -> Number:
- return self._title_box_height + max(self._expand_collapse_height, self._exec_height)
-
def expand_io(self):
self._height = self._expanded_height
for o in self.port_widgets:
o.show()
- # self.collapse_button.on_click() # Why doesn't this do the same as the next two lines??
- self.collapse_button.on_unpressed()
- self.collapse_button.pressed = False
+ self.collapse_button.unpress()
def collapse_io(self):
self._height = self._collapsed_height
for o in self.port_widgets:
o.hide()
- # TODO: The expand and collapse buttons are effectively an XOR toggle...improve this awkward implementation
- self.expand_button.on_unpressed()
- self.expand_button.pressed = False
+ self.expand_button.unpress()
class ButtonNodeWidget(NodeWidget):
@@ -464,7 +448,7 @@ def __init__(
self,
x: Number,
y: Number,
- parent: Union[FlowCanvas, CanvasWidget],
+ parent: FlowCanvas | CanvasWidget,
layout: NodeLayout,
node: Node,
selected: bool = False,
@@ -478,7 +462,7 @@ def __init__(
button_layout = ButtonLayout()
self.exec_button = ExecButtonWidget(
x=0.8 * (self.width - button_layout.width),
- y=self._port_y_locs[0] - 0.5 * button_layout.height,
+ y=self._subwidget_y_locs[0] - 0.5 * button_layout.height,
parent=self,
layout=button_layout,
port=self.node.outputs[0],
@@ -501,7 +485,7 @@ def __init__(
self.title = title
self.pressed = pressed
- def on_click(self, last_selected_object: Optional[CanvasWidget]) -> Optional[CanvasWidget]:
+ def on_click(self, last_selected_object: Optional[CanvasWidget]) -> CanvasWidget | None:
if self.pressed:
self.unpress()
else:
@@ -550,7 +534,7 @@ def __init__(
parent: DisplayableNodeWidget,
layout: ButtonLayout,
selected: bool = False,
- title="PLOT",
+ title="SHOW",
):
super().__init__(x, y, parent, layout, selected, title=title)
@@ -572,7 +556,7 @@ def __init__(
self,
x: Number,
y: Number,
- parent: Union[FlowCanvas, CanvasWidget],
+ parent: FlowCanvas | CanvasWidget,
layout: NodeLayout,
node: Node,
selected: bool = False,
@@ -647,9 +631,8 @@ def __init__(
if size is not None:
layout.width = size
layout.height = size
- dpl = DataPortLayout()
- layout.background_color = dpl.background_color
- layout.pressed_color = dpl.background_color
+ layout.background_color = parent.node.color
+ layout.pressed_color = parent.node.color
ButtonWidget.__init__(self, x=x, y=y, parent=parent, layout=layout, selected=selected, title=title,
pressed=pressed)
@@ -718,7 +701,7 @@ def __init__(
parent=parent,
layout=layout,
selected=selected,
- title=port.label_str if port.label_str is not None else title,
+ title=port.label_str if port.label_str != '' else title,
pressed=pressed
)
self.port = port
diff --git a/ryven/ironflow/flow_canvas.py b/ryven/ironflow/flow_canvas.py
index b0d8220c..6e738cf4 100644
--- a/ryven/ironflow/flow_canvas.py
+++ b/ryven/ironflow/flow_canvas.py
@@ -7,12 +7,12 @@
from ipycanvas import Canvas, hold_canvas
from time import time
-from .canvas_widgets import (
+from ryven.ironflow.canvas_widgets import (
NodeWidget, PortWidget, CanvasWidget, ButtonNodeWidget, DisplayableNodeWidget, DisplayButtonWidget
)
-from .layouts import NodeLayout
+from ryven.ironflow.layouts import NodeLayout
-from typing import TYPE_CHECKING, Optional, Union, List
+from typing import TYPE_CHECKING, Optional, Union
if TYPE_CHECKING:
from gui import GUI
from ryven.NENV import Node
@@ -55,28 +55,24 @@ def __init__(self, gui: GUI, flow: Optional[Flow] = None, width: int = 2000, hei
self.flow = flow if flow is not None else gui.flow
self._width, self._height = width, height
- self._col_background = "black" # "#584f4e"
- self._col_node_header = "blue" # "#38a8a4"
- self._col_node_selected = "#9dcea6"
- self._col_node_unselected = "#dee7bc"
-
- self._font_size = 30
- self._node_box_size = 160, 70
+ self._canvas_color = "black" # "#584f4e"
+ self._connection_style = "white"
+ self._connection_width = 3
self._canvas = Canvas(width=width, height=height)
- self._canvas.fill_style = self._col_background
+ self._canvas.fill_style = self._canvas_color
self._canvas.fill_rect(0, 0, width, height)
self._canvas.layout.width = "100%"
self._canvas.layout.height = "auto"
- self.objects_to_draw = []
- self.connections = []
-
self._canvas.on_mouse_down(self.handle_mouse_down)
self._canvas.on_mouse_up(self.handle_mouse_up)
self._canvas.on_mouse_move(self.handle_mouse_move)
self._canvas.on_key_down(self.handle_keyboard_event)
+ self.objects_to_draw = []
+ self.connections = []
+
self.x = 0
self.y = 0
self._x_move_anchor = None
@@ -88,9 +84,6 @@ def __init__(self, gui: GUI, flow: Optional[Flow] = None, width: int = 2000, hei
self._last_mouse_down = time()
self._double_click_speed = 0.25 # In seconds. TODO: Put this in a config somewhere
- self._connection_in = None
- self._node_widget = None
-
self._object_to_gui_dict = {}
@property
@@ -102,15 +95,12 @@ def gui(self):
return self._gui
def draw_connection(self, port_1: int, port_2: int) -> None:
- # i_out, i_in = path
- # out = self.objects_to_draw[i_out]
- # inp = self.objects_to_draw[i_in]
out = self._object_to_gui_dict[port_1]
inp = self._object_to_gui_dict[port_2]
canvas = self._canvas
- canvas.stroke_style = "white"
- canvas.line_width = 3
+ canvas.stroke_style = self._connection_style
+ canvas.line_width = self._connection_width
canvas.move_to(out.x, out.y)
canvas.line_to(inp.x, inp.y)
canvas.stroke()
@@ -125,27 +115,12 @@ def _built_object_to_gui_dict(self) -> None:
def canvas_restart(self) -> None:
self._canvas.clear()
- self._canvas.fill_style = self._col_background
+ self._canvas.fill_style = self._canvas_color
self._canvas.fill_rect(0, 0, self._width, self._height)
def handle_keyboard_event(self, key: str, shift_key, ctrl_key, meta_key) -> None:
pass # TODO
- def set_connection(self, ind_node: int) -> None:
- if self._connection_in is None:
- self._connection_in = ind_node
- else:
- out = self.objects_to_draw[self._connection_in].node.outputs[0]
- inp = self.objects_to_draw[ind_node].node.inputs[-1]
- if self.flow.connect_nodes(inp, out) is None:
- i_con = self.connections.index([self._connection_in, ind_node])
- del self.connections[i_con]
- else:
- self.connections.append([self._connection_in, ind_node])
-
- self._connection_in = None
- self.deselect_all()
-
def deselect_all(self) -> None:
[o.deselect() for o in self.objects_to_draw]
self.redraw()
@@ -181,13 +156,13 @@ def handle_mouse_down(self, x: Number, y: Number):
def handle_mouse_up(self, x: Number, y: Number):
self._mouse_is_down = False
- def get_element_at_xy(self, x_in: Number, y_in: Number) -> Union[CanvasWidget, None]:
+ def get_element_at_xy(self, x_in: Number, y_in: Number) -> CanvasWidget | None:
for o in self.objects_to_draw:
if o.is_here(x_in, y_in):
return o.get_element_at_xy(x_in, y_in)
return None
- def get_selected_objects(self) -> List[CanvasWidget]:
+ def get_selected_objects(self) -> list[CanvasWidget]:
return [o for o in self.objects_to_draw if o.selected]
def handle_mouse_move(self, x: Number, y: Number) -> None:
@@ -212,20 +187,18 @@ def redraw(self) -> None:
self.draw_connection(c.inp, c.out)
def load_node(self, x: Number, y: Number, node: Node) -> NodeWidget:
- # print ('node: ', node.identifier, node.GLOBAL_ID)
-
layout = NodeLayout()
- if hasattr(node, "main_widget_class"):
- if node.main_widget_class is not None:
- # node.title = str(node.main_widget_class)
- f = eval(node.main_widget_class)
- s = f(x, y, parent=self, layout=layout, node=node)
+ if hasattr(node, "main_widget_class") and node.main_widget_class is not None:
+ if isinstance(node.main_widget_class, str):
+ node_class = eval(node.main_widget_class)
+ elif issubclass(node.main_widget_class, NodeWidget):
+ node_class = node.main_widget_class
else:
- s = NodeWidget(x, y, parent=self, layout=layout, node=node)
- # print ('s: ', s)
+ raise TypeError(f"main_widget_class {node.main_widget_class} not recognized")
else:
- s = NodeWidget(x, y, parent=self, layout=layout, node=node)
+ node_class = NodeWidget
+ s = node_class(x=x, y=y, parent=self, layout=layout, node=node)
self.objects_to_draw.append(s)
return s
diff --git a/ryven/ironflow/gui.py b/ryven/ironflow/gui.py
index e3226b1c..5774489a 100644
--- a/ryven/ironflow/gui.py
+++ b/ryven/ironflow/gui.py
@@ -21,7 +21,7 @@
from ryven.ironflow.node_interface import NodeInterface
from ryven.ironflow.flow_canvas import FlowCanvas
-from typing import Optional, Dict
+from typing import Optional
from ryvencore import Session
alg_modes = ["data", "exec"]
@@ -49,7 +49,7 @@ def create_script(
self,
title: Optional[str] = None,
create_default_logs: bool = True,
- data: Optional[Dict] = None
+ data: Optional[dict] = None
) -> None:
super().create_script(title=title, create_default_logs=create_default_logs, data=data)
self._flow_canvases.append(FlowCanvas(gui=self))
@@ -59,14 +59,26 @@ def delete_script(self) -> None:
super().delete_script()
@property
- def flow_canvas_widget(self):
+ def flow_canvas(self):
return self._flow_canvases[self.active_script_index]
@property
def new_node_class(self):
return self._nodes_dict[self.modules_dropdown.value][self.node_selector.value]
- def load_from_data(self, data: Dict) -> None:
+ def serialize(self) -> dict:
+ data = super().serialize()
+ currently_active = self.active_script_index
+ for i_script, script in enumerate(self.session.scripts):
+ all_data = data["scripts"][i_script]["flow"]["nodes"]
+ self.active_script_index = i_script
+ for i, node_widget in enumerate(self.flow_canvas.objects_to_draw):
+ all_data[i]["pos x"] = node_widget.x
+ all_data[i]["pos y"] = node_widget.y
+ self.active_script_index = currently_active
+ return data
+
+ def load_from_data(self, data: dict) -> None:
super().load_from_data(data)
self._flow_canvases = []
for i_script, script in enumerate(self.session.scripts):
@@ -126,6 +138,17 @@ def draw(self) -> widgets.VBox:
icon="map-marker", # TODO: Use location-dot once this is available
layout=button_layout
)
+ buttons = [
+ self.btn_save,
+ self.btn_load,
+ self.btn_help_node,
+ self.btn_add_node,
+ self.btn_delete_node,
+ self.btn_create_script,
+ self.btn_rename_script,
+ self.btn_delete_script,
+ self.btn_zero_location,
+ ]
self.text_input_panel = widgets.HBox([])
self.text_input_field = widgets.Text(value="INIT VALUE", description="DESCRIPTION")
@@ -144,8 +167,6 @@ def draw(self) -> widgets.VBox:
self.node_selector = widgets.RadioButtons(
options=nodes_options,
value=list(nodes_options)[0],
- # layout={'width': 'max-content'}, # If the items' names are long
- # description='Nodes:',
disabled=False,
)
@@ -162,7 +183,7 @@ def draw(self) -> widgets.VBox:
self.btn_rename_script.on_click(self.click_rename_script)
self.btn_input_text_ok.on_click(self.click_input_text_ok)
self.text_input_field.on_submit(self.click_input_text_ok)
- # ^ Ignore the deprecation warning, 'observe' does function the way we actually want
+ # ^ Ignore the deprecation warning, 'observe' doesn't function the way we actually want
# https://github.com/jupyter-widgets/ipywidgets/issues/2446
self.btn_input_text_cancel.on_click(self.click_input_text_cancel)
self.btn_delete_script.on_click(self.click_delete_script)
@@ -175,15 +196,7 @@ def draw(self) -> widgets.VBox:
[
self.modules_dropdown,
self.alg_mode_dropdown,
- self.btn_save,
- self.btn_load,
- self.btn_help_node,
- self.btn_add_node,
- self.btn_delete_node,
- self.btn_create_script,
- self.btn_rename_script,
- self.btn_delete_script,
- self.btn_zero_location,
+ *buttons,
]
),
self.text_input_panel,
@@ -198,73 +211,16 @@ def draw(self) -> widgets.VBox:
# Type hinting for unused `change` argument in callbacks taken from ipywidgets docs:
# https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html#Traitlet-events
- def click_add_node(self, change: dict) -> None:
- self.flow_canvas_widget.add_node(10, 10, self.new_node_class)
-
- def click_delete_node(self, change: Dict) -> None:
- self.flow_canvas_widget.delete_selected()
-
- def change_modules_dropdown(self, change: Dict) -> None:
+ def change_modules_dropdown(self, change: dict) -> None:
self.node_selector.options = sorted(self._nodes_dict[self.modules_dropdown.value].keys())
- def change_alg_mode_dropdown(self, change: Dict) -> None:
+ def change_alg_mode_dropdown(self, change: dict) -> None:
# Current behaviour: Updates the flow mode for all scripts
# TODO: Change only for the active script, and update the dropdown on tab (script) switching
for script in self.session.scripts:
script.flow.set_algorithm_mode(self.alg_mode_dropdown.value)
- def change_script_tabs(self, change: Dict):
- if change['name'] == 'selected_index' and change['new'] is not None:
- self._depopulate_text_input_panel()
- if self.script_tabs.selected_index == self.n_scripts:
- self.create_script()
- self._update_tabs_from_model()
- else:
- self.active_script_index = self.script_tabs.selected_index
- self.flow_canvas_widget.redraw()
-
- def _populate_text_input_panel(self, description, initial_value, description_tooltip=None):
- self.text_input_panel.children = [
- self.text_input_field,
- self.btn_input_text_ok,
- self.btn_input_text_cancel
- ]
- self.text_input_field.description = description
- description_tooltip = description_tooltip if description_tooltip is not None else description
- self.text_input_field.description_tooltip = description_tooltip
- self.text_input_field.value = initial_value
-
- def _depopulate_text_input_panel(self) -> None:
- self.text_input_panel.children = []
-
- def click_input_text_ok(self, change: Dict) -> None:
- self._context_actions[self._context](self.text_input_field.value)
- self._depopulate_text_input_panel()
-
- def click_input_text_cancel(self, change: Dict) -> None:
- self._depopulate_text_input_panel()
- self._print("")
-
- def _set_context(self, context):
- if context not in self._context_actions.keys():
- raise KeyError(f"Expected a context action among {list(self._context_actions.keys())} but got {context}.")
- self._context = context
-
- def click_node_help(self, change: dict) -> None:
- def _pretty_docstring(node_class):
- """
- If we just pass a string, `display` doesn't resolve newlines.
- If we pass a `print`ed string, `display` also shows the `None` value returned by `print`
- So we use this ugly hack.
- """
- string = f"{node_class.__name__.replace('_Node', '')}:\n{node_class.__doc__}"
- return HTML(string.replace("\n", "
").replace("\t", " ").replace(" ", " "))
-
- self.out_log.clear_output()
- with self.out_log:
- display(_pretty_docstring(self.new_node_class))
-
- def click_save(self, change: Dict) -> None:
+ def click_save(self, change: dict) -> None:
self._depopulate_text_input_panel()
self._populate_text_input_panel(
"Save file",
@@ -278,7 +234,7 @@ def _save_context_action(self, file_name):
self.save(f"{file_name}.json")
self._print(f"Session saved to {file_name}.json")
- def click_load(self, change: Dict) -> None:
+ def click_load(self, change: dict) -> None:
self._depopulate_text_input_panel()
self._populate_text_input_panel(
"Load file",
@@ -295,14 +251,34 @@ def _load_context_action(self, file_name):
self.out_log.clear_output()
self._print(f"Session loaded from {file_name}.json")
+ def click_node_help(self, change: dict) -> None:
+ def _pretty_docstring(node_class):
+ """
+ If we just pass a string, `display` doesn't resolve newlines.
+ If we pass a `print`ed string, `display` also shows the `None` value returned by `print`
+ So we use this ugly hack.
+ """
+ string = f"{node_class.__name__.replace('_Node', '')}:\n{node_class.__doc__}"
+ return HTML(string.replace("\n", "
").replace("\t", " ").replace(" ", " "))
+
+ self.out_log.clear_output()
+ with self.out_log:
+ display(_pretty_docstring(self.new_node_class))
+
+ def click_add_node(self, change: dict) -> None:
+ self.flow_canvas.add_node(10, 10, self.new_node_class)
+
+ def click_delete_node(self, change: dict) -> None:
+ self.flow_canvas.delete_selected()
+
def click_create_script(self, change: dict) -> None:
self.create_script()
self._update_tabs_from_model()
self.script_tabs.selected_index = self.n_scripts - 1
self.active_script_index = self.script_tabs.selected_index
- self.flow_canvas_widget.redraw()
+ self.flow_canvas.redraw()
- def click_rename_script(self, change: Dict) -> None:
+ def click_rename_script(self, change: dict) -> None:
self._depopulate_text_input_panel()
self._populate_text_input_panel(
"New name",
@@ -321,14 +297,51 @@ def _rename_context_action(self, new_name):
else:
self._print(f"INVALID NAME: Failed to rename script '{self.script.title}' to '{new_name}'.")
- def click_delete_script(self, change: Dict) -> None:
+ def click_delete_script(self, change: dict) -> None:
self.delete_script()
self._update_tabs_from_model()
def click_zero_location(self, change: dict) -> None:
- self.flow_canvas_widget.x = 0
- self.flow_canvas_widget.y = 0
- self.flow_canvas_widget.redraw()
+ self.flow_canvas.x = 0
+ self.flow_canvas.y = 0
+ self.flow_canvas.redraw()
+
+ def _populate_text_input_panel(self, description, initial_value, description_tooltip=None):
+ self.text_input_panel.children = [
+ self.text_input_field,
+ self.btn_input_text_ok,
+ self.btn_input_text_cancel
+ ]
+ self.text_input_field.description = description
+ description_tooltip = description_tooltip if description_tooltip is not None else description
+ self.text_input_field.description_tooltip = description_tooltip
+ self.text_input_field.value = initial_value
+
+ def _depopulate_text_input_panel(self) -> None:
+ self.text_input_panel.children = []
+
+ def click_input_text_ok(self, change: dict) -> None:
+ self._context_actions[self._context](self.text_input_field.value)
+ self._depopulate_text_input_panel()
+
+ def click_input_text_cancel(self, change: dict) -> None:
+ self._depopulate_text_input_panel()
+ self._print("")
+
+ def _set_context(self, context):
+ if context not in self._context_actions.keys():
+ raise KeyError(f"Expected a context action among {list(self._context_actions.keys())} but got {context}.")
+ self._context = context
+
+ def change_script_tabs(self, change: dict):
+ if change['name'] == 'selected_index' and change['new'] is not None:
+ self._depopulate_text_input_panel()
+ if self.script_tabs.selected_index == self.n_scripts:
+ self.create_script()
+ self._update_tabs_from_model()
+ else:
+ self.active_script_index = self.script_tabs.selected_index
+ self.flow_canvas.redraw()
def _update_tabs_from_model(self):
self.script_tabs.selected_index = None
@@ -352,5 +365,4 @@ def _add_new_script_tab(self):
def _print(self, text: str) -> None:
with self.out_log:
self.out_log.clear_output()
-
print(text)
diff --git a/ryven/ironflow/layouts.py b/ryven/ironflow/layouts.py
index b1224bc2..37d8d02f 100644
--- a/ryven/ironflow/layouts.py
+++ b/ryven/ironflow/layouts.py
@@ -38,6 +38,7 @@ class NodeLayout(Layout):
width: int = 200
height: int = 100
font_size: int = 22
+ title_box_height: int = 30
@dataclass
@@ -61,7 +62,7 @@ class ExecPortLayout(PortLayout):
@dataclass
class ButtonLayout(Layout):
font_size: int = 16
- width: int = 50
+ width: int = 60
height: int = 20
background_color: str = "darkgray"
pressed_color: str = "dimgray"
diff --git a/ryven/ironflow/models.py b/ryven/ironflow/models.py
index e8797cad..3bfdff61 100644
--- a/ryven/ironflow/models.py
+++ b/ryven/ironflow/models.py
@@ -22,7 +22,7 @@
from ryvencore import Session, Script, Flow
from ryven.main.utils import import_nodes_package, NodesPackage
-from typing import Optional, Dict, Type
+from typing import Optional, Type
import ryven.NENV as NENV
@@ -34,7 +34,7 @@
("built_in",),
("std",),
("pyiron",),
-]] # , ("mynodes",)
+]]
class HasSession(ABC):
@@ -94,7 +94,7 @@ def create_script(
self,
title: Optional[str] = None,
create_default_logs: bool = True,
- data: Optional[Dict] = None
+ data: Optional[dict] = None
) -> None:
self.session.create_script(
title=title if title is not None else self.next_auto_script_name,
@@ -120,17 +120,8 @@ def save(self, file_path: str) -> None:
with open(file_path, "w") as f:
f.write(json.dumps(data, indent=4))
- def serialize(self) -> Dict:
- currently_active = self.active_script_index
- data = self.session.serialize()
- for i_script, script in enumerate(self.session.scripts):
- all_data = data["scripts"][i_script]["flow"]["nodes"]
- self.active_script_index = i_script
- for i, node_widget in enumerate(self.flow_canvas_widget.objects_to_draw):
- all_data[i]["pos x"] = node_widget.x
- all_data[i]["pos y"] = node_widget.y
- self.active_script_index = currently_active
- return data
+ def serialize(self) -> dict:
+ return self.session.serialize()
def load(self, file_path: str) -> None:
with open(file_path, "r") as f:
@@ -138,7 +129,7 @@ def load(self, file_path: str) -> None:
self.load_from_data(data)
- def load_from_data(self, data: Dict) -> None:
+ def load_from_data(self, data: dict) -> None:
for script in self.session.scripts[::-1]:
self.session.delete_script(script)
self.session.load(data)
diff --git a/ryven/ironflow/node_interface.py b/ryven/ironflow/node_interface.py
index a80984bd..848b9508 100644
--- a/ryven/ironflow/node_interface.py
+++ b/ryven/ironflow/node_interface.py
@@ -11,7 +11,7 @@
import pickle
import base64
-from typing import TYPE_CHECKING, Dict, Union, Callable
+from typing import TYPE_CHECKING, Callable
if TYPE_CHECKING:
from gui import GUI
from ryven.NENV import Node
@@ -42,7 +42,7 @@ def __init__(self, central_gui: GUI):
self._central_gui = central_gui
# self.input = []
- def gui_object(self) -> Union[widgets.FloatSlider, widgets.Box]:
+ def gui_object(self) -> widgets.FloatSlider | widgets.Box:
if "slider" in self.node.title.lower():
self.gui = widgets.FloatSlider(
value=self.node.val, min=0, max=10, continuous_update=False
@@ -53,10 +53,10 @@ def gui_object(self) -> Union[widgets.FloatSlider, widgets.Box]:
self.gui = widgets.Box()
return self.gui
- def gui_object_change(self, change: Dict) -> None:
+ def gui_object_change(self, change: dict) -> None:
self.node.set_state({"val": change["new"]}, 0)
self.node.update_event()
- self._central_gui.flow_canvas_widget.redraw()
+ self._central_gui.flow_canvas.redraw()
def input_widgets(self) -> None:
self._input = []
@@ -112,10 +112,10 @@ def input_widgets(self) -> None:
# inp_widget.value = dtype_state['default']
def input_change_i(self, i_c) -> Callable:
- def input_change(change: Dict) -> None:
+ def input_change(change: dict) -> None:
self.node.inputs[i_c].val = change["new"]
self.node.update_event()
- self._central_gui.flow_canvas_widget.redraw()
+ self._central_gui.flow_canvas.redraw()
return input_change
def draw(self) -> widgets.HBox:
diff --git a/ryven/nodes/pyiron/atomistics_nodes.py b/ryven/nodes/pyiron/atomistics_nodes.py
index 5650964e..fa2b97f1 100644
--- a/ryven/nodes/pyiron/atomistics_nodes.py
+++ b/ryven/nodes/pyiron/atomistics_nodes.py
@@ -6,12 +6,13 @@
import numpy as np
from pyiron_atomistics import Project
from ryven.NENV import Node, NodeInputBP, NodeOutputBP, dtypes
+from ryven.ironflow.canvas_widgets import DisplayableNodeWidget, ButtonNodeWidget
from abc import ABC, abstractmethod
from ryven.nodes.std.special_nodes import DualNodeBase
-__author__ = "Joerg Neugebauer"
+__author__ = "Joerg Neugebauer, Liam Huber"
__copyright__ = (
"Copyright 2020, Max-Planck-Institut für Eisenforschung GmbH - "
"Computational Materials Design (CM) Department"
@@ -33,7 +34,7 @@ def __init__(self, params):
class NodeWithDisplay(NodeBase, ABC):
- main_widget_class = "DisplayableNodeWidget"
+ main_widget_class = DisplayableNodeWidget
def __init__(self, params):
super().__init__(params)
@@ -220,7 +221,6 @@ class GenericOutput_Node(NodeWithDisplay):
"""Select Generic Output item"""
version = "v0.1"
-
title = "GenericOutput"
init_inputs = [
NodeInputBP(dtype=dtypes.Data(size="m"), label="job"),
@@ -234,9 +234,6 @@ class GenericOutput_Node(NodeWithDisplay):
init_outputs = [
NodeOutputBP(),
]
-
- # main_widget_class = widgets.Result_Node_MainWidget
- # main_widget_pos = 'between ports'
color = "#c69a15"
def __init__(self, params):
@@ -390,8 +387,6 @@ class Result_Node(NodeBase):
init_inputs = [
NodeInputBP(type_="data"),
]
- # main_widget_class = widgets.Result_Node_MainWidget
- # main_widget_pos = 'between ports'
color = "#c69a15"
def __init__(self, params):
@@ -438,12 +433,7 @@ def update_event(self, inp=-1):
self.exec_output(2)
elif inp > 0:
self._count = 0
- self.val = self._count
- # for e in self.input(0):
- # self.set_output_val(1, e)
- # self.exec_output(0)
-
- # self.exec_output(2)
+ self.val = self._count
class ExecCounter_Node(DualNodeBase):
@@ -472,8 +462,7 @@ def update_event(self, inp=-1):
class Click_Node(NodeBase):
title = "Click"
version = "v0.1"
- main_widget_class = "ButtonNodeWidget"
- main_widget_pos = "between ports"
+ main_widget_class = ButtonNodeWidget
init_inputs = []
init_outputs = [NodeOutputBP(type_="exec")]
color = "#99dd55"
diff --git a/tests/unit/test_flow_canvas.py b/tests/unit/test_flow_canvas.py
index 92d1006c..674600fb 100644
--- a/tests/unit/test_flow_canvas.py
+++ b/tests/unit/test_flow_canvas.py
@@ -11,7 +11,7 @@ class TestCanvasObect(TestCase):
def setUp(self):
self.gui = GUI('gui')
- self.canvas = self.gui.flow_canvas_widget
+ self.canvas = self.gui.flow_canvas
@classmethod
def tearDownClass(cls):
diff --git a/tests/unit/test_gui.py b/tests/unit/test_gui.py
index 1b8ee0fd..59a67704 100644
--- a/tests/unit/test_gui.py
+++ b/tests/unit/test_gui.py
@@ -17,9 +17,9 @@ def tearDown(self) -> None:
def test_multiple_scripts(self):
gui = GUI('foo')
- gui.flow_canvas_widget.add_node(0, 0, gui._nodes_dict['nodes']['val'])
+ gui.flow_canvas.add_node(0, 0, gui._nodes_dict['nodes']['val'])
gui.create_script()
- gui.flow_canvas_widget.add_node(1, 1, gui._nodes_dict['nodes']['result'])
+ gui.flow_canvas.add_node(1, 1, gui._nodes_dict['nodes']['result'])
canonical_file_name = f"{gui.session_title}.json"
gui.save(canonical_file_name)
new_gui = GUI('something_random')
@@ -45,7 +45,7 @@ def test_multiple_scripts(self):
def test_saving_and_loading(self):
title = 'foo'
gui = GUI(title)
- canvas = gui.flow_canvas_widget
+ canvas = gui.flow_canvas
flow = gui._session.scripts[0].flow
canvas.add_node(0, 0, gui._nodes_dict['nodes']['val']) # Need to create with canvas instead of flow
@@ -99,7 +99,7 @@ def update_event(self, inp=-1):
gui.register_user_node(MyNode)
self.assertIn(MyNode, gui.session.nodes)
- gui.flow_canvas_widget.add_node(0, 0, gui._nodes_dict["user"][MyNode.title])
+ gui.flow_canvas.add_node(0, 0, gui._nodes_dict["user"][MyNode.title])
gui.flow.nodes[0].inputs[0].update(1)
self.assertEqual(gui.flow.nodes[0].outputs[0].val, 43)
@@ -120,7 +120,7 @@ def update_event(self, inp=-1):
gui.flow.nodes[0].inputs[0].update(2)
self.assertEqual(gui.flow.nodes[0].outputs[0].val, 44, msg="Expected to be using instance of old class")
- gui.flow_canvas_widget.add_node(1, 1, gui._nodes_dict["user"][MyNode.title])
+ gui.flow_canvas.add_node(1, 1, gui._nodes_dict["user"][MyNode.title])
gui.flow.nodes[1].inputs[0].update(2)
self.assertEqual(gui.flow.nodes[1].outputs[0].val, -40, msg="New node instances should reflect updated class.")