Compare commits
6 Commits
4e029c59a2
...
94627debc6
Author | SHA1 | Date |
---|---|---|
|
94627debc6 | |
|
e4ca202608 | |
|
e65d6d8a75 | |
|
18f75eed25 | |
|
b251a3b122 | |
|
698ace38fd |
|
@ -10,11 +10,12 @@ bl_info = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
from . import ui, operators
|
from . import ui, operators, preferences
|
||||||
|
|
||||||
modules = (
|
modules = (
|
||||||
ui,
|
ui,
|
||||||
operators,
|
operators,
|
||||||
|
preferences
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
144
core/dumper.py
144
core/dumper.py
|
@ -1,61 +1,80 @@
|
||||||
import bpy
|
from __future__ import annotations
|
||||||
import mathutils
|
|
||||||
from pprint import pprint
|
|
||||||
import json
|
import json
|
||||||
import itertools
|
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from os.path import abspath
|
from os.path import abspath
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
from .. import utils
|
||||||
|
|
||||||
|
|
||||||
def get_default(prop):
|
format_token = "#FMT:NODE_KIT#"
|
||||||
"""Get the default value of a bl property"""
|
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
if getattr(prop, "is_array", False):
|
||||||
return list(prop.default_array)
|
return list(prop.default_array)
|
||||||
elif hasattr(prop, "default"):
|
elif hasattr(prop, "default"):
|
||||||
return prop.default
|
return prop.default
|
||||||
|
|
||||||
|
|
||||||
def get_dumper(bl_object, fallback=None):
|
def get_dumper(bl_object: bpy.types.bpy_struct) -> type[Dumper]:
|
||||||
"""Find the right dumper type by checking inheritance"""
|
"""Get the corresponding dumper for a given Blender object, or its closest base type using its MRO"""
|
||||||
for dp in dumpers:
|
|
||||||
if isinstance(bl_object, dp.bl_type):
|
|
||||||
return dp
|
|
||||||
|
|
||||||
return fallback or Dumper
|
for cls in bl_object.__class__.mro():
|
||||||
|
dumper_map = DumperRegistry().dumper_map
|
||||||
|
if cls in dumper_map:
|
||||||
|
return dumper_map[cls]
|
||||||
|
|
||||||
|
# Fallback to base Dumper if no matches are found
|
||||||
|
return Dumper
|
||||||
|
|
||||||
def get_bl_object(data):
|
def get_current_node_tree(data):
|
||||||
"""Find the bl object for loading data into it depending on the type and the context"""
|
|
||||||
if data.get("_new", {}).get("type") == "GeometryNodeTree":
|
if data.get("_new", {}).get("type") == "GeometryNodeTree":
|
||||||
return bpy.context.object.modifiers.active.node_group
|
return bpy.context.object.modifiers.active.node_group
|
||||||
|
|
||||||
|
|
||||||
def dump_nodes(ob):
|
def dump_nodes(nodes: list[bpy.types.Node]):
|
||||||
"""Generic Recursive Dump, convert any object into a dict"""
|
"""Generic Recursive Dump, convert any object into a dict"""
|
||||||
Dumper.pointers.clear()
|
Dumper.pointers.clear() # TODO: Bad global
|
||||||
|
|
||||||
if isinstance(ob, (list, tuple)):
|
data = [dump_node(node) for node in nodes]
|
||||||
data = [get_dumper(o).dump(o) for o in ob]
|
|
||||||
else:
|
|
||||||
data = get_dumper(ob).dump(ob)
|
|
||||||
|
|
||||||
Dumper.pointers.clear()
|
Dumper.pointers.clear()
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def dump_node(node: bpy.types.Node):
|
||||||
|
dumper = get_dumper(node)
|
||||||
|
return dumper.dump(node) # TODO: Break the recursivity, clear things up
|
||||||
|
|
||||||
def load_nodes(data, bl_object=None):
|
|
||||||
|
def load_nodes(data, node_tree=None):
|
||||||
"""Generic Load to create an object from a dict"""
|
"""Generic Load to create an object from a dict"""
|
||||||
|
|
||||||
Dumper.pointers.clear()
|
Dumper.pointers.clear()
|
||||||
# print(Dumper.pointers)
|
# print(Dumper.pointers)
|
||||||
|
|
||||||
if bl_object is None:
|
if node_tree is None:
|
||||||
bl_object = get_bl_object(data)
|
node_tree = get_current_node_tree(data)
|
||||||
|
|
||||||
dumper = get_dumper(bl_object)
|
dumper = get_dumper(node_tree)
|
||||||
dumper.load(data, bl_object)
|
dumper.load(data, node_tree)
|
||||||
|
|
||||||
Dumper.pointers.clear()
|
Dumper.pointers.clear()
|
||||||
|
|
||||||
|
@ -96,7 +115,6 @@ class Dumper:
|
||||||
if bl_object is None:
|
if bl_object is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# pprint(data)
|
|
||||||
if bl_pointer := data.get("bl_pointer"):
|
if bl_pointer := data.get("bl_pointer"):
|
||||||
cls.pointers[bl_pointer] = bl_object
|
cls.pointers[bl_pointer] = bl_object
|
||||||
|
|
||||||
|
@ -114,17 +132,12 @@ class Dumper:
|
||||||
dumper = PropCollection
|
dumper = PropCollection
|
||||||
if hasattr(attr, "bl_rna"):
|
if hasattr(attr, "bl_rna"):
|
||||||
bl_type = attr.bl_rna.type_recast()
|
bl_type = attr.bl_rna.type_recast()
|
||||||
dumper = get_dumper(bl_type, fallback=PropCollection)
|
dumper = PropCollection or get_dumper(bl_type)
|
||||||
|
|
||||||
dumper.load(value, attr)
|
dumper.load(value, attr)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
elif prop.type == "POINTER":
|
elif prop.type == "POINTER":
|
||||||
# if key == 'node_tree':
|
|
||||||
# print('--------------')
|
|
||||||
# print(bl_object, value)
|
|
||||||
# print(cls.pointers)
|
|
||||||
|
|
||||||
if isinstance(value, int): # It's a pointer
|
if isinstance(value, int): # It's a pointer
|
||||||
if value not in cls.pointers:
|
if value not in cls.pointers:
|
||||||
print(bl_object, "not loaded yet", prop)
|
print(bl_object, "not loaded yet", prop)
|
||||||
|
@ -138,14 +151,10 @@ class Dumper:
|
||||||
dumper = get_dumper(bl_type)
|
dumper = get_dumper(bl_type)
|
||||||
|
|
||||||
# If the pointer exist register the pointer then load data
|
# If the pointer exist register the pointer then load data
|
||||||
# print('-----', value)
|
|
||||||
# pointer =
|
|
||||||
if attr is None:
|
if attr is None:
|
||||||
attr = dumper.new(value)
|
attr = dumper.new(value)
|
||||||
|
|
||||||
dumper.load(value, attr)
|
dumper.load(value, attr)
|
||||||
# attr = getattr(bl_object, key)
|
|
||||||
# if not attr:
|
|
||||||
cls.pointers[value["bl_pointer"]] = attr
|
cls.pointers[value["bl_pointer"]] = attr
|
||||||
|
|
||||||
if hasattr(attr, "update"):
|
if hasattr(attr, "update"):
|
||||||
|
@ -161,7 +170,6 @@ class Dumper:
|
||||||
attr.update()
|
attr.update()
|
||||||
|
|
||||||
elif not prop.is_readonly:
|
elif not prop.is_readonly:
|
||||||
# print(key, value)
|
|
||||||
set_attribute(bl_object, key, value)
|
set_attribute(bl_object, key, value)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -172,8 +180,6 @@ class Dumper:
|
||||||
if isinstance(bl_object, (str, int, float, dict, list, type(None))):
|
if isinstance(bl_object, (str, int, float, dict, list, type(None))):
|
||||||
return bl_object
|
return bl_object
|
||||||
|
|
||||||
# print('Dumping object', bl_object)
|
|
||||||
|
|
||||||
data = {"bl_pointer": bl_object.as_pointer()}
|
data = {"bl_pointer": bl_object.as_pointer()}
|
||||||
cls.pointers[bl_object.as_pointer()] = bl_object
|
cls.pointers[bl_object.as_pointer()] = bl_object
|
||||||
|
|
||||||
|
@ -182,8 +188,6 @@ class Dumper:
|
||||||
print(f"{bl_object} has no attribute {prop.identifier}")
|
print(f"{bl_object} has no attribute {prop.identifier}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# print(prop.identifier)
|
|
||||||
|
|
||||||
value = getattr(bl_object, prop.identifier)
|
value = getattr(bl_object, prop.identifier)
|
||||||
|
|
||||||
# Not storing default value
|
# Not storing default value
|
||||||
|
@ -200,15 +204,10 @@ class Dumper:
|
||||||
value = PropCollection.dump(value)
|
value = PropCollection.dump(value)
|
||||||
|
|
||||||
elif prop.type == "POINTER" and value:
|
elif prop.type == "POINTER" and value:
|
||||||
# if prop.identifier == 'image':
|
|
||||||
# print(bl_object, cls.pointers)
|
|
||||||
if value.as_pointer() in cls.pointers:
|
if value.as_pointer() in cls.pointers:
|
||||||
value = value.as_pointer()
|
value = value.as_pointer()
|
||||||
else:
|
else:
|
||||||
# print('Register Pointer', value.as_pointer(), value)
|
|
||||||
cls.pointers[value.as_pointer()] = value
|
cls.pointers[value.as_pointer()] = value
|
||||||
# print(cls.pointers)
|
|
||||||
# print()
|
|
||||||
dumper = get_dumper(value)
|
dumper = get_dumper(value)
|
||||||
value = dumper.dump(value)
|
value = dumper.dump(value)
|
||||||
|
|
||||||
|
@ -278,17 +277,12 @@ class PropCollection(Dumper):
|
||||||
if not valid_pointers:
|
if not valid_pointers:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# print(param.identifier, cls.pointers[pointer_id])
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
item = coll.new(**params)
|
item = coll.new(**params)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
# print(e, coll.data)
|
|
||||||
# print()
|
|
||||||
try:
|
try:
|
||||||
item = coll[i]
|
item = coll[i]
|
||||||
except IndexError as e:
|
except IndexError as e:
|
||||||
# print(e, coll.data)
|
|
||||||
break
|
break
|
||||||
|
|
||||||
dumper = get_dumper(item)
|
dumper = get_dumper(item)
|
||||||
|
@ -515,8 +509,6 @@ class Nodes(PropCollection):
|
||||||
node = cls.pointers[node_data["bl_pointer"]]
|
node = cls.pointers[node_data["bl_pointer"]]
|
||||||
node.pair_with_output(cls.pointers[paired_output_id])
|
node.pair_with_output(cls.pointers[paired_output_id])
|
||||||
|
|
||||||
# print(node, node_data['outputs'])
|
|
||||||
|
|
||||||
Dumper.load(
|
Dumper.load(
|
||||||
{"inputs": node_data["inputs"], "outputs": node_data["outputs"]},
|
{"inputs": node_data["inputs"], "outputs": node_data["outputs"]},
|
||||||
node,
|
node,
|
||||||
|
@ -794,31 +786,19 @@ class ViewLayers(PropCollection):
|
||||||
Dumper.load(value, view_layer)
|
Dumper.load(value, view_layer)
|
||||||
|
|
||||||
|
|
||||||
dumpers = [
|
class DumperRegistry:
|
||||||
CompositorNodeRLayers,
|
"""Singleton-like class that holds a map of all parsers, constructed on first instantiation"""
|
||||||
CompositorNodeGlare,
|
dumper_map = None
|
||||||
Node,
|
|
||||||
NodeSocket,
|
def __init__(self):
|
||||||
NodeTree,
|
if self.dumper_map is None:
|
||||||
NodeLink,
|
self.construct_dumper_map()
|
||||||
NodeTreeInterface,
|
|
||||||
NodeTreeInterfaceSocket,
|
@classmethod
|
||||||
NodeGeometryRepeatOutputItems,
|
def construct_dumper_map(cls):
|
||||||
Image,
|
cls.dumper_map = {}
|
||||||
Material,
|
|
||||||
Object,
|
for subclass in utils.all_subclasses(Dumper):
|
||||||
Scene,
|
assert hasattr(subclass, "bl_type")
|
||||||
Collection,
|
cls.dumper_map[subclass.bl_type] = subclass
|
||||||
ViewLayer,
|
print(cls.dumper_map)
|
||||||
CurveMapPoints,
|
|
||||||
ColorRampElements,
|
|
||||||
NodeInputs,
|
|
||||||
NodeOutputs,
|
|
||||||
Nodes,
|
|
||||||
ViewLayers,
|
|
||||||
PropCollection,
|
|
||||||
AOVs,
|
|
||||||
PropArray,
|
|
||||||
CompositorNodeOutputFileLayerSlots,
|
|
||||||
CompositorNodeOutputFileFileSlots,
|
|
||||||
]
|
|
69
operators.py
69
operators.py
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
This module contains all addons operators
|
Node Kit Operators
|
||||||
|
|
||||||
:author: Autour de Minuit
|
:author: Autour de Minuit
|
||||||
:maintainers: Florentin LUCE
|
:maintainers: Florentin LUCE
|
||||||
|
@ -14,59 +14,71 @@ 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 node_kit.core.node_tree import NodeTree
|
from .core.dumper import dump_nodes, load_nodes, dump_nkit_format, parse_nkit_format
|
||||||
from .core.dumper import dump_nodes, load_nodes
|
|
||||||
from .core.node_utils import remap_node_group_duplicates
|
from .core.node_utils import remap_node_group_duplicates
|
||||||
from .core.pack_nodes import combine_objects, extract_objects
|
from .core.pack_nodes import combine_objects, extract_objects
|
||||||
|
|
||||||
|
|
||||||
class NODEKIT_OT_copy(Operator):
|
class NODEKIT_OT_copy(Operator):
|
||||||
bl_idname = "node_kit.copy_node_tree"
|
bl_idname = "node_kit.copy_nodes"
|
||||||
bl_label = "Copy nodes"
|
bl_label = "Copy Nodes"
|
||||||
bl_description = "Copy nodes to system clipboard"
|
bl_description = "Copy nodes to system clipboard"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
select_only: BoolProperty(default=True) # TODO: Expose/rework this property properly - No F3 panel in Node Editor - Only F9
|
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
ntree = context.space_data.edit_tree
|
ntree = context.space_data.edit_tree
|
||||||
if self.select_only:
|
selected_nodes = [node for node in ntree.nodes if node.select]
|
||||||
ntree_data = {
|
|
||||||
"nodes": dump_nodes(
|
ntree_data = {
|
||||||
[n for n in ntree.nodes if n.select]
|
"nodes": dump_nodes(selected_nodes),
|
||||||
), # [dump(n) for n in ntree.nodes if n.select],
|
"links": dump_nodes(
|
||||||
"links": dump_nodes(
|
[l for l in ntree.links if l.from_node.select and l.to_node.select]
|
||||||
[l for l in ntree.links if l.from_node.select and l.to_node.select]
|
),
|
||||||
),
|
}
|
||||||
}
|
|
||||||
else:
|
|
||||||
ntree_data = dump_nodes(ntree)
|
|
||||||
|
|
||||||
pprint(ntree_data)
|
pprint(ntree_data)
|
||||||
|
|
||||||
context.window_manager.clipboard = json.dumps(ntree_data)
|
context.window_manager.clipboard = dump_nkit_format(ntree_data)
|
||||||
|
|
||||||
self.report({"INFO"}, f"Copied 5 selected nodes to system clipboard")
|
self.report({"INFO"}, f"Copied {len(selected_nodes)} selected nodes to system clipboard")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class NODEKIT_OT_copy_tree(Operator):
|
||||||
|
bl_idname = "node_kit.copy_node_tree"
|
||||||
|
bl_label = "Copy Node Tree"
|
||||||
|
bl_description = "Copy node tree to system clipboard"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
ntree = context.space_data.edit_tree
|
||||||
|
ntree_data = dump_nodes(ntree)
|
||||||
|
|
||||||
|
pprint(ntree_data)
|
||||||
|
|
||||||
|
context.window_manager.clipboard = dump_nkit_format(ntree_data)
|
||||||
|
|
||||||
|
self.report({"INFO"}, f"Copied {len(ntree.nodes)} selected nodes to system clipboard")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class NODEKIT_OT_paste(Operator):
|
class NODEKIT_OT_paste(Operator):
|
||||||
bl_idname = "node_kit.paste_node_tree"
|
bl_idname = "node_kit.paste_nodes"
|
||||||
bl_label = "Paste nodes"
|
bl_label = "Paste Nodes"
|
||||||
bl_description = "Paste nodes from system clipboard"
|
bl_description = "Paste nodes from system clipboard"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
ntree_data = json.loads(context.window_manager.clipboard)
|
ntree_data = parse_nkit_format(context.window_manager.clipboard)
|
||||||
load_nodes(ntree_data, context.space_data.edit_tree)
|
load_nodes(ntree_data, context.space_data.edit_tree)
|
||||||
|
|
||||||
self.report({"INFO"}, f"5 node(s) pasted from system clipboard")
|
self.report({"INFO"}, f"X node(s) pasted from system clipboard") # TODO: Ge the number of parsed nodes returned
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
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 nodes"
|
bl_label = "Remap Node Groups Duplicates"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
selection: EnumProperty(
|
selection: EnumProperty(
|
||||||
|
@ -127,7 +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 node"
|
bl_label = "Update Nodes"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
selection: EnumProperty(
|
selection: EnumProperty(
|
||||||
|
@ -237,7 +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 = "Update node"
|
bl_label = "Pack Nodes"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
@ -247,7 +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 = "Update node"
|
bl_label = "Unpack Nodes"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
@ -257,6 +269,7 @@ class NODEKIT_OT_unpack_nodes(Operator):
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
NODEKIT_OT_copy,
|
NODEKIT_OT_copy,
|
||||||
|
NODEKIT_OT_copy_tree,
|
||||||
NODEKIT_OT_paste,
|
NODEKIT_OT_paste,
|
||||||
NODEKIT_OT_remap_node_group_duplicates,
|
NODEKIT_OT_remap_node_group_duplicates,
|
||||||
NODEKIT_OT_update_nodes,
|
NODEKIT_OT_update_nodes,
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import bpy
|
||||||
|
from bpy.types import AddonPreferences
|
||||||
|
from bpy.props import BoolProperty
|
||||||
|
|
||||||
|
|
||||||
|
class NodeKitPreferences(AddonPreferences):
|
||||||
|
bl_idname = __package__
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
NodeKitPreferences,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for c in classes:
|
||||||
|
bpy.utils.register_class(c)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for c in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(c)
|
||||||
|
|
34
ui.py
34
ui.py
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
This module contains blender UI elements
|
Node Kit UI elements and menus.
|
||||||
|
|
||||||
:author: Autour de Minuit
|
:author: Autour de Minuit
|
||||||
:maintainers: Florentin LUCE
|
:maintainers: Florentin LUCE
|
||||||
|
@ -15,22 +15,22 @@ class NODEKIT_MT_node_kit(bpy.types.Menu):
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
layout.operator("node_kit.copy_node_tree", text="Copy Nodes", icon="COPYDOWN")
|
layout.operator("node_kit.copy_nodes", icon="COPYDOWN")
|
||||||
layout.operator(
|
layout.operator("node_kit.paste_nodes", icon="PASTEDOWN")
|
||||||
"node_kit.paste_node_tree", text="Paste Nodes", icon="PASTEDOWN"
|
|
||||||
)
|
layout.separator()
|
||||||
|
|
||||||
|
layout.operator("node_kit.copy_node_tree", icon="NODETREE")
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.operator(
|
|
||||||
"node_kit.remap_node_group_duplicates",
|
layout.operator("node_kit.remap_node_group_duplicates",icon="NODE_INSERT_OFF")
|
||||||
text="Remap Node Groups Duplicates",
|
layout.operator("node_kit.update_nodes", icon="IMPORT")
|
||||||
icon="NODE_INSERT_OFF",
|
|
||||||
)
|
|
||||||
layout.operator("node_kit.update_nodes", text="Update Nodes", icon="IMPORT")
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.operator("node_kit.pack_nodes", text="Pack Nodes", icon="PACKAGE")
|
|
||||||
layout.operator(
|
layout.operator("node_kit.pack_nodes", icon="PACKAGE")
|
||||||
"node_kit.unpack_nodes", text="UnPack Nodes", icon="UGLYPACKAGE"
|
layout.operator("node_kit.unpack_nodes", icon="UGLYPACKAGE")
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
classes = (NODEKIT_MT_node_kit,)
|
classes = (NODEKIT_MT_node_kit,)
|
||||||
|
@ -48,7 +48,7 @@ def register():
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
|
bpy.types.NODE_MT_editor_menus.remove(draw_menu)
|
||||||
|
|
||||||
for c in reversed(classes):
|
for c in reversed(classes):
|
||||||
bpy.utils.unregister_class(c)
|
bpy.utils.unregister_class(c)
|
||||||
|
|
||||||
bpy.types.NODE_MT_editor_menus.remove(draw_menu)
|
|
||||||
|
|
Loading…
Reference in New Issue