def __init__(self, scene, title="Undefined Node", inputs=[], outputs=[]): super().__init__() self._title = title self.scene = scene self.content = QDMNodeContentWidget(self) self.grNode = QDMGraphicsNode(self) self.title = title self.scene.addNode(self) self.scene.grScene.addItem(self.grNode) self.socket_spacing = 22 # create socket for inputs and outputs self.inputs = [] self.outputs = [] counter = 0 for item in inputs: socket = Socket(node=self, index=counter, position=LEFT_BOTTOM, socket_type=item, multi_edges=False) counter += 1 self.inputs.append(socket) counter = 0 for item in outputs: socket = Socket(node=self, index=counter, position=RIGHT_TOP, socket_type=item, multi_edges=True) counter += 1 self.outputs.append(socket)
class Node(Serializable): def __init__(self, scene, title="Undefined Node", inputs=[], outputs=[]): super().__init__() self._title = title self.scene = scene self.initInnerClasses() self.initSettings() self.title = title self.scene.addNode(self) self.scene.grScene.addItem(self.grNode) # create socket for inputs and outputs self.inputs = [] self.outputs = [] self.initSockets(inputs, outputs) # dirty and evaluation self._is_dirty = False self._is_invalid = False def initInnerClasses(self): self.content = QDMNodeContentWidget(self) self.grNode = QDMGraphicsNode(self) def initSettings(self): self.socket_spacing = 22 self.input_socket_position = LEFT_BOTTOM self.output_socket_position = RIGHT_TOP self.input_multi_edged = False self.output_multi_edged = True def initSockets(self, inputs, outputs, reset=True): """ Create sockets for inputs and outputs""" if reset: # clear old sockets if hasattr(self, 'inputs') and hasattr(self, 'outputs'): # remove grSockets from scene for socket in (self.inputs + self.outputs): self.scene.grScene.removeItem(socket.grSocket) self.inputs = [] self.outputs = [] # create new sockets counter = 0 for item in inputs: socket = Socket(node=self, index=counter, position=self.input_socket_position, socket_type=item, multi_edges=self.input_multi_edged, count_on_this_node_side=len(inputs), is_input=True ) counter += 1 self.inputs.append(socket) counter = 0 for item in outputs: socket = Socket(node=self, index=counter, position=self.output_socket_position, socket_type=item, multi_edges=self.output_multi_edged, count_on_this_node_side=len(outputs), is_input=False ) counter += 1 self.outputs.append(socket) def onEdgeConnectionChanged(self, new_edge): print("%s::onEdgeConnectionChanged" % self.__class__.__name__, new_edge) def onInputChanged(self, new_edge): print("%s::onInputChanged" % self.__class__.__name__, new_edge) self.markDirty() self.markDescendantsDirty() def doSelect(self, new_state=True): self.grNode.doSelect(new_state) def __str__(self): return "<Node %s..%s>" % (hex(id(self))[2:5], hex(id(self))[-3:]) @property def pos(self): return self.grNode.pos() # QPointF def setPos(self, x, y): self.grNode.setPos(x, y) @property def title(self): return self._title @title.setter def title(self, value): self._title = value self.grNode.title = self._title def getSocketPosition(self, index, position, num_out_of=1): x = 0 if (position in (LEFT_TOP, LEFT_CENTER, LEFT_BOTTOM)) else self.grNode.width if position in (LEFT_BOTTOM, RIGHT_BOTTOM): # start from bottom y = self.grNode.height - self.grNode.edge_roundness - self.grNode.title_vertical_padding - index * self.socket_spacing elif position in (LEFT_CENTER, RIGHT_CENTER): num_sockets = num_out_of node_height = self.grNode.height top_offset = self.grNode.title_height + 2 * self.grNode.title_vertical_padding + self.grNode.edge_padding available_height = node_height - top_offset total_height_of_all_sockets = num_sockets * self.socket_spacing new_top = available_height - total_height_of_all_sockets # y = top_offset + index * self.socket_spacing + new_top / 2 y = top_offset + available_height / 2.0 + (index - 0.5) * self.socket_spacing if num_sockets > 1: y -= self.socket_spacing * (num_sockets - 1) / 2 elif position in (LEFT_TOP, RIGHT_TOP): # start from top y = self.grNode.title_height + self.grNode.title_vertical_padding + self.grNode.edge_roundness + index * self.socket_spacing else: # this should never happen y = 0 return [x, y] def updateConnectedEdges(self): for socket in self.inputs + self.outputs: # if socket.hasEdge(): for edge in socket.edges: edge.updatePositions() def remove(self): if DEBUG: print("> Removing Node", self) if DEBUG: print(" - remove all edges from sockets") for socket in (self.inputs + self.outputs): # if socket.hasEdge(): for edge in socket.edges: if DEBUG: print(" - removing from socket:", socket, "edge:", edge) edge.remove() if DEBUG: print(" - remove grNode") self.scene.grScene.removeItem(self.grNode) self.grNode = None if DEBUG: print(" - remove node from the scene") self.scene.removeNode(self) if DEBUG: print(" - everything was done.") # node evaluation stuff def isDirty(self): return self._is_dirty def markDirty(self, new_value=True): self._is_dirty = new_value if self._is_dirty: self.onMarkedDirty() def onMarkedDirty(self): pass def markChildrenDirty(self, new_value=True): for other_node in self.getChildrenNodes(): other_node.markDirty(new_value) def markDescendantsDirty(self, new_value=True): for other_node in self.getChildrenNodes(): other_node.markDirty(new_value) other_node.markChildrenDirty(new_value) def isInvalid(self): return self._is_invalid def markInvalid(self, new_value=True): self._is_invalid = new_value if self._is_invalid: self.onMarkedInvalid() def onMarkedInvalid(self): pass def markChildrenInvalid(self, new_value=True): for other_node in self.getChildrenNodes(): other_node.markInvalid(new_value) def markDescendantsInvalid(self, new_value=True): for other_node in self.getChildrenNodes(): other_node.markInvalid(new_value) other_node.markChildrenInvalid(new_value) def eval(self): self.markDirty(False) self.markInvalid(False) return 0 def evalChildren(self): for node in self.getChildrenNodes(): node.eval() # traversing nodes functions def getChildrenNodes(self): if self.outputs == []: return [] other_nodes = [] for ix in range(len(self.outputs)): for edge in self.outputs[ix].edges: other_node = edge.getOtherSocket(self.outputs[ix]).node other_nodes.append(other_node) return other_nodes def getInput(self, index=0): try: edge = self.inputs[index].edges[0] socket = edge.getOtherSocket(self.inputs[index]) return socket.node except IndexError: print("EXC: Trying to get input, but none is attached to", self) return None except Exception as e: dumpException(e) return None def getInputs(self, index=0): ins = [] for edge in self.inputs[index].edges: other_socket = edge.getOtherSocket(self.inputs[index]) ins.append(other_socket.node) return ins def getOutputs(self, index=0): outs = [] for edge in self.outputs[index].edges: other_socket = edge.getOtherSocket(self.outputs[index]) outs.append(other_socket.node) return outs # serialization functions def serialize(self): inputs, outputs = [], [] for socket in self.inputs: inputs.append(socket.serialize()) for socket in self.outputs: outputs.append(socket.serialize()) return OrderedDict([ ('id', self.id), ('title', self.title), ('pos_x', self.grNode.scenePos().x()), ('pos_y', self.grNode.scenePos().y()), ('inputs', inputs), ('outputs', outputs), ('content', self.content.serialize()), ]) def deserialize(self, data, hashmap={}, restore_id=True): try: if restore_id: self.id = data['id'] hashmap[data['id']] = self self.setPos(data['pos_x'], data['pos_y']) self.title = data['title'] data['inputs'].sort(key=lambda socket: socket['index'] + socket['position'] * 10000) data['outputs'].sort(key=lambda socket: socket['index'] + socket['position'] * 10000) num_inputs = len(data['inputs']) num_outputs = len(data['outputs']) self.inputs = [] for socket_data in data['inputs']: new_socket = Socket(node=self, index=socket_data['index'], position=socket_data['position'], socket_type=socket_data['socket_type'], count_on_this_node_side=num_inputs, is_input=True) new_socket.deserialize(socket_data, hashmap, restore_id) self.inputs.append(new_socket) self.outputs = [] for socket_data in data['outputs']: new_socket = Socket(node=self, index=socket_data['index'], position=socket_data['position'], socket_type=socket_data['socket_type'], count_on_this_node_side=num_outputs, is_input=False) new_socket.deserialize(socket_data, hashmap, restore_id) self.outputs.append(new_socket) except Exception as e: dumpException(e) # also deseralize the content of the node res = self.content.deserialize(data['content'], hashmap) return True & res
def initInnerClasses(self): self.content = QDMNodeContentWidget(self) self.grNode = QDMGraphicsNode(self)
class Node(Serializable): def __init__(self, scene, title="Undefined Node", inputs=[], outputs=[]): super().__init__() self._title = title self.scene = scene self.content = QDMNodeContentWidget(self) self.grNode = QDMGraphicsNode(self) self.title = title self.scene.addNode(self) self.scene.grScene.addItem(self.grNode) self.socket_spacing = 22 # create socket for inputs and outputs self.inputs = [] self.outputs = [] counter = 0 for item in inputs: socket = Socket(node=self, index=counter, position=LEFT_BOTTOM, socket_type=item, multi_edges=False) counter += 1 self.inputs.append(socket) counter = 0 for item in outputs: socket = Socket(node=self, index=counter, position=RIGHT_TOP, socket_type=item, multi_edges=True) counter += 1 self.outputs.append(socket) def __str__(self): return "<Node %s..%s>" % (hex(id(self))[2:5], hex(id(self))[-3:]) @property def pos(self): return self.grNode.pos() # QPointF def setPos(self, x, y): self.grNode.setPos(x, y) @property def title(self): return self._title @title.setter def title(self, value): self._title = value self.grNode.title = self._title def getSocketPosition(self, index, position): x = 0 if (position in (LEFT_TOP, LEFT_BOTTOM)) else self.grNode.width if position in (LEFT_BOTTOM, RIGHT_BOTTOM): # start from bottom y = self.grNode.height - self.grNode.edge_size - self.grNode._padding - index * self.socket_spacing else: # start from top y = self.grNode.title_height + self.grNode._padding + self.grNode.edge_size + index * self.socket_spacing return [x, y] def updateConnectedEdges(self): for socket in self.inputs + self.outputs: # if socket.hasEdge(): for edge in socket.edges: edge.updatePositions() def remove(self): if DEBUG: print("> Removing Node", self) if DEBUG: print(" - remove all edges from sockets") for socket in (self.inputs + self.outputs): # if socket.hasEdge(): for edge in socket.edges: if DEBUG: print(" - removing from socket:", socket, "edge:", edge) edge.remove() if DEBUG: print(" - remove grNode") self.scene.grScene.removeItem(self.grNode) self.grNode = None if DEBUG: print(" - remove node from the scene") self.scene.removeNode(self) if DEBUG: print(" - everything was done.") def serialize(self): inputs, outputs = [], [] for socket in self.inputs: inputs.append(socket.serialize()) for socket in self.outputs: outputs.append(socket.serialize()) return OrderedDict([ ('id', self.id), ('title', self.title), ('pos_x', self.grNode.scenePos().x()), ('pos_y', self.grNode.scenePos().y()), ('inputs', inputs), ('outputs', outputs), ('content', self.content.serialize()), ]) def deserialize(self, data, hashmap={}, restore_id=True): try: if restore_id: self.id = data['id'] hashmap[data['id']] = self self.setPos(data['pos_x'], data['pos_y']) self.title = data['title'] data['inputs'].sort(key=lambda socket: socket['index'] + socket[ 'position'] * 10000) data['outputs'].sort(key=lambda socket: socket['index'] + socket[ 'position'] * 10000) self.inputs = [] for socket_data in data['inputs']: new_socket = Socket(node=self, index=socket_data['index'], position=socket_data['position'], socket_type=socket_data['socket_type']) new_socket.deserialize(socket_data, hashmap, restore_id) self.inputs.append(new_socket) self.outputs = [] for socket_data in data['outputs']: new_socket = Socket(node=self, index=socket_data['index'], position=socket_data['position'], socket_type=socket_data['socket_type']) new_socket.deserialize(socket_data, hashmap, restore_id) self.outputs.append(new_socket) except Exception as e: dumpException(e) return True
def initInnerClasses(self): """Sets up graphics Node (PyQt) and Content Widget""" self.content = QDMNodeContentWidget(self) self.grNode = QDMGraphicsNode(self)
class Node(Serializable): """ Class representing `Node` in the `Scene`. """ def __init__(self, scene:'Scene', title:str="Undefined Node", inputs:list=[], outputs:list=[]): """ :param scene: reference to the :class:`~nodeeditor.node_scene.Scene` :type scene: :class:`~nodeeditor.node_scene.Scene` :param title: Node Title shown in Scene :type title: str :param inputs: list of :class:`~nodeeditor.node_socket.Socket` types from which the `Sockets` will be auto created :param outputs: list of :class:`~nodeeditor.node_socket.Socket` types from which the `Sockets` will be auto created :Instance Attributes: - **scene** - reference to the :class:`~nodeeditor.node_scene.Scene` - **grNode** - Instance of :class:`~nodeeditor.node_graphics_node.QDMGraphicsNode` handling graphical representation in the ``QGraphicsScene``. Automatically created in constructor - **content** - Instance of :class:`~nodeeditor.node_graphics_content.QDMGraphicsContent` which is child of ``QWidget`` representing container for all inner widgets inside of the Node. Automatically created in constructor - **inputs** - list containin Input :class:`~nodeeditor.node_socket.Socket` instances - **outputs** - list containin Output :class:`~nodeeditor.node_socket.Socket` instances """ super().__init__() self._title = title self.scene = scene self.initInnerClasses() self.initSettings() self.title = title self.scene.addNode(self) self.scene.grScene.addItem(self.grNode) # create socket for inputs and outputs self.inputs = [] self.outputs = [] self.initSockets(inputs, outputs) # dirty and evaluation self._is_dirty = False self._is_invalid = False def __str__(self): return "<Node %s..%s>" % (hex(id(self))[2:5], hex(id(self))[-3:]) @property def title(self): """ Title shown in the scene :getter: return current Node title :setter: sets Node title and passes it to Graphics Node class :type: ``str`` """ return self._title @title.setter def title(self, value): self._title = value self.grNode.title = self._title @property def pos(self): """ Retrieve Node's position in the Scene :return: Node position :rtype: ``QPointF`` """ return self.grNode.pos() # QPointF def setPos(self, x:float, y:float): """ Sets position of the Graphics Node :param x: X `Scene` position :param y: Y `Scene` position """ self.grNode.setPos(x, y) def initInnerClasses(self): """Sets up graphics Node (PyQt) and Content Widget""" self.content = QDMNodeContentWidget(self) self.grNode = QDMGraphicsNode(self) def initSettings(self): """Initialize properties and socket information""" self.socket_spacing = 22 self.input_socket_position = LEFT_BOTTOM self.output_socket_position = RIGHT_TOP self.input_multi_edged = False self.output_multi_edged = True def initSockets(self, inputs:list, outputs:list, reset:bool=True): """ Create sockets for inputs and outputs :param inputs: list of Socket Types (int) :type inputs: ``list`` :param outputs: list of Socket Types (int) :type outputs: ``list`` :param reset: if ``True`` destroys and removes old `Sockets` :type reset: ``bool`` """ if reset: # clear old sockets if hasattr(self, 'inputs') and hasattr(self, 'outputs'): # remove grSockets from scene for socket in (self.inputs+self.outputs): self.scene.grScene.removeItem(socket.grSocket) self.inputs = [] self.outputs = [] # create new sockets counter = 0 for item in inputs: socket = Socket(node=self, index=counter, position=self.input_socket_position, socket_type=item, multi_edges=self.input_multi_edged, count_on_this_node_side=len(inputs), is_input=True ) counter += 1 self.inputs.append(socket) counter = 0 for item in outputs: socket = Socket(node=self, index=counter, position=self.output_socket_position, socket_type=item, multi_edges=self.output_multi_edged, count_on_this_node_side=len(outputs), is_input=False ) counter += 1 self.outputs.append(socket) def onEdgeConnectionChanged(self, new_edge:'Edge'): """ Event handling that any connection (`Edge`) has changed. Currently not used... :param new_edge: reference to the changed :class:`~nodeeditor.node_edge.Edge` :type new_edge: :class:`~nodeeditor.node_edge.Edge` """ print("%s::onEdgeConnectionChanged" % self.__class__.__name__, new_edge) def onInputChanged(self, new_edge:'Edge'): """Event handling when Node's input Edge has changed. We auto-mark this `Node` to be `Dirty` with all it's descendants :param new_edge: reference to the changed :class:`~nodeeditor.node_edge.Edge` :type new_edge: :class:`~nodeeditor.node_edge.Edge` """ print("%s::onInputChanged" % self.__class__.__name__, new_edge) self.markDirty() self.markDescendantsDirty() def doSelect(self, new_state:bool=True): """Shortcut method for selecting/deselecting the `Node` :param new_state: ``True`` if you want to select the `Node`. ``False`` if you want to deselect the `Node` :type new_state: ``bool`` """ self.grNode.doSelect(new_state) def getSocketPosition(self, index:int, position:int, num_out_of:int=1) -> '(x, y)': """ Get the relative `x, y` position of a :class:`~nodeeditor.node_socket.Socket`. This is used for placing the `Graphics Sockets` on `Graphics Node`. :param index: Order number of the Socket. (0, 1, 2, ...) :type index: ``int`` :param position: `Socket Position Constant` describing where the Socket is located. See :ref:`socket-position-constants` :type position: ``int`` :param num_out_of: Total number of Sockets on this `Socket Position` :type num_out_of: ``int`` :return: Position of described Socket on the `Node` :rtype: ``x, y`` """ x = 0 if (position in (LEFT_TOP, LEFT_CENTER, LEFT_BOTTOM)) else self.grNode.width if position in (LEFT_BOTTOM, RIGHT_BOTTOM): # start from bottom y = self.grNode.height - self.grNode.edge_roundness - self.grNode.title_vertical_padding - index * self.socket_spacing elif position in (LEFT_CENTER, RIGHT_CENTER): num_sockets = num_out_of node_height = self.grNode.height top_offset = self.grNode.title_height + 2 * self.grNode.title_vertical_padding + self.grNode.edge_padding available_height = node_height - top_offset total_height_of_all_sockets = num_sockets * self.socket_spacing new_top = available_height - total_height_of_all_sockets # y = top_offset + index * self.socket_spacing + new_top / 2 y = top_offset + available_height/2.0 + (index-0.5)*self.socket_spacing if num_sockets > 1: y -= self.socket_spacing * (num_sockets-1)/2 elif position in (LEFT_TOP, RIGHT_TOP): # start from top y = self.grNode.title_height + self.grNode.title_vertical_padding + self.grNode.edge_roundness + index * self.socket_spacing else: # this should never happen y = 0 return [x, y] def updateConnectedEdges(self): """Recalculate (Refresh) positions of all connected `Edges`. Used for updating Graphics Edges""" for socket in self.inputs + self.outputs: # if socket.hasEdge(): for edge in socket.edges: edge.updatePositions() def remove(self): """ Safely remove this Node """ if DEBUG: print("> Removing Node", self) if DEBUG: print(" - remove all edges from sockets") for socket in (self.inputs+self.outputs): # if socket.hasEdge(): for edge in socket.edges: if DEBUG: print(" - removing from socket:", socket, "edge:", edge) edge.remove() if DEBUG: print(" - remove grNode") self.scene.grScene.removeItem(self.grNode) self.grNode = None if DEBUG: print(" - remove node from the scene") self.scene.removeNode(self) if DEBUG: print(" - everything was done.") # node evaluation stuff def isDirty(self) -> bool: """Is this node marked as `Dirty` :return: ``True`` if `Node` is marked as `Dirty` :rtype: ``bool`` """ return self._is_dirty def markDirty(self, new_value:bool=True): """Mark this `Node` as `Dirty`. See :ref:`evaluation` for more :param new_value: ``True`` if this `Node` should be `Dirty`. ``False`` if you want to un-dirty this `Node` :type new_value: ``bool`` """ self._is_dirty = new_value if self._is_dirty: self.onMarkedDirty() def onMarkedDirty(self): """Called when this `Node` has been marked as `Dirty`. This method is supposed to be overriden""" pass def markChildrenDirty(self, new_value:bool=True): """Mark all first level children of this `Node` to be `Dirty`. Not this `Node` it self. Not other descendants :param new_value: ``True`` if children should be `Dirty`. ``False`` if you want to un-dirty children :type new_value: ``bool`` """ for other_node in self.getChildrenNodes(): other_node.markDirty(new_value) def markDescendantsDirty(self, new_value:bool=True): """Mark all children and descendants of this `Node` to be `Dirty`. Not this `Node` it self :param new_value: ``True`` if children and descendants should be `Dirty`. ``False`` if you want to un-dirty children and descendants :type new_value: ``bool`` """ for other_node in self.getChildrenNodes(): other_node.markDirty(new_value) other_node.markChildrenDirty(new_value) def isInvalid(self) -> bool: """Is this node marked as `Invalid`? :return: ``True`` if `Node` is marked as `Invalid` :rtype: ``bool`` """ return self._is_invalid def markInvalid(self, new_value:bool=True): """Mark this `Node` as `Invalid`. See :ref:`evaluation` for more :param new_value: ``True`` if this `Node` should be `Invalid`. ``False`` if you want to make this `Node` valid :type new_value: ``bool`` """ self._is_invalid = new_value if self._is_invalid: self.onMarkedInvalid() def onMarkedInvalid(self): """Called when this `Node` has been marked as `Invalid`. This method is supposed to be overriden""" pass def markChildrenInvalid(self, new_value:bool=True): """Mark all first level children of this `Node` to be `Invalid`. Not this `Node` it self. Not other descendants :param new_value: ``True`` if children should be `Invalid`. ``False`` if you want to make children valid :type new_value: ``bool`` """ for other_node in self.getChildrenNodes(): other_node.markInvalid(new_value) def markDescendantsInvalid(self, new_value:bool=True): """Mark all children and descendants of this `Node` to be `Invalid`. Not this `Node` it self :param new_value: ``True`` if children and descendants should be `Invalid`. ``False`` if you want to make children and descendants valid :type new_value: ``bool`` """ for other_node in self.getChildrenNodes(): other_node.markInvalid(new_value) other_node.markChildrenInvalid(new_value) def eval(self): """Evaluate this `Node`. This is supposed to be overriden. See :ref:`evaluation` for more""" self.markDirty(False) self.markInvalid(False) return 0 def evalChildren(self): """Evaluate all children of this `Node`""" for node in self.getChildrenNodes(): node.eval() # traversing nodes functions def getChildrenNodes(self) -> 'List[Node]': """ Retreive all first-level children connected to this `Node` `Outputs` :return: list of `Nodes` connected to this `Node` from all `Outputs` :rtype: List[:class:`~nodeeditor.node_node.Node`] """ if self.outputs == []: return [] other_nodes = [] for ix in range(len(self.outputs)): for edge in self.outputs[ix].edges: other_node = edge.getOtherSocket(self.outputs[ix]).node other_nodes.append(other_node) return other_nodes def getInput(self, index:int=0) -> 'Node': """ Get the **first** `Node` connected to the Input specified by `index` :param index: Order number of the `Input Socket` :type index: ``int`` :return: :class:`~nodeeditor.node_node.Node` which is connected to the specified `Input` or ``None`` if there is no connection of index is out of range :rtype: :class:`~nodeeditor.node_node.Node` """ try: edge = self.inputs[index].edges[0] socket = edge.getOtherSocket(self.inputs[index]) return socket.node except IndexError: print("EXC: Trying to get input, but none is attached to", self) return None except Exception as e: dumpException(e) return None def getInputs(self, index:int=0) -> 'List[Node]': """ Get **all** `Nodes` connected to the Input specified by `index` :param index: Order number of the `Input Socket` :type index: ``int`` :return: all :class:`~nodeeditor.node_node.Node` instances which are connected to the specified `Input` or ``[]`` if there is no connection of index is out of range :rtype: List[:class:`~nodeeditor.node_node.Node`] """ ins = [] for edge in self.inputs[index].edges: other_socket = edge.getOtherSocket(self.inputs[index]) ins.append(other_socket.node) return ins def getOutputs(self, index:int=0) -> 'List[Node]': """ Get **all** `Nodes` connected to the Output specified by `index` :param index: Order number of the `Output Socket` :type index: ``int`` :return: all :class:`~nodeeditor.node_node.Node` instances which are connected to the specified `Output` or ``[]`` if there is no connection of index is out of range :rtype: List[:class:`~nodeeditor.node_node.Node`] """ outs = [] for edge in self.outputs[index].edges: other_socket = edge.getOtherSocket(self.outputs[index]) outs.append(other_socket.node) return outs # serialization functions def serialize(self) -> OrderedDict: inputs, outputs = [], [] for socket in self.inputs: inputs.append(socket.serialize()) for socket in self.outputs: outputs.append(socket.serialize()) return OrderedDict([ ('id', self.id), ('title', self.title), ('pos_x', self.grNode.scenePos().x()), ('pos_y', self.grNode.scenePos().y()), ('inputs', inputs), ('outputs', outputs), ('content', self.content.serialize()), ]) def deserialize(self, data:dict, hashmap:dict={}, restore_id:bool=True) -> bool: try: if restore_id: self.id = data['id'] hashmap[data['id']] = self self.setPos(data['pos_x'], data['pos_y']) self.title = data['title'] data['inputs'].sort(key=lambda socket: socket['index'] + socket['position'] * 10000 ) data['outputs'].sort(key=lambda socket: socket['index'] + socket['position'] * 10000 ) num_inputs = len( data['inputs'] ) num_outputs = len( data['outputs'] ) self.inputs = [] for socket_data in data['inputs']: new_socket = Socket(node=self, index=socket_data['index'], position=socket_data['position'], socket_type=socket_data['socket_type'], count_on_this_node_side=num_inputs, is_input=True) new_socket.deserialize(socket_data, hashmap, restore_id) self.inputs.append(new_socket) self.outputs = [] for socket_data in data['outputs']: new_socket = Socket(node=self, index=socket_data['index'], position=socket_data['position'], socket_type=socket_data['socket_type'], count_on_this_node_side=num_outputs, is_input=False) new_socket.deserialize(socket_data, hashmap, restore_id) self.outputs.append(new_socket) except Exception as e: dumpException(e) # also deseralize the content of the node res = self.content.deserialize(data['content'], hashmap) return True & res