import bpy from mathutils import Color, Vector from .sockets import Input, Output class Node: """Blender Node abstraction.""" def __init__(self, bl_node, parent): self.bl_node = bl_node self.tree = parent self.id = hex(id(self.bl_node)) self.data = {} self.parameters = [] self._parent = None self._scene = None for prop in self.bl_node.bl_rna.properties: if prop.is_readonly: continue prop_id = prop.identifier setattr(self, prop_id, getattr(self.bl_node, prop_id)) self.parameters.append(prop_id) self.inputs = [Input(ipt, self.tree) for ipt in self.bl_node.inputs] self.outputs = [Output(opt, self.tree) for opt in self.bl_node.outputs] @property def parent(self): """Get the Node from all the other nodes in the tree checking that the parent of its blender node is the same as the blender node we are comparing. Returns: Node: Node parent. """ if self._parent: return self._parent # if blender node doesn't have a parent if not self.bl_node.parent: self._parent = None return self._parent for node in self.tree.nodes: if node.bl_node == self.bl_node.parent: self._parent = node return self._parent @parent.setter def parent(self, value): """Set the Node parent, using the python object, it's id or the blender node. Args: value (Node|str|bpy.types.Node): Node, id or blender node to set as parent. """ # Node object case if isinstance(value, Node): self._parent = value # Node id case elif isinstance(value, str) and value.startswith('0x'): for node in self.tree.nodes: if node.id == value: self._parent = node else: print('Cannot find parent') # blender node case elif isinstance(value, bpy.types.Node): for node in self.tree.nodes: if node.bl_node == value: self._parent = node if self._parent: self.bl_node.parent = self._parent.bl_node @classmethod def from_blender_node(cls, bl_node, tree): """Instanciate an abstract class based of the blender node idname. Args: bl_node (bpy.types.Node): Blender Node To create abstraction from. tree (NodeTree): Node tree object node belongs to. Returns: Node: Node abstract according to the blender node type. """ if bl_node.bl_idname == 'CompositorNodeRLayers': return RenderLayersNode(bl_node, tree) elif bl_node.bl_idname == 'CompositorNodeValToRGB': return ColorRampNode(bl_node, tree) else: return cls(bl_node, tree) @classmethod def from_dict(cls, data, tree): """Create all nodes from their dict representation. Args: data (dict): dict nodes representation. tree (Tree): blender node tree abstraction. Returns: Node: Create abstract node. """ new_bl_node = tree.bl_node_tree.nodes.new(type=data['bl_idname']) node = cls.from_blender_node(new_bl_node, tree) node.id = data['id'] for p in node.parameters: setattr(node, p, data[p]) # set attribute on the blender node only if correct type is retrieve if p not in ('parent', 'scene'): setattr(node.bl_node, p, getattr(node, p)) node.inputs = [Input.from_dict(ipt_data, node) for ipt_data in data['inputs'].values()] node.outputs = [Output.from_dict(opt_data, node) for opt_data in data['outputs'].values()] return node def dump(self): """Export currrent Node to its dict representation. Returns: dict: Node dict representation. """ for prop_id in self.parameters: if not hasattr(self, prop_id): continue attr_value = getattr(self, prop_id) if attr_value is None: attr_value = None elif isinstance(attr_value, Node): attr_value = attr_value.id elif isinstance(attr_value, (Color, Vector)): attr_value = list(attr_value) self.data[prop_id] = attr_value self.data['id'] = self.id self.data['inputs'] = {ipt.id: ipt.dump() for ipt in self.inputs} self.data['outputs'] = {opt.id: opt.dump() for opt in self.outputs} return self.data class RenderLayersNode(Node): """Blender Render Layers Node abstraction""" @property def scene(self): """Get the name of the scene used by the node. Returns: str: scene name. """ if self._scene: return self._scene.name @scene.setter def scene(self, value): """Set the blender scene using the bpy Scene object or its name. Args: value (str|bpy.types.Scene): scene name or scene object to set the node. """ if isinstance(value, str): self._scene = bpy.data.scenes[value] elif isinstance(value, bpy.types.Scene): self._scene = value if self._scene: self.bl_node.scene = self._scene class Link: """Blender Link abstraction.""" def __init__(self, bl_link, parent): self.bl_link = bl_link self.tree = parent self.id = hex(id(self.bl_link)) self.input = self.bl_link.to_socket self.output = self.bl_link.from_socket self.data = {} def dump(self): self.data['id'] = self.id return self.data