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/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/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/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/markdown.ocbb b/blocks/markdown.ocbb new file mode 100644 index 00000000..b47dc633 --- /dev/null +++ b/blocks/markdown.ocbb @@ -0,0 +1,16 @@ +{ + "title": "Markdown", + "block_type": "OCBMarkdownBlock", + "text": "", + "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/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/examples/mnist.ipyg b/examples/mnist.ipyg index 43b367db..0b852e0f 100644 --- a/examples/mnist.ipyg +++ b/examples/mnist.ipyg @@ -4,12 +4,12 @@ { "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": [ 85, - 261 + 259 ], "position": [ 1062.374999999999, @@ -21,7 +21,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 12, + "size": 10, "padding": 4.0 } }, @@ -31,7 +31,7 @@ "type": "input", "position": [ 0.0, - 40.0 + 42.0 ], "metadata": { "color": "#e02c2c", @@ -45,7 +45,7 @@ "type": "output", "position": [ 1064.0, - 40.0 + 42.0 ], "metadata": { "color": "#35bc31", @@ -59,12 +59,12 @@ { "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": [ 0, - 280 + 278 ], "position": [ 2330.066406249998, @@ -76,7 +76,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 12, + "size": 10, "padding": 4.0 } }, @@ -86,7 +86,7 @@ "type": "input", "position": [ 0.0, - 40.0 + 42.0 ], "metadata": { "color": "#FF55FFF0", @@ -100,12 +100,12 @@ { "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": [ - 77, - 77 + 76, + 76 ], "position": [ 2295.17578125, @@ -117,7 +117,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 12, + "size": 10, "padding": 4.0 } }, @@ -127,7 +127,7 @@ "type": "input", "position": [ 0.0, - 40.0 + 42.0 ], "metadata": { "color": "#FF55FFF0", @@ -141,11 +141,11 @@ { "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": [ - 88, + 86, 0 ], "position": [ @@ -158,7 +158,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 12, + "size": 10, "padding": 4.0 } }, @@ -168,7 +168,7 @@ "type": "output", "position": [ 850.0, - 40.0 + 42.0 ], "metadata": { "color": "#FF55FFF0", @@ -182,12 +182,12 @@ { "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": [ - 85, - 208 + 206, + 85 ], "position": [ 44.48828125000068, @@ -199,7 +199,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 12, + "size": 10, "padding": 4.0 } }, @@ -209,7 +209,7 @@ "type": "input", "position": [ 0.0, - 40.0 + 42.0 ], "metadata": { "color": "#FF55FFF0", @@ -223,7 +223,7 @@ "type": "output", "position": [ 855.0, - 40.0 + 42.0 ], "metadata": { "color": "#FF55FFF0", @@ -237,11 +237,11 @@ { "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": [ - 420, + 418, 0 ], "position": [ @@ -254,7 +254,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 12, + "size": 10, "padding": 4.0 } }, @@ -264,7 +264,7 @@ "type": "output", "position": [ 1002.0, - 40.0 + 42.0 ], "metadata": { "color": "#FF55FFF0", @@ -277,25 +277,25 @@ }, { "id": 2828158533848, - "title": "Plot Dataset Example", - "block_type": "code", + "title": "Plot Image Dataset Example", + "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": [ 0, - 284 + 274 ], "position": [ - 95.60937500000011, - -728.9375 + 103.60937500000011, + -734.9375 ], "width": 300, - "height": 337, + "height": 329, "metadata": { "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 12, + "size": 10, "padding": 4.0 } }, @@ -305,7 +305,7 @@ "type": "input", "position": [ 0.0, - 40.0 + 42.0 ], "metadata": { "color": "#FF55FFF0", diff --git a/opencodeblocks/blocks/__init__.py b/opencodeblocks/blocks/__init__.py index f88fde65..82fed7bc 100644 --- a/opencodeblocks/blocks/__init__.py +++ b/opencodeblocks/blocks/__init__.py @@ -3,10 +3,8 @@ """ Module for the OCB Blocks of different types. """ +from opencodeblocks.blocks.sliderblock import OCBSliderBlock from opencodeblocks.blocks.block import OCBBlock from opencodeblocks.blocks.codeblock import OCBCodeBlock - -BLOCKS = { - 'base': OCBBlock, - 'code': OCBCodeBlock -} +from opencodeblocks.blocks.markdownblock import OCBMarkdownBlock +from opencodeblocks.blocks.drawingblock import OCBDrawingBlock diff --git a/opencodeblocks/blocks/block.py b/opencodeblocks/blocks/block.py index e6f3384a..2d9a02a4 100644 --- a/opencodeblocks/blocks/block.py +++ b/opencodeblocks/blocks/block.py @@ -21,7 +21,7 @@ from opencodeblocks.blocks.widgets import OCBSplitter, OCBSizeGrip, OCBTitle if TYPE_CHECKING: - from opencodeblocks.graphics.scene.scene import OCBScene + from opencodeblocks.scene.scene import OCBScene BACKGROUND_COLOR = QColor("#E3212121") @@ -274,8 +274,6 @@ def serialize(self) -> OrderedDict: ("id", self.id), ("title", self.title), ("block_type", self.block_type), - ("source", self.source), - ("stdout", self.stdout), ("splitter_pos", self.splitter.sizes()), ("position", [self.pos().x(), self.pos().y()]), ("width", self.width), @@ -295,7 +293,7 @@ def deserialize(self, data: dict, hashmap: dict = None, restore_id=True) -> None """ Restore the block from serialized data """ 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/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index a6f3733f..3b45ce81 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -3,6 +3,7 @@ """ Module for the base OCB Code Block. """ +from typing import OrderedDict from PyQt5.QtWidgets import QPushButton, QTextEdit from ansi2html import Ansi2HTMLConverter @@ -33,7 +34,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 @@ -66,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 @@ -83,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), @@ -91,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() @@ -98,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 @@ -113,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 @@ -136,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", "") @@ -148,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 @@ -162,8 +168,23 @@ 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): - """Handle the image signal""" - self.stdout = "" + image + """ 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'): + if dataname in data: + setattr(self, dataname, data[dataname]) + super().deserialize(data, hashmap, restore_id) diff --git a/opencodeblocks/blocks/drawingblock.py b/opencodeblocks/blocks/drawingblock.py new file mode 100644 index 00000000..f45cfc62 --- /dev/null +++ b/opencodeblocks/blocks/drawingblock.py @@ -0,0 +1,115 @@ +# 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 +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 + 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): + """ Clear the drawing """ + 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): + """ 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])) + + 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) + 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): + """ Signal that the drawing starts """ + self.mouse_down = True + + def mouseReleaseEvent(self, evt: QMouseEvent): + """ 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.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) + + @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) + + 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) diff --git a/opencodeblocks/blocks/markdownblock.py b/opencodeblocks/blocks/markdownblock.py new file mode 100644 index 00000000..9b3416d5 --- /dev/null +++ b/opencodeblocks/blocks/markdownblock.py @@ -0,0 +1,92 @@ +""" +Exports OCBMarkdownBlock. +""" + +from typing import OrderedDict +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): + """ A block that is able to render markdown text """ + + 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) + 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): + """ Update markdown rendering when the content of the markdown editor changes """ + t = self.editor.text() + + dark_theme = """ + + """ + + 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) diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py new file mode 100644 index 00000000..21505b78 --- /dev/null +++ b/opencodeblocks/blocks/sliderblock.py @@ -0,0 +1,86 @@ +# OpenCodeBlock an open-source tool for modular visual programing in python + +""" +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 + + +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.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.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 valueChanged(self): + """ This is called when the value of the slider changes """ + 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): + """ 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)) + + @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): + 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/blocks/widgets/blocksplitter.py b/opencodeblocks/blocks/widgets/blocksplitter.py index a7eee746..c9b0de53 100644 --- a/opencodeblocks/blocks/widgets/blocksplitter.py +++ b/opencodeblocks/blocks/widgets/blocksplitter.py @@ -27,5 +27,5 @@ def __init__(self, block: QWidget, orientation: int, parent: QWidget): self.block = block def createHandle(self): - """Return the middle handle of the splitter""" + """ Return the middle handle of the splitter """ return OCBSplitterHandle(self.orientation(), self) diff --git a/opencodeblocks/scene/clipboard.py b/opencodeblocks/scene/clipboard.py index 9e6290fd..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 @@ -80,6 +79,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] @@ -93,22 +94,10 @@ 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}) + block.setPos(block.x() + offset_x, block.y() + offset_y) # Create edges for edge_data in data['edges']: diff --git a/opencodeblocks/scene/scene.py b/opencodeblocks/scene/scene.py index 772ba2b2..e61c2d8c 100644 --- a/opencodeblocks/scene/scene.py +++ b/opencodeblocks/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 import blocks + 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 @@ -193,12 +194,20 @@ 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): + 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: diff --git a/requirements-dev.txt b/requirements-dev.txt index c49f27fb..889af2ec 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,4 +7,4 @@ pytest-qt pyautogui pylint>=2.12 pylint-pytest -autopep8 \ No newline at end of file +black \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a8159a30..df0d18d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,6 @@ 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 +pyqtwebengine>=5.15.5 \ No newline at end of file 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": "",