Compare commits
	
		
			6 Commits
		
	
	
		
			94627debc6
			...
			90aa72a767
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 90aa72a767 | |||
| fb1caf9174 | |||
| fc34669af7 | |||
| 0ab4ffc098 | |||
| 876511c435 | |||
| cbf1ea64e6 | 
							
								
								
									
										211
									
								
								core/_node.py
									
									
									
									
									
								
							
							
						
						
									
										211
									
								
								core/_node.py
									
									
									
									
									
								
							@ -1,211 +0,0 @@
 | 
				
			|||||||
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
 | 
					 | 
				
			||||||
@ -1,71 +0,0 @@
 | 
				
			|||||||
import json
 | 
					 | 
				
			||||||
from pathlib import Path
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .node import Node, Link
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class NodeTree:
 | 
					 | 
				
			||||||
    """Blender node tree abstraction."""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, bl_node_tree):
 | 
					 | 
				
			||||||
        self.bl_node_tree = bl_node_tree
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.data = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.links = [Link(lnk, parent=self) for lnk in self.bl_node_tree.links]
 | 
					 | 
				
			||||||
        self.nodes = []
 | 
					 | 
				
			||||||
        for n in self.bl_node_tree.nodes:
 | 
					 | 
				
			||||||
            self.nodes.append(Node.from_blender_node(n, self))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def dump(self, select_only=False):
 | 
					 | 
				
			||||||
        """Convert all blender nodes and links inside the tree into a dictionnary.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Args:
 | 
					 | 
				
			||||||
            select_only (bool, optional): True to convert only selected nodes.
 | 
					 | 
				
			||||||
                Defaults to False.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Returns:
 | 
					 | 
				
			||||||
            dict: Nodes and links as dict.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.data["nodes"] = {
 | 
					 | 
				
			||||||
            n.id: n.dump()
 | 
					 | 
				
			||||||
            for n in self.nodes
 | 
					 | 
				
			||||||
            if not select_only or (select_only and n.select)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        self.data["links"] = [l.id for l in self.links]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return self.data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def load(self, data):
 | 
					 | 
				
			||||||
        """From a Tree dict representation, create new nodes with their attributes.
 | 
					 | 
				
			||||||
        Then create a connection dict by comparing link id from inputs and outputs of each nodes.
 | 
					 | 
				
			||||||
        Use this dict to link nodes between each others.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Args:
 | 
					 | 
				
			||||||
            data (dict): Tree dict representation to generate nodes and links from.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        connections = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.data = data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for node_id, node_data in self.data["nodes"].items():
 | 
					 | 
				
			||||||
            new_node = Node.from_dict(node_data, self)
 | 
					 | 
				
			||||||
            self.nodes.append(new_node)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            new_node.bl_node.select = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for ipt in new_node.inputs:
 | 
					 | 
				
			||||||
                if ipt.is_linked:
 | 
					 | 
				
			||||||
                    connections.setdefault(ipt.link, {})["input"] = ipt.bl_input
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for opt in new_node.outputs:
 | 
					 | 
				
			||||||
                if opt.is_linked:
 | 
					 | 
				
			||||||
                    for link in opt.link:
 | 
					 | 
				
			||||||
                        connections.setdefault(link, {})["output"] = opt.bl_output
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for link_id in self.data["links"]:
 | 
					 | 
				
			||||||
            ipt = connections[link_id]["input"]
 | 
					 | 
				
			||||||
            opt = connections[link_id]["output"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            self.bl_node_tree.links.new(ipt, opt)
 | 
					 | 
				
			||||||
@ -1,97 +0,0 @@
 | 
				
			|||||||
class Socket:
 | 
					 | 
				
			||||||
    def __init__(self, bl_socket, tree):
 | 
					 | 
				
			||||||
        self.tree = tree
 | 
					 | 
				
			||||||
        self.bl_socket = bl_socket
 | 
					 | 
				
			||||||
        self.data = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.id = hex(id(bl_socket))
 | 
					 | 
				
			||||||
        self.identifier = bl_socket.identifier
 | 
					 | 
				
			||||||
        self.is_linked = bl_socket.is_linked
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._value = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if hasattr(bl_socket, "default_value"):
 | 
					 | 
				
			||||||
            self._value = bl_socket.default_value
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def value(self):
 | 
					 | 
				
			||||||
        if not isinstance(self._value, (str, int, float, bool)):
 | 
					 | 
				
			||||||
            self._value = [v for v in self._value]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return self._value
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @value.setter
 | 
					 | 
				
			||||||
    def value(self, v):
 | 
					 | 
				
			||||||
        self.bl_socket.default_value = v
 | 
					 | 
				
			||||||
        self._value = v
 | 
					 | 
				
			||||||
        return self._value
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def to_dict(self):
 | 
					 | 
				
			||||||
        self.data["id"] = self.id
 | 
					 | 
				
			||||||
        self.data["value"] = self.value
 | 
					 | 
				
			||||||
        self.data["identifier"] = self.identifier
 | 
					 | 
				
			||||||
        self.data["is_linked"] = self.is_linked
 | 
					 | 
				
			||||||
        self.data["link"] = self.get_link()
 | 
					 | 
				
			||||||
        return self.data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Input(Socket):
 | 
					 | 
				
			||||||
    def __init__(self, bl_input, tree):
 | 
					 | 
				
			||||||
        super().__init__(bl_input, tree)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.bl_input = bl_input
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def from_dict(cls, data, node):
 | 
					 | 
				
			||||||
        for bl_ipt in node.bl_node.inputs:
 | 
					 | 
				
			||||||
            if bl_ipt.identifier != data["identifier"]:
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            new_ipt = cls(bl_ipt, node.tree)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for k, v in data.items():
 | 
					 | 
				
			||||||
                setattr(new_ipt, k, v)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return new_ipt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_link(self):
 | 
					 | 
				
			||||||
        if not self.is_linked:
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for ipt_link in self.bl_input.links:
 | 
					 | 
				
			||||||
            for tree_link in self.tree.links:
 | 
					 | 
				
			||||||
                if ipt_link == tree_link.bl_link:
 | 
					 | 
				
			||||||
                    return tree_link.id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Output(Socket):
 | 
					 | 
				
			||||||
    def __init__(self, bl_output, tree):
 | 
					 | 
				
			||||||
        super().__init__(bl_output, tree)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.bl_output = bl_output
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def from_dict(cls, data, node):
 | 
					 | 
				
			||||||
        for bl_opt in node.bl_node.outputs:
 | 
					 | 
				
			||||||
            if bl_opt.identifier != data["identifier"]:
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            new_opt = cls(bl_opt, node.tree)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for k, v in data.items():
 | 
					 | 
				
			||||||
                setattr(new_opt, k, v)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return new_opt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_link(self):
 | 
					 | 
				
			||||||
        links = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not self.is_linked:
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for opt_link in self.bl_output.links:
 | 
					 | 
				
			||||||
            for tree_link in self.tree.links:
 | 
					 | 
				
			||||||
                if opt_link == tree_link.bl_link:
 | 
					 | 
				
			||||||
                    links.append(tree_link.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return links
 | 
					 | 
				
			||||||
@ -1,41 +1,15 @@
 | 
				
			|||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
from copy import copy
 | 
					from copy import copy
 | 
				
			||||||
from os.path import abspath
 | 
					from os.path import abspath
 | 
				
			||||||
from pprint import pprint
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import bpy
 | 
					import bpy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .. import utils
 | 
					from . import utils
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
format_token = "#FMT:NODE_KIT#"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def dump_nkit_format(data: str) -> str:
 | 
					 | 
				
			||||||
    return format_token + json.dumps(data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def parse_nkit_format(data: str) -> str | None:
 | 
					 | 
				
			||||||
    if data.startswith(format_token):
 | 
					 | 
				
			||||||
        print(data[len(format_token):])
 | 
					 | 
				
			||||||
        return json.loads(data[len(format_token):])
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_default(prop: bpy.types.Property):
 | 
					 | 
				
			||||||
    """Get the default value of a Blender property"""
 | 
					 | 
				
			||||||
    if getattr(prop, "is_array", False):
 | 
					 | 
				
			||||||
        return list(prop.default_array)
 | 
					 | 
				
			||||||
    elif hasattr(prop, "default"):
 | 
					 | 
				
			||||||
        return prop.default
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_dumper(bl_object: bpy.types.bpy_struct) -> type[Dumper]:
 | 
					def get_dumper(bl_object: bpy.types.bpy_struct) -> type[Dumper]:
 | 
				
			||||||
    """Get the corresponding dumper for a given Blender object, or its closest base type using its MRO"""
 | 
					    """Get the closest corresponding dumper for a given Blender object using its MRO"""
 | 
				
			||||||
 | 
					 | 
				
			||||||
    for cls in bl_object.__class__.mro():
 | 
					    for cls in bl_object.__class__.mro():
 | 
				
			||||||
        dumper_map = DumperRegistry().dumper_map
 | 
					        dumper_map = DumperRegistry().dumper_map
 | 
				
			||||||
        if cls in dumper_map:
 | 
					        if cls in dumper_map:
 | 
				
			||||||
@ -44,13 +18,9 @@ def get_dumper(bl_object: bpy.types.bpy_struct) -> type[Dumper]:
 | 
				
			|||||||
    # Fallback to base Dumper if no matches are found
 | 
					    # Fallback to base Dumper if no matches are found
 | 
				
			||||||
    return Dumper
 | 
					    return Dumper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_current_node_tree(data):
 | 
					 | 
				
			||||||
    if data.get("_new", {}).get("type") == "GeometryNodeTree":
 | 
					 | 
				
			||||||
        return bpy.context.object.modifiers.active.node_group
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def dump_nodes(nodes: list[bpy.types.Node]):
 | 
					def dump_nodes(nodes: list[bpy.types.Node]):
 | 
				
			||||||
    """Generic Recursive Dump, convert any object into a dict"""
 | 
					    """Generic recursive dump, convert nodes into a dict"""
 | 
				
			||||||
    Dumper.pointers.clear()  # TODO: Bad global
 | 
					    Dumper.pointers.clear()  # TODO: Bad global
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data = [dump_node(node) for node in nodes]
 | 
					    data = [dump_node(node) for node in nodes]
 | 
				
			||||||
@ -59,19 +29,15 @@ def dump_nodes(nodes: list[bpy.types.Node]):
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    return data
 | 
					    return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def dump_node(node: bpy.types.Node):
 | 
					def dump_node(node: bpy.types.Node):
 | 
				
			||||||
    dumper = get_dumper(node)
 | 
					    dumper = get_dumper(node)
 | 
				
			||||||
    return dumper.dump(node)  # TODO: Break the recursivity, clear things up
 | 
					    return dumper.dump(node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def load_nodes(data, node_tree=None):
 | 
					def load_nodes(data, node_tree):
 | 
				
			||||||
    """Generic Load to create an object from a dict"""
 | 
					    """Load/Dump nodes into a specific node tree"""
 | 
				
			||||||
 | 
					 | 
				
			||||||
    Dumper.pointers.clear()
 | 
					    Dumper.pointers.clear()
 | 
				
			||||||
    # print(Dumper.pointers)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if node_tree is None:
 | 
					 | 
				
			||||||
        node_tree = get_current_node_tree(data)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dumper = get_dumper(node_tree)
 | 
					    dumper = get_dumper(node_tree)
 | 
				
			||||||
    dumper.load(data, node_tree)
 | 
					    dumper.load(data, node_tree)
 | 
				
			||||||
@ -79,13 +45,6 @@ def load_nodes(data, node_tree=None):
 | 
				
			|||||||
    Dumper.pointers.clear()
 | 
					    Dumper.pointers.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def set_attribute(bl_object, attr, value):
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        setattr(bl_object, attr, value)
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        print(e)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Dumper:
 | 
					class Dumper:
 | 
				
			||||||
    pointers = {}
 | 
					    pointers = {}
 | 
				
			||||||
    includes = []
 | 
					    includes = []
 | 
				
			||||||
@ -144,7 +103,7 @@ class Dumper:
 | 
				
			|||||||
                    value = cls.pointers[value]
 | 
					                    value = cls.pointers[value]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                elif value is None:
 | 
					                elif value is None:
 | 
				
			||||||
                    set_attribute(bl_object, key, value)
 | 
					                    utils.set_bl_attribute(bl_object, key, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    bl_type = prop.fixed_type.bl_rna.type_recast()
 | 
					                    bl_type = prop.fixed_type.bl_rna.type_recast()
 | 
				
			||||||
@ -163,14 +122,14 @@ class Dumper:
 | 
				
			|||||||
                    value = attr
 | 
					                    value = attr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if not prop.is_readonly:
 | 
					                if not prop.is_readonly:
 | 
				
			||||||
                    set_attribute(bl_object, key, value)
 | 
					                    utils.set_bl_attribute(bl_object, key, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # Some coll needs a manual update like curve mapping
 | 
					                # Some coll needs a manual update like curve mapping
 | 
				
			||||||
                if hasattr(attr, "update"):
 | 
					                if hasattr(attr, "update"):
 | 
				
			||||||
                    attr.update()
 | 
					                    attr.update()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif not prop.is_readonly:
 | 
					            elif not prop.is_readonly:
 | 
				
			||||||
                set_attribute(bl_object, key, value)
 | 
					                utils.set_bl_attribute(bl_object, key, value)
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # return bl_object
 | 
					        # return bl_object
 | 
				
			||||||
@ -257,7 +216,7 @@ class PropCollection(Dumper):
 | 
				
			|||||||
                params = value["_new"]
 | 
					                params = value["_new"]
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                params = {
 | 
					                params = {
 | 
				
			||||||
                    k: value.get(k, get_default(v))
 | 
					                    k: value.get(k, utils.get_bl_default(v))
 | 
				
			||||||
                    for k, v in new_func.parameters.items()[:-1]
 | 
					                    for k, v in new_func.parameters.items()[:-1]
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -557,7 +516,7 @@ class Points(PropCollection):
 | 
				
			|||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def load(cls, values, coll):
 | 
					    def load(cls, values, coll):
 | 
				
			||||||
        new_func = coll.bl_rna.functions["new"]
 | 
					        new_func = coll.bl_rna.functions["new"]
 | 
				
			||||||
        params = {k: get_default(v) + 1.1 for k, v in new_func.parameters.items()[:-1]}
 | 
					        params = {k: utils.get_bl_default(v) + 1.1 for k, v in new_func.parameters.items()[:-1]}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Match the same number of elements in collection
 | 
					        # Match the same number of elements in collection
 | 
				
			||||||
        if len(values) > len(coll):
 | 
					        if len(values) > len(coll):
 | 
				
			||||||
							
								
								
									
										17
									
								
								formats.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								formats.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					format_token = "#FMT:NODE_KIT#"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def dump_nkit_format(data: str) -> str:
 | 
				
			||||||
 | 
					    return format_token + json.dumps(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_nkit_format(data: str) -> str | None:
 | 
				
			||||||
 | 
					    if data.startswith(format_token):
 | 
				
			||||||
 | 
					        print(data[len(format_token):])
 | 
				
			||||||
 | 
					        return json.loads(data[len(format_token):])
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										25
									
								
								operators.py
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								operators.py
									
									
									
									
									
								
							@ -14,9 +14,10 @@ import bpy
 | 
				
			|||||||
from bpy.props import BoolProperty, EnumProperty
 | 
					from bpy.props import BoolProperty, EnumProperty
 | 
				
			||||||
from bpy.types import Operator
 | 
					from bpy.types import Operator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .core.dumper import dump_nodes, load_nodes, dump_nkit_format, parse_nkit_format
 | 
					from .dumper import dump_nodes, load_nodes
 | 
				
			||||||
from .core.node_utils import remap_node_group_duplicates
 | 
					from .node_utils import remap_node_group_duplicates
 | 
				
			||||||
from .core.pack_nodes import combine_objects, extract_objects
 | 
					from .pack_nodes import combine_objects, extract_objects
 | 
				
			||||||
 | 
					from .formats import dump_nkit_format, parse_nkit_format
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NODEKIT_OT_copy(Operator):
 | 
					class NODEKIT_OT_copy(Operator):
 | 
				
			||||||
@ -36,8 +37,6 @@ class NODEKIT_OT_copy(Operator):
 | 
				
			|||||||
            ),
 | 
					            ),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pprint(ntree_data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        context.window_manager.clipboard = dump_nkit_format(ntree_data)
 | 
					        context.window_manager.clipboard = dump_nkit_format(ntree_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.report({"INFO"}, f"Copied {len(selected_nodes)} selected nodes to system clipboard")
 | 
					        self.report({"INFO"}, f"Copied {len(selected_nodes)} selected nodes to system clipboard")
 | 
				
			||||||
@ -52,9 +51,7 @@ class NODEKIT_OT_copy_tree(Operator):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def execute(self, context):
 | 
					    def execute(self, context):
 | 
				
			||||||
        ntree = context.space_data.edit_tree
 | 
					        ntree = context.space_data.edit_tree
 | 
				
			||||||
        ntree_data = dump_nodes(ntree)
 | 
					        ntree_data = dict(ntree)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        pprint(ntree_data)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        context.window_manager.clipboard = dump_nkit_format(ntree_data)
 | 
					        context.window_manager.clipboard = dump_nkit_format(ntree_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -78,7 +75,8 @@ class NODEKIT_OT_paste(Operator):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class NODEKIT_OT_remap_node_group_duplicates(Operator):
 | 
					class NODEKIT_OT_remap_node_group_duplicates(Operator):
 | 
				
			||||||
    bl_idname = "node_kit.remap_node_group_duplicates"
 | 
					    bl_idname = "node_kit.remap_node_group_duplicates"
 | 
				
			||||||
    bl_label = "Remap Node Groups Duplicates"
 | 
					    bl_label = "Clean Node Groups Duplicates"
 | 
				
			||||||
 | 
					    bl_description = "Remap Node Groups duplicates to the latest imported version"
 | 
				
			||||||
    bl_options = {"REGISTER", "UNDO"}
 | 
					    bl_options = {"REGISTER", "UNDO"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    selection: EnumProperty(
 | 
					    selection: EnumProperty(
 | 
				
			||||||
@ -139,7 +137,8 @@ class NODEKIT_OT_remap_node_group_duplicates(Operator):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class NODEKIT_OT_update_nodes(Operator):
 | 
					class NODEKIT_OT_update_nodes(Operator):
 | 
				
			||||||
    bl_idname = "node_kit.update_nodes"
 | 
					    bl_idname = "node_kit.update_nodes"
 | 
				
			||||||
    bl_label = "Update Nodes"
 | 
					    bl_label = "Update Nodes from Library"
 | 
				
			||||||
 | 
					    bl_description = "Overrides node group using the latest version from Asset Library"
 | 
				
			||||||
    bl_options = {"REGISTER", "UNDO"}
 | 
					    bl_options = {"REGISTER", "UNDO"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    selection: EnumProperty(
 | 
					    selection: EnumProperty(
 | 
				
			||||||
@ -249,7 +248,8 @@ class NODEKIT_OT_update_nodes(Operator):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class NODEKIT_OT_pack_nodes(Operator):
 | 
					class NODEKIT_OT_pack_nodes(Operator):
 | 
				
			||||||
    bl_idname = "node_kit.pack_nodes"
 | 
					    bl_idname = "node_kit.pack_nodes"
 | 
				
			||||||
    bl_label = "Pack Nodes"
 | 
					    bl_label = "Pack Modifiers as Nodes"
 | 
				
			||||||
 | 
					    bl_description = "Pack Geometry Nodes modifiers stack as a single node tree"
 | 
				
			||||||
    bl_options = {"REGISTER", "UNDO"}
 | 
					    bl_options = {"REGISTER", "UNDO"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def execute(self, context):
 | 
					    def execute(self, context):
 | 
				
			||||||
@ -259,7 +259,8 @@ class NODEKIT_OT_pack_nodes(Operator):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class NODEKIT_OT_unpack_nodes(Operator):
 | 
					class NODEKIT_OT_unpack_nodes(Operator):
 | 
				
			||||||
    bl_idname = "node_kit.unpack_nodes"
 | 
					    bl_idname = "node_kit.unpack_nodes"
 | 
				
			||||||
    bl_label = "Unpack Nodes"
 | 
					    bl_label = "Unpack Nodes as Modifiers"
 | 
				
			||||||
 | 
					    bl_description = "Unpack node tree as Geometry Nodes modifiers"
 | 
				
			||||||
    bl_options = {"REGISTER", "UNDO"}
 | 
					    bl_options = {"REGISTER", "UNDO"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def execute(self, context):
 | 
					    def execute(self, context):
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								utils.py
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								utils.py
									
									
									
									
									
								
							@ -1,6 +1,22 @@
 | 
				
			|||||||
 | 
					import bpy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def all_subclasses(cls):
 | 
					def all_subclasses(cls):
 | 
				
			||||||
    return set(cls.__subclasses__()).union(
 | 
					    return set(cls.__subclasses__()).union(
 | 
				
			||||||
        [s for c in cls.__subclasses__() for s in all_subclasses(c)]
 | 
					        [s for c in cls.__subclasses__() for s in all_subclasses(c)]
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_bl_default(prop: bpy.types.Property):
 | 
				
			||||||
 | 
					    """Get the default value of a Blender property"""
 | 
				
			||||||
 | 
					    if getattr(prop, "is_array", False):
 | 
				
			||||||
 | 
					        return list(prop.default_array)
 | 
				
			||||||
 | 
					    elif hasattr(prop, "default"):
 | 
				
			||||||
 | 
					        return prop.default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def set_bl_attribute(bl_object, attr, value):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        setattr(bl_object, attr, value)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        print(e)
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user