From 590f5b5a280672c9ddc2c601bdb29eb75eff47d2 Mon Sep 17 00:00:00 2001 From: Andrew Shen Date: Wed, 22 Apr 2020 10:56:32 -0700 Subject: [PATCH] Indev 14.1 - Serialization 100% completed --- CHANGELOG.md | 11 +- NewType/MDL0.exec.dat | 49 ------- NewType/MDL0.proj.dat | 0 NewType/project.yaml | 8 -- NewType2/MDL0.exec.dat | 84 ++++++------ NewType2/MDL0.proj.dat | 150 +++++++++++++-------- SerializationTest/MDL0.exec.dat | 45 ------- SerializationTest/MDL0.proj.dat | 144 -------------------- SerializationTest/project.yaml | 7 - TODO.md | 31 +++-- setup.py | 17 +++ src/components/model_manager.py | 15 ++- src/components/project_setup.py | 9 +- src/components/workspace/ai_backend.py | 3 + src/components/workspace/connector_type.py | 17 ++- src/components/workspace/executor.py | 9 +- src/components/workspace/nodes.py | 137 +++++++++++++------ src/constants.py | 3 +- src/gfx/connection.py | 2 - src/gfx/connector.py | 143 +++++--------------- src/gfx/constant_models.py | 26 ++-- src/gfx/node.py | 39 ++---- src/interface/project_file_interface.py | 130 +++++------------- src/widgets.py | 22 +-- 24 files changed, 415 insertions(+), 686 deletions(-) delete mode 100644 NewType/MDL0.exec.dat delete mode 100644 NewType/MDL0.proj.dat delete mode 100644 NewType/project.yaml delete mode 100644 SerializationTest/MDL0.exec.dat delete mode 100644 SerializationTest/MDL0.proj.dat delete mode 100644 SerializationTest/project.yaml create mode 100644 setup.py create mode 100644 src/components/workspace/ai_backend.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d97b626..5699321 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ -## Indev 14.0 [April 19, 2020] +## Indev 14.1 [April 22, 2020] +* Serialization now is able to serialize constant field datas +* New Optional Type (The last type we will implement for now) +* Added another node, TrainNN, for training Neural Networks +* Internal code clean-up +* Fixed: + * The FileDialog in the nodes does not close when clicked [CANCEL] + * Loading project, then creating a new Workspace with new name causes an E001 + +## Indev 14.0 [April 21, 2020] * Graphs Testing: * Linear Regression * Added few more node types: diff --git a/NewType/MDL0.exec.dat b/NewType/MDL0.exec.dat deleted file mode 100644 index 85b9396..0000000 --- a/NewType/MDL0.exec.dat +++ /dev/null @@ -1,49 +0,0 @@ -0 - 1 >> 193[13] - 2 >> 193[14] - 3 == C:/Users/Andrew Shen/Desktop/ProjectEmerald/MyModel/test/iris.csv - 4 == species - 5 == False - 6 == False -7 - 8 << 193[] - 9 == C:/Users/Andrew Shen/Desktop/ProjectEmerald/MyModel/test/out.csv - 10 == False - 11 == , -12 - 13 << 193[] - 14 << 193[] - 15 << 193[] - 16 >> 193[8] -17 - 18 >> 193[15] - 19 >> 193[] - 20 == C:/Users/Andrew Shen/Desktop/ProjectEmerald/MyModel/test/test.csv - 21 == - 22 == False - 23 == False -namespace - 0 CSVInput - 1 x - 2 y - 3 file input - 4 result column - 5 transpose - 6 result numerical - 7 CSVOutput - 8 data - 9 file output - 10 transpose - 11 seperator - 12 LinearRegression - 13 x - 14 y - 15 test - 16 result - 17 CSVInput - 18 x - 19 y - 20 file input - 21 result column - 22 transpose - 23 result numerical diff --git a/NewType/MDL0.proj.dat b/NewType/MDL0.proj.dat deleted file mode 100644 index e69de29..0000000 diff --git a/NewType/project.yaml b/NewType/project.yaml deleted file mode 100644 index 6e883e0..0000000 --- a/NewType/project.yaml +++ /dev/null @@ -1,8 +0,0 @@ -author: null -created: null -model: - index: 1 - tag: - 0: LinearReg -name: NewType -updated: null diff --git a/NewType2/MDL0.exec.dat b/NewType2/MDL0.exec.dat index e1ad737..f2870fb 100644 --- a/NewType2/MDL0.exec.dat +++ b/NewType2/MDL0.exec.dat @@ -1,49 +1,49 @@ 0 - 1 >> 193[20] - 2 >> 193[21] - 3 == C:/Users/Andrew Shen/Desktop/ProjectEmerald/MyModel/test/iris.csv - 4 == species - 5 == False - 6 == False -7 - 8 << 193[] - 9 == C:/Users/Andrew Shen/Desktop/ProjectEmerald/MyModel/test/out.csv + 1 << 193[] + 2 << 193[] + 3 << 193[] + 4 >> 193[13] +5 + 6 >> 193[3] + 7 >> 193[] + 8 == + 9 == 10 == False - 11 == , + 11 == False 12 - 13 >> 193[22] - 14 >> 193[] - 15 == C:/Users/Andrew Shen/Desktop/ProjectEmerald/MyModel/test/test.csv - 16 == - 17 == False - 18 == False -19 - 20 << 193[] - 21 << 193[] - 22 << 193[] - 23 >> 193[8] + 13 << 193[] + 14 == + 15 == True + 16 == ,$#$ +17 + 18 >> 193[1] + 19 >> 193[2] + 20 == + 21 == species + 22 == False + 23 == False namespace - 0 CSVInput + 0 LogisticRegression 1 x 2 y - 3 file input - 4 result column - 5 transpose - 6 result numerical - 7 CSVOutput - 8 data - 9 file output + 3 test + 4 result + 5 CSVInput + 6 x + 7 y + 8 file input + 9 result column 10 transpose - 11 seperator - 12 CSVInput - 13 x - 14 y - 15 file input - 16 result column - 17 transpose - 18 result numerical - 19 LogisticRegression - 20 x - 21 y - 22 test - 23 result + 11 result numerical + 12 CSVOutput + 13 data + 14 file output + 15 transpose + 16 seperator + 17 CSVInput + 18 x + 19 y + 20 file input + 21 result column + 22 transpose + 23 result numerical diff --git a/NewType2/MDL0.proj.dat b/NewType2/MDL0.proj.dat index 5e9c635..0af4458 100644 --- a/NewType2/MDL0.proj.dat +++ b/NewType2/MDL0.proj.dat @@ -1,58 +1,71 @@ 0: connector: - - 6 + - 9 + - 7 + - 5 - 3 - nd_cls: CSVInput + constants: {} + nd_cls: LogisticRegressionREG pos: - - 436 - - 251 -2: + - 766 + - 338 +1: connector_a: 3 - connector_b: 23 + connector_b: 18 3: connections: - - 2 + - 1 en: - inp field: - - y + - result - 193 tag: out 5: - connector_a: 6 - connector_b: 25 -6: - connections: - - 5 + connections: [] en: - - inp + - out field: - - x + - test - 193 - tag: out + tag: inp 7: - connector: - - 9 - nd_cls: CSVOutput - pos: - - 1097 - - 331 + connections: [] + en: + - out + field: + - y + - 193 + tag: inp 9: connections: [] en: - out field: - - data + - x - 193 tag: inp 10: connector: - 15 - 12 + constants: + file input: + single: true + text: MDL0.proj.dat + url: + - C:/Users/Andrew Shen/Desktop/ProjectEmerald/NewType2/MDL0.proj.dat + result column: + numerical: false + text: '' + result numerical: + checked: false + transpose: + checked: false nd_cls: CSVInput pos: - - 445 - - 475 + - 471 + - 466 12: connections: [] en: @@ -61,12 +74,12 @@ - y - 193 tag: out -14: +13: connector_a: 15 - connector_b: 21 + connector_b: 5 15: connections: - - 14 + - 13 en: - inp field: @@ -75,47 +88,72 @@ tag: out 16: connector: - - 25 - - 23 - - 21 - - 19 - nd_cls: LogisticRegressionREG + - 18 + constants: + file output: + single: true + text: MDL0.exec.dat + url: + - C:/Users/Andrew Shen/Desktop/ProjectEmerald/NewType2/MDL0.exec.dat + seperator: + numerical: false + text: ',$#$' + transpose: + checked: true + nd_cls: CSVOutput pos: - - 763 - - 347 -17: - connector_a: 19 - connector_b: 9 -19: - connections: - - 17 - en: - - inp - field: - - result - - 193 - tag: out -21: + - 1097 + - 331 +18: connections: [] en: - out field: - - test + - data - 193 tag: inp -23: - connections: [] +19: + connector: + - 25 + - 22 + constants: + file input: + single: true + text: project.yaml + url: + - C:/Users/Andrew Shen/Desktop/ProjectEmerald/NewType2/project.yaml + result column: + numerical: false + text: species + result numerical: + checked: false + transpose: + checked: false + nd_cls: CSVInput + pos: + - 472 + - 234 +20: + connector_a: 22 + connector_b: 7 +22: + connections: + - 20 en: - - out + - inp field: - y - 193 - tag: inp + tag: out +23: + connector_a: 25 + connector_b: 9 25: - connections: [] + connections: + - 23 en: - - out + - inp field: - x - 193 - tag: inp + tag: out diff --git a/SerializationTest/MDL0.exec.dat b/SerializationTest/MDL0.exec.dat deleted file mode 100644 index d704810..0000000 --- a/SerializationTest/MDL0.exec.dat +++ /dev/null @@ -1,45 +0,0 @@ -9 - 0 >> type[0] - 1 << type[] - 2 << type[] - 3 << type[] -10 - 4 << type[] - 11 == - 12 == False - 13 == , -14 - 5 >> type[] - 6 >> type[6] - 20 == - 21 == - 22 == False - 23 == False -19 - 7 >> type[7] - 8 >> type[8] - 20 == - 21 == - 22 == False - 23 == False -namespace - 9 LinearRegression - 0 result - 1 test - 2 y - 3 x - 10 CSVOutput - 4 data - 11 file output - 12 transpose - 13 seperator - 14 CSVInput - 5 y - 6 x - 20 file input - 21 result column - 22 transpose - 23 result numerical - 19 CSVInput - 7 y - 8 x diff --git a/SerializationTest/MDL0.proj.dat b/SerializationTest/MDL0.proj.dat deleted file mode 100644 index ddc2282..0000000 --- a/SerializationTest/MDL0.proj.dat +++ /dev/null @@ -1,144 +0,0 @@ -0: - connector: - - 9 - - 7 - - 5 - - 3 - nd_cls: LinearRegressionREG - pos: - - 774 - - 249 -1: - connector_a: 3 - connector_b: 12 -2: - connector_a: 3 - connector_b: null -3: - connections: - - 1 - en: - - inp - field: - - result - - type - tag: out -4: - connector_a: 5 - connector_b: null -5: - connections: [] - en: - - out - field: - - test - - type - tag: inp -6: - connector_a: 7 - connector_b: null -7: - connections: [] - en: - - out - field: - - y - - type - tag: inp -8: - connector_a: 9 - connector_b: null -9: - connections: [] - en: - - out - field: - - x - - type - tag: inp -10: - connector: - - 12 - nd_cls: CSVOutput - pos: - - 1115 - - 358 -11: - connector_a: 12 - connector_b: null -12: - connections: [] - en: - - out - field: - - data - - type - tag: inp -13: - connector: - - 18 - - 15 - nd_cls: CSVInput - pos: - - 400 - - 460 -14: - connector_a: 15 - connector_b: null -15: - connections: [] - en: - - inp - field: &id001 - - y - - type - tag: out -16: - connector_a: 18 - connector_b: 5 -17: - connector_a: 18 - connector_b: null -18: - connections: - - 16 - en: - - inp - field: &id002 - - x - - type - tag: out -19: - connector: - - 25 - - 22 - nd_cls: CSVInput - pos: - - 400 - - 200 -20: - connector_a: 22 - connector_b: 7 -21: - connector_a: 22 - connector_b: null -22: - connections: - - 20 - en: - - inp - field: *id001 - tag: out -23: - connector_a: 25 - connector_b: 9 -24: - connector_a: 25 - connector_b: null -25: - connections: - - 23 - en: - - inp - field: *id002 - tag: out diff --git a/SerializationTest/project.yaml b/SerializationTest/project.yaml deleted file mode 100644 index 2b7bd63..0000000 --- a/SerializationTest/project.yaml +++ /dev/null @@ -1,7 +0,0 @@ -author: null -created: null -model: - index: 1 - tag: {} -name: SerializationTest -updated: null diff --git a/TODO.md b/TODO.md index cd714eb..abe1a9d 100644 --- a/TODO.md +++ b/TODO.md @@ -9,7 +9,7 @@ so small bugs no need to be issued *for now*. ##### Software [Desktop Platform (Win10, Linux:Gnome, MacOS, etc.)] -[Software Version (Indev 1.0, Alpha 2.1, etc.)] +[Software Version (Indev 1.0, 0.2.1, etc.)] ##### Effect What effect had it made on software or interfering external application? @@ -25,22 +25,27 @@ Things can try. __(At your own risk!)__ * Re-install the software ## Known Issue -* Execute backflow input connector forever loop -* Unicode and normal characters asynced when typeing resulting continuous typeing -* Pressing shift for a few second enables the continues key input thus -allowing new printable character to be "" - ## TODO #### Next Release Candidates -- The FileDialog in the nodes does not close when clicked [CANCEL] - Add an option "Load Empty" when the file failed to load -- Serialize each constant fields - README.md: Add an installation info - ... we'll be using Cx_freeze for making executables for distribution - -#### Near Future - - -#### Vision \ No newline at end of file +- The text size changes on the QComboBox on the side of each Model Workspace +- Add an delete button on top corners of each Node to delete the relative Node +- Add a pre-process normalize image node +- Add read image node +- Ask to save the project (if not saved) after the user decides to quit +- Don't save model if the user hadnt click [save model] + +#### Near Future (Ordered) +- A better way to create nodes +- Serializing Constant Field based on objects instead of relying on names (possible duplicate names) +- More descriptive errors +- Support circular reference for executing nodes (will implemented when needed) +- Text break when the Model Workspace Title reach pass a limit +- Add a boosting/bagging model + +#### Vision +- Move codebase from Python to possibly C++/C, Rust \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fb7d769 --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +import sys +from cx_Freeze import setup, Executable + +# Dependencies are automatically detected, but it might need fine tuning. +build_exe_options = {"packages": ["os"], "excludes": ["tkinter"]} + +# GUI applications require a different base on Windows (the default is for a +# console application). +base = None +if sys.platform == "win32": + base = "Win32GUI" + +setup( name = "guifoo", + version = "0.1", + description = "My GUI application!", + options = {"build_exe": build_exe_options}, + executables = [Executable("guifoo.py", base=base)]) diff --git a/src/components/model_manager.py b/src/components/model_manager.py index 484685d..3dd94fd 100644 --- a/src/components/model_manager.py +++ b/src/components/model_manager.py @@ -138,16 +138,16 @@ def __init__(self, proj: ProjectFI, parent=None): self.spacer = QVLine(visible=False) # vertical spacer self.spacer.hide() self.cur_mdl: Optional[ModelWorkspace] = None - self.project: Optional[ProjectFI] = None + self.project: Optional[ProjectFI] = proj - self.inst_ui(proj) - self.add_model(proj) + self.inst_ui() + self.add_model() - def inst_ui(self, proj: ProjectFI): + def inst_ui(self): self.mdl_slctr = ModelWorkspaceSelector(self.models) self.mdl_slctr.activated.connect(lambda ind: self.select_model(ind)) b_new_mdl = QPushButton("New Model") - b_new_mdl.clicked.connect(lambda checked: self.add_model(proj)) + b_new_mdl.clicked.connect(lambda checked: self.add_model()) b_rem_mdl = QPushButton("Remove Current Model") b_rem_mdl.clicked.connect(lambda checked: self.rem_model(self.cur_mdl)) @@ -169,8 +169,8 @@ def inst_ui(self, proj: ProjectFI): self.setLayout(self.central) - def add_model(self, proj: ProjectFI): - mdl_wksp = ModelWorkspace(proj) + def add_model(self): + mdl_wksp = ModelWorkspace(self.project) mdl_wksp.nameChanged.connect(lambda s: self.mdl_slctr.update_list(self.models)) mdl_wksp.modelUpdate.connect(lambda o: self.modelUpdate.emit(o)) self.models.append(mdl_wksp) @@ -224,4 +224,5 @@ def load_prj(self, proj: ProjectFI): # the loaded model workspace would APPEND self.models.append(mdl_wksp) except: ErrorBox(**ErrorBox.E006).exec() + if DEBUG__: raise self.mdl_slctr.update_list(self.models) diff --git a/src/components/project_setup.py b/src/components/project_setup.py index 496551f..add17f7 100644 --- a/src/components/project_setup.py +++ b/src/components/project_setup.py @@ -4,6 +4,7 @@ from src.widgets import ErrorBox, NewProject, LoadProject from src.interface.project_file_interface import ProjectFI +from src.constants import DEBUG__ from typing import Optional import os @@ -60,7 +61,13 @@ def new_proj(self, name: str, path: str): self.proj_name.setPalette(p) def load_project(self, dir: str): - self.project = ProjectFI.load(dir) + try: + self.project = ProjectFI.load(dir) + except: + ErrorBox(**ErrorBox.E007).exec() + self.project = None + if DEBUG__: raise + if self.project is not None: self.projectLoaded.emit(self.project) diff --git a/src/components/workspace/ai_backend.py b/src/components/workspace/ai_backend.py new file mode 100644 index 0000000..4696636 --- /dev/null +++ b/src/components/workspace/ai_backend.py @@ -0,0 +1,3 @@ +import tensorflow as tf + + diff --git a/src/components/workspace/connector_type.py b/src/components/workspace/connector_type.py index 24ed539..ef4b174 100644 --- a/src/components/workspace/connector_type.py +++ b/src/components/workspace/connector_type.py @@ -2,13 +2,14 @@ class ConnectorType: # Requires at least typing of Matrix and Scalar - Matrix = 0b10000001 # requires matrix type - Scalar = 0b10000010 # requires scalar type - String = 0b00000100 # requires text/string type - Float = 0b00001000 # requires float type - Int = 0b00010000 # requires integer type - Bool = 0b00100000 # requires boolean type (true/false) - Any = 0b11000000 # any type + Matrix = 0b000000001 # requires matrix type (wrapper type) + Scalar = 0b000000010 # requires scalar type (wrapper type) + String = 0b000000100 # requires text/string type + Float = 0b000001000 # requires float type + Int = 0b000010000 # requires integer type + Bool = 0b000100000 # requires boolean type (true/false) + Any = 0b001000000 # any type + Optional = 0b010000000 # nullable (wrapper type) # Matrix: A Square Design @@ -19,6 +20,7 @@ class ConnectorType: # Bool: Red Color # Any (Discrete): Black Color # Any (Matrix|Scalar): Black Triangle +# Optional: Black Circle (for now) # Typing Hierarchical # -- every type can only be compatible by itself -- @@ -28,3 +30,4 @@ class ConnectorType: # Scalar | Any [= ( Scalar | ( Any | String | Float | Int | Bool ) ) # Matrix | Int [= Matrix | ( Int | Bool ) # Scalar | Int [= Matrix | ( Int | Bool ) +# Optional | ( [Any kind of type] ) [= ( Optional | ( [Any kind of type] ) ) | ( [Any kind of type] ) diff --git a/src/components/workspace/executor.py b/src/components/workspace/executor.py index df041b0..3c85e0c 100644 --- a/src/components/workspace/executor.py +++ b/src/components/workspace/executor.py @@ -84,7 +84,6 @@ def execNode(self, node: int): model_class = self.mdl_map[self.id_map[node]] model_incomplete = False model_type_error = [] # []: No Error; [{field name (INP): field type, field name(OUT): field type},...] - int_fld_map = {self.id_map[fld]:fld for fld in self.tree[node]} # (internal field map); local reverse field identification map inp_field = {} for field in self.tree[node]: @@ -99,8 +98,7 @@ def execNode(self, node: int): ext_fdata = self.tree[ext_node][ext_field] # external field data; improve readability if ext_fdata[self.FLD_TYP] == self.tOUT: # note: the pandas dataframe results a value error because of ambiguity and trying to cast the dataframe so it can easily be compared - if field in ext_fdata[self.IO_CONNECT] and \ - not self._is_null(ext_fdata[self.IO_VAL]): + if field in ext_fdata[self.IO_CONNECT] and not self._is_null(ext_fdata[self.IO_VAL]): if self._compat_type(int_fdata[self.IO_TYP], ext_fdata[self.IO_TYP]): ext_val.append(ext_fdata[self.IO_VAL]) else: model_incomplete = True; model_type_error.append({self.id_map[field]: int_fdata[self.IO_TYP], self.id_map[ext_field]: ext_fdata[self.IO_TYP]}) @@ -114,8 +112,7 @@ def execNode(self, node: int): for ext_field in self.tree[ext_node]: ext_fdata = self.tree[ext_node][ext_field] # external field data; improve readability if ext_fdata[self.FLD_TYP] == self.tOUT: - if ext_field in int_fdata[self.IO_CONNECT] and \ - not self._is_null(ext_fdata[self.IO_VAL]): + if ext_field in int_fdata[self.IO_CONNECT] and not self._is_null(ext_fdata[self.IO_VAL]): if self._compat_type(int_fdata[self.IO_TYP], ext_fdata[self.IO_TYP]): ext_val.append(ext_fdata[self.IO_VAL]) else: model_incomplete = True; model_type_error.append({self.id_map[field]: int_fdata[self.IO_TYP], self.id_map[ext_field]: ext_fdata[self.IO_TYP]}) @@ -140,8 +137,6 @@ def execNode(self, node: int): if not model_incomplete: fnlMod = lambda dct: {k:self._type_conversion(dct[k]) for k in dct} - # print(self.tree) - # print(inp_field) out_data = model_class.execute(inp=fnlMod(inp_field), const=fnlMod(const_field), out=fnlMod(out_field), inst=fnlMod(self.instance)) if [fld for fld in out_data if fld == Null] == []: # shows all the output data has been set (not Null) diff --git a/src/components/workspace/nodes.py b/src/components/workspace/nodes.py index 7148489..f3bf579 100644 --- a/src/components/workspace/nodes.py +++ b/src/components/workspace/nodes.py @@ -10,6 +10,9 @@ from sklearn.tree import DecisionTreeClassifier from sklearn.svm import SVC +import tensorflow as tf +import tensorflow.keras as keras + """ class name suffix: @@ -27,12 +30,12 @@ class Nodes: name = "#[Abstract]" # user oriented @staticmethod - def create(view, pos): + def create(view, pos, const=None): """ Creates an instance of the node from this custom node class ** NOTE **: Removed this as statimethod from classmethod because the .field attr was storing to the class itself (which had an unintended effect on serialization of node) - :param w: - :param pos: + :param view: the graphical object list of the Model Workspace Scene + :param pos: position of each node :return: """ raise NotImplementedError @@ -56,8 +59,32 @@ def descriptor(): pass -Matrix = "mtx" -ScalarInt = "scl_int" +class NodeRev2(Nodes): + title = "#[Abstract]" + name = "#[Abstract]" + + def inp(self, **kwargs): pass + + def out(self, **kwargs): pass + + def const(self, **kwargs): pass + + @classmethod + def create(cls, view, pos, **load_const): + node = cls() + + # replace the underscore with space when users sees it + node.inp( x=CT.Any, x2=CT.Any, ) + node.out( y=CT.Any, other=CT.Any, file_output=CT.Any, ) + if load_const == {}: node.const( file_input=FileDialog() ) + else: node.const( **load_const ) + + return node + + @staticmethod + def execute(inp, const, out, inst) -> dict: + out.x = const.file_input() + return out class CSVInput(Nodes): @@ -65,12 +92,13 @@ class CSVInput(Nodes): name = "Read CSV" @staticmethod - def create(view, pos): + def create(view, pos, const=None): cls = CSVInput() cls.field = { "input": [], "output": [("x", CT.Matrix | CT.Any), ("y", CT.Matrix | CT.Any)], - "constant": [("file input", FileDialog()), ("result column", LineInput()), ("transpose", CheckBox()), + "constant": const if const is not None else + [("file input", FileDialog()), ("result column", LineInput()), ("transpose", CheckBox()), ("result numerical", CheckBox()), ], } return Node(view, pos, cls) @@ -110,12 +138,13 @@ class CSVOutput(Nodes): name = "Write CSV" @staticmethod - def create(view, pos): + def create(view, pos, const=None): cls = CSVOutput() cls.field = { "input": [("data", CT.Matrix | CT.Any)], "output": [], - "constant": [("file output", FileDialog()), ("transpose", CheckBox()), ("seperator", LineInput(","))], + "constant": const if const is not None else + [("file output", FileDialog()), ("transpose", CheckBox()), ("seperator", LineInput(","))], } return Node(view, pos, cls) @@ -138,12 +167,12 @@ class LinearRegressionREG(Nodes): name = "Linear Regression" @staticmethod - def create(view, pos): + def create(view, pos, const=None): cls = LinearRegressionREG() cls.field = { "input": [("x", CT.Matrix | CT.Any), ("y", CT.Matrix | CT.Any), ("test", CT.Matrix | CT.Any)], "output": [("result", CT.Matrix | CT.Any)], - "constant": [], + "constant": const if const is not None else [], } return Node(view, pos, cls) @@ -161,12 +190,12 @@ class LogisticRegressionREG(Nodes): name = "Logistic Regression" @staticmethod - def create(view, pos): + def create(view, pos, const=None): cls = LogisticRegressionREG() cls.field = { "input": [("x", CT.Matrix | CT.Any), ("y", CT.Matrix | CT.Any), ("test", CT.Matrix | CT.Any)], "output": [("result", CT.Matrix | CT.Any)], - "constant": [], + "constant": const if const is not None else [], } return Node(view, pos, cls) @@ -184,12 +213,12 @@ class KMeansCLF(Nodes): name = "K-Means Cluster" @staticmethod - def create(view, pos): + def create(view, pos, const=None): cls = KMeansCLF() cls.field = { "input": [("x", CT.Matrix | CT.Any), ("test", CT.Matrix | CT.Any)], "output": [("result", CT.Matrix | CT.Any)], - "constant": [("clusters", LineInput(numerical=True))], + "constant": const if const is not None else [("clusters", LineInput(numerical=True))], } return Node(view, pos, cls) @@ -207,12 +236,13 @@ class DecisionTreeCLF(Nodes): name = "Std Decision Tree" @staticmethod - def create(view, pos): + def create(view, pos, const=None): cls = DecisionTreeCLF() cls.field = { "input": [("x", CT.Matrix | CT.Any), ("y", CT.Matrix | CT.Any), ("test", CT.Matrix | CT.Any)], "output": [("result", CT.Matrix | CT.Any), ("probability", CT.Matrix | CT.Any)], - "constant": [("max depth", LineInput(numerical=True)), ("min samples leaf", LineInput("1", numerical=True)), + "constant": const if const is not None else + [("max depth", LineInput(numerical=True)), ("min samples leaf", LineInput("1", numerical=True)), ("min samples split", LineInput("2", numerical=True))], } return Node(view, pos, cls) @@ -233,12 +263,13 @@ class SupportVectorCLF(Nodes): name = "Support Vector Classifier" @staticmethod - def create(view, pos): + def create(view, pos, const=None): cls = SupportVectorCLF() cls.field = { "input": [("x", CT.Matrix | CT.Any), ("y", CT.Matrix | CT.Any), ("test", CT.Matrix | CT.Any)], "output": [("result", CT.Matrix | CT.Any), ], - "constant": [("kernel", Selector({"rbf (default)": "rbf", "linear": "linear", "poly": "poly", "sigmoid": "sigmoid"}, default="rbf (default)"))], + "constant": const if const is not None else + [("kernel", Selector({"rbf (default)": "rbf", "linear": "linear", "poly": "poly", "sigmoid": "sigmoid"}, default="rbf (default)"))], } return Node(view, pos, cls) @@ -256,33 +287,55 @@ class InputLayerNN(Nodes): name = "Input Layer NN" @staticmethod - def create(view, pos): + def create(view, pos, const=None): cls = InputLayerNN() cls.field = { - "input": [], - "output": [("input nodes", CT.Matrix | CT.Any), ], - "constant": [("file name", FileDialog()), ("input", Selector({"row": "row", "column": "column"}))], + "input": [("x", CT.Optional | CT.Matrix | CT.Any)], + "output": [("nodes", CT.Matrix | CT.Any), ], + "constant": const if const is not None else + [("input number", LineInput(numerical=True))], } return Node(view, pos, cls) @staticmethod def execute(inp, const, out, inst): - + print(inp["constant"]) out["input nodes"] = [] return out +class OutputLayerNN(Nodes): + title = "OutputLayerNN" + name = "Output Layer NN" + + @staticmethod + def create(view, pos, const=None): + cls = OutputLayerNN() + cls.field = { + "input": [("class", CT.Matrix | CT.Any), ("output nodes", CT.Matrix | CT.Any), ], + "output": [("model", CT.Matrix | CT.Any),], + "constant": const if const is not None else [("nodes", LineInput(numerical=True))], + } + return Node(view, pos, cls) + + @staticmethod + def execute(inp, const, out, inst): + return out + + class HiddenLayerNN(Nodes): title = "HiddenLayerNN" name = "Hidden Layer NN" @staticmethod - def create(view, pos): + def create(view, pos, const=None): cls = HiddenLayerNN() cls.field = { - "input": [], - "output": [("input nodes", CT.Matrix | CT.Any), ], - "constant": [("file name", FileDialog()), ("input", Selector({"row": "row", "column": "column"}))], + "input": [("inp", CT.Matrix | CT.Any), ], + "output": [("out", CT.Matrix | CT.Any), ], + "constant": const if const is not None else + [("nodes", LineInput(numerical=True)), + ("activation", Selector({"Linear": "linear", "ReLU": "ReLU", "Softmax": "softmax"}))], } return Node(view, pos, cls) @@ -291,17 +344,19 @@ def execute(inp, const, out, inst): return out -class OutputLayerNN(Nodes): - title = "OutputLayerNN" - name = "Output Layer NN" +class TrainNN(Nodes): + title = "TrainNN" + name = "Training NN" @staticmethod - def create(view, pos): - cls = OutputLayerNN() + def create(view, pos, const=None): + cls = HiddenLayerNN() cls.field = { - "input": [], - "output": [("input nodes", CT.Matrix | CT.Any), ], - "constant": [("file name", FileDialog()), ("input", Selector({"row": "row", "column": "column"}))], + "input": [("model", CT.Matrix | CT.Any), ], + "output": [("classification", CT.Matrix | CT.Any), ], + "constant": const if const is not None else + [("nodes", LineInput(numerical=True)), + ("loss", Selector({"...": "...",}))], } return Node(view, pos, cls) @@ -315,12 +370,13 @@ class ConvolutionalLayerMTX(Nodes): name = "Convolutional Layer" @staticmethod - def create(view, pos): + def create(view, pos, const=None): cls = ConvolutionalLayerMTX() cls.field = { "input": [], "output": [("input nodes", CT.Matrix | CT.Any), ], - "constant": [("file name", FileDialog()), ("input", Selector({"row": "row", "column": "column"}))], + "constant": const if const is not None else + [("file name", FileDialog()), ("input", Selector({"row": "row", "column": "column"}))], } return Node(view, pos, cls) @@ -334,12 +390,13 @@ class PoolingMTX(Nodes): name = "Pooling Layer" @staticmethod - def create(view, pos): + def create(view, pos, const=None): cls = PoolingMTX() cls.field = { "input": [], "output": [("input nodes", CT.Matrix | CT.Any), ], - "constant": [("file name", FileDialog()), ("input", Selector({"row": "row", "column": "column"}))], + "constant": const if const is not None else + [("file name", FileDialog()), ("input", Selector({"row": "row", "column": "column"}))], } return Node(view, pos, cls) diff --git a/src/constants.py b/src/constants.py index e880248..8917637 100644 --- a/src/constants.py +++ b/src/constants.py @@ -5,4 +5,5 @@ class Null: TG_OUTPUT = "out" # tag output FLD_CONST = "const" # const field -DEBUG__ = True \ No newline at end of file + +DEBUG__ = True diff --git a/src/gfx/connection.py b/src/gfx/connection.py index 43dbebc..b4e290e 100644 --- a/src/gfx/connection.py +++ b/src/gfx/connection.py @@ -31,6 +31,4 @@ def update_end(self, p2): # updates the position of the connection (the line that is connecting between the connectors) IF the connector is movable def update_pair(self): O = self.connector_b.mapToItem(self.connector_a, self.connector_b.spos) - # O = self.connector_b.mapRectToItem(self.connector_a, self.connector_b.rect()) self.update_end(O) - # self.update_end(QPoint(O.x()+O.width()/2, O.y()+O.height()/2)) diff --git a/src/gfx/connector.py b/src/gfx/connector.py index 7b78310..b7d7fd1 100644 --- a/src/gfx/connector.py +++ b/src/gfx/connector.py @@ -20,44 +20,11 @@ Black Triangle = Misc. Type (TRY TO MINIMALLY USE MISC. TYPE) """ -class nConnector(QGraphicsItem): - def __init__(self, cnc_typ: CT, tag, en, field, view, parent=None): - super().__init__(parent=parent) - self.setPos(QPoint(500, 500)) - S = QRect(0,0,10,10) - - self.connections = [] # list of `Connection` class - self.selector = Connection(QPoint(S.x()+S.width()/2, S.y()+S.height()/2), - QPoint(S.x()+S.width()/2, S.y()+S.height()/2), self, external=None, color=Qt.red) - self.type = cnc_typ # connector type - self.tag = tag # the tag identifies what group this connector is - self.en = en # the en attribute is a list of tag (or a specific group) it allows to connect with - self.field = field # the field name of the connector - self.view = view - - self.polygon = QPolygonF() - self.polygon << QPoint(-100, 500) - self.polygon << QPoint(-100, 1000) - self.polygon << QPoint(200, 1000) - self.polygon << QPoint(200, 500) - - def paint(self, painter: QtGui.QPainter, option: QStyleOptionGraphicsItem, widget) -> None: - painter.setBrush(Qt.cyan) - painter.drawPolygon(self.polygon) - - def boundingRect(self) -> QtCore.QRectF: - return QtCore.QRectF(0.0,0.0,2000.0,2000.0) - - -class hConnector(QGraphicsItem): +class Connector(QGraphicsItem): clicked = False SIZE = (8, 8) - CUBE = 10 - CIRCLE = 11 - TRIANGLE = 12 - FNAME = 0 FTYPE = 1 @@ -78,18 +45,32 @@ def __init__(self, pos: QPoint, tag, en, field: Tuple[str, CT], view): self.selector.hide() + def __del__(self): + # removes all the connection referring to this Connector class + self.view.scene().removeItem(self.selector) + cnntn: Connection + [self.view.scene().removeItem(cnntn) for cnntn in self.connections] + del self.connections[:] + def _create_polygon(self): """ Only draws non-circular figure """ M = self.spos # get the center position self.polygon = QPolygonF() - if self.field[hConnector.FTYPE] == CT.Any: + if self.field[Connector.FTYPE] & CT.Optional == CT.Optional: # Excotic Shape - means special wrapper type + self.polygon << QPoint(M.x()-self.SIZE[0] , M.y()-self.SIZE[1]) + self.polygon << QPoint(M.x()-self.SIZE[0]*0.6, M.y()) + self.polygon << QPoint(M.x()-self.SIZE[0] , M.y()+self.SIZE[1]) + self.polygon << QPoint(M.x()+self.SIZE[0]*1.2, M.y()+self.SIZE[1]) + self.polygon << QPoint(M.x()+self.SIZE[0]*0.6, M.y() ) + self.polygon << QPoint(M.x()+self.SIZE[0]*1.2, M.y()-self.SIZE[1]) + elif self.field[Connector.FTYPE] & CT.Matrix == CT.Matrix: # Cube self.polygon << QPoint(M.x()-self.SIZE[0], M.y()-self.SIZE[1]) self.polygon << QPoint(M.x()-self.SIZE[0], M.y()+self.SIZE[1]) self.polygon << QPoint(M.x()+self.SIZE[0], M.y()+self.SIZE[1]) self.polygon << QPoint(M.x()+self.SIZE[0], M.y()-self.SIZE[1]) - else: + elif self.field[Connector.FTYPE] & CT.Any == CT.Any: # Triangle self.polygon << QPoint(M.x() , M.y()+self.SIZE[1]/1.5) self.polygon << QPoint(M.x()+self.SIZE[0], M.y() ) self.polygon << QPoint(M.x() , M.y()-self.SIZE[1]/1.5) @@ -101,22 +82,20 @@ def _create_polygon(self): # self.polygon << QPoint(M.x()+self.SIZE[0], M.y()-self.SIZE[1]) def paint(self, painter: QtGui.QPainter, option: QStyleOptionGraphicsItem, widget: Optional[QWidget] = ...) -> None: - if self.field[hConnector.FTYPE] & CT.Int == CT.Int: - painter.setBrush(Qt.cyan) - elif self.field[hConnector.FTYPE] & CT.Float == CT.Float: - painter.setBrush(Qt.blue) - elif self.field[hConnector.FTYPE] & CT.Bool == CT.Bool: - painter.setBrush(Qt.red) - elif self.field[hConnector.FTYPE] & CT.String == CT.String: - painter.setBrush(Qt.green) - elif self.field[hConnector.FTYPE] & CT.Any == CT.Any: - painter.setBrush(Qt.black) - - if self.field[hConnector.FTYPE] & CT.Matrix == CT.Matrix: + if self.field[Connector.FTYPE] & CT.Int == CT.Int: painter.setBrush(Qt.cyan) + elif self.field[Connector.FTYPE] & CT.Float == CT.Float: painter.setBrush(Qt.blue) + elif self.field[Connector.FTYPE] & CT.Bool == CT.Bool: painter.setBrush(Qt.red) + elif self.field[Connector.FTYPE] & CT.String == CT.String: painter.setBrush(Qt.green) + elif self.field[Connector.FTYPE] & CT.Any == CT.Any: painter.setBrush(Qt.black) + + if self.field[Connector.FTYPE] & CT.Optional == CT.Optional: + painter.drawPolygon(self.polygon) + elif self.field[Connector.FTYPE] & CT.Matrix == CT.Matrix: painter.drawPolygon(self.polygon) - elif self.field[hConnector.FTYPE] & CT.Scalar == CT.Matrix: + elif self.field[Connector.FTYPE] & CT.Scalar == CT.Scalar: + painter.drawEllipse(self.spos, self.SIZE[0], self.SIZE[1]) + else: # if no wrapper type was turned in, it will assume its a Scalar painter.drawEllipse(self.spos, self.SIZE[0], self.SIZE[1]) - def boundingRect(self) -> QtCore.QRectF: self._create_polygon() return QtCore.QRectF(self.spos.x()-self.SIZE[0]-1,self.spos.y()-self.SIZE[1]-1, self.SIZE[0]*2+1,self.SIZE[1]*2+1) @@ -143,8 +122,8 @@ def mouseMoveEvent(self, event): def mouseReleaseEvent(self, event): sp = self.mapToScene(event.pos()) - obj: hConnector - for obj in filter(lambda x: isinstance(x, hConnector), self.view.items()): + obj: Connector + for obj in filter(lambda x: isinstance(x, Connector), self.view.items()): is_duplicate = obj in map(lambda x: x.connector_b, self.connections) o: QPoint = obj.mapToScene(obj.spos) # GLOBAL SCREEN external object rect @@ -157,63 +136,3 @@ def mouseReleaseEvent(self, event): self.connections.append(conc) self.selector.hide() - -class Connector(QGraphicsRectItem): - clicked = False - - def __init__(self, rect, tag, en, field, view): - super().__init__() - S = self.rect() - - self.setBrush(Qt.cyan) - self.setRect(*rect) - - self.connections = [] # list of `Connection` class - self.selector = Connection(QPoint(S.x()+S.width()/2, S.y()+S.height()/2), - QPoint(S.x()+S.width()/2, S.y()+S.height()/2), self, external=None, color=Qt.red) - self.tag = tag # the tag identifies what group this connector is - self.en = en # the en attribute is a list of tag (or a specific group) it allows to connect with - self.field = field # the field name of the connector - self.view = view - - self.selector.hide() - - # due to the dynamic nature of the geometric shape of the node, this is to set the position at any time - def set_selector(self): - S = self.rect() - self.view.scene().removeItem(self.selector) # synchronize items to the `view` - self.selector = Connection(QPoint(S.x()+S.width()/2, S.y()+S.height()/2), - QPoint(S.x()+S.width()/2, S.y()+S.height()/2), self, external=None, color=Qt.red) - self.selector.hide() - - def mousePressEvent(self, event): - vp = event.pos() - sp = self.mapFromItem(self, vp) - - self.selector.update_end(sp) - self.selector.show() - - def mouseMoveEvent(self, event): - vp = event.pos() - sp = self.mapFromItem(self, vp) - - self.selector.show() - self.selector.update_end(sp) - - def mouseReleaseEvent(self, event): - vp = event.pos() - self.sp = self.mapRectToScene(vp.x(), vp.y(), 0, 0) - - for obj in filter(lambda x: isinstance(x, Connector), self.view.items()): - is_duplicate = obj in map(lambda x: x.connector_b, self.connections) - - o = obj.mapRectToScene(obj.rect()) # GLOBAL SCREEN external object rect - if o.x() < self.sp.x() < o.x()+o.width() and o.y() < self.sp.y() < o.y()+o.height() and \ - obj.tag in self.en and not is_duplicate: - S = self.mapRectToItem(self, self.rect()) # LOCAL SELF self rect - O = obj.mapRectToItem(self, obj.rect()) # LOCAL SELF external object rect; uses this obj's pos to map - - conc = Connection(QPoint(S.x()+S.width()/2, S.y()+S.height()/2), - QPoint(O.x()+O.width()/2, O.y()+O.height()/2), self, obj, color=Qt.green) - self.connections.append(conc) - self.selector.hide() diff --git a/src/gfx/constant_models.py b/src/gfx/constant_models.py index d0d5310..bd756dd 100644 --- a/src/gfx/constant_models.py +++ b/src/gfx/constant_models.py @@ -1,5 +1,5 @@ from PyQt5.QtWidgets import * -from PyQt5.Qt import QValidator, QIntValidator, QDoubleValidator +from PyQt5.Qt import QIntValidator import os.path @@ -45,7 +45,7 @@ def __init__(self, *args, single=True): self.clicked.connect(lambda checked: self.onClick(checked)) - self.file_dialog = None + self.fl_dlg = None def onClick(self, clicked): self.configDialog() @@ -55,13 +55,14 @@ def configDialog(self): self.wind.setWindowTitle("File Dialogue") self.wind.setGeometry(0, 0, 1000, 400) - self.file_dialog = QFileDialog() - self.file_dialog.setFileMode(self.file_dialog.ExistingFile) - self.file_dialog.setViewMode(self.file_dialog.List) + self.fl_dlg = QFileDialog() + self.fl_dlg.setFileMode(self.fl_dlg.ExistingFile) + self.fl_dlg.setViewMode(self.fl_dlg.List) - self.file_dialog.filesSelected.connect(lambda url: self.setUrl(url)) + self.fl_dlg.filesSelected.connect(lambda url: self.setUrl(url)) + self.fl_dlg.finished.connect(lambda code: self.wind.close()) - self.wind.setMenuWidget(self.file_dialog) + self.wind.setMenuWidget(self.fl_dlg) self.wind.show() def setUrl(self, url): @@ -71,17 +72,18 @@ def setUrl(self, url): def value(self): if hasattr(self, "file_dialog"): - if self.file_dialog is None: print("WARNING: NO FILES SELECTED") - elif len(self.file_dialog.selectedFiles()) == 0: + if self.fl_dlg is None: print("WARNING: NO FILES SELECTED") + elif len(self.fl_dlg.selectedFiles()) == 0: print("WARNING: NO FILES SELECTED") - elif self.single and len(self.file_dialog.selectedFiles()) > 0: - return self.file_dialog.selectedFiles()[0] + elif self.single and len(self.fl_dlg.selectedFiles()) > 0: + return self.fl_dlg.selectedFiles()[0] else: - return self.file_dialog.selectedFiles() + return self.fl_dlg.selectedFiles() print("WARNING: NO FILES SELECTED") return "" def save(self): + print(self, self.single, self.url, self.text()) return {"single": self.single, "url": self.url, "text": self.text()} @staticmethod diff --git a/src/gfx/node.py b/src/gfx/node.py index 2229efc..3f248b7 100644 --- a/src/gfx/node.py +++ b/src/gfx/node.py @@ -7,9 +7,8 @@ from src.debug import * from src.constants import * -from src.gfx.connector import Connector, nConnector, hConnector +from src.gfx.connector import Connector from src.gfx.connection import Connection # [WARN] diff between the import between src.gfx vs gfx -from src.components.workspace.connector_type import ConnectorType as CT class Node(QGraphicsProxyWidget): @@ -22,6 +21,11 @@ def __init__(self, view, pos, nd_cls, parent=None): self.central = NodeInternal(view, pos, nd_cls, parent=None) self.setWidget(self.central) + def __del__(self): + # removes all the connector referring to this Node class + [self.central.view.scene().removeItem(connc) for connc in self.central.connector] + del self.central.connector[:] # removes all Connector except the attribute itself + class NodeInternal(QWidget): """ Model Class @@ -34,21 +38,20 @@ class NodeInternal(QWidget): FIELD_OFSY = 6 # field's offset on the y-axis MOUSE_OFS = (0, 0) # position offset when mouse on trigger - def __init__(self, view, pos: Tuple[int, int], nd_cls, parent=None): + def __init__(self, view: QGraphicsView, pos: Tuple[int, int], nd_cls, parent=None): super().__init__(parent=parent) + self.view = view self.clicked = False self.pos = pos self.nd_cls = nd_cls - self.connector = [] - - self.view = view + self.connector: List[Connector] = [] self._inst_basic_ui(view) self.setStyleSheet("background-color: #CCDDFF") - def _inst_basic_ui(self, view): + def _inst_basic_ui(self, view): # graphics=Also instantiate/update graphical objects title = QLabel(self.nd_cls.name, self) title.setAlignment(Qt.AlignTop) title.setFont(QtGui.QFont("courier", 10, QtGui.QFont.Bold)) @@ -63,7 +66,7 @@ def _inst_basic_ui(self, view): out = QVBoxLayout() # output area selection [out.addWidget(QLabel(f[0])) for f in self.nd_cls.field["output"]] out.addStretch() - print("====", self.nd_cls.field["constant"]) + print([f[1] for f in self.nd_cls.field["constant"]]) usr = QFormLayout() # user input area selection [usr.addRow(QLabel(f[0]), f[1]) for f in self.nd_cls.field["constant"]] @@ -82,31 +85,15 @@ def _inst_basic_ui(self, view): self.setLayout(layout) self.setGeometry(self.pos[0], self.pos[1], self.NODE_SIZE[0], self.NODE_SIZE[1]) - # o = hConnector(CT.Matrix | CT.String, QPoint(-100, -100), TG_INPUT, [TG_OUTPUT], ("a", CT.Matrix | CT.String), view) - # view.scene().addItem(o) - # self.connector.append(o) - # print(view.items()) - # NOTE: The following initializes the starting position of the connector, which effects how the position of # the connector gets updated. for ind, fld in enumerate(self.nd_cls.field["input"]): - # cnc = Connector((0, title.rect().height()+self.FIELD_PADY*ind+self.FIELD_OFSY, *self.CONNECTOR_SIZE), - # TG_INPUT, [TG_OUTPUT], fld, view) - # self.connector.append(cnc) - # view.scene().addItem(cnc) - - o = hConnector(QPoint(0, title.rect().height()+self.FIELD_PADY*ind+self.FIELD_OFSY), + o = Connector(QPoint(0, title.rect().height()+self.FIELD_PADY*ind+self.FIELD_OFSY), TG_INPUT, [TG_OUTPUT], fld, view) self.connector.append(o) view.scene().addItem(o) for ind, fld in enumerate(self.nd_cls.field["output"]): - print("===", fld) - # cnc = Connector((0, title.rect().height()+self.FIELD_PADY*ind+self.FIELD_OFSY, *self.CONNECTOR_SIZE), - # TG_OUTPUT, [TG_INPUT], fld, view) - # self.connector.append(cnc) - # view.scene().addItem(cnc) - - o = hConnector(QPoint(0, title.rect().height()+self.FIELD_PADY*ind+self.FIELD_OFSY), + o = Connector(QPoint(0, title.rect().height()+self.FIELD_PADY*ind+self.FIELD_OFSY), TG_OUTPUT, [TG_INPUT], fld, view) self.connector.append(o) view.scene().addItem(o) diff --git a/src/interface/project_file_interface.py b/src/interface/project_file_interface.py index bf7403c..c64eda8 100644 --- a/src/interface/project_file_interface.py +++ b/src/interface/project_file_interface.py @@ -51,17 +51,14 @@ from src.debug import * from src.gfx.connection import Connection -from src.gfx.connector import hConnector +from src.gfx.connector import Connector from src.gfx.node import Node from src.constants import * from typing import List, Optional, Union -from enum import Enum -# TODO: There could be a file serialization 'memory' leak - -def model_saver(items: List[Union[Node, hConnector, Connection]]): # TODO: Optimize and compact +def model_saver(items: List[Union[Node, Connector, Connection]]): # TODO: Optimize and compact skl_tree = {} # skeleton tree: pre-tree generation dat_tree = {} # data tree: using skl_tree to generate data tree id_map = {} @@ -75,10 +72,10 @@ def model_saver(items: List[Union[Node, hConnector, Connection]]): # TODO: Opti node_id = id_counter id_counter += 1 - connc: hConnector # only input|output connector + connc: Connector # only input|output connector for connc in n.central.connector: skl_tree[node_id][id_counter] = connc - id_map[id_counter] = connc.field[hConnector.FNAME] + id_map[id_counter] = connc.field[Connector.FNAME] id_counter += 1 for const in n.central.nd_cls.field["constant"]: # only constant field @@ -92,9 +89,9 @@ def model_saver(items: List[Union[Node, hConnector, Connection]]): # TODO: Opti dat_tree[n] = {} for field in skl_tree[n]: connc = skl_tree[n][field] - if isinstance(connc, hConnector): # input|output field + if isinstance(connc, Connector): # input|output field cnc_typ = connc.tag - fld_typ = connc.field[hConnector.FTYPE] + fld_typ = connc.field[Connector.FTYPE] cnc_cnctn = [] cnntn: Connection for cnntn in connc.connections: @@ -110,47 +107,6 @@ def model_saver(items: List[Union[Node, hConnector, Connection]]): # TODO: Opti return _dat_file_saver(dat_tree, id_map) -def old_model_saver(key: int, items: List[Union[Node, hConnector, Connection]]): - """ - Saves the model data to a file. - items: a list of items in a graphics view - """ - dat = {} - nodes = [nd for nd in items if isinstance(nd, Node)] - - dt_obj = [i for i in items if isinstance(i, hConnector)] - for m in nodes: - dt_obj.append(m) - for fld in m.central.nd_cls.field["constant"]: - dt_obj.append(fld[1]) - - obj_id = 0 - obj_map = {} - for i in dt_obj: - obj_map[i] = obj_id - obj_id += 1 - - model_id = 0 - model = {} - for m in nodes: - model[m] = model_id - model_id += 1 - - id_map = {} # ID # mapping to name - for m in nodes: - dat[obj_map[m]] = {} - for i in items: - if isinstance(i, hConnector): # connectors - if i in m.central.connector: - id_map[obj_map[m]] = m.central.nd_cls.title - id_map[obj_map[i]] = i.field[0] - dat[obj_map[m]][obj_map[i]] = (i.tag, i.field[1], [obj_map[c.connector_a] for c in i.connections]) - for c in m.central.nd_cls.field["constant"]: # constants - id_map[obj_map[m]] = m.central.nd_cls.title - id_map[obj_map[c[1]]] = c[0] - dat[obj_map[m]][obj_map[c[1]]] = ("const", c[1].value()) - - return _dat_file_saver(dat, id_map) def _dat_file_saver(model_tree, id_map): """ @@ -223,12 +179,6 @@ def dat_file_loader(fdt): return mdl_tree, id_map -class FileType(Enum): - Executor = 1 # each node's execution ready format - Project = 2 # each node's property in each model workspac - Graph = 3 # graphs and visualization of the result - - class ProjectFI: """ Project File Interface """ @@ -244,8 +194,6 @@ class ProjectFI: prj_file = "project.yaml" - __id = 0 - def __init__(self, *, name: str, path: str): # name: valid directory name if os.path.exists(path): self.path = os.path.join(path, name) @@ -342,7 +290,7 @@ def change_name(self, key: int, name: str): else: print("Error: Invalid key") - def save_mdl_exec(self, key: int, items: List[Union[Node, hConnector, Connection]]): + def save_mdl_exec(self, key: int, items: List[Union[Node, Connector, Connection]]): if self.valid_key(key): fdt = model_saver(items) with open(os.path.join(self.path, "MDL"+str(key)+self.mdl_exec), "w") as fbj: @@ -359,16 +307,20 @@ def read_mdl_exec(self, key: int) -> (dict, dict): else: print("Error: Invalid key") - def save_mdl_proj(self, key: int, items: List[Union[Node, hConnector, Connection]]): + def save_mdl_proj(self, key: int, items: List[Union[Node, Connector, Connection]]): if self.valid_key(key): fdt = {} for itm in items: - if isinstance(itm, Node): fdt[items.index(itm)] = { - "pos":itm.central.pos, - "nd_cls":itm.central.nd_cls.__class__.__name__, - "connector":[items.index(c) for c in itm.central.connector], + if isinstance(itm, Node): + print({c[0]:c[1] for c in itm.central.nd_cls.field["constant"]}) + fdt[items.index(itm)] = { + "pos": itm.central.pos, + "nd_cls": itm.central.nd_cls.__class__.__name__, + "connector": [items.index(c) for c in itm.central.connector], + "constants": {c[0]:c[1].save() for c in itm.central.nd_cls.field["constant"]}, } - elif isinstance(itm, hConnector): fdt[items.index(itm)] = { + elif isinstance(itm, Connector): + fdt[items.index(itm)] = { "tag": itm.tag, "en": itm.en, "field": itm.field, @@ -401,12 +353,20 @@ def read_mdl_proj(self, key: int) -> QGraphicsView: # used in the eval() from src.components.workspace import nodes - for i in fdt: + for i in fdt: # TODO: A Cleaner way to deserialize constant fields if fdt[i].get("connector") is not None: # Node o: Node = eval(f"nodes.{fdt[i]['nd_cls']}").create(view, fdt[i]['pos']) - for ind, c in enumerate(o.central.connector): + consts = [] + + for ind, const in enumerate(o.central.nd_cls.field["constant"]): + consts.append((const[0], const[1].load(fdt[i]["constants"][const[0]]))) + del o + + node: Node = eval(f"nodes.{fdt[i]['nd_cls']}").create(view, fdt[i]['pos'], const=consts) + # also updates the connector, so this must be place after the update + for ind, c in enumerate(node.central.connector): c.TEMP_ind = fdt[i]["connector"][ind] - view.scene().addItem(o) + view.scene().addItem(node) for i in fdt: if fdt[i].get("connector_a") is not None: # Connection @@ -415,24 +375,20 @@ def read_mdl_proj(self, key: int) -> QGraphicsView: cnc_b = None if fdt[i]["connector_a"] != None: for ca in item: - if isinstance(ca, hConnector): + if isinstance(ca, Connector): if fdt[i]["connector_a"] == ca.TEMP_ind: cnc_a = ca if fdt[i]["connector_b"] != None: for cb in item: - if isinstance(cb, hConnector): + if isinstance(cb, Connector): if fdt[i]["connector_b"] == cb.TEMP_ind: cnc_b = cb if cnc_b is not None: # connector connections S = cnc_a.mapToItem(cnc_a, cnc_a.spos) O = cnc_b.mapToItem(cnc_a, cnc_b.spos) - # S = cnc_a.mapRectToItem(cnc_a, cnc_a.rect()) - # O = cnc_b.mapRectToItem(cnc_a, cnc_b.rect()) - view.scene().addItem(Connection(S, # QPoint(S.x()+S.width()/2, S.y()+S.height()/2), - O, # QPoint(O.x()+O.width()/2, O.y()+O.height()/2), - parent=cnc_a,external=cnc_b, color=Qt.green)) - for c in view.items(): # using the `view` for the generated connectors - if isinstance(c, hConnector): # Connector + view.scene().addItem(Connection(S, O, parent=cnc_a,external=cnc_b, color=Qt.green)) + for c in view.items(): # using the list `view` for the generated connectors from the Node class + if isinstance(c, Connector): # Connector del c.TEMP_ind for i in view.items(): if isinstance(i, Connection): @@ -467,22 +423,4 @@ def read_mdl_grph(self, mid: int): pass # THAN instantiate the graphical parts # ALSO instantiate with the missing index which later will be replaced with - print("~~~") - fdt = {} - with open(os.path.join("C:/users/andrew shen/desktop/projectemerald/mymodel/MDL0.proj.dat"), "r") as fbj: - fdt = yaml.safe_load(fbj) - print("~~~") - from PyQt5.QtWidgets import QGraphicsView - view = QGraphicsView() - nodes = __import__("src", fromlist=["nodes"]) - print("@@@") - for i in fdt: - print(fdt[i]) - if fdt[i].get("connector") is not None: # Node - o = eval(f"nodes.{fdt[i]['nd_cls']}.create({view}, {fdt[i]['pos']})") - view.scene().addItem(o) - elif fdt[i].get("connections") is not None: # Connector - pass - elif fdt[i].get("connector_a") is not None: # Connection - pass - print(view.items()) + pass diff --git a/src/widgets.py b/src/widgets.py index 6918652..fcddd6c 100644 --- a/src/widgets.py +++ b/src/widgets.py @@ -193,19 +193,21 @@ def __init__(self, visible=True): class ErrorBox(QMessageBox): - E000 = {"level": QMessageBox.Information, "title": "No Title", "txt": "No Text"} - E001 = {"level": QMessageBox.Critical, "title": "Project", "txt": "The project directory has not been set."} - E002 = {"level": QMessageBox.Warning, "title": "Project", "txt": "Invalid project file"} - E003 = {"level": QMessageBox.Warning, "title": "Project", "txt": "Invalid project directory"} - E004 = {"level": QMessageBox.Warning, "title": "Project", "txt": "Invalid project key\n(You have not set the model name)"} - E005 = {"level": QMessageBox.Critical, "title": "Executor", "txt": "Runtime Error"} - E006 = {"level": QMessageBox.Critical, "title": "Project", "txt": "Model file failed to load"} - - def __init__(self, title="No Title", level=QMessageBox.Information, txt="No Text"): + # once the error is created, it can not be removed from the list unless it was removed before a commit + E000 = {"code": "E000", "level": QMessageBox.Information, "title": "No Title", "txt": "No Text"} + E001 = {"code": "E001", "level": QMessageBox.Critical, "title": "Project", "txt": "The project directory has not been set."} + E002 = {"code": "E002", "level": QMessageBox.Warning, "title": "Project", "txt": "Invalid project file"} + E003 = {"code": "E003", "level": QMessageBox.Warning, "title": "Project", "txt": "Invalid project directory"} + E004 = {"code": "E004", "level": QMessageBox.Warning, "title": "Project", "txt": "Invalid project key\n(You have not set the model name)"} + E005 = {"code": "E005", "level": QMessageBox.Critical, "title": "Executor", "txt": "Runtime Error"} + E006 = {"code": "E006", "level": QMessageBox.Critical, "title": "Project", "txt": "Executor file failed to load"} + E007 = {"code": "E007", "level": QMessageBox.Critical, "title": "Project", "txt": "Model file failed to load"} + + def __init__(self, code="E000", title="No Title", level=QMessageBox.Information, txt="No Text"): super().__init__() self.setIcon(level) self.setText(txt) - self.setWindowTitle(title) + self.setWindowTitle(code+" - "+title) class ModelWorkspaceSelector(QComboBox):