Compare commits
	
		
			No commits in common. "90aa72a767cf44d5f491e02b285acfda27a0ad82" and "94627debc6580513136b18bffd327ed41511a8d1" have entirely different histories.
		
	
	
		
			90aa72a767
			...
			94627debc6
		
	
		
							
								
								
									
										0
									
								
								core/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								core/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										211
									
								
								core/_node.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								core/_node.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,211 @@ | |||||||
|  | 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 | ||||||
							
								
								
									
										71
									
								
								core/_node_tree.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								core/_node_tree.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | |||||||
|  | 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) | ||||||
							
								
								
									
										97
									
								
								core/_sockets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								core/_sockets.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | |||||||
|  | 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,15 +1,41 @@ | |||||||
| 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 closest corresponding dumper for a given Blender object using its MRO""" |     """Get the corresponding dumper for a given Blender object, or its closest base type 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: | ||||||
| @ -18,9 +44,13 @@ 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 nodes into a dict""" |     """Generic Recursive Dump, convert any object 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] | ||||||
| @ -29,15 +59,19 @@ 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) |     return dumper.dump(node)  # TODO: Break the recursivity, clear things up | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def load_nodes(data, node_tree): | def load_nodes(data, node_tree=None): | ||||||
|     """Load/Dump nodes into a specific node tree""" |     """Generic Load to create an object from a dict""" | ||||||
|  | 
 | ||||||
|     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) | ||||||
| @ -45,6 +79,13 @@ def load_nodes(data, node_tree): | |||||||
|     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 = [] | ||||||
| @ -103,7 +144,7 @@ class Dumper: | |||||||
|                     value = cls.pointers[value] |                     value = cls.pointers[value] | ||||||
| 
 | 
 | ||||||
|                 elif value is None: |                 elif value is None: | ||||||
|                     utils.set_bl_attribute(bl_object, key, value) |                     set_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() | ||||||
| @ -122,14 +163,14 @@ class Dumper: | |||||||
|                     value = attr |                     value = attr | ||||||
| 
 | 
 | ||||||
|                 if not prop.is_readonly: |                 if not prop.is_readonly: | ||||||
|                     utils.set_bl_attribute(bl_object, key, value) |                     set_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: | ||||||
|                 utils.set_bl_attribute(bl_object, key, value) |                 set_attribute(bl_object, key, value) | ||||||
|                 continue |                 continue | ||||||
| 
 | 
 | ||||||
|         # return bl_object |         # return bl_object | ||||||
| @ -216,7 +257,7 @@ class PropCollection(Dumper): | |||||||
|                 params = value["_new"] |                 params = value["_new"] | ||||||
|             else: |             else: | ||||||
|                 params = { |                 params = { | ||||||
|                     k: value.get(k, utils.get_bl_default(v)) |                     k: value.get(k, get_default(v)) | ||||||
|                     for k, v in new_func.parameters.items()[:-1] |                     for k, v in new_func.parameters.items()[:-1] | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
| @ -516,7 +557,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: utils.get_bl_default(v) + 1.1 for k, v in new_func.parameters.items()[:-1]} |         params = {k: get_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
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								formats.py
									
									
									
									
									
								
							| @ -1,17 +0,0 @@ | |||||||
| 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,10 +14,9 @@ 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 .dumper import dump_nodes, load_nodes | from .core.dumper import dump_nodes, load_nodes, dump_nkit_format, parse_nkit_format | ||||||
| from .node_utils import remap_node_group_duplicates | from .core.node_utils import remap_node_group_duplicates | ||||||
| from .pack_nodes import combine_objects, extract_objects | from .core.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): | ||||||
| @ -37,6 +36,8 @@ 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") | ||||||
| @ -51,7 +52,9 @@ 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 = dict(ntree) |         ntree_data = dump_nodes(ntree) | ||||||
|  | 
 | ||||||
|  |         pprint(ntree_data) | ||||||
| 
 | 
 | ||||||
|         context.window_manager.clipboard = dump_nkit_format(ntree_data) |         context.window_manager.clipboard = dump_nkit_format(ntree_data) | ||||||
| 
 | 
 | ||||||
| @ -75,8 +78,7 @@ 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 = "Clean Node Groups Duplicates" |     bl_label = "Remap 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( | ||||||
| @ -137,8 +139,7 @@ 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 from Library" |     bl_label = "Update Nodes" | ||||||
|     bl_description = "Overrides node group using the latest version from Asset Library" |  | ||||||
|     bl_options = {"REGISTER", "UNDO"} |     bl_options = {"REGISTER", "UNDO"} | ||||||
| 
 | 
 | ||||||
|     selection: EnumProperty( |     selection: EnumProperty( | ||||||
| @ -248,8 +249,7 @@ 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 Modifiers as Nodes" |     bl_label = "Pack 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,8 +259,7 @@ 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 as Modifiers" |     bl_label = "Unpack Nodes" | ||||||
|     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,22 +1,6 @@ | |||||||
| 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