diff --git a/blocks/cnn_model.ocbb b/blocks/cnn_model.ocbb
new file mode 100644
index 00000000..56edefa6
--- /dev/null
+++ b/blocks/cnn_model.ocbb
@@ -0,0 +1,18 @@
+{
+ "title": "CNN",
+ "block_type": "code",
+ "source": "input_size = 28\r\nclasses = 5\r\nmodel = Sequential()\r\nmodel.add(layers.Conv2D(input_size, kernel_size=(3,3), input_shape=(input_size,input_size,1)))\r\nmodel.add(layers.MaxPooling2D(pool_size=(2, 2)))\r\nmodel.add(layers.Flatten())\r\nmodel.add(layers.Dense(128, activation=tf.nn.relu))\r\nmodel.add(layers.Dropout(0.2))\r\nmodel.add(layers.Dense(classes,activation=tf.nn.softmax))\r\n\r\nmodel.compile(optimizer='adam', \r\n loss='sparse_categorical_crossentropy')",
+ "stdout": "",
+ "image": "",
+ "splitter_pos": [80,50],
+ "width": 600,
+ "height": 400,
+ "metadata": {
+ "title_metadata": {
+ "color": "white",
+ "font": "Ubuntu",
+ "size": 10,
+ "padding": 4.0
+ }
+ }
+}
\ No newline at end of file
diff --git a/blocks/empty.ocbb b/blocks/empty.ocbb
new file mode 100644
index 00000000..4ff5bdd3
--- /dev/null
+++ b/blocks/empty.ocbb
@@ -0,0 +1,18 @@
+{
+ "title": "Empty",
+ "block_type": "code",
+ "source": "",
+ "stdout": "",
+ "image": "",
+ "splitter_pos": [88,41],
+ "width": 618,
+ "height": 184,
+ "metadata": {
+ "title_metadata": {
+ "color": "white",
+ "font": "Ubuntu",
+ "size": 10,
+ "padding": 4.0
+ }
+ }
+}
\ No newline at end of file
diff --git a/blocks/import_ml.ocbb b/blocks/import_ml.ocbb
new file mode 100644
index 00000000..6b6440fa
--- /dev/null
+++ b/blocks/import_ml.ocbb
@@ -0,0 +1,18 @@
+{
+ "title": "Imports for ML",
+ "block_type": "code",
+ "source": "import tensorflow as tf\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.models import Sequential",
+ "stdout": "",
+ "image": "",
+ "splitter_pos": [80,50],
+ "width": 600,
+ "height": 400,
+ "metadata": {
+ "title_metadata": {
+ "color": "white",
+ "font": "Ubuntu",
+ "size": 10,
+ "padding": 4.0
+ }
+ }
+}
\ No newline at end of file
diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py
index 855ede76..0d13dd0f 100644
--- a/opencodeblocks/graphics/blocks/block.py
+++ b/opencodeblocks/graphics/blocks/block.py
@@ -325,7 +325,8 @@ def deserialize(self, data: dict, hashmap: dict = None,
socket = OCBSocket(block=self)
socket.deserialize(socket_data, hashmap, restore_id)
self.add_socket(socket)
- hashmap.update({socket_data['id']: socket})
+ if hashmap is not None:
+ hashmap.update({socket_data['id']: socket})
class OCBSplitterHandle(QSplitterHandle):
diff --git a/opencodeblocks/graphics/blocks/blocksizegrip.py b/opencodeblocks/graphics/blocks/blocksizegrip.py
index c86c5c32..2c8eb4ed 100644
--- a/opencodeblocks/graphics/blocks/blocksizegrip.py
+++ b/opencodeblocks/graphics/blocks/blocksizegrip.py
@@ -33,7 +33,7 @@ def mousePressEvent(self, mouseEvent: QMouseEvent):
self.mouseY = mouseEvent.globalY()
self.resizing = True
- def mouseReleaseEvent(self, mouseEvent: QMouseEvent):
+ def mouseReleaseEvent(self, mouseEvent: QMouseEvent): # pylint:disable=unused-argument
""" Stop the resizing """
self.resizing = False
self.block.scene().history.checkpoint("Resized block", set_modified=True)
diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py
index 8db7f8dd..976ac50e 100644
--- a/opencodeblocks/graphics/blocks/codeblock.py
+++ b/opencodeblocks/graphics/blocks/codeblock.py
@@ -112,7 +112,7 @@ def image(self, value: str):
ba = QByteArray.fromBase64(str.encode(self.image))
pixmap = QPixmap()
pixmap.loadFromData(ba)
- text = '
'.format(self.image)
+ text = f'
'
self.output_panel.setText(text)
@source.setter
diff --git a/opencodeblocks/graphics/scene/scene.py b/opencodeblocks/graphics/scene/scene.py
index 6708e1ea..fa99e84b 100644
--- a/opencodeblocks/graphics/scene/scene.py
+++ b/opencodeblocks/graphics/scene/scene.py
@@ -25,10 +25,10 @@ class OCBScene(QGraphicsScene, Serializable):
""" Scene for the OCB Window. """
def __init__(self, parent=None,
- background_color:str="#393939",
- grid_color:str="#292929", grid_light_color:str="#2f2f2f",
- width:int=64000, height:int=64000,
- grid_size:int=20, grid_squares:int=5):
+ background_color: str = "#393939",
+ grid_color: str = "#292929", grid_light_color: str = "#2f2f2f",
+ width: int = 64000, height: int = 64000,
+ grid_size: int = 20, grid_squares: int = 5):
Serializable.__init__(self)
QGraphicsScene.__init__(self, parent=parent)
@@ -39,7 +39,8 @@ def __init__(self, parent=None,
self.grid_squares = grid_squares
self.width, self.height = width, height
- self.setSceneRect(-self.width//2, -self.height//2, self.width, self.height)
+ self.setSceneRect(-self.width // 2, -self.height //
+ 2, self.width, self.height)
self.setBackgroundBrush(self._background_color)
self._has_been_modified = False
@@ -52,13 +53,14 @@ def __init__(self, parent=None,
def has_been_modified(self):
""" True if the scene has been modified, False otherwise. """
return self._has_been_modified
+
@has_been_modified.setter
- def has_been_modified(self, value:bool):
+ def has_been_modified(self, value: bool):
self._has_been_modified = value
for callback in self._has_been_modified_listeners:
callback()
- def addHasBeenModifiedListener(self, callback:FunctionType):
+ def addHasBeenModifiedListener(self, callback: FunctionType):
""" Add a callback that will trigger when the scene has been modified. """
self._has_been_modified_listeners.append(callback)
@@ -112,12 +114,12 @@ def drawGrid(self, painter: QPainter, rect: QRectF):
painter.setPen(pen)
painter.drawLines(*lines_light)
- def save(self, filepath:str):
+ def save(self, filepath: str):
""" Save the scene into filepath. """
self.save_to_ipyg(filepath)
self.has_been_modified = False
- def save_to_ipyg(self, filepath:str):
+ def save_to_ipyg(self, filepath: str):
""" Save the scene into filepath as interactive python graph (.ipyg). """
if '.' not in filepath:
filepath += '.ipyg'
@@ -129,7 +131,7 @@ def save_to_ipyg(self, filepath:str):
with open(filepath, 'w', encoding='utf-8') as file:
file.write(json.dumps(self.serialize(), indent=4))
- def load(self, filepath:str):
+ def load(self, filepath: str):
""" Load a saved scene.
Args:
@@ -145,7 +147,7 @@ def load(self, filepath:str):
self.history.checkpoint("Loaded scene")
self.has_been_modified = False
- def load_from_ipyg(self, filepath:str):
+ def load_from_ipyg(self, filepath: str):
""" Load an interactive python graph (.ipyg) into the scene.
Args:
@@ -177,7 +179,34 @@ def serialize(self) -> OrderedDict:
('edges', [edge.serialize() for edge in edges]),
])
- def deserialize(self, data: OrderedDict, hashmap:dict=None, restore_id=True):
+ def create_block_from_file(
+ self, filepath: str, x: float = 0, y: float = 0):
+ """ Create a new block from a .ocbb file """
+ with open(filepath, 'r', encoding='utf-8') as file:
+ data = json.loads(file.read())
+ data["position"] = [x, y]
+ data["sockets"] = {}
+ self.create_block(data, None, False)
+
+ def create_block(self, data: OrderedDict, hashmap: dict = None,
+ restore_id: bool = True) -> OCBBlock:
+ """ Create a new block from an OrderedDict """
+
+ block = None
+ if data['block_type'] == 'base':
+ block = OCBBlock()
+ elif data['block_type'] == 'code':
+ block = OCBCodeBlock()
+ else:
+ raise NotImplementedError()
+ block.deserialize(data, hashmap, restore_id)
+ self.addItem(block)
+ if hashmap is not None:
+ hashmap.update({data['id']: block})
+ return block
+
+ def deserialize(self, data: OrderedDict,
+ hashmap: dict = None, restore_id: bool = True):
self.clear()
hashmap = hashmap if hashmap is not None else {}
if restore_id:
@@ -185,15 +214,7 @@ def deserialize(self, data: OrderedDict, hashmap:dict=None, restore_id=True):
# Create blocks
for block_data in data['blocks']:
- if block_data['block_type'] == 'base':
- block = OCBBlock()
- elif block_data['block_type'] == 'code':
- block = OCBCodeBlock()
- else:
- raise NotImplementedError()
- block.deserialize(block_data, hashmap, restore_id)
- self.addItem(block)
- hashmap.update({block_data['id']: block})
+ self.create_block(block_data, hashmap, restore_id)
# Create edges
for edge_data in data['edges']:
diff --git a/opencodeblocks/graphics/view.py b/opencodeblocks/graphics/view.py
index 6d399240..aebd6ef0 100644
--- a/opencodeblocks/graphics/view.py
+++ b/opencodeblocks/graphics/view.py
@@ -3,11 +3,16 @@
""" Module for the OCB View """
+import json
+import os
+from typing import List, Tuple
+
from PyQt5.QtCore import QEvent, QPointF, Qt
-from PyQt5.QtGui import QMouseEvent, QPainter, QWheelEvent
-from PyQt5.QtWidgets import QGraphicsView
+from PyQt5.QtGui import QMouseEvent, QPainter, QWheelEvent, QContextMenuEvent
+from PyQt5.QtWidgets import QGraphicsView, QMenu
from PyQt5.sip import isdeleted
+
from opencodeblocks.graphics.scene import OCBScene
from opencodeblocks.graphics.socket import OCBSocket
from opencodeblocks.graphics.edge import OCBEdge
@@ -28,8 +33,8 @@ class OCBView(QGraphicsView):
""" View for the OCB Window. """
- def __init__(self, scene:OCBScene, parent=None,
- zoom_step:float=1.25, zoom_min:float=0.2, zoom_max:float=5):
+ def __init__(self, scene: OCBScene, parent=None,
+ zoom_step: float = 1.25, zoom_min: float = 0.2, zoom_max: float = 5):
super().__init__(parent=parent)
self.mode = MODE_NOOP
self.zoom = 1
@@ -56,10 +61,12 @@ def init_ui(self):
QGraphicsView.ViewportUpdateMode.FullViewportUpdate
)
# Remove scroll bars
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
+ self.setHorizontalScrollBarPolicy(
+ Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
# Zoom on cursor
- self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
+ self.setTransformationAnchor(
+ QGraphicsView.ViewportAnchor.AnchorUnderMouse)
# Selection box
self.setDragMode(QGraphicsView.DragMode.RubberBandDrag)
@@ -73,8 +80,6 @@ def mousePressEvent(self, event: QMouseEvent):
self.middleMouseButtonPress(event)
elif event.button() == Qt.MouseButton.LeftButton:
self.leftMouseButtonPress(event)
- elif event.button() == Qt.MouseButton.RightButton:
- self.rightMouseButtonPress(event)
else:
super().mousePressEvent(event)
@@ -84,8 +89,6 @@ def mouseReleaseEvent(self, event: QMouseEvent):
self.middleMouseButtonRelease(event)
elif event.button() == Qt.MouseButton.LeftButton:
self.leftMouseButtonRelease(event)
- elif event.button() == Qt.MouseButton.RightButton:
- self.rightMouseButtonRelease(event)
else:
super().mouseReleaseEvent(event)
@@ -96,21 +99,13 @@ def mouseMoveEvent(self, event: QMouseEvent) -> None:
if event is not None:
super().mouseMoveEvent(event)
- def middleMouseButtonPress(self, event: QMouseEvent):
- """ OCBView reaction to middleMouseButtonPress event. """
- super().mousePressEvent(event)
-
- def middleMouseButtonRelease(self, event: QMouseEvent):
- """ OCBView reaction to middleMouseButtonRelease event. """
- super().mouseReleaseEvent(event)
-
def leftMouseButtonPress(self, event: QMouseEvent):
""" OCBView reaction to leftMouseButtonPress event. """
# If clicked on a block, bring it forward.
item_at_click = self.itemAt(event.pos())
if item_at_click is not None:
while item_at_click.parentItem() is not None:
- if isinstance(item_at_click,OCBBlock):
+ if isinstance(item_at_click, OCBBlock):
break
item_at_click = item_at_click.parentItem()
@@ -128,17 +123,43 @@ def leftMouseButtonRelease(self, event: QMouseEvent):
if event is not None:
super().mouseReleaseEvent(event)
- def rightMouseButtonPress(self, event: QMouseEvent):
- """ OCBView reaction to rightMouseButtonPress event. """
+ def middleMouseButtonPress(self, event: QMouseEvent):
+ """ OCBView reaction to middleMouseButtonPress event. """
event = self.drag_scene(event, "press")
super().mousePressEvent(event)
- def rightMouseButtonRelease(self, event: QMouseEvent):
- """ OCBView reaction to rightMouseButtonRelease event. """
+ def middleMouseButtonRelease(self, event: QMouseEvent):
+ """ OCBView reaction to middleMouseButtonRelease event. """
event = self.drag_scene(event, "release")
super().mouseReleaseEvent(event)
self.setDragMode(QGraphicsView.DragMode.RubberBandDrag)
+ def retreiveBlockTypes(self) -> List[Tuple[str]]:
+ block_type_files = os.listdir("blocks")
+ block_types = []
+ for b in block_type_files:
+ filepath = os.path.join("blocks", b)
+ with open(filepath, "r", encoding="utf-8") as file:
+ data = json.loads(file.read())
+ title = "New Block"
+ if "title" in data:
+ title = f"New {data['title']} Block"
+ block_types.append((filepath, title))
+ return block_types
+
+ def contextMenuEvent(self, event: QContextMenuEvent):
+ """ Displays the context menu when inside a view """
+ menu = QMenu(self)
+ actionPool = []
+ for filepath, block_name in self.retreiveBlockTypes():
+ actionPool.append((filepath, menu.addAction(block_name)))
+
+ selectedAction = menu.exec_(self.mapToGlobal(event.pos()))
+ for filepath, action in actionPool:
+ if action == selectedAction:
+ p = self.mapToScene(event.pos())
+ self.scene().create_block_from_file(filepath, p.x(), p.y())
+
def wheelEvent(self, event: QWheelEvent):
""" Handles zooming with mouse wheel events. """
if Qt.Modifier.CTRL == int(event.modifiers()):
@@ -168,7 +189,8 @@ def bring_block_forward(self, block: OCBBlock):
block: Block to bring forward.
"""
- if self.currentSelectedBlock is not None and not isdeleted(self.currentSelectedBlock):
+ if self.currentSelectedBlock is not None and not isdeleted(
+ self.currentSelectedBlock):
self.currentSelectedBlock.setZValue(0)
block.setZValue(1)
self.currentSelectedBlock = block
@@ -177,16 +199,19 @@ def drag_scene(self, event: QMouseEvent, action="press"):
""" Drag the scene around. """
if action == "press":
releaseEvent = QMouseEvent(QEvent.Type.MouseButtonRelease,
- event.localPos(), event.screenPos(),
- Qt.MouseButton.LeftButton, Qt.MouseButton.NoButton, event.modifiers())
+ event.localPos(), event.screenPos(),
+ Qt.MouseButton.LeftButton, Qt.MouseButton.NoButton,
+ event.modifiers())
super().mouseReleaseEvent(releaseEvent)
self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
return QMouseEvent(event.type(), event.localPos(), event.screenPos(),
- Qt.MouseButton.LeftButton, event.buttons() | Qt.MouseButton.LeftButton,
- event.modifiers())
+ Qt.MouseButton.LeftButton,
+ event.buttons() | Qt.MouseButton.LeftButton,
+ event.modifiers())
return QMouseEvent(event.type(), event.localPos(), event.screenPos(),
- Qt.MouseButton.LeftButton,event.buttons() & ~Qt.MouseButton.LeftButton,
- event.modifiers())
+ Qt.MouseButton.LeftButton,
+ event.buttons() & ~Qt.MouseButton.LeftButton,
+ event.modifiers())
def drag_edge(self, event: QMouseEvent, action="press"):
""" Create an edge by drag and drop. """
@@ -209,7 +234,8 @@ def drag_edge(self, event: QMouseEvent, action="press"):
and item_at_click is not self.edge_drag.source_socket \
and item_at_click.socket_type != 'output':
self.edge_drag.destination_socket = item_at_click
- scene.history.checkpoint("Created edge by dragging", set_modified=True)
+ scene.history.checkpoint(
+ "Created edge by dragging", set_modified=True)
else:
self.edge_drag.remove()
self.edge_drag = None
@@ -219,7 +245,7 @@ def drag_edge(self, event: QMouseEvent, action="press"):
self.edge_drag.destination = self.mapToScene(event.pos())
return event
- def set_mode(self, mode:str):
+ def set_mode(self, mode: str):
""" Change the view mode.
Args:
@@ -228,7 +254,7 @@ def set_mode(self, mode:str):
"""
self.mode = MODES[mode]
- def is_mode(self, mode:str):
+ def is_mode(self, mode: str):
""" Return True if the view is in the given mode.
Args:
diff --git a/opencodeblocks/graphics/window.py b/opencodeblocks/graphics/window.py
index b355956a..7e0b134e 100644
--- a/opencodeblocks/graphics/window.py
+++ b/opencodeblocks/graphics/window.py
@@ -176,8 +176,8 @@ def createMenus(self):
def updateThemeMenu(self):
self.thememenu.clear()
theme_names = theme_manager().list_themes()
- for i in range(len(theme_names)):
- action = self.thememenu.addAction(theme_names[i])
+ for i, theme in enumerate(theme_names):
+ action = self.thememenu.addAction(theme)
action.setCheckable(True)
action.setChecked(i == theme_manager().selected_theme_index)
action.triggered.connect(self.themeMapper.map)
diff --git a/opencodeblocks/graphics/worker.py b/opencodeblocks/graphics/worker.py
index 331d6b15..333f451e 100644
--- a/opencodeblocks/graphics/worker.py
+++ b/opencodeblocks/graphics/worker.py
@@ -18,7 +18,7 @@ class Worker(QRunnable):
def __init__(self, kernel, code):
""" Initialize the worker object. """
- super(Worker, self).__init__()
+ super().__init__()
self.kernel = kernel
self.code = code