Compare commits
No commits in common. "90aa72a767cf44d5f491e02b285acfda27a0ad82" and "94627debc6580513136b18bffd327ed41511a8d1" have entirely different histories.
90aa72a767
...
94627debc6
|
@ -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
|
|
@ -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)
|
|
@ -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…
Reference in New Issue