From 826e5e2bcfe508114db0fdf9e2d09ee3b0c0a5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Tue, 30 Nov 2021 18:17:29 +0100 Subject: [PATCH 01/16] :tada: Add support for new block type. Reworked the block type system as well as the file format for this. The new file format is not backwards compatible. The slider block exists in this version but is in no way finished. --- blocks/cnn_model.ocbb | 2 +- blocks/empty.ocbb | 2 +- blocks/import_ml.ocbb | 2 +- blocks/slider.ocbb | 16 ++++++++++++ opencodeblocks/graphics/blocks/__init__.py | 6 +---- opencodeblocks/graphics/blocks/block.py | 10 +++---- opencodeblocks/graphics/blocks/codeblock.py | 9 ++++++- opencodeblocks/graphics/blocks/sliderblock.py | 21 +++++++++++++++ opencodeblocks/graphics/scene/scene.py | 26 +++++++++++++------ 9 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 blocks/slider.ocbb create mode 100644 opencodeblocks/graphics/blocks/sliderblock.py diff --git a/blocks/cnn_model.ocbb b/blocks/cnn_model.ocbb index 56edefa6..2e123482 100644 --- a/blocks/cnn_model.ocbb +++ b/blocks/cnn_model.ocbb @@ -1,6 +1,6 @@ { "title": "CNN", - "block_type": "code", + "block_type": "OCBCodeBlock", "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": "", diff --git a/blocks/empty.ocbb b/blocks/empty.ocbb index 4ff5bdd3..f5435526 100644 --- a/blocks/empty.ocbb +++ b/blocks/empty.ocbb @@ -1,6 +1,6 @@ { "title": "Empty", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "", "stdout": "", "image": "", diff --git a/blocks/import_ml.ocbb b/blocks/import_ml.ocbb index 6b6440fa..fdb57216 100644 --- a/blocks/import_ml.ocbb +++ b/blocks/import_ml.ocbb @@ -1,6 +1,6 @@ { "title": "Imports for ML", - "block_type": "code", + "block_type": "OCBCodeBlock", "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": "", diff --git a/blocks/slider.ocbb b/blocks/slider.ocbb new file mode 100644 index 00000000..c49a9362 --- /dev/null +++ b/blocks/slider.ocbb @@ -0,0 +1,16 @@ +{ + "title": "Slider", + "block_type": "OCBSliderBlock", + "source": "", + "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/opencodeblocks/graphics/blocks/__init__.py b/opencodeblocks/graphics/blocks/__init__.py index 893604f8..969bf675 100644 --- a/opencodeblocks/graphics/blocks/__init__.py +++ b/opencodeblocks/graphics/blocks/__init__.py @@ -5,8 +5,4 @@ from opencodeblocks.graphics.blocks.block import OCBBlock from opencodeblocks.graphics.blocks.codeblock import OCBCodeBlock - -BLOCKS = { - 'base': OCBBlock, - 'code': OCBCodeBlock -} +from opencodeblocks.graphics.blocks.sliderblock import OCBSliderBlock \ No newline at end of file diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index 5dd9852a..329ef9c5 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -23,14 +23,13 @@ class OCBBlock(QGraphicsItem, Serializable): """ Base class for blocks in OpenCodeBlocks. """ - def __init__(self, block_type: str = 'base', source: str = '', position: tuple = (0, 0), + def __init__(self, source: str = '', position: tuple = (0, 0), width: int = 300, height: int = 200, edge_size: float = 10.0, title: str = 'New block', title_color: str = 'white', title_font: str = "Ubuntu", title_size: int = 10, title_padding=4.0, parent: Optional['QGraphicsItem'] = None): """ Base class for blocks in OpenCodeBlocks. Args: - block_type: Block type. source: Block source text. position: Block position in the scene. width: Block width. @@ -47,7 +46,8 @@ def __init__(self, block_type: str = 'base', source: str = '', position: tuple = QGraphicsItem.__init__(self, parent=parent) Serializable.__init__(self) - self.block_type = block_type + self.block_type = type(self).__name__ + print("Type: ",self.block_type) self.source = source self.stdout = "" self.setPos(QPointF(*position)) @@ -305,10 +305,10 @@ def serialize(self) -> OrderedDict: ]) def deserialize(self, data: dict, hashmap: dict = None, - restore_id=True) -> None: + restore_id=True): if restore_id: self.id = data['id'] - for dataname in ('title', 'block_type', 'source', 'stdout', 'width', 'height'): + for dataname in ('title', 'block_type', 'width', 'height'): setattr(self, dataname, data[dataname]) self.setPos(QPointF(*data['position'])) diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index aba18bb7..6829893f 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -3,6 +3,7 @@ """ Module for the base OCB Code Block. """ +from typing import OrderedDict from PyQt5.QtCore import QByteArray from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QPushButton, QTextEdit @@ -32,7 +33,7 @@ def __init__(self, **kwargs): self.source_editor = PythonEditor(self) - super().__init__(block_type='code', **kwargs) + super().__init__(**kwargs) self.output_panel_height = self.height / 3 self._min_output_panel_height = 20 @@ -162,3 +163,9 @@ def b64_to_html(image: str): def handle_image(self, image: str): """ Handle the image signal """ self.stdout = '' + image + + def deserialize(self, data: OrderedDict, + hashmap: dict = None, restore_id: bool = True): + for dataname in ('source', 'stdout'): + setattr(self, dataname, data[dataname]) + super().deserialize(data,hashmap,restore_id) diff --git a/opencodeblocks/graphics/blocks/sliderblock.py b/opencodeblocks/graphics/blocks/sliderblock.py new file mode 100644 index 00000000..25e5a796 --- /dev/null +++ b/opencodeblocks/graphics/blocks/sliderblock.py @@ -0,0 +1,21 @@ +# OpenCodeBlock an open-source tool for modular visual programing in python + +from PyQt5.QtWidgets import QSlider +from opencodeblocks.graphics.blocks.block import OCBBlock + +class OCBSliderBlock(OCBBlock): + """ + Features a slider ranging from 0 to 1 and an area to choose what value to assign the slider to. + """ + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.slider = QSlider() + + self.splitter.addWidget(self.slider) + + self.holder.setWidget(self.root) + + def update_all(self): + """ Update the slider. """ + super().update_all() \ No newline at end of file diff --git a/opencodeblocks/graphics/scene/scene.py b/opencodeblocks/graphics/scene/scene.py index fa99e84b..80504cf6 100644 --- a/opencodeblocks/graphics/scene/scene.py +++ b/opencodeblocks/graphics/scene/scene.py @@ -5,16 +5,17 @@ import math import json -from types import FunctionType +from types import FunctionType, ModuleType from typing import List, OrderedDict, Union from PyQt5.QtCore import QLine, QRectF from PyQt5.QtGui import QColor, QPainter, QPen from PyQt5.QtWidgets import QGraphicsScene +from opencodeblocks.graphics import blocks + from opencodeblocks.core.serializable import Serializable from opencodeblocks.graphics.blocks.block import OCBBlock -from opencodeblocks.graphics.blocks.codeblock import OCBCodeBlock from opencodeblocks.graphics.edge import OCBEdge from opencodeblocks.graphics.scene.clipboard import SceneClipboard from opencodeblocks.graphics.scene.history import SceneHistory @@ -193,12 +194,21 @@ def create_block(self, data: OrderedDict, hashmap: dict = None, """ 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_constructor = None + block_files = blocks.__dict__ + + for block_name in block_files: + block_module = getattr(blocks,block_name) + if isinstance(block_module, ModuleType): + print(block_module.__dict__.keys()) + if hasattr(block_module, data['block_type']): + block_constructor = getattr(blocks,data['block_type']) + + if block_constructor is None: + raise NotImplementedError(f"{data['block_type']} is not a known block type") + + block = block_constructor() block.deserialize(data, hashmap, restore_id) self.addItem(block) if hashmap is not None: From b9381628386ea07037a10be8149f97f5bcaff820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Tue, 30 Nov 2021 18:24:31 +0100 Subject: [PATCH 02/16] :tada: Removed prints and made the slider look nice --- opencodeblocks/graphics/blocks/sliderblock.py | 12 ++++++++++-- opencodeblocks/graphics/scene/scene.py | 1 - 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/opencodeblocks/graphics/blocks/sliderblock.py b/opencodeblocks/graphics/blocks/sliderblock.py index 25e5a796..a28b6862 100644 --- a/opencodeblocks/graphics/blocks/sliderblock.py +++ b/opencodeblocks/graphics/blocks/sliderblock.py @@ -1,5 +1,6 @@ # OpenCodeBlock an open-source tool for modular visual programing in python +from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QSlider from opencodeblocks.graphics.blocks.block import OCBBlock @@ -10,7 +11,7 @@ class OCBSliderBlock(OCBBlock): def __init__(self, **kwargs): super().__init__(**kwargs) - self.slider = QSlider() + self.slider = QSlider(Qt.Horizontal) self.splitter.addWidget(self.slider) @@ -18,4 +19,11 @@ def __init__(self, **kwargs): def update_all(self): """ Update the slider. """ - super().update_all() \ No newline at end of file + super().update_all() + if hasattr(self, 'slider'): + self.slider.setGeometry( + int(2 * self.edge_size), + int(self.title_height + 2 * self.edge_size), + int(self.width - 8 * self.edge_size), + int(2 * self.edge_size) + ) \ No newline at end of file diff --git a/opencodeblocks/graphics/scene/scene.py b/opencodeblocks/graphics/scene/scene.py index 80504cf6..ae038677 100644 --- a/opencodeblocks/graphics/scene/scene.py +++ b/opencodeblocks/graphics/scene/scene.py @@ -201,7 +201,6 @@ def create_block(self, data: OrderedDict, hashmap: dict = None, for block_name in block_files: block_module = getattr(blocks,block_name) if isinstance(block_module, ModuleType): - print(block_module.__dict__.keys()) if hasattr(block_module, data['block_type']): block_constructor = getattr(blocks,data['block_type']) From 2560b805b83982e6d8ebe0e86975ee4edcfc50d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Tue, 30 Nov 2021 18:27:25 +0100 Subject: [PATCH 03/16] :umbrella: Changed the examples and tests to match the new format. --- examples/mnist.ipyg | 14 +++++++------- tests/assets/example_graph1.ipyg | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/mnist.ipyg b/examples/mnist.ipyg index 35c2b1f9..0b852e0f 100644 --- a/examples/mnist.ipyg +++ b/examples/mnist.ipyg @@ -4,7 +4,7 @@ { "id": 2443477874008, "title": "Model Train", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "model.fit(x=x_train,y=y_train, epochs=4)\r\n", "stdout": "Epoch 1/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 10s 4ms/step - loss: 0.2116 - accuracy: 0.9367\nEpoch 2/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 7s 4ms/step - loss: 0.0853 - accuracy: 0.9738\nEpoch 3/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 7s 4ms/step - loss: 0.0597 - accuracy: 0.9813\nEpoch 4/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 7s 4ms/step - loss: 0.0472 - accuracy: 0.9848\n", "splitter_pos": [ @@ -59,7 +59,7 @@ { "id": 2443477924600, "title": "Keras Model Predict", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "rd_index = np.random.randint(len(x_test))\r\nprediction = np.argmax(model.predict(x_test[rd_index].reshape(1, 28, 28, 1)))\r\nplt.imshow(x_test[rd_index], cmap='gray')\r\nplt.title(\"Predicted: \" + str(prediction))", "stdout": "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\n", "splitter_pos": [ @@ -100,7 +100,7 @@ { "id": 2443477997032, "title": "Keras Model eval", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "metrics = model.evaluate(x_test, y_test)\r\nprint(f\"mean_loss:{metrics[0]:.2f}, mean_acc:{metrics[1]:.2f}\")\r\n", "stdout": "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r313/313 [==============================] - 1s 2ms/step - loss: 0.0552 - accuracy: 0.9823\nmean_loss:0.06, mean_acc:0.98\n", "splitter_pos": [ @@ -141,7 +141,7 @@ { "id": 2443478874872, "title": "Load MNIST Dataset", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "from tensorflow.keras.datasets import mnist\r\n(x_train, y_train), (x_test, y_test) = mnist.load_data()\r\n", "stdout": "", "splitter_pos": [ @@ -182,7 +182,7 @@ { "id": 2443478982728, "title": "Normalize Image Dataset", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "x_train = x_train.astype('float32') / 255.0\r\nx_test = x_test.astype('float32') / 255.0\r\n\r\nx_train = x_train.reshape(x_train.shape[0], 28, 28, 1)\r\nx_test = x_test.reshape(x_test.shape[0], 28, 28, 1)\r\n\r\nprint('train:', x_train.shape, '|test:', x_test.shape)\r\n", "stdout": "train: (60000, 28, 28, 1) |test: (10000, 28, 28, 1)\n", "splitter_pos": [ @@ -237,7 +237,7 @@ { "id": 2443479017656, "title": "Build Keras CNN", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "import tensorflow as tf\r\nfrom tensorflow.keras.layers import (Dense, Flatten,\r\nConv2D, MaxPooling2D, Dropout)\r\nfrom tensorflow.keras.models import Sequential\r\n\r\nmodel = Sequential()\r\nmodel.add(Conv2D(28, kernel_size=(3,3), input_shape=x_train.shape[1:]))\r\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\r\nmodel.add(Flatten())\r\nmodel.add(Dense(128, activation=tf.nn.relu))\r\nmodel.add(Dropout(0.2))\r\nmodel.add(Dense(10,activation=tf.nn.softmax))\r\n\r\nmodel.compile(optimizer='adam', \r\n loss='sparse_categorical_crossentropy', \r\n metrics=['accuracy'])\r\n", "stdout": "", "splitter_pos": [ @@ -278,7 +278,7 @@ { "id": 2828158533848, "title": "Plot Image Dataset Example", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "import matplotlib.pyplot as plt\r\nimport numpy as np\r\n\r\n# Display an example from the dataset\r\nrd_index = np.random.randint(len(x_train))\r\nplt.imshow(x_train[rd_index], cmap='gray')\r\nplt.title('Class '+ str(y_train[rd_index]))\r\n", "stdout": "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\n", "splitter_pos": [ diff --git a/tests/assets/example_graph1.ipyg b/tests/assets/example_graph1.ipyg index 9bc51a65..7158ae33 100644 --- a/tests/assets/example_graph1.ipyg +++ b/tests/assets/example_graph1.ipyg @@ -4,7 +4,7 @@ { "id": 2443477874008, "title": "Model Train", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "print(\"training \")\r\nmodel.fit(x=x_train,y=y_train, epochs=10)\r\n\r\n", "stdout": "", "image": "", @@ -56,7 +56,7 @@ { "id": 2443477924600, "title": "Keras Model Predict", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "prediction = model.predict(x_test[9].reshape(1, 28, 28, 1))", "stdout": "", "image": "", @@ -108,7 +108,7 @@ { "id": 2443477997032, "title": "Keras Model eval", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "model.evaluate(x_test, y_test)\r\n", "stdout": "", "image": "", @@ -160,7 +160,7 @@ { "id": 2443478874872, "title": "Load MNIST Dataset", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "print(\"Hello, world\")\r\nfrom tensorflow.keras.datasets import mnist\r\n(x_train, y_train), (x_test, y_test) = mnist.load_data()\r\n", "stdout": "", "image": "", @@ -198,7 +198,7 @@ { "id": 2443478982728, "title": "Normalize Image Dataset", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "x_train = x_train.astype('float32') / 255.0\r\nx_test = x_test.astype('float32') / 255.0\r\n\r\n\r\nx_train = x_train.reshape(x_train.shape[0], 28, 28, 1)\r\nx_test = x_test.reshape(x_test.shape[0], 28, 28, 1)\r\n\r\nprint('train:', x_train.shape, '|test:', x_test.shape)", "stdout": "", "image": "", @@ -250,7 +250,7 @@ { "id": 2443479017656, "title": "Build Keras CNN", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "import tensorflow as tf\r\nfrom tensorflow.keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Dropout\r\nfrom tensorflow.keras.models import Sequential\r\n\r\nmodel = Sequential()\r\nmodel.add(Conv2D(28, kernel_size=(3,3), input_shape=x_train.shape[1:]))\r\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\r\nmodel.add(Flatten())\r\nmodel.add(Dense(128, activation=tf.nn.relu))\r\nmodel.add(Dropout(0.2))\r\nmodel.add(Dense(10,activation=tf.nn.softmax))\r\nprint(\"..\")\r\nmodel.compile(optimizer='adam', \r\n loss='sparse_categorical_crossentropy', \r\n metrics=['accuracy'])\r\n", "stdout": "", "image": "", @@ -302,7 +302,7 @@ { "id": 2828158533848, "title": "Plot Image Dataset Example", - "block_type": "code", + "block_type": "OCBCodeBlock", "source": "import matplotlib.pyplot as plt\r\nimport numpy as np\r\n\r\n# Display an example from the dataset\r\nrd_index = np.random.randint(len(x_train))\r\nplt.imshow(x_train[rd_index], cmap='gray')\r\nplt.title('Class '+ str(y_train[0]))\r\n", "stdout": "", "image": "", From 3dcd205422ca1289716e31be833df3a7ef1ab573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Tue, 30 Nov 2021 18:29:47 +0100 Subject: [PATCH 04/16] :fire: Remove debug print --- opencodeblocks/graphics/blocks/block.py | 1 - 1 file changed, 1 deletion(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index 329ef9c5..38ad6829 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -47,7 +47,6 @@ def __init__(self, source: str = '', position: tuple = (0, 0), Serializable.__init__(self) self.block_type = type(self).__name__ - print("Type: ",self.block_type) self.source = source self.stdout = "" self.setPos(QPointF(*position)) From 682c5d21024324ba58a9c0724149f89c75a3274c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Tue, 30 Nov 2021 23:35:48 +0100 Subject: [PATCH 05/16] :tada: Add back the slider block --- opencodeblocks/blocks/sliderblock.py | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 opencodeblocks/blocks/sliderblock.py diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py new file mode 100644 index 00000000..3a9acc88 --- /dev/null +++ b/opencodeblocks/blocks/sliderblock.py @@ -0,0 +1,29 @@ +# OpenCodeBlock an open-source tool for modular visual programing in python + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QSlider +from opencodeblocks.blocks.block import OCBBlock + +class OCBSliderBlock(OCBBlock): + """ + Features a slider ranging from 0 to 1 and an area to choose what value to assign the slider to. + """ + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.slider = QSlider(Qt.Horizontal) + + self.splitter.addWidget(self.slider) + + self.holder.setWidget(self.root) + + def update_all(self): + """ Update the slider. """ + super().update_all() + if hasattr(self, 'slider'): + self.slider.setGeometry( + int(2 * self.edge_size), + int(self.title_widget.height() + 2 * self.edge_size), + int(self.width - 8 * self.edge_size), + int(2 * self.edge_size) + ) \ No newline at end of file From 86f132a5962c049c72f44d2f5ee9c0ecb215eca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Wed, 1 Dec 2021 00:15:12 +0100 Subject: [PATCH 06/16] :tada: Add ability for slider to execute python code --- opencodeblocks/blocks/sliderblock.py | 61 ++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index 3a9acc88..91d67557 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -1,8 +1,11 @@ # OpenCodeBlock an open-source tool for modular visual programing in python from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QSlider +from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout from opencodeblocks.blocks.block import OCBBlock +from opencodeblocks.blocks.codeblock import OCBCodeBlock +from opencodeblocks.graphics.kernel import Kernel +from opencodeblocks.graphics.worker import Worker class OCBSliderBlock(OCBBlock): """ @@ -11,19 +14,53 @@ class OCBSliderBlock(OCBBlock): def __init__(self, **kwargs): super().__init__(**kwargs) + self.layout = QVBoxLayout(self.root) + + self.slider = QSlider(Qt.Horizontal) + self.slider.valueChanged.connect(self.valueChanged) + + self.variable_layout = QHBoxLayout(self.root) + self.variable_text = QLineEdit("slider_value") + self.variable_value = QLabel(f"{self.slider.value()/100}") + + self.variable_text.setFixedWidth(self.root.width() / 2) + + self.variable_layout.addWidget(self.variable_text) + self.variable_layout.addWidget(self.variable_value) - self.splitter.addWidget(self.slider) + self.layout.setContentsMargins( + self.edge_size*2, + self.title_widget.height() + self.edge_size*2, + self.edge_size*2, + self.edge_size*2 + ) + self.layout.addWidget(self.slider) + self.layout.addLayout(self.variable_layout) self.holder.setWidget(self.root) + + def findKernel(self): + """ Retreive a kernel by looking for one in the output blocks we are connected to. """ + for output_socket in self.sockets_out: + if output_socket.edges != []: + for edge in output_socket.edges: + output_block = edge.source_socket.block + if type(output_block) == OCBCodeBlock: + return output_block.kernel, output_block.threadpool + return None,None + def valueChanged(self): + """ This is called when the value of the slider changes """ + val = self.slider.value() / 100 + var_name = self.variable_text.text() + python_code = f"{var_name} = {val}" + self.variable_value.setText(f"{val}") + # We fetch the kernel from the codeblock we are connected to and we execute the code. - def update_all(self): - """ Update the slider. """ - super().update_all() - if hasattr(self, 'slider'): - self.slider.setGeometry( - int(2 * self.edge_size), - int(self.title_widget.height() + 2 * self.edge_size), - int(self.width - 8 * self.edge_size), - int(2 * self.edge_size) - ) \ No newline at end of file + kernel, thread_pool = self.findKernel() + if kernel is not None: + # This needs to change once execution flow is merged. + # A refactor will be needed. + # kernel.execution_queue.append((self, python_code)) + worker = Worker(kernel, python_code) + thread_pool.start(worker) From 2305e3c92d88d2fd32644e7eab4c4e40246675d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Thu, 2 Dec 2021 17:15:18 +0100 Subject: [PATCH 07/16] :tada: :memo: Create a basic markdown block ! While it's lacking many features, this commit is a perfect illustration of the minimal amount of code required to create a new block type. If you want to create a new block type, feel free to take inspiration from this commit. --- blocks/markdown.ocbb | 16 ++++++++++++++++ opencodeblocks/blocks/__init__.py | 3 ++- opencodeblocks/blocks/markdownblock.py | 15 +++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 blocks/markdown.ocbb create mode 100644 opencodeblocks/blocks/markdownblock.py diff --git a/blocks/markdown.ocbb b/blocks/markdown.ocbb new file mode 100644 index 00000000..5197de83 --- /dev/null +++ b/blocks/markdown.ocbb @@ -0,0 +1,16 @@ +{ + "title": "Markdown", + "block_type": "OCBMarkdownBlock", + "source": "", + "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/opencodeblocks/blocks/__init__.py b/opencodeblocks/blocks/__init__.py index ad9a1468..3804621d 100644 --- a/opencodeblocks/blocks/__init__.py +++ b/opencodeblocks/blocks/__init__.py @@ -5,4 +5,5 @@ from opencodeblocks.blocks.sliderblock import OCBSliderBlock from opencodeblocks.blocks.block import OCBBlock -from opencodeblocks.blocks.codeblock import OCBCodeBlock \ No newline at end of file +from opencodeblocks.blocks.codeblock import OCBCodeBlock +from opencodeblocks.blocks.markdownblock import OCBMarkdownBlock \ No newline at end of file diff --git a/opencodeblocks/blocks/markdownblock.py b/opencodeblocks/blocks/markdownblock.py new file mode 100644 index 00000000..534c8b2c --- /dev/null +++ b/opencodeblocks/blocks/markdownblock.py @@ -0,0 +1,15 @@ +from PyQt5.Qsci import QsciLexerMarkdown, QsciScintilla +from opencodeblocks.blocks.block import OCBBlock + +class OCBMarkdownBlock(OCBBlock): + def __init__(self, **kwargs): + """ + Create a new OCBMarkdownBlock, a block that renders markdown + """ + super().__init__(**kwargs) + self.editor = QsciScintilla() + self.lexer = QsciLexerMarkdown() + self.editor.setLexer(self.lexer) + + self.splitter.addWidget(self.editor) + self.holder.setWidget(self.root) \ No newline at end of file From f3f5fc704f9797bfa64d37c432fc01c9b81c1760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Thu, 2 Dec 2021 17:22:16 +0100 Subject: [PATCH 08/16] :memo: :sparkles: Whitespace fixes and docstrings --- opencodeblocks/blocks/__init__.py | 2 +- opencodeblocks/blocks/markdownblock.py | 10 +++++++--- opencodeblocks/blocks/sliderblock.py | 4 ++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/opencodeblocks/blocks/__init__.py b/opencodeblocks/blocks/__init__.py index 3804621d..9f33a06a 100644 --- a/opencodeblocks/blocks/__init__.py +++ b/opencodeblocks/blocks/__init__.py @@ -6,4 +6,4 @@ from opencodeblocks.blocks.sliderblock import OCBSliderBlock from opencodeblocks.blocks.block import OCBBlock from opencodeblocks.blocks.codeblock import OCBCodeBlock -from opencodeblocks.blocks.markdownblock import OCBMarkdownBlock \ No newline at end of file +from opencodeblocks.blocks.markdownblock import OCBMarkdownBlock diff --git a/opencodeblocks/blocks/markdownblock.py b/opencodeblocks/blocks/markdownblock.py index 534c8b2c..297f9e27 100644 --- a/opencodeblocks/blocks/markdownblock.py +++ b/opencodeblocks/blocks/markdownblock.py @@ -1,10 +1,14 @@ +""" +Exports OCBMarkdownBlock. +""" + from PyQt5.Qsci import QsciLexerMarkdown, QsciScintilla from opencodeblocks.blocks.block import OCBBlock class OCBMarkdownBlock(OCBBlock): def __init__(self, **kwargs): - """ - Create a new OCBMarkdownBlock, a block that renders markdown + """ + Create a new OCBMarkdownBlock, a block that renders markdown """ super().__init__(**kwargs) self.editor = QsciScintilla() @@ -12,4 +16,4 @@ def __init__(self, **kwargs): self.editor.setLexer(self.lexer) self.splitter.addWidget(self.editor) - self.holder.setWidget(self.root) \ No newline at end of file + self.holder.setWidget(self.root) diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index 91d67557..edcac3d6 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -1,5 +1,9 @@ # OpenCodeBlock an open-source tool for modular visual programing in python +""" +Exports OCBSliderBlock. +""" + from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout from opencodeblocks.blocks.block import OCBBlock From 0174653cd8964e1ddfb9cdbef0ab69583f4a9878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Thu, 2 Dec 2021 22:00:03 +0100 Subject: [PATCH 09/16] :tada: Full support for markdown is here ! Display all your favorite documents in your notebook ! Keep documentation right next to you ! :truck: Updated requirements and dev to add the markdown package and the black code formatting. :memo: Added documentation on how to create a new block --- CONTRIBUTING.md | 5 +++ opencodeblocks/blocks/codeblock.py | 2 -- opencodeblocks/blocks/markdownblock.py | 44 ++++++++++++++++++++++++++ opencodeblocks/blocks/sliderblock.py | 25 +++------------ requirements-dev.txt | 3 +- requirements.txt | 3 +- 6 files changed, 58 insertions(+), 24 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74a714df..d1379f8c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,6 +59,11 @@ You should also start your commit message with one or two applicable emoji. This This section was inspired by [This repository](https://github.com/schneegans/dynamic-badges-action). +## Creating a new block type + +You can checkout [this commit](https://github.com/MathisFederico/OpenCodeBlocks/commits/2305e3c92d88d2fd32644e7eab4c4e40246675d3) which contains the minimal amount of code required to +create a new block type. + ## Version Numbers Version numbers will be assigned according to the [Semantic Versioning](https://semver.org/) scheme. diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index 912dae8a..0ad81051 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -4,8 +4,6 @@ """ Module for the base OCB Code Block. """ from typing import OrderedDict -from PyQt5.QtCore import QByteArray -from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QPushButton, QTextEdit from ansi2html import Ansi2HTMLConverter diff --git a/opencodeblocks/blocks/markdownblock.py b/opencodeblocks/blocks/markdownblock.py index 297f9e27..894b62be 100644 --- a/opencodeblocks/blocks/markdownblock.py +++ b/opencodeblocks/blocks/markdownblock.py @@ -2,8 +2,15 @@ Exports OCBMarkdownBlock. """ +from markdown import markdown + +from PyQt5.QtWebEngineWidgets import QWebEngineView from PyQt5.Qsci import QsciLexerMarkdown, QsciScintilla +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QColor, QFont from opencodeblocks.blocks.block import OCBBlock +from opencodeblocks.graphics.theme_manager import theme_manager + class OCBMarkdownBlock(OCBBlock): def __init__(self, **kwargs): @@ -11,9 +18,46 @@ def __init__(self, **kwargs): Create a new OCBMarkdownBlock, a block that renders markdown """ super().__init__(**kwargs) + self.editor = QsciScintilla() + self.editor.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0) + self.editor.setWindowFlags(Qt.WindowType.FramelessWindowHint) + self.editor.setAutoFillBackground(False) + self.lexer = QsciLexerMarkdown() + theme_manager().current_theme().apply_to_lexer(self.lexer) + # Customize lexer background color: + self.lexer.setColor(QColor.fromRgb(255,255,255),-1) + self.editor.setCaretForegroundColor(QColor("#FFFFFF")) self.editor.setLexer(self.lexer) + font = QFont() + font.setFamily(theme_manager().recommended_font_family) + font.setFixedPitch(True) + font.setPointSize(11) + self.editor.setFont(font) + self.editor.setMarginWidth(QsciScintilla.SC_MARGIN_NUMBER,0) + self.editor.setStyleSheet("background:transparent") + self.editor.textChanged.connect(self.valueChanged) + self.splitter.addWidget(self.editor) + + self.rendered_markdown = QWebEngineView() + self.rendered_markdown.page().setBackgroundColor(QColor.fromRgba64(0,0,0,alpha = 0)); + + self.splitter.addWidget(self.rendered_markdown) self.holder.setWidget(self.root) + + def valueChanged(self): + t = self.editor.text() + + dark_theme = """ + + """ + + self.rendered_markdown.setHtml(f"{dark_theme}{markdown(t)}") \ No newline at end of file diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index edcac3d6..ee5f7bad 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -7,9 +7,6 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout from opencodeblocks.blocks.block import OCBBlock -from opencodeblocks.blocks.codeblock import OCBCodeBlock -from opencodeblocks.graphics.kernel import Kernel -from opencodeblocks.graphics.worker import Worker class OCBSliderBlock(OCBBlock): """ @@ -19,7 +16,7 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.layout = QVBoxLayout(self.root) - + self.slider = QSlider(Qt.Horizontal) self.slider.valueChanged.connect(self.valueChanged) @@ -44,27 +41,15 @@ def __init__(self, **kwargs): self.holder.setWidget(self.root) - def findKernel(self): - """ Retreive a kernel by looking for one in the output blocks we are connected to. """ - for output_socket in self.sockets_out: - if output_socket.edges != []: - for edge in output_socket.edges: - output_block = edge.source_socket.block - if type(output_block) == OCBCodeBlock: - return output_block.kernel, output_block.threadpool return None,None + def valueChanged(self): """ This is called when the value of the slider changes """ val = self.slider.value() / 100 var_name = self.variable_text.text() python_code = f"{var_name} = {val}" self.variable_value.setText(f"{val}") - # We fetch the kernel from the codeblock we are connected to and we execute the code. - kernel, thread_pool = self.findKernel() - if kernel is not None: - # This needs to change once execution flow is merged. - # A refactor will be needed. - # kernel.execution_queue.append((self, python_code)) - worker = Worker(kernel, python_code) - thread_pool.start(worker) + # The code execution part will be added when the execution flow is merged. + # We print for now + print(python_code) diff --git a/requirements-dev.txt b/requirements-dev.txt index c49f27fb..baa7fc60 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,4 +7,5 @@ pytest-qt pyautogui pylint>=2.12 pylint-pytest -autopep8 \ No newline at end of file +autopep8 +black \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a8159a30..f51aa73d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ qscintilla>=2.13.0 Ipython>=7.27.0 jupyter_client>=7.0.6 ipykernel>=6.5.0 -ansi2html>=1.6.0 \ No newline at end of file +ansi2html>=1.6.0 +markdown>=3.3.6 \ No newline at end of file From 62340766236278170ca699795d5cb37efdfdfb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Thu, 2 Dec 2021 22:00:30 +0100 Subject: [PATCH 10/16] :sparkles: Add docstring to improve pylint --- opencodeblocks/blocks/codeblock.py | 21 ++++++++++++------- opencodeblocks/blocks/markdownblock.py | 12 +++++++---- opencodeblocks/blocks/sliderblock.py | 19 ++++++++--------- .../blocks/widgets/blocksplitter.py | 2 +- opencodeblocks/scene/scene.py | 1 - 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index 0ad81051..5141a9bb 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -28,8 +28,8 @@ class OCBCodeBlock(OCBBlock): """ def __init__(self, **kwargs): - """ - Create a new OCBCodeBlock. + """ + Create a new OCBCodeBlock. Initialize all the child widgets specific to this block type """ self.source_editor = PythonEditor(self) @@ -67,7 +67,8 @@ def init_run_button(self): """Initialize the run button""" run_button = QPushButton(">", self.root) run_button.move(int(self.edge_size), int(self.edge_size / 2)) - run_button.setFixedSize(int(3 * self.edge_size), int(3 * self.edge_size)) + run_button.setFixedSize(int(3 * self.edge_size), + int(3 * self.edge_size)) run_button.clicked.connect(self.run_code) return run_button @@ -84,6 +85,7 @@ def run_code(self): self.source_editor.threadpool.start(worker) def update_title(self): + """ Change the geometry of the title widget """ self.title_widget.setGeometry( int(self.edge_size) + self.run_button.width(), int(self.edge_size / 2), @@ -92,6 +94,7 @@ def update_title(self): ) def update_output_panel(self): + """ Change the geometry of the output panel """ # Close output panel if no output if self.stdout == "": self.previous_splitter_size = self.splitter.sizes() @@ -99,13 +102,13 @@ def update_output_panel(self): self.splitter.setSizes([1, 0]) def update_all(self): - """Update the code block parts.""" + """ Update the code block parts """ super().update_all() self.update_output_panel() @property def source(self) -> str: - """Source code.""" + """ Source code """ return self.source_editor.text() @source.setter @@ -114,6 +117,7 @@ def source(self, value: str): @property def stdout(self) -> str: + """ Access the content of the output panel of the block """ return self._stdout @stdout.setter @@ -137,6 +141,7 @@ def stdout(self, value: str): @staticmethod def str_to_html(text: str): + """ Format text so that it's properly displayed by the code block """ # Remove carriage returns and backspaces text = text.replace("\x08", "") text = text.replace("\r", "") @@ -149,7 +154,7 @@ def str_to_html(text: str): return text def handle_stdout(self, value: str): - """Handle the stdout signal""" + """ Handle the stdout signal """ # If there is a new line # Save every line but the last one @@ -163,6 +168,7 @@ def handle_stdout(self, value: str): @staticmethod def b64_to_html(image: str): + """ Transform a base64 encoded image into a html image""" return f'' def handle_image(self, image: str): @@ -171,6 +177,7 @@ def handle_image(self, image: str): def deserialize(self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True): + """ Restore a codeblock from it's serialized state """ for dataname in ('source', 'stdout'): setattr(self, dataname, data[dataname]) - super().deserialize(data,hashmap,restore_id) \ No newline at end of file + super().deserialize(data, hashmap, restore_id) diff --git a/opencodeblocks/blocks/markdownblock.py b/opencodeblocks/blocks/markdownblock.py index 894b62be..70719332 100644 --- a/opencodeblocks/blocks/markdownblock.py +++ b/opencodeblocks/blocks/markdownblock.py @@ -13,6 +13,8 @@ class OCBMarkdownBlock(OCBBlock): + """ A block that is able to render markdown text """ + def __init__(self, **kwargs): """ Create a new OCBMarkdownBlock, a block that renders markdown @@ -27,7 +29,7 @@ def __init__(self, **kwargs): self.lexer = QsciLexerMarkdown() theme_manager().current_theme().apply_to_lexer(self.lexer) # Customize lexer background color: - self.lexer.setColor(QColor.fromRgb(255,255,255),-1) + self.lexer.setColor(QColor.fromRgb(255, 255, 255), -1) self.editor.setCaretForegroundColor(QColor("#FFFFFF")) self.editor.setLexer(self.lexer) @@ -36,19 +38,21 @@ def __init__(self, **kwargs): font.setFixedPitch(True) font.setPointSize(11) self.editor.setFont(font) - self.editor.setMarginWidth(QsciScintilla.SC_MARGIN_NUMBER,0) + self.editor.setMarginWidth(QsciScintilla.SC_MARGIN_NUMBER, 0) self.editor.setStyleSheet("background:transparent") self.editor.textChanged.connect(self.valueChanged) self.splitter.addWidget(self.editor) self.rendered_markdown = QWebEngineView() - self.rendered_markdown.page().setBackgroundColor(QColor.fromRgba64(0,0,0,alpha = 0)); + self.rendered_markdown.page().setBackgroundColor( + QColor.fromRgba64(0, 0, 0, alpha=0)) self.splitter.addWidget(self.rendered_markdown) self.holder.setWidget(self.root) def valueChanged(self): + """ Update markdown rendering when the content of the markdown editor changes """ t = self.editor.text() dark_theme = """ @@ -60,4 +64,4 @@ def valueChanged(self): """ - self.rendered_markdown.setHtml(f"{dark_theme}{markdown(t)}") \ No newline at end of file + self.rendered_markdown.setHtml(f"{dark_theme}{markdown(t)}") diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index ee5f7bad..874fe4d4 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -8,15 +8,16 @@ from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout from opencodeblocks.blocks.block import OCBBlock + class OCBSliderBlock(OCBBlock): """ Features a slider ranging from 0 to 1 and an area to choose what value to assign the slider to. """ + def __init__(self, **kwargs): super().__init__(**kwargs) - - self.layout = QVBoxLayout(self.root) + self.layout = QVBoxLayout(self.root) self.slider = QSlider(Qt.Horizontal) self.slider.valueChanged.connect(self.valueChanged) @@ -31,17 +32,15 @@ def __init__(self, **kwargs): self.variable_layout.addWidget(self.variable_value) self.layout.setContentsMargins( - self.edge_size*2, - self.title_widget.height() + self.edge_size*2, - self.edge_size*2, - self.edge_size*2 + self.edge_size * 2, + self.title_widget.height() + self.edge_size * 2, + self.edge_size * 2, + self.edge_size * 2 ) self.layout.addWidget(self.slider) self.layout.addLayout(self.variable_layout) - + self.holder.setWidget(self.root) - - return None,None def valueChanged(self): """ This is called when the value of the slider changes """ @@ -49,7 +48,7 @@ def valueChanged(self): var_name = self.variable_text.text() python_code = f"{var_name} = {val}" self.variable_value.setText(f"{val}") - + # The code execution part will be added when the execution flow is merged. # We print for now print(python_code) diff --git a/opencodeblocks/blocks/widgets/blocksplitter.py b/opencodeblocks/blocks/widgets/blocksplitter.py index 31137fb0..3f9c9d97 100644 --- a/opencodeblocks/blocks/widgets/blocksplitter.py +++ b/opencodeblocks/blocks/widgets/blocksplitter.py @@ -28,4 +28,4 @@ def __init__(self, block: QWidget, orientation: int, parent: QWidget): def createHandle(self): """ Return the middle handle of the splitter """ - return OCBSplitterHandle(self.orientation(), self) \ No newline at end of file + return OCBSplitterHandle(self.orientation(), self) diff --git a/opencodeblocks/scene/scene.py b/opencodeblocks/scene/scene.py index 346e8e36..e61c2d8c 100644 --- a/opencodeblocks/scene/scene.py +++ b/opencodeblocks/scene/scene.py @@ -16,7 +16,6 @@ from opencodeblocks.core.serializable import Serializable from opencodeblocks.blocks.block import OCBBlock -from opencodeblocks.blocks.codeblock import OCBCodeBlock from opencodeblocks.graphics.edge import OCBEdge from opencodeblocks.scene.clipboard import SceneClipboard from opencodeblocks.scene.history import SceneHistory From 1909b8f3155523765e6964fd861f1e2e107da014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Thu, 2 Dec 2021 22:09:38 +0100 Subject: [PATCH 11/16] :beetle: Update requirements.txt for the webview --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f51aa73d..df0d18d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ Ipython>=7.27.0 jupyter_client>=7.0.6 ipykernel>=6.5.0 ansi2html>=1.6.0 -markdown>=3.3.6 \ No newline at end of file +markdown>=3.3.6 +pyqtwebengine>=5.15.5 \ No newline at end of file From 3e2f6de87593dfbd10e8b21cf2fdafed938b5d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Thu, 2 Dec 2021 23:29:17 +0100 Subject: [PATCH 12/16] :tada: Implement the drawing block. A block where you can draw things. This will be useful to test CNN and do cool demos. It can just draw in black and white and you can only clear everything but it's a start. More work can be done in the futur. --- blocks/drawing.ocbb | 16 +++++++ opencodeblocks/blocks/__init__.py | 1 + opencodeblocks/blocks/drawingblock.py | 67 +++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 blocks/drawing.ocbb create mode 100644 opencodeblocks/blocks/drawingblock.py diff --git a/blocks/drawing.ocbb b/blocks/drawing.ocbb new file mode 100644 index 00000000..25503ea0 --- /dev/null +++ b/blocks/drawing.ocbb @@ -0,0 +1,16 @@ +{ + "title": "Drawing", + "block_type": "OCBDrawingBlock", + "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/blocks/__init__.py b/opencodeblocks/blocks/__init__.py index 9f33a06a..82fed7bc 100644 --- a/opencodeblocks/blocks/__init__.py +++ b/opencodeblocks/blocks/__init__.py @@ -7,3 +7,4 @@ from opencodeblocks.blocks.block import OCBBlock from opencodeblocks.blocks.codeblock import OCBCodeBlock from opencodeblocks.blocks.markdownblock import OCBMarkdownBlock +from opencodeblocks.blocks.drawingblock import OCBDrawingBlock diff --git a/opencodeblocks/blocks/drawingblock.py b/opencodeblocks/blocks/drawingblock.py new file mode 100644 index 00000000..20c7aa2d --- /dev/null +++ b/opencodeblocks/blocks/drawingblock.py @@ -0,0 +1,67 @@ +from math import floor + +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QColor, QMouseEvent, QPaintEvent, QPainter, QPen +from PyQt5.QtWidgets import QPushButton, QWidget +from opencodeblocks.blocks.block import OCBBlock + + +eps = 1 + +class DrawableWidget(QWidget): + def __init__(self, parent: QWidget): + super().__init__(parent) + self.setAttribute(Qt.WA_PaintOnScreen) + self.pixel_width = 24 + self.pixel_height = 24 + self.color_buffer = [] + self.mouse_down = False + for _ in range(self.pixel_width): + self.color_buffer.append([]) + for _ in range(self.pixel_height): + # color hex encoded as AARRGGBB + self.color_buffer[-1].append(0xFFFFFFFF) + + def clearDrawing(self): + for i in range(self.pixel_width): + for j in range(self.pixel_height): + self.color_buffer[i][j] = 0xFFFFFFFF + + def paintEvent(self, evt: QPaintEvent): + painter = QPainter(self) + + for i in range(self.pixel_width): + self.color_buffer.append([]) + for j in range(self.pixel_height): + w = self.width() / self.pixel_width + h = self.height() / self.pixel_height + painter.fillRect(w*i,h*j,w + eps,h + eps,QColor.fromRgb(self.color_buffer[i][j])) + + def mouseMoveEvent(self, evt: QMouseEvent): + if self.mouse_down: + x = floor(evt.x() / self.width() * self.pixel_width) + y = floor(evt.y() / self.height() * self.pixel_height) + if 0 <= x < self.pixel_width and 0 <= y < self.pixel_height: + self.color_buffer[x][y] = 0xFF000000 + self.repaint() + + def mousePressEvent(self, evt: QMouseEvent): + self.mouse_down = True + + def mouseReleaseEvent(self, evt: QMouseEvent): + self.mouse_down = False + +class OCBDrawingBlock(OCBBlock): + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.draw_area = DrawableWidget(self.root) + + self.splitter.addWidget(self.draw_area) # QGraphicsView + self.run_button = QPushButton("Clear", self.root) + self.run_button.move(int(self.edge_size * 2), int(self.title_widget.height() + self.edge_size * 2)) + self.run_button.setFixedSize(int(8 * self.edge_size),int(3 * self.edge_size)) + self.run_button.clicked.connect(self.draw_area.clearDrawing) + self.holder.setWidget(self.root) + From 10e6db7511f9b08703c2a8b6c7418bfce241bd74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Thu, 2 Dec 2021 23:55:29 +0100 Subject: [PATCH 13/16] :beetle: Fix serialization issue of codeblock :beetle: Fix crash when attempting to paste invalid json --- opencodeblocks/blocks/codeblock.py | 9 ++++++++- opencodeblocks/scene/clipboard.py | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index 5141a9bb..b8b8dd41 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -175,9 +175,16 @@ def handle_image(self, image: str): """ Handle the image signal """ self.stdout = '' + image + def serialize(self): + base_dict = super().serialize() + base_dict["source"] = self.source + base_dict["stdout"] = self.stdout + + return base_dict def deserialize(self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True): """ Restore a codeblock from it's serialized state """ for dataname in ('source', 'stdout'): - setattr(self, dataname, data[dataname]) + if dataname in data: + setattr(self, dataname, data[dataname]) super().deserialize(data, hashmap, restore_id) diff --git a/opencodeblocks/scene/clipboard.py b/opencodeblocks/scene/clipboard.py index 9e6290fd..c1e10e8c 100644 --- a/opencodeblocks/scene/clipboard.py +++ b/opencodeblocks/scene/clipboard.py @@ -80,6 +80,8 @@ def _find_bbox_center(self, blocks_data): return (xmin + xmax) / 2, (ymin + ymax) / 2 def _deserializeData(self, data:OrderedDict, set_selected=True): + if data is None: return + hashmap = {} view = self.scene.views()[0] From b37a4aff381b27e53ba7dd1c495b3008b4657bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Fri, 3 Dec 2021 00:20:36 +0100 Subject: [PATCH 14/16] :tada: Add serialization support for all the new block types. This means that call the new blocks can be copied, pasted and saved ! Also, clipboard now supports all the new blocks ! --- blocks/markdown.ocbb | 2 +- opencodeblocks/blocks/drawingblock.py | 24 ++++++++++++++++ opencodeblocks/blocks/markdownblock.py | 27 +++++++++++++++++- opencodeblocks/blocks/sliderblock.py | 38 +++++++++++++++++++++++--- opencodeblocks/scene/clipboard.py | 15 +--------- 5 files changed, 86 insertions(+), 20 deletions(-) diff --git a/blocks/markdown.ocbb b/blocks/markdown.ocbb index 5197de83..b47dc633 100644 --- a/blocks/markdown.ocbb +++ b/blocks/markdown.ocbb @@ -1,7 +1,7 @@ { "title": "Markdown", "block_type": "OCBMarkdownBlock", - "source": "", + "text": "", "splitter_pos": [88,41], "width": 618, "height": 184, diff --git a/opencodeblocks/blocks/drawingblock.py b/opencodeblocks/blocks/drawingblock.py index 20c7aa2d..5b8da635 100644 --- a/opencodeblocks/blocks/drawingblock.py +++ b/opencodeblocks/blocks/drawingblock.py @@ -1,4 +1,6 @@ from math import floor +import json +from typing import OrderedDict from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor, QMouseEvent, QPaintEvent, QPainter, QPen @@ -65,3 +67,25 @@ def __init__(self, **kwargs): self.run_button.clicked.connect(self.draw_area.clearDrawing) self.holder.setWidget(self.root) + @property + def drawing(self): + return json.dumps(self.draw_area.color_buffer) + @drawing.setter + def drawing(self, value: str): + self.draw_area.color_buffer = json.loads(value) + + def serialize(self): + """ Return a serialized version of this widget """ + base_dict = super().serialize() + base_dict["drawing"] = self.drawing + + return base_dict + + def deserialize(self, data: OrderedDict, + hashmap: dict = None, restore_id: bool = True): + """ Restore a markdown block from it's serialized state """ + for dataname in ['drawing']: + if dataname in data: + setattr(self, dataname, data[dataname]) + + super().deserialize(data, hashmap, restore_id) \ No newline at end of file diff --git a/opencodeblocks/blocks/markdownblock.py b/opencodeblocks/blocks/markdownblock.py index 70719332..a02caaf3 100644 --- a/opencodeblocks/blocks/markdownblock.py +++ b/opencodeblocks/blocks/markdownblock.py @@ -2,6 +2,7 @@ Exports OCBMarkdownBlock. """ +from typing import OrderedDict from markdown import markdown from PyQt5.QtWebEngineWidgets import QWebEngineView @@ -28,7 +29,6 @@ def __init__(self, **kwargs): self.lexer = QsciLexerMarkdown() theme_manager().current_theme().apply_to_lexer(self.lexer) - # Customize lexer background color: self.lexer.setColor(QColor.fromRgb(255, 255, 255), -1) self.editor.setCaretForegroundColor(QColor("#FFFFFF")) self.editor.setLexer(self.lexer) @@ -65,3 +65,28 @@ def valueChanged(self): """ self.rendered_markdown.setHtml(f"{dark_theme}{markdown(t)}") + + @property + def text(self) -> str: + """ The content of the markdown block """ + return self.editor.text() + + @text.setter + def text(self, value: str): + self.editor.setText(value) + self.valueChanged() + + def serialize(self): + base_dict = super().serialize() + base_dict["text"] = self.text + + return base_dict + + def deserialize(self, data: OrderedDict, + hashmap: dict = None, restore_id: bool = True): + """ Restore a markdown block from it's serialized state """ + for dataname in ['text']: + if dataname in data: + setattr(self, dataname, data[dataname]) + + super().deserialize(data, hashmap, restore_id) \ No newline at end of file diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index 874fe4d4..089b5826 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -4,6 +4,7 @@ Exports OCBSliderBlock. """ +from typing import OrderedDict from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout from opencodeblocks.blocks.block import OCBBlock @@ -44,11 +45,40 @@ def __init__(self, **kwargs): def valueChanged(self): """ This is called when the value of the slider changes """ - val = self.slider.value() / 100 - var_name = self.variable_text.text() - python_code = f"{var_name} = {val}" - self.variable_value.setText(f"{val}") + python_code = f"{self.var_name} = {self.value}" + self.variable_value.setText(f"{self.value}") # The code execution part will be added when the execution flow is merged. # We print for now print(python_code) + + @property + def value(self): + return str(self.slider.value() / 100) + @value.setter + def value(self, value: str): + self.slider.setValue(int(float(value) * 100)) + + @property + def var_name(self): + return self.variable_text.text() + @var_name.setter + def var_name(self, value: str): + self.variable_text.setText(value) + + def serialize(self): + """ Return a serialized version of this widget """ + base_dict = super().serialize() + base_dict["value"] = self.value + base_dict["var_name"] = self.var_name + + return base_dict + + def deserialize(self, data: OrderedDict, + hashmap: dict = None, restore_id: bool = True): + """ Restore a slider block from it's serialized state """ + for dataname in ['value','var_name']: + if dataname in data: + setattr(self, dataname, data[dataname]) + + super().deserialize(data, hashmap, restore_id) diff --git a/opencodeblocks/scene/clipboard.py b/opencodeblocks/scene/clipboard.py index c1e10e8c..5aab9ad2 100644 --- a/opencodeblocks/scene/clipboard.py +++ b/opencodeblocks/scene/clipboard.py @@ -95,22 +95,9 @@ def _deserializeData(self, data:OrderedDict, set_selected=True): # Create blocks for block_data in data['blocks']: - block_type = block_data['block_type'] - if block_type == 'base': - block = OCBBlock() - elif block_type == 'code': - block = OCBCodeBlock() - else: - raise NotImplementedError(f'Unsupported block type: {block_type}') - block.deserialize(block_data, hashmap, restore_id=False) - - block_pos = block.pos() - block.setPos(block_pos.x() + offset_x, block_pos.y() + offset_y) - + block = self.scene.create_block(block_data, hashmap, restore_id = False) if set_selected: block.setSelected(True) - self.scene.addItem(block) - hashmap.update({block.id: block}) # Create edges for edge_data in data['edges']: From 5eca2aaaded0e1844f055411158cf0969b213926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Fri, 3 Dec 2021 00:27:48 +0100 Subject: [PATCH 15/16] :sparkles: :memo: Add docstrings and better whitespaces --- opencodeblocks/blocks/drawingblock.py | 46 ++++++++++++++++++++------ opencodeblocks/blocks/markdownblock.py | 4 +-- opencodeblocks/blocks/sliderblock.py | 4 ++- opencodeblocks/scene/clipboard.py | 4 +-- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/opencodeblocks/blocks/drawingblock.py b/opencodeblocks/blocks/drawingblock.py index 5b8da635..f45cfc62 100644 --- a/opencodeblocks/blocks/drawingblock.py +++ b/opencodeblocks/blocks/drawingblock.py @@ -1,17 +1,23 @@ +# pylint:disable=unused-argument + from math import floor import json from typing import OrderedDict from PyQt5.QtCore import Qt -from PyQt5.QtGui import QColor, QMouseEvent, QPaintEvent, QPainter, QPen +from PyQt5.QtGui import QColor, QMouseEvent, QPaintEvent, QPainter from PyQt5.QtWidgets import QPushButton, QWidget from opencodeblocks.blocks.block import OCBBlock eps = 1 + class DrawableWidget(QWidget): + """ A drawable widget is a canvas like widget on which you can doodle """ + def __init__(self, parent: QWidget): + """ Create a new Drawable widget """ super().__init__(parent) self.setAttribute(Qt.WA_PaintOnScreen) self.pixel_width = 24 @@ -25,21 +31,30 @@ def __init__(self, parent: QWidget): self.color_buffer[-1].append(0xFFFFFFFF) def clearDrawing(self): + """ Clear the drawing """ for i in range(self.pixel_width): for j in range(self.pixel_height): - self.color_buffer[i][j] = 0xFFFFFFFF + self.color_buffer[i][j] = 0xFFFFFFFF def paintEvent(self, evt: QPaintEvent): + """ Draw the content of the widget """ painter = QPainter(self) - + for i in range(self.pixel_width): self.color_buffer.append([]) for j in range(self.pixel_height): w = self.width() / self.pixel_width h = self.height() / self.pixel_height - painter.fillRect(w*i,h*j,w + eps,h + eps,QColor.fromRgb(self.color_buffer[i][j])) + painter.fillRect( + w * i, + h * j, + w + eps, + h + eps, + QColor.fromRgb( + self.color_buffer[i][j])) def mouseMoveEvent(self, evt: QMouseEvent): + """ Change the drawing when dragging the mouse around""" if self.mouse_down: x = floor(evt.x() / self.width() * self.pixel_width) y = floor(evt.y() / self.height() * self.pixel_height) @@ -48,31 +63,40 @@ def mouseMoveEvent(self, evt: QMouseEvent): self.repaint() def mousePressEvent(self, evt: QMouseEvent): - self.mouse_down = True + """ Signal that the drawing starts """ + self.mouse_down = True def mouseReleaseEvent(self, evt: QMouseEvent): - self.mouse_down = False + """ Signal that the drawing stops """ + self.mouse_down = False + class OCBDrawingBlock(OCBBlock): + """ An OCBBlock on which you can draw, to test your CNNs for example""" def __init__(self, **kwargs): + """ Create a new OCBBlock""" super().__init__(**kwargs) self.draw_area = DrawableWidget(self.root) - self.splitter.addWidget(self.draw_area) # QGraphicsView + self.splitter.addWidget(self.draw_area) # QGraphicsView self.run_button = QPushButton("Clear", self.root) - self.run_button.move(int(self.edge_size * 2), int(self.title_widget.height() + self.edge_size * 2)) - self.run_button.setFixedSize(int(8 * self.edge_size),int(3 * self.edge_size)) + self.run_button.move(int(self.edge_size * 2), + int(self.title_widget.height() + self.edge_size * 2)) + self.run_button.setFixedSize( + int(8 * self.edge_size), int(3 * self.edge_size)) self.run_button.clicked.connect(self.draw_area.clearDrawing) self.holder.setWidget(self.root) @property def drawing(self): + """ A json-encoded representation of the drawing """ return json.dumps(self.draw_area.color_buffer) + @drawing.setter def drawing(self, value: str): - self.draw_area.color_buffer = json.loads(value) + self.draw_area.color_buffer = json.loads(value) def serialize(self): """ Return a serialized version of this widget """ @@ -88,4 +112,4 @@ def deserialize(self, data: OrderedDict, if dataname in data: setattr(self, dataname, data[dataname]) - super().deserialize(data, hashmap, restore_id) \ No newline at end of file + super().deserialize(data, hashmap, restore_id) diff --git a/opencodeblocks/blocks/markdownblock.py b/opencodeblocks/blocks/markdownblock.py index a02caaf3..9b3416d5 100644 --- a/opencodeblocks/blocks/markdownblock.py +++ b/opencodeblocks/blocks/markdownblock.py @@ -65,7 +65,7 @@ def valueChanged(self): """ self.rendered_markdown.setHtml(f"{dark_theme}{markdown(t)}") - + @property def text(self) -> str: """ The content of the markdown block """ @@ -89,4 +89,4 @@ def deserialize(self, data: OrderedDict, if dataname in data: setattr(self, dataname, data[dataname]) - super().deserialize(data, hashmap, restore_id) \ No newline at end of file + super().deserialize(data, hashmap, restore_id) diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index 089b5826..21505b78 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -54,13 +54,15 @@ def valueChanged(self): @property def value(self): + """ The value of the slider """ return str(self.slider.value() / 100) @value.setter def value(self, value: str): - self.slider.setValue(int(float(value) * 100)) + self.slider.setValue(int(float(value) * 100)) @property def var_name(self): + """ The name of the python variable associated with the slider """ return self.variable_text.text() @var_name.setter def var_name(self, value: str): diff --git a/opencodeblocks/scene/clipboard.py b/opencodeblocks/scene/clipboard.py index 5aab9ad2..eb2f6b65 100644 --- a/opencodeblocks/scene/clipboard.py +++ b/opencodeblocks/scene/clipboard.py @@ -9,11 +9,10 @@ import json from PyQt5.QtWidgets import QApplication -from opencodeblocks.blocks import OCBBlock, OCBCodeBlock from opencodeblocks.graphics.edge import OCBEdge if TYPE_CHECKING: - from opencodeblocks.graphics.scene import OCBScene + from opencodeblocks.scene import OCBScene from opencodeblocks.graphics.view import OCBView @@ -98,6 +97,7 @@ def _deserializeData(self, data:OrderedDict, set_selected=True): block = self.scene.create_block(block_data, hashmap, restore_id = False) if set_selected: block.setSelected(True) + block.setPos(block.x() + offset_x, block.y() + offset_y) # Create edges for edge_data in data['edges']: From 04d7912e546b79e6d8d5a74a95ec231cc4a29a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Fri, 3 Dec 2021 14:23:21 +0100 Subject: [PATCH 16/16] :fire: Removed autopep8 as the linker. We now use black. --- requirements-dev.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index baa7fc60..889af2ec 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,5 +7,4 @@ pytest-qt pyautogui pylint>=2.12 pylint-pytest -autopep8 black \ No newline at end of file