Create addon with copy paste operators

master
florentin.luce 2024-02-22 10:09:34 +01:00
parent 9db9538172
commit 4fb9bb8c69
7 changed files with 408 additions and 0 deletions

40
__init__.py Normal file
View File

@ -0,0 +1,40 @@
bl_info = {
"name": "Node Kit",
"author": "Florentin Luce",
"version": (0, 1),
"blender": (4, 0, 2),
"category": "Node"}
import sys
import importlib
from pathlib import Path
# Ensure the name of the module in python import
module_name = Path(__file__).parent.name
sys.modules.update({'node_kit': importlib.import_module(module_name)})
from node_kit import ui, operators
modules = (
ui,
operators,
)
if "bpy" in locals():
import importlib
for mod in modules:
importlib.reload(mod)
def register():
print('Register Noke kit')
for mod in modules:
mod.register()
def unregister():
for mod in reversed(modules):
mod.unregister()

0
core/__init__.py Normal file
View File

97
core/node.py Normal file
View File

@ -0,0 +1,97 @@
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 = []
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]
@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.nodes.new(type=data['bl_idname'])
node = cls(new_bl_node, parent=tree)
for p in node.parameters:
setattr(node, p, data[p])
setattr(node.bl_node, p, data[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 to_dict(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 not isinstance(attr_value, (str, int, float, list, tuple)):
attr_value = list(attr_value)
self.data[prop_id] = attr_value
self.data['id'] = self.id
self.data['inputs'] = {ipt.id: ipt.to_dict() for ipt in self.inputs}
self.data['outputs'] = {opt.id: opt.to_dict() for opt in self.outputs}
return self.data
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 to_dict(self):
self.data['id'] = self.id
return self.data

66
core/node_tree.py Normal file
View File

@ -0,0 +1,66 @@
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.tmp_file = Path('/home/florentin.luce/Bureau/copy_nodes.json')
self.nodes = [Node(n, parent=self) for n in self.bl_node_tree.nodes]
self.links = [Link(l, parent=self) for l in self.bl_node_tree.links]
def to_dict(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.to_dict() 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 ingest_dict(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.bl_node_tree)
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)

107
core/sockets.py Normal file
View File

@ -0,0 +1,107 @@
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 = 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

55
operators.py Normal file
View File

@ -0,0 +1,55 @@
"""
This module contains all addons operators
:author: Autour de Minuit
:maintainers: Florentin LUCE
:date: 2024
"""
import json
import bpy
from node_kit.core.node_tree import NodeTree
class NODEKIT_OT_copy(bpy.types.Operator):
bl_idname = 'node_kit.copy_node_tree'
bl_label = 'Copy node tree'
bl_options = {'REGISTER', 'UNDO'}
select_only: bpy.props.BoolProperty(default=False)
def execute(self, context):
tree = NodeTree(context.space_data.node_tree)
context.window_manager.clipboard = json.dumps(tree.to_dict(select_only=self.select_only))
return {'FINISHED'}
class NODEKIT_OT_paste(bpy.types.Operator):
bl_idname = 'node_kit.paste_node_tree'
bl_label = 'Paste node tree'
def execute(self, context):
tree = NodeTree(context.space_data.node_tree)
tree.ingest_dict(json.loads(context.window_manager.clipboard))
return {'FINISHED'}
classes = (
NODEKIT_OT_copy,
NODEKIT_OT_paste,
)
def register():
for c in classes:
bpy.utils.register_class(c)
def unregister():
for c in reversed(classes):
bpy.utils.unregister_class(c)

43
ui.py Normal file
View File

@ -0,0 +1,43 @@
"""
This module contains blender UI elements
:author: Autour de Minuit
:maintainers: Florentin LUCE
:date: 2024
"""
import bpy
class NODEKIT_MT_node_kit(bpy.types.Menu):
bl_label = "Node kit"
def draw(self, context):
layout = self.layout
layout.operator('node_kit.copy_node_tree', text='Copy node tree', icon='COPYDOWN')
layout.operator('node_kit.paste_node_tree', text='Paste node tree', icon='PASTEDOWN')
layout.separator()
classes = (
NODEKIT_MT_node_kit,
)
def draw_menu(self, context):
self.layout.menu('NODEKIT_MT_node_kit')
def register():
for c in classes:
bpy.utils.register_class(c)
bpy.types.NODE_MT_editor_menus.append(draw_menu)
def unregister():
for c in reversed(classes):
bpy.utils.unregister_class(c)
bpy.types.NODE_MT_editor_menus.remove(draw_menu)