From eb05738d804f03eb51415f3d2c6fe140ef2b218a Mon Sep 17 00:00:00 2001 From: Durman Date: Sat, 22 May 2021 22:52:52 +0400 Subject: [PATCH 01/29] add module for handling JSON serialization in a new way - #4096 --- utils/sv_json_struct.py | 500 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 500 insertions(+) create mode 100644 utils/sv_json_struct.py diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py new file mode 100644 index 0000000000..9126948173 --- /dev/null +++ b/utils/sv_json_struct.py @@ -0,0 +1,500 @@ +# This file is part of project Sverchok. It's copyrighted by the contributors +# recorded in the version control history of the file, available from +# its original location https://github.com/nortikin/sverchok/commit/master +# +# SPDX-License-Identifier: GPL3 +# License-Filename: LICENSE + +from __future__ import annotations + +import inspect +import sys +from abc import abstractmethod, ABC +from enum import Enum, auto +from typing import Type, Generator, TYPE_CHECKING, Dict, Tuple, Optional, List, Any + +import bpy +from sverchok.utils.handle_blender_data import BPYPointers, BPYProperty +from sverchok.utils.sv_node_utils import recursive_framed_location_finder + +if TYPE_CHECKING: + from sverchok.utils.sv_json_import import FailsLog + + +class StructFactory: + # it's possible to use version factory in the code to make the code more specific + # if struct_factories.link.version == 0.2: + def __init__(self, factories: List[Type[Struct]]): + self._factory_names = { + StrTypes.TREE: 'tree', + StrTypes.NODE: 'node', + StrTypes.LINK: 'link', + StrTypes.PROP: 'prop' + } + + # optional for fast access of main structures + self.tree: Optional[Type[Struct]] = None + self.node: Optional[Type[Struct]] = None + self.link: Optional[Type[Struct]] = None + self.prop: Optional[Type[Struct]] = None + + for factory in factories: + if factory.type in self._factory_names: + factory_name = self._factory_names[factory.type] + setattr(self, factory_name, factory) + else: + raise TypeError(f'Factory with type: {factory.type}' + f' is not among supported: {self._factory_names.keys()}') + + def get_factory(self, struct_type: StrTypes) -> Type[Struct]: + if struct_type in self._factory_names: + factory_name = self._factory_names[struct_type] + return getattr(self, factory_name) + else: + raise TypeError(f'Given struct type: {struct_type} is not among supported {self._factory_names.keys()}') + + @classmethod + def __match_factories(cls, version: float) -> StructFactory: # todo for future automatization? + """Choose factories which are most appropriate for given version""" + struct_factories = cls([]) + module_classes = inspect.getmembers(sys.modules[__name__], + lambda member: inspect.isclass(member) and member.__module__ == __name__) + for module_class in module_classes: + pass + + +class StrTypes(Enum): + FILE = auto() + TREE = auto() + NODE = auto() + LINK = auto() + PROP = auto() + + def get_bpy_pointer(self) -> BPYPointers: + mapping = { + StrTypes.TREE: BPYPointers.NODE_TREE + } + if self not in mapping: + raise TypeError(f'Given StrType: {self} is not a data block') + return mapping[self] + + @classmethod + def get_type(cls, block_type: BPYPointers) -> StrTypes: + mapping = { + BPYPointers.NODE_TREE: StrTypes.TREE + } + if block_type not in mapping: + raise TypeError(f'Given block type: {block_type} is not among supported: {mapping.keys()}') + return mapping[block_type] + + +class Struct(ABC): + # I was trying to make API of all abstract method the same between child classes but it looks it's not possible + # for example to build a node we can send to method a tree where the node should be duilded + version = 0.2 # override the property if necessary + type: StrTypes = None # should be overridden + + @abstractmethod + def __init__(self, name: Optional[str], logger: FailsLog, struct: dict = None): + ... + + @abstractmethod + def export(self, data_block, struct_factories: StructFactory, dependencies: List[Tuple[BPYPointers, str]]) -> dict: + ... + + @abstractmethod + def build(self, *args): + ... + + @abstractmethod + def read(self): + ... + + def read_bl_type(self) -> str: + """typically should return bl_idname of the structure""" + return None + + def read_collection(self, collection: dict): + for name, structure in collection.items(): + with self.logger.add_fail("Reading collection"): + yield name, structure + + +class FileStruct(Struct): + type = StrTypes.FILE + + def __init__(self, name=None, logger: FailsLog = None, struct: dict = None): + self._struct: Dict[str, Any] = struct or {"export_version": str(self.version)} + self.logger: FailsLog = logger + + def export(self, tree): + struct_factories = StructFactory([TreeStruct, NodeStruct, LinkStruct, PropertyStruct]) # todo to args? + dependencies: List[Tuple[BPYPointers, str]] = [(BPYPointers.NODE_TREE, tree.name)] + # it looks good place for exporting dependent data blocks because probably we do not always want to export them + # from this place we have more control over it + while dependencies: + block_type, block_name = dependencies.pop() + struct_type = StrTypes.get_type(block_type) + if struct_type.name not in self._struct: + self._struct[struct_type.name] = dict() + if block_name not in self._struct[struct_type.name]: + factory = struct_factories.get_factory(struct_type) + data_block = block_type.collection[block_name] + structure = factory(block_name, self.logger).export(data_block, struct_factories, dependencies) + self._struct[struct_type.name][block_name] = structure + + return self._struct + + # todo export selected nodes here? + + def build(self, *_): + raise NotImplementedError + + def build_into_tree(self, tree): + # it looks that recursive import should be avoided by any cost, it's too difficult to pass data + # luckily it's possible to create all data block first + # and then they will be available for assigning to pointer properties + # with tree data blocks it can be a beat trickier, + # all trees should be created and only after that field with content + + factories = StructFactory([TreeStruct, NodeStruct, LinkStruct, PropertyStruct]) + imported_structs = OldNewNames() + trees_to_build = [] + version, data_blocks = self.read() + for struct_type, block_name, raw_struct in data_blocks: + if struct_type == StrTypes.TREE: + tree_struct = factories.tree(block_name, self.logger, raw_struct) + if not trees_to_build: + # this is first tree and it should be main, does not need create anything + data_block = tree + else: + # this tree should be created + data_block = bpy.data.node_groups.new(block_name, tree_struct.read_bl_type()) + imported_structs[(struct_type, block_name)] = data_block + trees_to_build.append(tree_struct) + else: + # all data block except node trees + block_type = struct_type.get_bpy_pointer() + data_block = block_type.collection.new(block_name) + imported_structs[(struct_type, block_name)] = data_block + factories.get_factory(struct_type)(block_name, self.logger, raw_struct).build() + + # todo before building trees should be registered old and dummy nodes if necessary + + for tree_struct in trees_to_build: + # build all trees + new_name = imported_structs[StrTypes.TREE, tree_struct.name] + data_block = bpy.data.node_groups[new_name] + tree_struct.build(data_block, factories, imported_structs) # todo add throttling tree + + def read(self): + with self.logger.add_fail("Reading version of the file"): + version = float(self._struct["export_version"]) + + return version, self._data_blocks_reader() + + def _data_blocks_reader(self): # todo add logger? + struct_type: StrTypes + for struct_type, structures in self._struct.items(): + if struct_type in (it.name for it in StrTypes): + for block_name, block_struct in structures.items(): + yield struct_type, block_name, block_struct + + +class TreeStruct(Struct): + type = StrTypes.TREE + + def __init__(self, name: str, logger: FailsLog, structure: dict = None): + default_structure = { + "nodes": dict(), + "links": [], + "bl_idname": "", + } + self._struct = structure or default_structure + self._name = name + self.logger = logger + + def export(self, tree, factories: StructFactory, dependencies) -> dict: + for node in tree.nodes: + raw_struct = factories.node(node.name, self.logger).export(node, factories, dependencies) + self._struct['nodes'][node.name] = raw_struct + + for link in _ordered_links(tree): + self._struct["links"].append(factories.link(None, self.logger).export(link, factories, dependencies)) + + self._struct["bl_idname"] = tree.bl_idname + return self._struct + + def export_nodes(self, nodes, factories: StructFactory, dependencies) -> dict: # todo check whether all nodes from the same tree? + tree = nodes[0].bl_idname + for node in nodes: + raw_struct = factories.node(node.name, self.logger).export(node, factories, dependencies) + self._struct["nodes"][node.name] = raw_struct + + input_node_names = {node.name for node in nodes} + for link in _ordered_links(tree): + if link.from_node.name in input_node_names and link.to_node.name in input_node_names: + self._struct["links"].append(factories.link(None, self.logger).export(link, None, None)) + + self._struct["bl_idname"] = tree.bl_idname + return self._struct + + def build(self, tree, factories: StructFactory, imported_structs: OldNewNames): + """Reads and generates nodes, links, dependent data blocks""" + nodes, links = self.read() + + # first all nodes should be created without applying their inner data + # because some nodes can have `parent` property which points into another node + node_structs = [] + for node_name, raw_structure in nodes: # todo probably here is convenient place for registering node classes + node_struct = factories.node(node_name, self.logger, raw_structure) + node = tree.nodes.new(node_struct.read_bl_type()) + node.name = node_name + imported_structs[(node_struct.type, node_name)] = node + node_structs.append(node_struct) + + for node_struct in node_structs: + new_name = imported_structs[(node_struct.type, node_struct.name)] + node = tree.nodes[new_name] + node_struct.build(node, factories, imported_structs) + + for raw_struct in links: + factories.link(None, self.logger, raw_struct).build(tree, imported_structs) + + def read(self): + with self.logger.add_fail("Reading nodes"): + nodes_struct = self._struct["nodes"] + nodes_reader = self.read_collection(nodes_struct) + + with self.logger.add_fail("Reading links"): + links_struct = self._struct["links"] + links_reader = (link_struct for link_struct in links_struct) + + return nodes_reader, links_reader + + def read_bl_type(self): + return self._struct["bl_idname"] + + +class NodeStruct(Struct): + type = StrTypes.NODE + + def __init__(self, name: str, logger: FailsLog, structure: dict = None): + default_structure = { + "attributes": dict(), + "properties": dict(), + "inputs": dict(), + "outputs": dict(), + "bl_idname": "" + } + self._struct = structure or default_structure + self.name = name + self.logger = logger + self._attributes = { + "height": None, + "width": None, + "label": '', + "hide": False, + "location": (0, 0), + "color": (0, 0, 0), + "use_custom_color": False, + "parent": None + } + + def export(self, node, factories: StructFactory, dependencies) -> dict: + # add_mandatory_attributes + self._struct['bl_idname'] = node.bl_idname + self._struct["attributes"]['height'] = node.height + self._struct["attributes"]['width'] = node.width + self._struct["attributes"]['label'] = node.label + self._struct["attributes"]['hide'] = node.hide + self._struct["attributes"]['location'] = recursive_framed_location_finder(node, node.location[:]) + + # optional attributes + if node.use_custom_color: + self._struct["attributes"]['color'] = node.color[:] + self._struct["attributes"]['use_custom_color'] = True + if node.parent: # the node is inside of a frame node + self._struct["attributes"]["parent"] = node.parent.name + + # add non default node properties + for prop_name in node.keys(): + prop = BPYProperty(node, prop_name) + if prop.is_valid and prop.is_to_save: + raw_struct = factories.prop(prop.name, self.logger).export(prop, factories, dependencies) + self._struct["properties"][prop.name] = raw_struct + + # todo add sockets + + if hasattr(node, 'save_to_json'): + node.save_to_json(self._struct) + + return self._struct + + def build(self, node, factories: StructFactory, imported_data: OldNewNames): + + attributes, properties = self.read() + for attr_name, attr_value in attributes: + with self.logger.add_fail("Setting node attribute", + f'Tree: {node.id_data.name}, Node: {node.name}, attr: {attr_name}'): + factories.prop(attr_name, self.logger, attr_value).build(node, factories, imported_data) + + for prop_name, prop_value in properties: + with self.logger.add_fail("Setting node property", + f'Tree: {node.id_data.name}, Node: {node.name}, prop: {prop_name}'): + factories.prop(prop_name, self.logger, prop_value) + + # this block is before applying socket properties because some nodes can generate them in load method + if hasattr(node, 'load_from_json'): + with self.logger.add_fail("Setting advance node properties", + f'Tree: {node.id_data.name}, Node: {node.name}'): + node.load_from_json(self._struct, self.version) + + # todo can we generate sockets now? + + def read(self): + """Reads node attributes from node structure, returns (attr_name, value)""" + with self.logger.add_fail("Reading node attributes"): + attrs_struct = self._struct["attributes"] + attributes = self.read_collection(attrs_struct) + + with self.logger.add_fail("Reading node properties"): + props_struct = self._struct["properties"] + properties = self.read_collection(props_struct) + + # todo read sockets + + return attributes, properties + + def read_bl_type(self): + return self._struct['bl_idname'] + + +class LinkStruct(Struct): + type = StrTypes.LINK + + def __init__(self, name=None, logger: FailsLog = None, structure: dict = None): + default_struct = { + "from_node": "", + "from_socket": "", # identifier + "to_node": "", + "to_socket": ""} # identifier + self._struct = structure or default_struct + self.logger = logger + + def export(self, link, *_): + self._struct["from_node"] = link.from_node.name + self._struct["from_socket"] = link.from_socket.identifier + self._struct["to_node"] = link.to_node.name + self._struct["to_socket"] = link.to_socket.identifier + return self._struct + + def build(self, tree, imported_structs: OldNewNames): + from_node_name, from_sock_identifier, to_node_name, to_sock_identifier = self.read() + from_node_new_name = imported_structs[(self.type, from_node_name)] + from_socket = self._search_socket(tree, from_node_new_name, from_sock_identifier, "OUTPUT") + to_node_new_name = imported_structs[(self.type, to_node_name)] + to_socket = self._search_socket(tree, to_node_new_name, to_sock_identifier, "INPUT") + if from_socket and to_socket: + tree.links.new(to_socket, from_socket) + + def read(self): + with self.logger.add_fail("Read socket data"): + return self._struct["from_node"], self._struct["from_socket"], \ + self._struct["to_node"], self._struct["to_socket"] + + def _search_socket(self, tree, node_name: str, socket_identifier: str, sock_type): + with self.logger.add_fail(f"Building link, trying to find node: {node_name}"): + node = tree.nodes[node_name] + with self.logger.add_fail(f"Building link, trying to find socket {socket_identifier}"): + for sock in node.inputs if sock_type == "INPUT" else node.outputs: + if sock.identifier == socket_identifier: + return sock + raise LookupError + + +class PropertyStruct(Struct): + type = StrTypes.PROP + + def __init__(self, name: str, logger: FailsLog, structure: dict = None): + default_struct = { + "type": "", + "value": "" + } + self._struct = structure or default_struct + self.name = name + self.logger = logger + + def export(self, prop: BPYProperty, _, dependencies): + """It can return just value like float, bool etc or a structure""" + if prop.is_valid and prop.is_to_save: + if prop.type == 'COLLECTION': + return self._handle_collection_prop(prop, dependencies) + if prop.type == 'POINTER' and prop.value is not None: # skip empty pointers + self._struct["type"] = prop.pointer_type.name + self._struct["value"] = prop.value + dependencies.append((prop.pointer_type, prop.value)) + return self._struct + else: + return prop.value + + def build(self, obj, factories: StructFactory, imported_structs: OldNewNames): + with self.logger.add_fail("Assigning value"): + prop = BPYProperty(obj, self.name) + + # this is structure (pointer property) + if isinstance(self._struct, dict): + struct_type, old_obj_name = self.read() + new_name = imported_structs[(struct_type, old_obj_name)] + data_block_type = struct_type.get_bpy_pointer() + data_block = data_block_type.collection[new_name] + setattr(obj, self.name, data_block) + + # this is property + elif prop.is_valid: + prop.value = self._struct + + # this is attribute + else: + setattr(obj, self.name, self._struct) + + def read(self) -> Tuple[StrTypes, str]: + with self.logger.add_fail("Reading property (value)"): + struct_type = StrTypes[self._struct["type"]] + old_obj_name = self._struct["name"] + return struct_type, old_obj_name + + def _handle_collection_prop(self, col_prop, dependencies): + collection = [] + for item in col_prop.collection_to_list(): + item_props = dict() + for prop in item: + item_props[prop.name] = PropertyStruct(prop.name, self.logger).export(prop, None, dependencies) + collection.append(item_props) + return collection + + +class OldNewNames: + """This class should solve problem of old new names, when created object with one name get another one""" + Old, New = str, str + + def __init__(self): + self._old_new_names: Dict[Tuple[StrTypes, OldNewNames.Old], OldNewNames.New] = dict() + + def __contains__(self, type_old_name: Tuple[StrTypes, OldNewNames.Old]): + return type_old_name in self._old_new_names + + def __getitem__(self, type_old_name: Tuple[StrTypes, OldNewNames.Old]): + return self._old_new_names[type_old_name] + + def __setitem__(self, type_old_name: Tuple[StrTypes, OldNewNames.Old], data_block): + new_name = data_block.name + self._old_new_names[type_old_name] = new_name + + +def _ordered_links(tree) -> Generator[bpy.types.NodeLink]: + """Returns all links in whole tree where links always are going in order from top input socket to bottom""" + for node in tree.nodes: + for input_socket in node.inputs: + for link in input_socket.links: + yield link From 76eada0edf744e7d2d8f53f0eee89afe429bba81 Mon Sep 17 00:00:00 2001 From: Durman Date: Sun, 23 May 2021 17:05:12 +0400 Subject: [PATCH 02/29] bug fixing --- utils/__init__.py | 2 +- utils/sv_json_struct.py | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/utils/__init__.py b/utils/__init__.py index 40eaf79393..87d2f515fe 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -130,7 +130,7 @@ def app_handler_ops(append=None, remove=None): "avl_tree", "sv_nodeview_draw_helper", "sv_font_xml_parser", "exception_drawing_with_bgl", "wfc_algorithm", "handling_nodes", "handle_blender_data", "nodes_mixins.generating_objects", "nodes_mixins.show_3d_properties", "modules_inspection", "sv_json_export", "sv_json_import", - "meshes", "tree_walk", "mesh_functions", 'mesh.inset_faces', 'mesh.extrude_edges', + "meshes", "tree_walk", "mesh_functions", 'mesh.inset_faces', 'mesh.extrude_edges', "sv_json_struct", # UI text editor ui "text_editor_submenu", "text_editor_plugins", # UI operators and tools diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 9126948173..80c03e0a70 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -195,10 +195,10 @@ def read(self): def _data_blocks_reader(self): # todo add logger? struct_type: StrTypes - for struct_type, structures in self._struct.items(): - if struct_type in (it.name for it in StrTypes): + for struct_type_name, structures in self._struct.items(): + if struct_type_name in (it.name for it in StrTypes): for block_name, block_struct in structures.items(): - yield struct_type, block_name, block_struct + yield StrTypes[struct_type_name], block_name, block_struct class TreeStruct(Struct): @@ -211,7 +211,7 @@ def __init__(self, name: str, logger: FailsLog, structure: dict = None): "bl_idname": "", } self._struct = structure or default_structure - self._name = name + self.name = name self.logger = logger def export(self, tree, factories: StructFactory, dependencies) -> dict: @@ -259,7 +259,7 @@ def build(self, tree, factories: StructFactory, imported_structs: OldNewNames): node_struct.build(node, factories, imported_structs) for raw_struct in links: - factories.link(None, self.logger, raw_struct).build(tree, imported_structs) + factories.link(None, self.logger, raw_struct).build(tree, factories, imported_structs) def read(self): with self.logger.add_fail("Reading nodes"): @@ -342,7 +342,7 @@ def build(self, node, factories: StructFactory, imported_data: OldNewNames): for prop_name, prop_value in properties: with self.logger.add_fail("Setting node property", f'Tree: {node.id_data.name}, Node: {node.name}, prop: {prop_name}'): - factories.prop(prop_name, self.logger, prop_value) + factories.prop(prop_name, self.logger, prop_value).build(node, factories, imported_data) # this block is before applying socket properties because some nodes can generate them in load method if hasattr(node, 'load_from_json'): @@ -389,11 +389,11 @@ def export(self, link, *_): self._struct["to_socket"] = link.to_socket.identifier return self._struct - def build(self, tree, imported_structs: OldNewNames): + def build(self, tree, factories: StructFactory, imported_structs: OldNewNames): from_node_name, from_sock_identifier, to_node_name, to_sock_identifier = self.read() - from_node_new_name = imported_structs[(self.type, from_node_name)] + from_node_new_name = imported_structs[(factories.node.type, from_node_name)] from_socket = self._search_socket(tree, from_node_new_name, from_sock_identifier, "OUTPUT") - to_node_new_name = imported_structs[(self.type, to_node_name)] + to_node_new_name = imported_structs[(factories.node.type, to_node_name)] to_socket = self._search_socket(tree, to_node_new_name, to_sock_identifier, "INPUT") if from_socket and to_socket: tree.links.new(to_socket, from_socket) @@ -421,7 +421,7 @@ def __init__(self, name: str, logger: FailsLog, structure: dict = None): "type": "", "value": "" } - self._struct = structure or default_struct + self._struct = structure if structure is not None else default_struct self.name = name self.logger = logger @@ -459,7 +459,7 @@ def build(self, obj, factories: StructFactory, imported_structs: OldNewNames): setattr(obj, self.name, self._struct) def read(self) -> Tuple[StrTypes, str]: - with self.logger.add_fail("Reading property (value)"): + with self.logger.add_fail(f"Reading property (value): {self.name}"): struct_type = StrTypes[self._struct["type"]] old_obj_name = self._struct["name"] return struct_type, old_obj_name @@ -474,7 +474,7 @@ def _handle_collection_prop(self, col_prop, dependencies): return collection -class OldNewNames: +class OldNewNames: # todo can't this be regular dictionary? """This class should solve problem of old new names, when created object with one name get another one""" Old, New = str, str From d6e514c2421bddc14538ec403513b234a31e8c47 Mon Sep 17 00:00:00 2001 From: durman Date: Mon, 24 May 2021 15:24:00 +0400 Subject: [PATCH 03/29] add socket struct - can be example of how new structures can be added but particular in this case I did not think about back capability because JSON files of this format was not created yet --- utils/sv_json_struct.py | 109 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 98 insertions(+), 11 deletions(-) diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 80c03e0a70..4e6ec0761e 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -28,6 +28,7 @@ def __init__(self, factories: List[Type[Struct]]): self._factory_names = { StrTypes.TREE: 'tree', StrTypes.NODE: 'node', + StrTypes.SOCK: 'sock', StrTypes.LINK: 'link', StrTypes.PROP: 'prop' } @@ -35,6 +36,7 @@ def __init__(self, factories: List[Type[Struct]]): # optional for fast access of main structures self.tree: Optional[Type[Struct]] = None self.node: Optional[Type[Struct]] = None + self.sock: Optional[Type[Struct]] = None self.link: Optional[Type[Struct]] = None self.prop: Optional[Type[Struct]] = None @@ -67,6 +69,7 @@ class StrTypes(Enum): FILE = auto() TREE = auto() NODE = auto() + SOCK = auto() LINK = auto() PROP = auto() @@ -128,7 +131,7 @@ def __init__(self, name=None, logger: FailsLog = None, struct: dict = None): self.logger: FailsLog = logger def export(self, tree): - struct_factories = StructFactory([TreeStruct, NodeStruct, LinkStruct, PropertyStruct]) # todo to args? + struct_factories = StructFactory([TreeStruct, NodeStruct, SocketStruct, LinkStruct, PropertyStruct]) # todo to args? dependencies: List[Tuple[BPYPointers, str]] = [(BPYPointers.NODE_TREE, tree.name)] # it looks good place for exporting dependent data blocks because probably we do not always want to export them # from this place we have more control over it @@ -150,14 +153,14 @@ def export(self, tree): def build(self, *_): raise NotImplementedError - def build_into_tree(self, tree): + def build_into_tree(self, tree): # todo add protection from exporting inside node groups # it looks that recursive import should be avoided by any cost, it's too difficult to pass data # luckily it's possible to create all data block first # and then they will be available for assigning to pointer properties # with tree data blocks it can be a beat trickier, # all trees should be created and only after that field with content - factories = StructFactory([TreeStruct, NodeStruct, LinkStruct, PropertyStruct]) + factories = StructFactory([TreeStruct, NodeStruct, SocketStruct, LinkStruct, PropertyStruct]) imported_structs = OldNewNames() trees_to_build = [] version, data_blocks = self.read() @@ -324,7 +327,15 @@ def export(self, node, factories: StructFactory, dependencies) -> dict: raw_struct = factories.prop(prop.name, self.logger).export(prop, factories, dependencies) self._struct["properties"][prop.name] = raw_struct - # todo add sockets + # all sockets should be kept in a file because it's possible to create UI + # where sockets would be defined by pressing buttons for example like in the node group interface + for socket in node.inputs: + raw_struct = factories.sock(socket.identifier, self.logger).export(socket, factories, dependencies) + self._struct["inputs"][socket.identifier] = raw_struct + + for socket in node.outputs: + raw_struct = factories.sock(socket.identifier, self.logger).export(socket, factories, dependencies) + self._struct["outputs"][socket.identifier] = raw_struct if hasattr(node, 'save_to_json'): node.save_to_json(self._struct) @@ -332,8 +343,8 @@ def export(self, node, factories: StructFactory, dependencies) -> dict: return self._struct def build(self, node, factories: StructFactory, imported_data: OldNewNames): - - attributes, properties = self.read() + # todo it would be nice? to add context to logger of what is currently is being created (not only here) + attributes, properties, inputs, outputs = self.read() for attr_name, attr_value in attributes: with self.logger.add_fail("Setting node attribute", f'Tree: {node.id_data.name}, Node: {node.name}, attr: {attr_name}'): @@ -344,14 +355,22 @@ def build(self, node, factories: StructFactory, imported_data: OldNewNames): f'Tree: {node.id_data.name}, Node: {node.name}, prop: {prop_name}'): factories.prop(prop_name, self.logger, prop_value).build(node, factories, imported_data) - # this block is before applying socket properties because some nodes can generate them in load method + # does not trust to correctness of socket collections created by an init method + node.inputs.clear() + for sock_name, raw_struct in inputs: + with self.logger.add_fail(f"Add socket: {sock_name} to node {node.name}"): + factories.sock(sock_name, self.logger, raw_struct).build(node.inputs, factories, imported_data) + + node.outputs.clear() + for sock_name, raw_struct in outputs: + with self.logger.add_fail(f"Add socket: {sock_name} ot node {node.name}"): + factories.sock(sock_name, self.logger, raw_struct).build(node.outputs, factories, imported_data) + if hasattr(node, 'load_from_json'): with self.logger.add_fail("Setting advance node properties", f'Tree: {node.id_data.name}, Node: {node.name}'): node.load_from_json(self._struct, self.version) - # todo can we generate sockets now? - def read(self): """Reads node attributes from node structure, returns (attr_name, value)""" with self.logger.add_fail("Reading node attributes"): @@ -362,14 +381,82 @@ def read(self): props_struct = self._struct["properties"] properties = self.read_collection(props_struct) - # todo read sockets + with self.logger.add_fail("Reading input sockets"): + input_struct = self._struct["inputs"] + inputs = self.read_collection(input_struct) - return attributes, properties + with self.logger.add_fail("Reading output sockets"): + output_struct = self._struct["outputs"] + outputs = self.read_collection(output_struct) + + return attributes, properties, inputs, outputs def read_bl_type(self): return self._struct['bl_idname'] +class SocketStruct(Struct): + type = StrTypes.SOCK + + def __init__(self, name, logger: FailsLog, structure: dict = None): + default_struct = { + "bl_idname": "", + "identifier": "", + "properties": dict(), + "attributes": dict() + } + self.name = name + self.logger = logger + self._struct = structure or default_struct + + def export(self, socket, factories, dependencies): + self._struct['bl_idname'] = socket.bl_idname + self._struct['identifier'] = socket.identifier + self._struct['attributes']['hide'] = socket.hide + + for prop_name in socket.keys(): + prop = BPYProperty(socket, prop_name) + if prop.is_valid and prop.is_to_save: + raw_struct = factories.prop(prop.name, self.logger).export(prop, factories, dependencies) + self._struct["properties"][prop.name] = raw_struct + + return self._struct + + def build(self, sockets, factories, imported_structs): + attributes, identifier, properties = self.read() + + # create the socket in the method because identifier is hidden is shown only inside the class + socket = sockets.new(self.read_bl_type(), self.name, identifier=identifier) + + for attr_name, attr_value in attributes: + with self.logger.add_fail( + "Setting socket attribute", + f'Tree: {socket.id_data.name}, Node: {socket.node.name}, socket: {socket.name}, attr: {attr_name}'): + factories.prop(attr_name, self.logger, attr_value).build(socket, factories, imported_structs) + + for prop_name, prop_value in properties: + with self.logger.add_fail( + "Setting socket property", + f'Tree: {socket.id_data.name}, Node: {socket.node.name}, socket: {socket.name}, prop: {prop_name}'): + factories.prop(prop_name, self.logger, prop_value).build(socket, factories, imported_structs) + + def read(self): + with self.logger.add_fail("Reading socket attributes"): + attrs_struct = self._struct["attributes"] + attributes = self.read_collection(attrs_struct) + identifier = self._struct['identifier'] + + with self.logger.add_fail("Reading socket properties"): + props_struct = self._struct["properties"] + properties = self.read_collection(props_struct) + + return attributes, identifier, properties + + def read_bl_type(self) -> str: + with self.logger.add_fail("Reading socket bl_idname"): + return self._struct['bl_idname'] + + class LinkStruct(Struct): type = StrTypes.LINK From 62c4fe17401e9a42b9db3e948a013308315c1d4a Mon Sep 17 00:00:00 2001 From: durman Date: Tue, 25 May 2021 08:55:02 +0400 Subject: [PATCH 04/29] add properly supporting of frame nodes and extra little fixes --- utils/handle_blender_data.py | 4 +- utils/sv_json_struct.py | 102 ++++++++++++++++++++--------------- 2 files changed, 61 insertions(+), 45 deletions(-) diff --git a/utils/handle_blender_data.py b/utils/handle_blender_data.py index d345c9cee3..29925a4e1f 100644 --- a/utils/handle_blender_data.py +++ b/utils/handle_blender_data.py @@ -290,6 +290,7 @@ class BPYPointers(Enum): OBJECT = bpy.types.Object MESH = bpy.types.Mesh NODE_TREE = bpy.types.NodeTree + NODE = bpy.types.Node # there is pointers to nodes in Blender like node.parent property MATERIAL = bpy.types.Material COLLECTION = bpy.types.Collection TEXT = bpy.types.Text @@ -306,6 +307,7 @@ def collection(self): BPYPointers.OBJECT: bpy.data.objects, BPYPointers.MESH: bpy.data.meshes, BPYPointers.NODE_TREE: bpy.data.node_groups, + BPYPointers.NODE: None, BPYPointers.MATERIAL: bpy.data.materials, BPYPointers.COLLECTION: bpy.data.collections, BPYPointers.TEXT: bpy.data.texts, @@ -326,7 +328,7 @@ def type(self): def get_type(cls, bl_rna) -> Union[BPYPointers, None]: """Return Python pointer corresponding to given Blender pointer class (bpy.types.Mesh.bl_rna)""" for pointer in BPYPointers: - if pointer.type.bl_rna == bl_rna: + if pointer.type.bl_rna == bl_rna or pointer.type.bl_rna == bl_rna.base: return pointer raise TypeError(f'Type: "{bl_rna}" was not found in: {[t.type.bl_rna for t in BPYPointers]}') diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 4e6ec0761e..b0938b0f59 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -14,6 +14,7 @@ from typing import Type, Generator, TYPE_CHECKING, Dict, Tuple, Optional, List, Any import bpy +from sverchok.core.update_system import build_update_list, process_tree from sverchok.utils.handle_blender_data import BPYPointers, BPYProperty from sverchok.utils.sv_node_utils import recursive_framed_location_finder @@ -75,7 +76,8 @@ class StrTypes(Enum): def get_bpy_pointer(self) -> BPYPointers: mapping = { - StrTypes.TREE: BPYPointers.NODE_TREE + StrTypes.TREE: BPYPointers.NODE_TREE, + StrTypes.NODE: BPYPointers.NODE, } if self not in mapping: raise TypeError(f'Given StrType: {self} is not a data block') @@ -84,7 +86,8 @@ def get_bpy_pointer(self) -> BPYPointers: @classmethod def get_type(cls, block_type: BPYPointers) -> StrTypes: mapping = { - BPYPointers.NODE_TREE: StrTypes.TREE + BPYPointers.NODE_TREE: StrTypes.TREE, + BPYPointers.NODE: StrTypes.NODE, } if block_type not in mapping: raise TypeError(f'Given block type: {block_type} is not among supported: {mapping.keys()}') @@ -160,35 +163,38 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no # with tree data blocks it can be a beat trickier, # all trees should be created and only after that field with content - factories = StructFactory([TreeStruct, NodeStruct, SocketStruct, LinkStruct, PropertyStruct]) - imported_structs = OldNewNames() - trees_to_build = [] - version, data_blocks = self.read() - for struct_type, block_name, raw_struct in data_blocks: - if struct_type == StrTypes.TREE: - tree_struct = factories.tree(block_name, self.logger, raw_struct) - if not trees_to_build: - # this is first tree and it should be main, does not need create anything - data_block = tree + with tree.throttle_update(): # todo it is required only for current update system can be deleted later + factories = StructFactory([TreeStruct, NodeStruct, SocketStruct, LinkStruct, PropertyStruct]) + imported_structs = OldNewNames() + trees_to_build = [] + version, data_blocks = self.read() + for struct_type, block_name, raw_struct in data_blocks: + if struct_type == StrTypes.TREE: + tree_struct = factories.tree(block_name, self.logger, raw_struct) + if not trees_to_build: + # this is first tree and it should be main, does not need create anything + data_block = tree + else: + # this tree should be created + data_block = bpy.data.node_groups.new(block_name, tree_struct.read_bl_type()) + imported_structs[(struct_type, block_name)] = data_block + trees_to_build.append(tree_struct) else: - # this tree should be created - data_block = bpy.data.node_groups.new(block_name, tree_struct.read_bl_type()) - imported_structs[(struct_type, block_name)] = data_block - trees_to_build.append(tree_struct) - else: - # all data block except node trees - block_type = struct_type.get_bpy_pointer() - data_block = block_type.collection.new(block_name) - imported_structs[(struct_type, block_name)] = data_block - factories.get_factory(struct_type)(block_name, self.logger, raw_struct).build() - - # todo before building trees should be registered old and dummy nodes if necessary - - for tree_struct in trees_to_build: - # build all trees - new_name = imported_structs[StrTypes.TREE, tree_struct.name] - data_block = bpy.data.node_groups[new_name] - tree_struct.build(data_block, factories, imported_structs) # todo add throttling tree + # all data block except node trees + block_type = struct_type.get_bpy_pointer() + data_block = block_type.collection.new(block_name) + imported_structs[(struct_type, block_name)] = data_block + factories.get_factory(struct_type)(block_name, self.logger, raw_struct).build() + + # todo before building trees should be registered old and dummy nodes if necessary + + for tree_struct in trees_to_build: + # build all trees + new_name = imported_structs[StrTypes.TREE, tree_struct.name] + data_block = bpy.data.node_groups[new_name] + tree_struct.build(data_block, factories, imported_structs) # todo add throttling tree + build_update_list(tree) + process_tree(tree) def read(self): with self.logger.add_fail("Reading version of the file"): @@ -318,14 +324,17 @@ def export(self, node, factories: StructFactory, dependencies) -> dict: self._struct["attributes"]['color'] = node.color[:] self._struct["attributes"]['use_custom_color'] = True if node.parent: # the node is inside of a frame node - self._struct["attributes"]["parent"] = node.parent.name + prop = BPYProperty(node, "parent") + raw_struct = factories.prop("parent", self.logger).export(prop, factories, dependencies) + self._struct["attributes"]["parent"] = raw_struct # add non default node properties for prop_name in node.keys(): prop = BPYProperty(node, prop_name) if prop.is_valid and prop.is_to_save: raw_struct = factories.prop(prop.name, self.logger).export(prop, factories, dependencies) - self._struct["properties"][prop.name] = raw_struct + if raw_struct is not None: # todo check every where + self._struct["properties"][prop.name] = raw_struct # all sockets should be kept in a file because it's possible to create UI # where sockets would be defined by pressing buttons for example like in the node group interface @@ -430,8 +439,8 @@ def build(self, sockets, factories, imported_structs): for attr_name, attr_value in attributes: with self.logger.add_fail( - "Setting socket attribute", - f'Tree: {socket.id_data.name}, Node: {socket.node.name}, socket: {socket.name}, attr: {attr_name}'): + "Setting socket attribute", # socket.node can be None sometimes 0_o + f'Tree: {socket.id_data.name}, socket: {socket.name}, attr: {attr_name}'): factories.prop(attr_name, self.logger, attr_value).build(socket, factories, imported_structs) for prop_name, prop_value in properties: @@ -520,21 +529,26 @@ def export(self, prop: BPYProperty, _, dependencies): if prop.type == 'POINTER' and prop.value is not None: # skip empty pointers self._struct["type"] = prop.pointer_type.name self._struct["value"] = prop.value - dependencies.append((prop.pointer_type, prop.value)) + if prop.data_collection is not None: # skipping nodes + dependencies.append((prop.pointer_type, prop.value)) return self._struct else: return prop.value - + def build(self, obj, factories: StructFactory, imported_structs: OldNewNames): with self.logger.add_fail("Assigning value"): prop = BPYProperty(obj, self.name) # this is structure (pointer property) if isinstance(self._struct, dict): - struct_type, old_obj_name = self.read() - new_name = imported_structs[(struct_type, old_obj_name)] - data_block_type = struct_type.get_bpy_pointer() - data_block = data_block_type.collection[new_name] + pointer_type, old_obj_name = self.read() + new_name = imported_structs[(StrTypes.get_type(pointer_type), old_obj_name)] + if pointer_type == BPYPointers.NODE: + # this should work in case obj is a node or socket + # but in other cases probably extra information should be kept in the property structure + data_block = obj.id_data.nodes[new_name] + else: + data_block = pointer_type.collection[new_name] setattr(obj, self.name, data_block) # this is property @@ -545,11 +559,11 @@ def build(self, obj, factories: StructFactory, imported_structs: OldNewNames): else: setattr(obj, self.name, self._struct) - def read(self) -> Tuple[StrTypes, str]: + def read(self) -> Tuple[BPYPointers, str]: with self.logger.add_fail(f"Reading property (value): {self.name}"): - struct_type = StrTypes[self._struct["type"]] - old_obj_name = self._struct["name"] - return struct_type, old_obj_name + pointer_type = BPYPointers[self._struct["type"]] + old_obj_name = self._struct["value"] + return pointer_type, old_obj_name def _handle_collection_prop(self, col_prop, dependencies): collection = [] From 5e109877ae010f4274e860815b36bd39737b9246 Mon Sep 17 00:00:00 2001 From: durman Date: Tue, 25 May 2021 16:46:51 +0400 Subject: [PATCH 05/29] support importing node groups --- utils/sv_json_struct.py | 109 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index b0938b0f59..3f097aec28 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -30,14 +30,15 @@ def __init__(self, factories: List[Type[Struct]]): StrTypes.TREE: 'tree', StrTypes.NODE: 'node', StrTypes.SOCK: 'sock', + StrTypes.INTERFACE: 'interface', StrTypes.LINK: 'link', StrTypes.PROP: 'prop' } - # optional for fast access of main structures self.tree: Optional[Type[Struct]] = None self.node: Optional[Type[Struct]] = None self.sock: Optional[Type[Struct]] = None + self.interface: Optional[Type[Struct]] = None self.link: Optional[Type[Struct]] = None self.prop: Optional[Type[Struct]] = None @@ -71,6 +72,7 @@ class StrTypes(Enum): TREE = auto() NODE = auto() SOCK = auto() + INTERFACE = auto() # node groups sockets LINK = auto() PROP = auto() @@ -134,7 +136,8 @@ def __init__(self, name=None, logger: FailsLog = None, struct: dict = None): self.logger: FailsLog = logger def export(self, tree): - struct_factories = StructFactory([TreeStruct, NodeStruct, SocketStruct, LinkStruct, PropertyStruct]) # todo to args? + struct_factories = StructFactory( + [TreeStruct, NodeStruct, SocketStruct, InterfaceStruct, LinkStruct, PropertyStruct]) # todo to args? dependencies: List[Tuple[BPYPointers, str]] = [(BPYPointers.NODE_TREE, tree.name)] # it looks good place for exporting dependent data blocks because probably we do not always want to export them # from this place we have more control over it @@ -164,7 +167,9 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no # all trees should be created and only after that field with content with tree.throttle_update(): # todo it is required only for current update system can be deleted later - factories = StructFactory([TreeStruct, NodeStruct, SocketStruct, LinkStruct, PropertyStruct]) + + factories = StructFactory( + [TreeStruct, NodeStruct, SocketStruct, InterfaceStruct, LinkStruct, PropertyStruct]) imported_structs = OldNewNames() trees_to_build = [] version, data_blocks = self.read() @@ -175,8 +180,10 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no # this is first tree and it should be main, does not need create anything data_block = tree else: - # this tree should be created + # this is node group tree, should be created? data_block = bpy.data.node_groups.new(block_name, tree_struct.read_bl_type()) + # interface should be created before building all trees + tree_struct.build_interface(data_block, factories, imported_structs) imported_structs[(struct_type, block_name)] = data_block trees_to_build.append(tree_struct) else: @@ -217,6 +224,8 @@ def __init__(self, name: str, logger: FailsLog, structure: dict = None): default_structure = { "nodes": dict(), "links": [], + "inputs": dict(), + "outputs": dict(), "bl_idname": "", } self._struct = structure or default_structure @@ -231,6 +240,14 @@ def export(self, tree, factories: StructFactory, dependencies) -> dict: for link in _ordered_links(tree): self._struct["links"].append(factories.link(None, self.logger).export(link, factories, dependencies)) + for socket in tree.inputs: + raw_struct = factories.interface(socket.name, self.logger).export(socket, factories, dependencies) + self._struct["inputs"][socket.name] = raw_struct + + for socket in tree.outputs: + raw_struct = factories.interface(socket.name, self.logger).export(socket, factories, dependencies) + self._struct["outputs"][socket.name] = raw_struct + self._struct["bl_idname"] = tree.bl_idname return self._struct @@ -245,6 +262,8 @@ def export_nodes(self, nodes, factories: StructFactory, dependencies) -> dict: if link.from_node.name in input_node_names and link.to_node.name in input_node_names: self._struct["links"].append(factories.link(None, self.logger).export(link, None, None)) + # todo add tree sockets + self._struct["bl_idname"] = tree.bl_idname return self._struct @@ -281,6 +300,27 @@ def read(self): return nodes_reader, links_reader + def build_interface(self, tree, factories, imported_structs): + inputs, outputs = self.read_interface_data() + # create tree sockets + with self.logger.add_fail("Create tree socket"): + for sock_name, raw_struct in inputs: + factories.interface(sock_name, self.logger, raw_struct).build(tree.inputs, factories, imported_structs) + + with self.logger.add_fail("Create tree socket"): + for sock_name, raw_struct in outputs: + factories.interface(sock_name, self.logger, raw_struct).build(tree.outputs, factories, imported_structs) + + def read_interface_data(self): + with self.logger.add_fail("Reading inputs"): + input_struct = self._struct["inputs"] + input_reader = self.read_collection(input_struct) + + with self.logger.add_fail("Reading outpus"): + outputs_struct = self._struct["outputs"] + output_reader = self.read_collection(outputs_struct) + return input_reader, output_reader + def read_bl_type(self): return self._struct["bl_idname"] @@ -466,6 +506,67 @@ def read_bl_type(self) -> str: return self._struct['bl_idname'] +class InterfaceStruct(Struct): + type = StrTypes.INTERFACE + + def __init__(self, name, logger: FailsLog, structure=None): + default_struct = { + "bl_idname": "", + "properties": dict(), + "attributes": dict() + } + self.name = name + self.logger = logger + self._struct = structure or default_struct + + def export(self, socket, factories, dependencies): + self._struct['bl_idname'] = socket.bl_idname + + for prop_name in socket.keys(): + prop = BPYProperty(socket, prop_name) + if prop.is_valid and prop.is_to_save: + raw_struct = factories.prop(prop.name, self.logger).export(prop, factories, dependencies) + self._struct["properties"][prop.name] = raw_struct + + return self._struct + + def build(self, sockets, factories, imported_structs): + attributes, properties = self.read() + + # create the socket in the method because identifier is hidden is shown only inside the class + with self.logger.add_fail("Create interface socket"): + interface_class = bpy.types.NodeSocketInterface.bl_rna_get_subclass_py(self.read_bl_type()) + socket_type = interface_class.bl_socket_idname + socket = sockets.new(socket_type, self.name) + + for attr_name, attr_value in attributes: + with self.logger.add_fail( + "Setting interface socket attribute", # socket.node can be None sometimes 0_o + f'Tree: {socket.id_data.name}, socket: {socket.name}, attr: {attr_name}'): + factories.prop(attr_name, self.logger, attr_value).build(socket, factories, imported_structs) + + for prop_name, prop_value in properties: + with self.logger.add_fail( + "Setting interface socket property", + f'Tree: {socket.id_data.name}, Node: {socket.node.name}, socket: {socket.name}, prop: {prop_name}'): + factories.prop(prop_name, self.logger, prop_value).build(socket, factories, imported_structs) + + def read(self): + with self.logger.add_fail("Reading interface socket attributes"): + attrs_struct = self._struct["attributes"] + attributes = self.read_collection(attrs_struct) + + with self.logger.add_fail("Reading interface socket properties"): + props_struct = self._struct["properties"] + properties = self.read_collection(props_struct) + + return attributes, properties + + def read_bl_type(self) -> str: + with self.logger.add_fail("Reading interface socket bl_idname"): + return self._struct['bl_idname'] + + class LinkStruct(Struct): type = StrTypes.LINK From 68221a802e2d42b2c5396201444ea875ec1f3374 Mon Sep 17 00:00:00 2001 From: durman Date: Wed, 26 May 2021 15:47:28 +0400 Subject: [PATCH 06/29] add unit test for export/import all nodes one by one, and related fixes --- tests/json_export_import_simple_tests.py | 39 ++++++++++++++++++++++++ utils/sv_json_struct.py | 8 +++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/tests/json_export_import_simple_tests.py b/tests/json_export_import_simple_tests.py index 4048624bc9..c06d190851 100644 --- a/tests/json_export_import_simple_tests.py +++ b/tests/json_export_import_simple_tests.py @@ -1,5 +1,7 @@ from sverchok.utils.testing import * from sverchok.utils.sv_json_export import JSONExporter +from sverchok.utils.sv_json_import import FailsLog +from sverchok.utils.sv_json_struct import FileStruct from sverchok.utils.modules_inspection import iter_classes_from_module @@ -31,3 +33,40 @@ def test_export_import_all_nodes(self): @property def known_troubles(self) -> set: return {'SvScriptNodeLite', 'SvTextInNodeMK2'} + + def test_export_import_all_nodes_new(self): + for node_class in iter_classes_from_module(sverchok.nodes, [bpy.types.Node]): + if node_class.bl_idname in self.known_troubles or node_class.bl_idname in self.new_known_troubles: + continue + + try: + create_node(node_class.bl_idname, self.tree.name) + except RuntimeError: # the node probably was not registered for missing dependencies + pass + else: + structure = None + with self.subTest(type='NEW EXPORT', node=node_class.bl_idname): + with self.assert_logs_no_errors(): + structure = FileStruct().export(self.tree) + if structure is not None: + with self.subTest(type='NEW IMPORT', node=node_class.bl_idname): + with self.assert_logs_no_errors(): + logger = FailsLog() + FileStruct(None, logger, structure).build_into_tree(self.tree) + if logger.has_fails: + raise (ImportError(logger.fail_message)) + finally: + # you have to clean tree by yourself + self.tree.nodes.clear() + + @property + def new_known_troubles(self) -> set: + return {'SvGroupInputsNodeExp', 'SvGroupOutputsNodeExp', # todo this two can be removed with monads + 'SvInputSwitchNodeMOD', # sv_update method of this node is beyond my ability to understand such code + 'SvObjInLite', # this one just unable to save empty object + } + + +if __name__ == '__main__': + import unittest + unittest.main(exit=False) diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 3f097aec28..8e9ed97aea 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -484,9 +484,9 @@ def build(self, sockets, factories, imported_structs): factories.prop(attr_name, self.logger, attr_value).build(socket, factories, imported_structs) for prop_name, prop_value in properties: - with self.logger.add_fail( + with self.logger.add_fail( # I think when socket is just created socket.node is None due Blender limitation "Setting socket property", - f'Tree: {socket.id_data.name}, Node: {socket.node.name}, socket: {socket.name}, prop: {prop_name}'): + f'Tree: {socket.id_data.name}, socket: {socket.name}, prop: {prop_name}'): factories.prop(prop_name, self.logger, prop_value).build(socket, factories, imported_structs) def read(self): @@ -671,7 +671,9 @@ def _handle_collection_prop(self, col_prop, dependencies): for item in col_prop.collection_to_list(): item_props = dict() for prop in item: - item_props[prop.name] = PropertyStruct(prop.name, self.logger).export(prop, None, dependencies) + raw_struct = PropertyStruct(prop.name, self.logger).export(prop, None, dependencies) + if raw_struct is not None: + item_props[prop.name] = raw_struct collection.append(item_props) return collection From db0b5ddb75753f475c9d46c1e3eb4deebacb13a7 Mon Sep 17 00:00:00 2001 From: durman Date: Wed, 26 May 2021 15:52:13 +0400 Subject: [PATCH 07/29] add converting collection property into list of list of properties, and add pointer type property. This changes are required by new JSON serialization module --- utils/handle_blender_data.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/utils/handle_blender_data.py b/utils/handle_blender_data.py index 29925a4e1f..08d2189f8b 100644 --- a/utils/handle_blender_data.py +++ b/utils/handle_blender_data.py @@ -168,6 +168,12 @@ def type(self) -> str: raise TypeError(f'Can not read "type" of invalid property "{self.name}"') return self._data.bl_rna.properties[self.name].type + @property + def pointer_type(self) -> BPYPointers: + if self.type != 'POINTER': + raise TypeError(f'This property is only valid for "POINTER" types, {self.type} type is given') + return BPYPointers.get_type(self._data.bl_rna) + @property def default_value(self) -> Any: """Returns default value, None for pointers, list of dicts of default values for collections""" @@ -250,6 +256,26 @@ def filter_collection_values(self, skip_default=True, skip_save=True): items.append(item_props) return items + def collection_to_list(self): + """Returns data structure like this [[p1, p2, p3], [p4, p5, p6]] + in this example the collection has two items, each item has 3 properties""" + if self.type != 'COLLECTION': + raise TypeError(f'Method supported only "collection" types, "{self.type}" was given') + if not self.is_valid: + raise TypeError(f'Can not read "non default collection values" of invalid property "{self.name}"') + + collection = [] + for item in getattr(self._data, self.name): + prop_list = [] + # in some nodes collections are getting just PropertyGroup type instead of its subclasses + # PropertyGroup itself does not have any properties + item_properties = item.__annotations__ if hasattr(item, '__annotations__') else [] + for prop_name in chain(['name'], item_properties): # item.items() will return only changed values + prop = BPYProperty(item, prop_name) + prop_list.append(prop) + collection.append(prop_list) + return collection + def _extract_collection_values(self, default_value: bool = False): """returns something like this: [{"name": "", "my_prop": 1.0}, {"name": "", "my_prop": 2.0}, ...]""" items = [] From 02bf25a6899ef8c7d7f3c0a0eacf5794abb29089 Mon Sep 17 00:00:00 2001 From: durman Date: Thu, 27 May 2021 12:55:57 +0400 Subject: [PATCH 08/29] connect new json exporter to Sverchok UI --- core/monad.py | 2 +- core/sockets.py | 16 ++++++------ ui/presets.py | 2 +- ui/sv_IO_panel.py | 34 ++++--------------------- utils/sv_json_export.py | 14 ++++++++--- utils/sv_json_struct.py | 56 +++++++++++++++++++++++++++++++---------- 6 files changed, 68 insertions(+), 56 deletions(-) diff --git a/core/monad.py b/core/monad.py index 632dd9a3aa..a4e8e871ed 100644 --- a/core/monad.py +++ b/core/monad.py @@ -91,7 +91,7 @@ def monad_make_unique(node): # the new tree dict will contain information about 1 node only, and # the node_group too (at the moment) but the node_group data can be ignored. - layout_json = JSONExporter.get_nodes_structure([node]) + layout_json = JSONExporter._get_nodes_structure([node]) # do not restore links this way. wipe this entry and restore at a later stage. layout_json['update_lists'] = [] diff --git a/core/sockets.py b/core/sockets.py index 0f86f4cdca..6a46a8a1dd 100644 --- a/core/sockets.py +++ b/core/sockets.py @@ -313,15 +313,13 @@ class SvSocketCommon(SvSocketProcessing): color = (1, 0, 0, 1) # base color, other sockets should override the property, use FloatProperty for dynamic label: StringProperty() # It will be drawn instead of name if given quick_link_to_node = str() # sockets which often used with other nodes can fill its `bl_idname` here - link_menu_handler : StringProperty(default='', options={'SKIP_SAVE'}) # To specify additional entries in the socket link menu - enable_input_link_menu : BoolProperty(default = True, options={'SKIP_SAVE'}) + link_menu_handler : StringProperty(default='') # To specify additional entries in the socket link menu + enable_input_link_menu : BoolProperty(default = True) # set True to use default socket property if it has got it - use_prop: BoolProperty(default=False, options={'SKIP_SAVE'}) - custom_draw: StringProperty(description="For name of method which will draw socket UI (optionally)", - options={'SKIP_SAVE'}) - prop_name: StringProperty(default='', description="For displaying node property in socket UI", - options={'SKIP_SAVE'}) + use_prop: BoolProperty(default=False) + custom_draw: StringProperty(description="For name of method which will draw socket UI (optionally)") + prop_name: StringProperty(default='', description="For displaying node property in socket UI") # utility field for showing number of objects in sockets data objects_number: IntProperty(min=0, options={'SKIP_SAVE'}) @@ -598,7 +596,7 @@ def filter_kinds(self, objs): return True color = (0.69, 0.74, 0.73, 1.0) - use_prop: BoolProperty(default=True, options={'SKIP_SAVE'}) + use_prop: BoolProperty(default=True) object_kinds: StringProperty(default='ALL') # use for filtering objects, see filter_kinds method object_ref: StringProperty(update=process_from_socket) @@ -894,7 +892,7 @@ def get_prop_data(self): else: return {} - quick_link_to_node: StringProperty(options={'SKIP_SAVE'}) # this can be overridden by socket instances + quick_link_to_node: StringProperty() # this can be overridden by socket instances default_property_type: bpy.props.EnumProperty(items=[(i, i, '') for i in ['float', 'int']]) default_float_property: bpy.props.FloatProperty(update=process_from_socket) diff --git a/ui/presets.py b/ui/presets.py index 6a31291b58..5d54a60014 100644 --- a/ui/presets.py +++ b/ui/presets.py @@ -430,7 +430,7 @@ def execute(self, context): self.report({'ERROR'}, msg) return {'CANCELLED'} - layout_dict = JSONExporter.get_nodes_structure([n for n in ng.nodes if n.select]) + layout_dict = JSONExporter.get_tree_structure(ng, True) preset = SvPreset(name=self.preset_name, category = self.category) preset.make_add_operator() destination_path = preset.path diff --git a/ui/sv_IO_panel.py b/ui/sv_IO_panel.py index 83aae627a2..a5ade35668 100644 --- a/ui/sv_IO_panel.py +++ b/ui/sv_IO_panel.py @@ -102,20 +102,7 @@ def sv_tree_filter(self, context): import_tree: bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=sv_tree_filter) -class ExportToJSONOperator: - selected_only: bpy.props.BoolProperty(name="Selected only") - - def can_be_exported(self, tree: bpy.types.NodeTree) -> Tuple[bool, str]: - for node in tree.nodes: - if self.selected_only and not node.select: - continue - if hasattr(node, 'node_tree'): # looks like it is group node - return False, f'Tree has at least one group node "{node.name}". ' \ - f'Importing group nodes is not supported at the present' - return True, '' - - -class SvNodeTreeExporter(ExportToJSONOperator, bpy.types.Operator): +class SvNodeTreeExporter(bpy.types.Operator): '''Export will let you pick a .json file name''' bl_idname = "node.tree_exporter" bl_label = "sv NodeTree Export Operator" @@ -131,6 +118,7 @@ class SvNodeTreeExporter(ExportToJSONOperator, bpy.types.Operator): id_tree: bpy.props.StringProperty() compress: bpy.props.BoolProperty() + selected_only: bpy.props.BoolProperty(name="Selected only") @classmethod def poll(cls, context): @@ -139,21 +127,13 @@ def poll(cls, context): def execute(self, context): ng = bpy.data.node_groups[self.id_tree] - is_tree_exportable, msg = self.can_be_exported(ng) - if not is_tree_exportable: - self.report({'ERROR'}, msg) - return {'CANCELLED'} - destination_path = self.filepath if not destination_path.lower().endswith('.json'): destination_path += '.json' # future: should check if filepath is a folder or ends in \ - if self.selected_only: - layout_dict = JSONExporter.get_nodes_structure([node for node in ng.nodes if node.select]) - else: - layout_dict = JSONExporter.get_tree_structure(ng) + layout_dict = JSONExporter.get_tree_structure(ng, self.selected_only) if not layout_dict: msg = 'no update list found - didn\'t export' @@ -283,7 +263,7 @@ def execute(self, context): return {'FINISHED'} -class SvNodeTreeExportToGist(ExportToJSONOperator, bpy.types.Operator): +class SvNodeTreeExportToGist(bpy.types.Operator): """Export to anonymous gist and copy id to clipboard""" bl_idname = "node.tree_export_to_gist" bl_label = "Export to GIST (github account)" @@ -309,11 +289,7 @@ def execute(self, context): license = 'license: CC BY-SA' gist_description = f"Sverchok.{version_and_sha} | Blender.{app_version} | {ng.name} | {time_stamp} | {license}" - # layout_dict = create_dict_of_tree(ng, skip_set={}, selected=self.selected_only) - if self.selected_only: - layout_dict = JSONExporter.get_nodes_structure([node for node in ng.nodes if node.select]) - else: - layout_dict = JSONExporter.get_tree_structure(ng) + layout_dict = JSONExporter.get_tree_structure(ng, self.selected_only) try: gist_body = json.dumps(layout_dict, sort_keys=True, indent=2) diff --git a/utils/sv_json_export.py b/utils/sv_json_export.py index 027fba6dfc..dd40fe1965 100644 --- a/utils/sv_json_export.py +++ b/utils/sv_json_export.py @@ -14,6 +14,7 @@ from sverchok.utils.sv_node_utils import recursive_framed_location_finder from sverchok.utils.handle_blender_data import BPYProperty from sverchok.utils.sv_IO_monad_helpers import pack_monad +from sverchok.utils.sv_json_struct import FileStruct if TYPE_CHECKING: from sverchok.node_tree import SverchCustomTree, SverchCustomTreeNode @@ -24,12 +25,19 @@ class JSONExporter: """Static class for responsible for exporting into JSON format""" @staticmethod - def get_tree_structure(tree: SverchCustomTree) -> dict: + def get_tree_structure(tree: SverchCustomTree, use_selection=False) -> dict: """Generate structure of given tree which van be saved into json format""" - return TreeExporter01().export_tree(tree) + has_monads = any('SvGroupNodeMonad' in n.bl_idname for n in tree.nodes) + if has_monads: # todo this part should be deleted with monads + if use_selection: + return JSONExporter._get_nodes_structure([n for n in tree.nodes if n.select]) + else: + return TreeExporter01().export_tree(tree) + else: + return FileStruct().export_tree(tree, use_selection) @staticmethod - def get_nodes_structure(nodes: List[SverchCustomTreeNode]) -> dict: + def _get_nodes_structure(nodes: List[SverchCustomTreeNode]) -> dict: """Generate structure of given nodes which can be saved into json format""" return TreeExporter01().export_nodes(nodes) diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 8e9ed97aea..4be35495bb 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -11,6 +11,7 @@ import sys from abc import abstractmethod, ABC from enum import Enum, auto +from itertools import chain from typing import Type, Generator, TYPE_CHECKING, Dict, Tuple, Optional, List, Any import bpy @@ -135,10 +136,27 @@ def __init__(self, name=None, logger: FailsLog = None, struct: dict = None): self._struct: Dict[str, Any] = struct or {"export_version": str(self.version)} self.logger: FailsLog = logger - def export(self, tree): + def export(self): + # I would expect this method import the whole Sverchok data in a file + raise NotImplementedError + + def export_tree(self, tree, use_selection=False): + if tree.bl_idname != 'SverchCustomTreeType': + raise TypeError(f'Only exporting main trees is supported, {tree.bl_label} is given') + self._struct["main_tree"] = dict() struct_factories = StructFactory( [TreeStruct, NodeStruct, SocketStruct, InterfaceStruct, LinkStruct, PropertyStruct]) # todo to args? - dependencies: List[Tuple[BPYPointers, str]] = [(BPYPointers.NODE_TREE, tree.name)] + dependencies: List[Tuple[BPYPointers, str]] = [] + + # export main tree first + factory = struct_factories.get_factory(StrTypes.TREE) + struct = factory(tree.name, self.logger) + if use_selection: + raw_struct = struct.export_nodes([n for n in tree.nodes if n.select], struct_factories, dependencies) + else: + raw_struct = struct.export(tree, struct_factories, dependencies) + self._struct["main_tree"][tree.name] = raw_struct + # it looks good place for exporting dependent data blocks because probably we do not always want to export them # from this place we have more control over it while dependencies: @@ -154,8 +172,6 @@ def export(self, tree): return self._struct - # todo export selected nodes here? - def build(self, *_): raise NotImplementedError @@ -166,14 +182,16 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no # with tree data blocks it can be a beat trickier, # all trees should be created and only after that field with content - with tree.throttle_update(): # todo it is required only for current update system can be deleted later + with tree.throttle_update(): # todo it is required only for current update system can be deleted later?? factories = StructFactory( [TreeStruct, NodeStruct, SocketStruct, InterfaceStruct, LinkStruct, PropertyStruct]) imported_structs = OldNewNames() trees_to_build = [] - version, data_blocks = self.read() - for struct_type, block_name, raw_struct in data_blocks: + version, main_tree, data_blocks = self.read() + + # initialize trees and build other data block types + for struct_type, block_name, raw_struct in chain(main_tree, data_blocks): if struct_type == StrTypes.TREE: tree_struct = factories.tree(block_name, self.logger, raw_struct) if not trees_to_build: @@ -199,7 +217,8 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no # build all trees new_name = imported_structs[StrTypes.TREE, tree_struct.name] data_block = bpy.data.node_groups[new_name] - tree_struct.build(data_block, factories, imported_structs) # todo add throttling tree + tree_struct.build(data_block, factories, imported_structs) + build_update_list(tree) process_tree(tree) @@ -207,7 +226,10 @@ def read(self): with self.logger.add_fail("Reading version of the file"): version = float(self._struct["export_version"]) - return version, self._data_blocks_reader() + main_tree_struct = self._struct.get("main_tree") + main_tree_reader = ((StrTypes.TREE, name, raw_struct) for name, raw_struct in main_tree_struct.items()) + + return version, main_tree_reader, self._data_blocks_reader() def _data_blocks_reader(self): # todo add logger? struct_type: StrTypes @@ -251,18 +273,26 @@ def export(self, tree, factories: StructFactory, dependencies) -> dict: self._struct["bl_idname"] = tree.bl_idname return self._struct - def export_nodes(self, nodes, factories: StructFactory, dependencies) -> dict: # todo check whether all nodes from the same tree? - tree = nodes[0].bl_idname + def export_nodes(self, nodes, factories: StructFactory, dependencies) -> dict: + tree = nodes[0].id_data for node in nodes: + if node.id_data != tree: + raise TypeError(f"Given node from different trees: {tree.name} and {node.id_data.name}") raw_struct = factories.node(node.name, self.logger).export(node, factories, dependencies) self._struct["nodes"][node.name] = raw_struct input_node_names = {node.name for node in nodes} for link in _ordered_links(tree): if link.from_node.name in input_node_names and link.to_node.name in input_node_names: - self._struct["links"].append(factories.link(None, self.logger).export(link, None, None)) + self._struct["links"].append(factories.link(None, self.logger).export(link, factories, dependencies)) + + for socket in tree.inputs: + raw_struct = factories.interface(socket.name, self.logger).export(socket, factories, dependencies) + self._struct["inputs"][socket.name] = raw_struct - # todo add tree sockets + for socket in tree.outputs: + raw_struct = factories.interface(socket.name, self.logger).export(socket, factories, dependencies) + self._struct["outputs"][socket.name] = raw_struct self._struct["bl_idname"] = tree.bl_idname return self._struct From 4659c9e6137493e0bc646bcf22e02685f2b4439d Mon Sep 17 00:00:00 2001 From: Durman Date: Sat, 29 May 2021 14:33:50 +0400 Subject: [PATCH 09/29] Fixing the problem of socket identifiers of group nodes and group input and output nodes. They have dynamic modifiers which are determine when interface sockets of group tree are creating. Solution was to keep socket identifiers in similar way as new socket names. For Group input and output nodes there is decision not saving their sockets. Data blocks of pointer properties, export of that is not supported, are ignored during export. Also fixing some bugs. --- utils/sv_json_struct.py | 191 ++++++++++++++++++++++++++-------------- 1 file changed, 126 insertions(+), 65 deletions(-) diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 4be35495bb..43862c33b0 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -96,6 +96,14 @@ def get_type(cls, block_type: BPYPointers) -> StrTypes: raise TypeError(f'Given block type: {block_type} is not among supported: {mapping.keys()}') return mapping[block_type] + @classmethod + def is_supported_block(cls, block_type: BPYPointers) -> bool: + try: + cls.get_type(block_type) + return True + except TypeError: + return False + class Struct(ABC): # I was trying to make API of all abstract method the same between child classes but it looks it's not possible @@ -161,14 +169,15 @@ def export_tree(self, tree, use_selection=False): # from this place we have more control over it while dependencies: block_type, block_name = dependencies.pop() - struct_type = StrTypes.get_type(block_type) - if struct_type.name not in self._struct: - self._struct[struct_type.name] = dict() - if block_name not in self._struct[struct_type.name]: - factory = struct_factories.get_factory(struct_type) - data_block = block_type.collection[block_name] - structure = factory(block_name, self.logger).export(data_block, struct_factories, dependencies) - self._struct[struct_type.name][block_name] = structure + if StrTypes.is_supported_block(block_type): + struct_type = StrTypes.get_type(block_type) + if struct_type.name not in self._struct: + self._struct[struct_type.name] = dict() + if block_name not in self._struct[struct_type.name]: + factory = struct_factories.get_factory(struct_type) + data_block = block_type.collection[block_name] + structure = factory(block_name, self.logger).export(data_block, struct_factories, dependencies) + self._struct[struct_type.name][block_name] = structure return self._struct @@ -202,20 +211,20 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no data_block = bpy.data.node_groups.new(block_name, tree_struct.read_bl_type()) # interface should be created before building all trees tree_struct.build_interface(data_block, factories, imported_structs) - imported_structs[(struct_type, block_name)] = data_block + imported_structs[(struct_type, '', block_name)] = data_block.name trees_to_build.append(tree_struct) else: # all data block except node trees block_type = struct_type.get_bpy_pointer() data_block = block_type.collection.new(block_name) - imported_structs[(struct_type, block_name)] = data_block + imported_structs[(struct_type, '', block_name)] = data_block.name factories.get_factory(struct_type)(block_name, self.logger, raw_struct).build() # todo before building trees should be registered old and dummy nodes if necessary for tree_struct in trees_to_build: # build all trees - new_name = imported_structs[StrTypes.TREE, tree_struct.name] + new_name = imported_structs[StrTypes.TREE, '', tree_struct.name] data_block = bpy.data.node_groups[new_name] tree_struct.build(data_block, factories, imported_structs) @@ -255,6 +264,7 @@ def __init__(self, name: str, logger: FailsLog, structure: dict = None): self.logger = logger def export(self, tree, factories: StructFactory, dependencies) -> dict: + # todo export tree properties, for all trees? only for group trees? for node in tree.nodes: raw_struct = factories.node(node.name, self.logger).export(node, factories, dependencies) self._struct['nodes'][node.name] = raw_struct @@ -264,11 +274,11 @@ def export(self, tree, factories: StructFactory, dependencies) -> dict: for socket in tree.inputs: raw_struct = factories.interface(socket.name, self.logger).export(socket, factories, dependencies) - self._struct["inputs"][socket.name] = raw_struct + self._struct["inputs"][socket.identifier] = raw_struct for socket in tree.outputs: raw_struct = factories.interface(socket.name, self.logger).export(socket, factories, dependencies) - self._struct["outputs"][socket.name] = raw_struct + self._struct["outputs"][socket.identifier] = raw_struct self._struct["bl_idname"] = tree.bl_idname return self._struct @@ -288,11 +298,11 @@ def export_nodes(self, nodes, factories: StructFactory, dependencies) -> dict: for socket in tree.inputs: raw_struct = factories.interface(socket.name, self.logger).export(socket, factories, dependencies) - self._struct["inputs"][socket.name] = raw_struct + self._struct["inputs"][socket.identifier] = raw_struct for socket in tree.outputs: raw_struct = factories.interface(socket.name, self.logger).export(socket, factories, dependencies) - self._struct["outputs"][socket.name] = raw_struct + self._struct["outputs"][socket.identifier] = raw_struct self._struct["bl_idname"] = tree.bl_idname return self._struct @@ -308,11 +318,11 @@ def build(self, tree, factories: StructFactory, imported_structs: OldNewNames): node_struct = factories.node(node_name, self.logger, raw_structure) node = tree.nodes.new(node_struct.read_bl_type()) node.name = node_name - imported_structs[(node_struct.type, node_name)] = node + imported_structs[(StrTypes.NODE, tree.name, node_name)] = node.name node_structs.append(node_struct) for node_struct in node_structs: - new_name = imported_structs[(node_struct.type, node_struct.name)] + new_name = imported_structs[(StrTypes.NODE, tree.name, node_struct.name)] node = tree.nodes[new_name] node_struct.build(node, factories, imported_structs) @@ -407,14 +417,17 @@ def export(self, node, factories: StructFactory, dependencies) -> dict: self._struct["properties"][prop.name] = raw_struct # all sockets should be kept in a file because it's possible to create UI - # where sockets would be defined by pressing buttons for example like in the node group interface - for socket in node.inputs: - raw_struct = factories.sock(socket.identifier, self.logger).export(socket, factories, dependencies) - self._struct["inputs"][socket.identifier] = raw_struct - - for socket in node.outputs: - raw_struct = factories.sock(socket.identifier, self.logger).export(socket, factories, dependencies) - self._struct["outputs"][socket.identifier] = raw_struct + # where sockets would be defined by pressing buttons for example like in the node group interface. + # there is no sense of exporting information about sockets of group input and output nodes + # they are totally controlled by Blender update system. + if node.bl_idname not in ['NodeGroupInput', 'NodeGroupOutput']: + for socket in node.inputs: + raw_struct = factories.sock(socket.identifier, self.logger).export(socket, factories, dependencies) + self._struct["inputs"][socket.identifier] = raw_struct + + for socket in node.outputs: + raw_struct = factories.sock(socket.identifier, self.logger).export(socket, factories, dependencies) + self._struct["outputs"][socket.identifier] = raw_struct if hasattr(node, 'save_to_json'): node.save_to_json(self._struct) @@ -434,16 +447,23 @@ def build(self, node, factories: StructFactory, imported_data: OldNewNames): f'Tree: {node.id_data.name}, Node: {node.name}, prop: {prop_name}'): factories.prop(prop_name, self.logger, prop_value).build(node, factories, imported_data) - # does not trust to correctness of socket collections created by an init method + # does not trust to correctness of socket collections created by an init method. + # clearing sockets calls update methods of the node and the tree. + # the methods are called again each time new socket is added. + # if group node has sockets with identifiers which are not the same as identifiers of group tree sockets + # then when first node will be added to the group tree (or any other changes in the group tree) + # it will cause replacing of all sockets with wrong identifiers in the group node. + # clearing and adding sockets of Group input and Group output nodes + # immediately cause their rebuilding by Blender, so JSON file does not save information about their sockets. node.inputs.clear() - for sock_name, raw_struct in inputs: - with self.logger.add_fail(f"Add socket: {sock_name} to node {node.name}"): - factories.sock(sock_name, self.logger, raw_struct).build(node.inputs, factories, imported_data) + for sock_identifier, raw_struct in inputs: + with self.logger.add_fail(f"Add socket: {sock_identifier} to node {node.name}"): + factories.sock(sock_identifier, self.logger, raw_struct).build(node.inputs, factories, imported_data) node.outputs.clear() - for sock_name, raw_struct in outputs: - with self.logger.add_fail(f"Add socket: {sock_name} ot node {node.name}"): - factories.sock(sock_name, self.logger, raw_struct).build(node.outputs, factories, imported_data) + for sock_identifier, raw_struct in outputs: + with self.logger.add_fail(f"Add socket: {sock_identifier} ot node {node.name}"): + factories.sock(sock_identifier, self.logger, raw_struct).build(node.outputs, factories, imported_data) if hasattr(node, 'load_from_json'): with self.logger.add_fail("Setting advance node properties", @@ -477,20 +497,26 @@ def read_bl_type(self): class SocketStruct(Struct): type = StrTypes.SOCK - def __init__(self, name, logger: FailsLog, structure: dict = None): + def __init__(self, identifier, logger: FailsLog, structure: dict = None): + # socket names can't be used here because sockets can have the same names (unlike trees or nodes) default_struct = { "bl_idname": "", - "identifier": "", + "name": "", + "tree": "", # util information for group nodes about the name of their node tree + "attributes": dict(), "properties": dict(), - "attributes": dict() } - self.name = name + self.identifier = identifier self.logger = logger self._struct = structure or default_struct def export(self, socket, factories, dependencies): self._struct['bl_idname'] = socket.bl_idname - self._struct['identifier'] = socket.identifier + self._struct['name'] = socket.name + if hasattr(socket.node, 'node_tree'): + self._struct['tree'] = socket.node.node_tree.name + else: + del self._struct['tree'] self._struct['attributes']['hide'] = socket.hide for prop_name in socket.keys(): @@ -502,10 +528,19 @@ def export(self, socket, factories, dependencies): return self._struct def build(self, sockets, factories, imported_structs): - attributes, identifier, properties = self.read() + name, group_tree_name, attributes, properties = self.read() + + # check whether the socket is of group tree + if group_tree_name is not None: + # identifier of the socket should be always the same as identifier of the interface socket of the group tree + # otherwise it will be recreated by Blender update system and its links (and properties?) will be lost + new_node_tree_name = imported_structs[StrTypes.TREE, '', group_tree_name] + identifier = imported_structs[StrTypes.INTERFACE, new_node_tree_name, self.identifier] + else: + identifier = self.identifier # create the socket in the method because identifier is hidden is shown only inside the class - socket = sockets.new(self.read_bl_type(), self.name, identifier=identifier) + socket = sockets.new(self.read_bl_type(), name, identifier=identifier) for attr_name, attr_value in attributes: with self.logger.add_fail( @@ -523,13 +558,14 @@ def read(self): with self.logger.add_fail("Reading socket attributes"): attrs_struct = self._struct["attributes"] attributes = self.read_collection(attrs_struct) - identifier = self._struct['identifier'] + name = self._struct['name'] + tree = self._struct.get('tree') with self.logger.add_fail("Reading socket properties"): props_struct = self._struct["properties"] properties = self.read_collection(props_struct) - return attributes, identifier, properties + return name, tree, attributes, properties def read_bl_type(self) -> str: with self.logger.add_fail("Reading socket bl_idname"): @@ -539,18 +575,20 @@ def read_bl_type(self) -> str: class InterfaceStruct(Struct): type = StrTypes.INTERFACE - def __init__(self, name, logger: FailsLog, structure=None): + def __init__(self, identifier, logger: FailsLog, structure=None): default_struct = { "bl_idname": "", + "name": "", "properties": dict(), "attributes": dict() } - self.name = name + self.identifier = identifier self.logger = logger self._struct = structure or default_struct def export(self, socket, factories, dependencies): self._struct['bl_idname'] = socket.bl_idname + self._struct['name'] = socket.name for prop_name in socket.keys(): prop = BPYProperty(socket, prop_name) @@ -561,13 +599,14 @@ def export(self, socket, factories, dependencies): return self._struct def build(self, sockets, factories, imported_structs): - attributes, properties = self.read() + attributes, properties, name = self.read() # create the socket in the method because identifier is hidden is shown only inside the class with self.logger.add_fail("Create interface socket"): interface_class = bpy.types.NodeSocketInterface.bl_rna_get_subclass_py(self.read_bl_type()) socket_type = interface_class.bl_socket_idname - socket = sockets.new(socket_type, self.name) + socket = sockets.new(socket_type, name) # the method gives its own identifier + imported_structs[self.type, socket.id_data.name, self.identifier] = socket.identifier for attr_name, attr_value in attributes: with self.logger.add_fail( @@ -578,19 +617,20 @@ def build(self, sockets, factories, imported_structs): for prop_name, prop_value in properties: with self.logger.add_fail( "Setting interface socket property", - f'Tree: {socket.id_data.name}, Node: {socket.node.name}, socket: {socket.name}, prop: {prop_name}'): + f'Tree: {socket.id_data.name}, socket: {socket.name}, prop: {prop_name}'): factories.prop(prop_name, self.logger, prop_value).build(socket, factories, imported_structs) def read(self): with self.logger.add_fail("Reading interface socket attributes"): attrs_struct = self._struct["attributes"] attributes = self.read_collection(attrs_struct) + name = self._struct["name"] with self.logger.add_fail("Reading interface socket properties"): props_struct = self._struct["properties"] properties = self.read_collection(props_struct) - return attributes, properties + return attributes, properties, name def read_bl_type(self) -> str: with self.logger.add_fail("Reading interface socket bl_idname"): @@ -612,27 +652,48 @@ def __init__(self, name=None, logger: FailsLog = None, structure: dict = None): def export(self, link, *_): self._struct["from_node"] = link.from_node.name self._struct["from_socket"] = link.from_socket.identifier + if hasattr(link.from_node, 'node_tree'): + self._struct["from_tree"] = link.from_node.node_tree.name + elif link.from_node.bl_idname == 'NodeGroupInput': + self._struct["from_tree"] = link.id_data.name self._struct["to_node"] = link.to_node.name self._struct["to_socket"] = link.to_socket.identifier + if hasattr(link.to_node, 'node_tree'): + self._struct["to_tree"] = link.to_node.node_tree.name + elif link.to_node.bl_idname == 'NodeGroupOutput': + self._struct["to_tree"] = link.id_data.name return self._struct def build(self, tree, factories: StructFactory, imported_structs: OldNewNames): - from_node_name, from_sock_identifier, to_node_name, to_sock_identifier = self.read() - from_node_new_name = imported_structs[(factories.node.type, from_node_name)] - from_socket = self._search_socket(tree, from_node_new_name, from_sock_identifier, "OUTPUT") - to_node_new_name = imported_structs[(factories.node.type, to_node_name)] - to_socket = self._search_socket(tree, to_node_new_name, to_sock_identifier, "INPUT") + from_node_name, from_sock_identifier, from_tree, to_node_name, to_sock_identifier, to_tree = self.read() + + # all nodes can has different names + from_node_new_name = imported_structs[(factories.node.type, tree.name, from_node_name)] + to_node_new_name = imported_structs[(factories.node.type, tree.name, to_node_name)] + from_node = tree.nodes[from_node_new_name] + to_node = tree.nodes[to_node_new_name] + + # sockets of group_nodes can have different identifiers, unlike other sockets + # this should certainly be called after nodes get their properties + if from_tree is not None: + # new identifiers are bound to group trees where they was born + new_node_tree_name = imported_structs[StrTypes.TREE, '', from_tree] + from_sock_identifier = imported_structs[StrTypes.INTERFACE, new_node_tree_name, from_sock_identifier] + if to_tree is not None: + new_node_tree_name = imported_structs[StrTypes.TREE, '', to_tree] + to_sock_identifier = imported_structs[StrTypes.INTERFACE, new_node_tree_name, to_sock_identifier] + + from_socket = self._search_socket(from_node, from_sock_identifier, "OUTPUT") + to_socket = self._search_socket(to_node, to_sock_identifier, "INPUT") if from_socket and to_socket: tree.links.new(to_socket, from_socket) def read(self): with self.logger.add_fail("Read socket data"): - return self._struct["from_node"], self._struct["from_socket"], \ - self._struct["to_node"], self._struct["to_socket"] + return self._struct["from_node"], self._struct["from_socket"], self._struct.get("from_tree"), \ + self._struct["to_node"], self._struct["to_socket"], self._struct.get("to_tree") - def _search_socket(self, tree, node_name: str, socket_identifier: str, sock_type): - with self.logger.add_fail(f"Building link, trying to find node: {node_name}"): - node = tree.nodes[node_name] + def _search_socket(self, node, socket_identifier: str, sock_type): with self.logger.add_fail(f"Building link, trying to find socket {socket_identifier}"): for sock in node.inputs if sock_type == "INPUT" else node.outputs: if sock.identifier == socket_identifier: @@ -673,12 +734,13 @@ def build(self, obj, factories: StructFactory, imported_structs: OldNewNames): # this is structure (pointer property) if isinstance(self._struct, dict): pointer_type, old_obj_name = self.read() - new_name = imported_structs[(StrTypes.get_type(pointer_type), old_obj_name)] if pointer_type == BPYPointers.NODE: + new_name = imported_structs[(StrTypes.get_type(pointer_type), obj.id_data.name, old_obj_name)] # this should work in case obj is a node or socket # but in other cases probably extra information should be kept in the property structure data_block = obj.id_data.nodes[new_name] else: + new_name = imported_structs[(StrTypes.get_type(pointer_type), '', old_obj_name)] data_block = pointer_type.collection[new_name] setattr(obj, self.name, data_block) @@ -710,20 +772,19 @@ def _handle_collection_prop(self, col_prop, dependencies): class OldNewNames: # todo can't this be regular dictionary? """This class should solve problem of old new names, when created object with one name get another one""" - Old, New = str, str + Old, New, Owner = str, str, str def __init__(self): - self._old_new_names: Dict[Tuple[StrTypes, OldNewNames.Old], OldNewNames.New] = dict() + self._old_new_names: Dict[Tuple[StrTypes, OldNewNames.Owner, OldNewNames.Old], OldNewNames.New] = dict() - def __contains__(self, type_old_name: Tuple[StrTypes, OldNewNames.Old]): + def __contains__(self, type_old_name: Tuple[StrTypes, OldNewNames.Owner, OldNewNames.Old]): return type_old_name in self._old_new_names - def __getitem__(self, type_old_name: Tuple[StrTypes, OldNewNames.Old]): + def __getitem__(self, type_old_name: Tuple[StrTypes, OldNewNames.Owner, OldNewNames.Old]): return self._old_new_names[type_old_name] - def __setitem__(self, type_old_name: Tuple[StrTypes, OldNewNames.Old], data_block): - new_name = data_block.name - self._old_new_names[type_old_name] = new_name + def __setitem__(self, type_old_name: Tuple[StrTypes, OldNewNames.Owner, OldNewNames.Old], name: OldNewNames.New): + self._old_new_names[type_old_name] = name def _ordered_links(tree) -> Generator[bpy.types.NodeLink]: From 92ee1b1974a39100dd443c7b7be9b8accae92961 Mon Sep 17 00:00:00 2001 From: Durman Date: Sat, 29 May 2021 22:53:12 +0400 Subject: [PATCH 10/29] fixing unit tests --- nodes/script/mesh_eval.py | 6 +++- nodes/script/profile_mk3.py | 6 +++- nodes/script/script1_lite.py | 6 +++- tests/json_export_import_simple_tests.py | 6 ++-- utils/sv_json_struct.py | 37 +++++++++++++----------- 5 files changed, 38 insertions(+), 23 deletions(-) diff --git a/nodes/script/mesh_eval.py b/nodes/script/mesh_eval.py index bc1d26e8b9..bd48ca4b14 100644 --- a/nodes/script/mesh_eval.py +++ b/nodes/script/mesh_eval.py @@ -546,7 +546,11 @@ def load_from_json(self, node_data: dict, import_version): if 'geom' not in node_data: return # looks like the node was empty when it was exported geom = node_data['geom'] - filename = node_data['params']['filename'] + + if import_version < 1.0: + filename = node_data['params']['filename'] + else: + filename = self.filename bpy.data.texts.new(filename) bpy.data.texts[filename].clear() diff --git a/nodes/script/profile_mk3.py b/nodes/script/profile_mk3.py index bf90bbc89b..20bc93750d 100644 --- a/nodes/script/profile_mk3.py +++ b/nodes/script/profile_mk3.py @@ -689,7 +689,11 @@ def load_from_json(self, node_data: dict, import_version: float): if 'profile' not in node_data: return # looks like a node was empty when it was exported profile = node_data['profile'] - filename = node_data['params']['filename'] + + if import_version < 1.0: + filename = node_data['params']['filename'] + else: + filename = self.filename bpy.data.texts.new(filename) bpy.data.texts[filename].clear() diff --git a/nodes/script/script1_lite.py b/nodes/script/script1_lite.py index 027725a008..cdf9388114 100644 --- a/nodes/script/script1_lite.py +++ b/nodes/script/script1_lite.py @@ -551,7 +551,11 @@ def load_from_json(self, node_data: dict, import_version: float): If you have files that work differently but have the same name, stop. ''' - params = node_data.get('params') + if import_version < 1.0: + params = node_data.get('params') + else: + # really not good thing to do, the import should not relay on structure of the importer module + params = node_data.get('properties') if params: script_name = params.get('script_name') diff --git a/tests/json_export_import_simple_tests.py b/tests/json_export_import_simple_tests.py index c06d190851..a081ce305e 100644 --- a/tests/json_export_import_simple_tests.py +++ b/tests/json_export_import_simple_tests.py @@ -1,5 +1,5 @@ from sverchok.utils.testing import * -from sverchok.utils.sv_json_export import JSONExporter +from sverchok.utils.sv_json_export import TreeExporter01 from sverchok.utils.sv_json_import import FailsLog from sverchok.utils.sv_json_struct import FileStruct from sverchok.utils.modules_inspection import iter_classes_from_module @@ -19,7 +19,7 @@ def test_export_import_all_nodes(self): else: tree_structure = None with self.subTest(type='EXPORT', node=node_class.bl_idname): - tree_structure = JSONExporter.get_tree_structure(self.tree) + tree_structure = TreeExporter01().export_tree(self.tree) if tree_structure is not None: with self.subTest(type='IMPORT', node=node_class.bl_idname): importer = JSONImporter(tree_structure) @@ -47,7 +47,7 @@ def test_export_import_all_nodes_new(self): structure = None with self.subTest(type='NEW EXPORT', node=node_class.bl_idname): with self.assert_logs_no_errors(): - structure = FileStruct().export(self.tree) + structure = FileStruct().export_tree(self.tree) if structure is not None: with self.subTest(type='NEW IMPORT', node=node_class.bl_idname): with self.assert_logs_no_errors(): diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 43862c33b0..09756351d9 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -108,7 +108,7 @@ def is_supported_block(cls, block_type: BPYPointers) -> bool: class Struct(ABC): # I was trying to make API of all abstract method the same between child classes but it looks it's not possible # for example to build a node we can send to method a tree where the node should be duilded - version = 0.2 # override the property if necessary + version = 1.0 # override the property if necessary type: StrTypes = None # should be overridden @abstractmethod @@ -169,15 +169,14 @@ def export_tree(self, tree, use_selection=False): # from this place we have more control over it while dependencies: block_type, block_name = dependencies.pop() - if StrTypes.is_supported_block(block_type): - struct_type = StrTypes.get_type(block_type) - if struct_type.name not in self._struct: - self._struct[struct_type.name] = dict() - if block_name not in self._struct[struct_type.name]: - factory = struct_factories.get_factory(struct_type) - data_block = block_type.collection[block_name] - structure = factory(block_name, self.logger).export(data_block, struct_factories, dependencies) - self._struct[struct_type.name][block_name] = structure + struct_type = StrTypes.get_type(block_type) + if struct_type.name not in self._struct: + self._struct[struct_type.name] = dict() + if block_name not in self._struct[struct_type.name]: + factory = struct_factories.get_factory(struct_type) + data_block = block_type.collection[block_name] + structure = factory(block_name, self.logger).export(data_block, struct_factories, dependencies) + self._struct[struct_type.name][block_name] = structure return self._struct @@ -718,17 +717,21 @@ def export(self, prop: BPYProperty, _, dependencies): if prop.is_valid and prop.is_to_save: if prop.type == 'COLLECTION': return self._handle_collection_prop(prop, dependencies) - if prop.type == 'POINTER' and prop.value is not None: # skip empty pointers - self._struct["type"] = prop.pointer_type.name - self._struct["value"] = prop.value - if prop.data_collection is not None: # skipping nodes - dependencies.append((prop.pointer_type, prop.value)) - return self._struct + if prop.type == 'POINTER': + # skip empty and unsupported pointers + if prop.value is not None and StrTypes.is_supported_block(prop.pointer_type): + self._struct["type"] = prop.pointer_type.name + self._struct["value"] = prop.value + if prop.data_collection is not None: # skipping nodes + dependencies.append((prop.pointer_type, prop.value)) + return self._struct + else: + return None else: return prop.value def build(self, obj, factories: StructFactory, imported_structs: OldNewNames): - with self.logger.add_fail("Assigning value"): + with self.logger.add_fail("Assigning value", f"Name: {self.name}"): prop = BPYProperty(obj, self.name) # this is structure (pointer property) From 4a9c036b87dc224608912f21eb487629b8912e56 Mon Sep 17 00:00:00 2001 From: Durman Date: Sat, 29 May 2021 22:55:47 +0400 Subject: [PATCH 11/29] connect new JSON importer --- ui/sv_IO_panel.py | 4 ++-- utils/sv_json_import.py | 16 +++++++++++++--- utils/sv_json_struct.py | 3 --- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/ui/sv_IO_panel.py b/ui/sv_IO_panel.py index a5ade35668..e44cbdc7e7 100644 --- a/ui/sv_IO_panel.py +++ b/ui/sv_IO_panel.py @@ -141,7 +141,7 @@ def execute(self, context): warning(msg) return {'CANCELLED'} - json.dump(layout_dict, open(destination_path, 'w'), sort_keys=True, indent=2) + json.dump(layout_dict, open(destination_path, 'w'), indent=2) # json_struct does not expect sort_keys = True msg = 'exported to: ' + destination_path self.report({"INFO"}, msg) info(msg) @@ -292,7 +292,7 @@ def execute(self, context): layout_dict = JSONExporter.get_tree_structure(ng, self.selected_only) try: - gist_body = json.dumps(layout_dict, sort_keys=True, indent=2) + gist_body = json.dumps(layout_dict, indent=2) # json_struct does not expect sort_keys = True except Exception as err: if 'not JSON serializable' in repr(err): error(layout_dict) diff --git a/utils/sv_json_import.py b/utils/sv_json_import.py index 20e2603112..17a142b4d6 100644 --- a/utils/sv_json_import.py +++ b/utils/sv_json_import.py @@ -20,6 +20,7 @@ from sverchok.utils.logging import info, warning, getLogger, logging from sverchok.utils.handle_blender_data import BPYProperty, BPYNode from sverchok.utils.sv_IO_monad_helpers import unpack_monad +from sverchok.utils.sv_json_struct import FileStruct if TYPE_CHECKING: from sverchok.node_tree import SverchCustomTree, SverchCustomTreeNode @@ -48,8 +49,13 @@ def init_from_path(cls, path: str) -> JSONImporter: def import_into_tree(self, tree: SverchCustomTree, print_log: bool = True): """Import json structure into given tree and update it""" - root_tree_builder = TreeImporter01(tree, self._structure, self._fails_log) - root_tree_builder.import_tree() + if self.structure_version < 0.1001: + root_tree_builder = TreeImporter01(tree, self._structure, self._fails_log) + root_tree_builder.import_tree() + else: + importer = FileStruct(logger=self._fails_log, struct=self._structure) + importer.build_into_tree(tree) + if print_log: self._fails_log.report_log_result() @@ -57,7 +63,7 @@ def import_into_tree(self, tree: SverchCustomTree, print_log: bool = True): build_update_list(tree) process_tree(tree) - def import_node_settings(self, node: SverchCustomTreeNode): + def import_node_settings(self, node: SverchCustomTreeNode): # todo should be done something for new importer """ It takes first node from file and apply its settings to given node It is strange but it is how it was originally implemented @@ -83,6 +89,10 @@ def fail_massage(self) -> str: """Brief information about fails if their was""" return self._fails_log.fail_message + @property + def structure_version(self): + return float(self._structure["export_version"]) + class TreeImporter01: """ diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 09756351d9..b9547c6068 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -227,9 +227,6 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no data_block = bpy.data.node_groups[new_name] tree_struct.build(data_block, factories, imported_structs) - build_update_list(tree) - process_tree(tree) - def read(self): with self.logger.add_fail("Reading version of the file"): version = float(self._struct["export_version"]) From 706e769761ef46807977c358c8c9354cc20a09dc Mon Sep 17 00:00:00 2001 From: Durman Date: Sun, 30 May 2021 08:15:17 +0400 Subject: [PATCH 12/29] add handling registering custom node classes and little bug fix --- utils/dummy_nodes.py | 2 +- utils/sv_json_struct.py | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/utils/dummy_nodes.py b/utils/dummy_nodes.py index 1d14b0960e..181d1182d9 100644 --- a/utils/dummy_nodes.py +++ b/utils/dummy_nodes.py @@ -15,7 +15,6 @@ from bpy.props import StringProperty -from sverchok.node_tree import SverchCustomTreeNode from sverchok.utils.sv_oldnodes_parser import get_old_node_bl_idnames from sverchok.utils.logging import error, exception @@ -97,6 +96,7 @@ def reload_dummy(ng=False): load_dummy(ng) def create_dummy_class(bl_id): + from sverchok.node_tree import SverchCustomTreeNode node = dummy_nodes_dict[bl_id] cls = type(bl_id, (bpy.types.Node, SverchCustomTreeNode, SvDummyNode), diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index b9547c6068..64c2752398 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -15,7 +15,8 @@ from typing import Type, Generator, TYPE_CHECKING, Dict, Tuple, Optional, List, Any import bpy -from sverchok.core.update_system import build_update_list, process_tree +from sverchok import old_nodes +from sverchok.utils import dummy_nodes from sverchok.utils.handle_blender_data import BPYPointers, BPYProperty from sverchok.utils.sv_node_utils import recursive_framed_location_finder @@ -310,8 +311,16 @@ def build(self, tree, factories: StructFactory, imported_structs: OldNewNames): # first all nodes should be created without applying their inner data # because some nodes can have `parent` property which points into another node node_structs = [] - for node_name, raw_structure in nodes: # todo probably here is convenient place for registering node classes + for node_name, raw_structure in nodes: node_struct = factories.node(node_name, self.logger, raw_structure) + + # register optional node classes + if old_nodes.is_old(node_struct.read_bl_type()): + old_nodes.register_old(node_struct.read_bl_type()) + if dummy_nodes.is_dependent(node_struct.read_bl_type()): + dummy_nodes.register_dummy(node_struct.read_bl_type()) + + # add node an save its new name node = tree.nodes.new(node_struct.read_bl_type()) node.name = node_name imported_structs[(StrTypes.NODE, tree.name, node_name)] = node.name @@ -519,7 +528,8 @@ def export(self, socket, factories, dependencies): prop = BPYProperty(socket, prop_name) if prop.is_valid and prop.is_to_save: raw_struct = factories.prop(prop.name, self.logger).export(prop, factories, dependencies) - self._struct["properties"][prop.name] = raw_struct + if raw_struct is not None: + self._struct["properties"][prop.name] = raw_struct return self._struct From f0c6a0742eca117f04f8e45467c43bb436c5f1ad Mon Sep 17 00:00:00 2001 From: Durman Date: Sun, 30 May 2021 19:37:45 +0400 Subject: [PATCH 13/29] add material structure and fixing relevant bugs --- utils/sv_json_struct.py | 86 ++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 64c2752398..add39cce5f 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -34,7 +34,8 @@ def __init__(self, factories: List[Type[Struct]]): StrTypes.SOCK: 'sock', StrTypes.INTERFACE: 'interface', StrTypes.LINK: 'link', - StrTypes.PROP: 'prop' + StrTypes.PROP: 'prop', + StrTypes.MATERIAL: 'material', } self.tree: Optional[Type[Struct]] = None @@ -43,11 +44,15 @@ def __init__(self, factories: List[Type[Struct]]): self.interface: Optional[Type[Struct]] = None self.link: Optional[Type[Struct]] = None self.prop: Optional[Type[Struct]] = None + self.material: Optional[Type[Struct]] = None for factory in factories: if factory.type in self._factory_names: factory_name = self._factory_names[factory.type] setattr(self, factory_name, factory) + # probably we would never want using file structure from inside others + elif factory.type == StrTypes.FILE: + continue else: raise TypeError(f'Factory with type: {factory.type}' f' is not among supported: {self._factory_names.keys()}') @@ -60,13 +65,15 @@ def get_factory(self, struct_type: StrTypes) -> Type[Struct]: raise TypeError(f'Given struct type: {struct_type} is not among supported {self._factory_names.keys()}') @classmethod - def __match_factories(cls, version: float) -> StructFactory: # todo for future automatization? - """Choose factories which are most appropriate for given version""" - struct_factories = cls([]) + def gram_from_module(cls) -> StructFactory: + """Grab all factories in the module""" + factory_classes = [] module_classes = inspect.getmembers(sys.modules[__name__], lambda member: inspect.isclass(member) and member.__module__ == __name__) - for module_class in module_classes: - pass + for class_name, module_class in module_classes: + if hasattr(module_class, 'type') and module_class.type in StrTypes: + factory_classes.append(module_class) + return cls(factory_classes) class StrTypes(Enum): @@ -77,11 +84,13 @@ class StrTypes(Enum): INTERFACE = auto() # node groups sockets LINK = auto() PROP = auto() + MATERIAL = auto() def get_bpy_pointer(self) -> BPYPointers: mapping = { StrTypes.TREE: BPYPointers.NODE_TREE, StrTypes.NODE: BPYPointers.NODE, + StrTypes.MATERIAL: BPYPointers.MATERIAL, } if self not in mapping: raise TypeError(f'Given StrType: {self} is not a data block') @@ -92,6 +101,7 @@ def get_type(cls, block_type: BPYPointers) -> StrTypes: mapping = { BPYPointers.NODE_TREE: StrTypes.TREE, BPYPointers.NODE: StrTypes.NODE, + BPYPointers.MATERIAL: StrTypes.MATERIAL, } if block_type not in mapping: raise TypeError(f'Given block type: {block_type} is not among supported: {mapping.keys()}') @@ -124,10 +134,6 @@ def export(self, data_block, struct_factories: StructFactory, dependencies: List def build(self, *args): ... - @abstractmethod - def read(self): - ... - def read_bl_type(self) -> str: """typically should return bl_idname of the structure""" return None @@ -152,9 +158,9 @@ def export(self): def export_tree(self, tree, use_selection=False): if tree.bl_idname != 'SverchCustomTreeType': raise TypeError(f'Only exporting main trees is supported, {tree.bl_label} is given') + self._struct["main_tree"] = dict() - struct_factories = StructFactory( - [TreeStruct, NodeStruct, SocketStruct, InterfaceStruct, LinkStruct, PropertyStruct]) # todo to args? + struct_factories = StructFactory.gram_from_module() # todo to args? dependencies: List[Tuple[BPYPointers, str]] = [] # export main tree first @@ -193,8 +199,7 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no with tree.throttle_update(): # todo it is required only for current update system can be deleted later?? - factories = StructFactory( - [TreeStruct, NodeStruct, SocketStruct, InterfaceStruct, LinkStruct, PropertyStruct]) + factories = StructFactory.gram_from_module() imported_structs = OldNewNames() trees_to_build = [] version, main_tree, data_blocks = self.read() @@ -215,12 +220,8 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no trees_to_build.append(tree_struct) else: # all data block except node trees - block_type = struct_type.get_bpy_pointer() - data_block = block_type.collection.new(block_name) - imported_structs[(struct_type, '', block_name)] = data_block.name - factories.get_factory(struct_type)(block_name, self.logger, raw_struct).build() - - # todo before building trees should be registered old and dummy nodes if necessary + block_struct = factories.get_factory(struct_type)(block_name, self.logger, raw_struct) + block_struct.build(factories, imported_structs) for tree_struct in trees_to_build: # build all trees @@ -723,7 +724,7 @@ def export(self, prop: BPYProperty, _, dependencies): """It can return just value like float, bool etc or a structure""" if prop.is_valid and prop.is_to_save: if prop.type == 'COLLECTION': - return self._handle_collection_prop(prop, dependencies) + return self._export_collection_values(prop, dependencies) if prop.type == 'POINTER': # skip empty and unsupported pointers if prop.value is not None and StrTypes.is_supported_block(prop.pointer_type): @@ -754,6 +755,10 @@ def build(self, obj, factories: StructFactory, imported_structs: OldNewNames): data_block = pointer_type.collection[new_name] setattr(obj, self.name, data_block) + # this is collection property + elif prop.type == 'COLLECTION': + self._set_collection_values(obj, factories, imported_structs) + # this is property elif prop.is_valid: prop.value = self._struct @@ -768,7 +773,7 @@ def read(self) -> Tuple[BPYPointers, str]: old_obj_name = self._struct["value"] return pointer_type, old_obj_name - def _handle_collection_prop(self, col_prop, dependencies): + def _export_collection_values(self, col_prop, dependencies): collection = [] for item in col_prop.collection_to_list(): item_props = dict() @@ -779,6 +784,43 @@ def _handle_collection_prop(self, col_prop, dependencies): collection.append(item_props) return collection + def _set_collection_values(self, obj, factories, imported_structs): + """Assign Python data to collection property""" + collection = getattr(obj, self.name) + for item_index, item_values in enumerate(self._struct): + # Some collections can be empty, in this case they should be expanded to be able to get new values + if item_index == len(collection): + item = collection.add() + else: + item = collection[item_index] + + for prop_name, prop_value in item_values.items(): + factories.prop(prop_name, self.logger, prop_value).build(item, factories, imported_structs) + + +class MaterialStruct(Struct): + # this structure can be more complex if we want to save state of the material tree + type = StrTypes.MATERIAL + + def __init__(self, name, logger=None, structure=None): + default_struct = { + "name": "", + } + self.name = name + self.logger = logger + self._struct = structure or default_struct + + def export(self, mat, factories, dependencies): + self._struct["name"] = mat.name + return self._struct + + def build(self, factories, imported_structs): + with self.logger.add_fail("Build material", f"Name: {self.name}"): + material = bpy.data.materials.get(self._struct["name"]) + if material is None: + material = bpy.data.materials.new(self._struct["name"]) + imported_structs[(StrTypes.MATERIAL, '', self._struct["name"])] = material.name + class OldNewNames: # todo can't this be regular dictionary? """This class should solve problem of old new names, when created object with one name get another one""" From 4cab2153f258ad8c3b690c64f204a5e598297d3a Mon Sep 17 00:00:00 2001 From: Durman Date: Sun, 30 May 2021 19:50:42 +0400 Subject: [PATCH 14/29] simplify material structure --- utils/sv_json_struct.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index add39cce5f..0a8f8e11f6 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -803,23 +803,21 @@ class MaterialStruct(Struct): type = StrTypes.MATERIAL def __init__(self, name, logger=None, structure=None): - default_struct = { - "name": "", - } + default_struct = {} self.name = name self.logger = logger self._struct = structure or default_struct def export(self, mat, factories, dependencies): - self._struct["name"] = mat.name + # probably something will be added here in the future return self._struct def build(self, factories, imported_structs): with self.logger.add_fail("Build material", f"Name: {self.name}"): - material = bpy.data.materials.get(self._struct["name"]) + material = bpy.data.materials.get(self.name) if material is None: - material = bpy.data.materials.new(self._struct["name"]) - imported_structs[(StrTypes.MATERIAL, '', self._struct["name"])] = material.name + material = bpy.data.materials.new(self.name) + imported_structs[(StrTypes.MATERIAL, '', self.name)] = material.name class OldNewNames: # todo can't this be regular dictionary? From 3ff6111b67fbcb825b3fb8a39ccd2425f1850be9 Mon Sep 17 00:00:00 2001 From: Durman Date: Sun, 30 May 2021 20:26:44 +0400 Subject: [PATCH 15/29] add collection structure --- utils/sv_json_struct.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 0a8f8e11f6..1f486f286e 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -36,6 +36,7 @@ def __init__(self, factories: List[Type[Struct]]): StrTypes.LINK: 'link', StrTypes.PROP: 'prop', StrTypes.MATERIAL: 'material', + StrTypes.COLLECTION: 'collection' } self.tree: Optional[Type[Struct]] = None @@ -45,6 +46,7 @@ def __init__(self, factories: List[Type[Struct]]): self.link: Optional[Type[Struct]] = None self.prop: Optional[Type[Struct]] = None self.material: Optional[Type[Struct]] = None + self.collection: Optional[Type[Struct]] = None for factory in factories: if factory.type in self._factory_names: @@ -85,12 +87,14 @@ class StrTypes(Enum): LINK = auto() PROP = auto() MATERIAL = auto() + COLLECTION = auto() def get_bpy_pointer(self) -> BPYPointers: mapping = { StrTypes.TREE: BPYPointers.NODE_TREE, StrTypes.NODE: BPYPointers.NODE, StrTypes.MATERIAL: BPYPointers.MATERIAL, + StrTypes.COLLECTION: BPYPointers.COLLECTION, } if self not in mapping: raise TypeError(f'Given StrType: {self} is not a data block') @@ -102,6 +106,7 @@ def get_type(cls, block_type: BPYPointers) -> StrTypes: BPYPointers.NODE_TREE: StrTypes.TREE, BPYPointers.NODE: StrTypes.NODE, BPYPointers.MATERIAL: StrTypes.MATERIAL, + BPYPointers.COLLECTION: StrTypes.COLLECTION, } if block_type not in mapping: raise TypeError(f'Given block type: {block_type} is not among supported: {mapping.keys()}') @@ -820,6 +825,27 @@ def build(self, factories, imported_structs): imported_structs[(StrTypes.MATERIAL, '', self.name)] = material.name +class CollectionStruct(Struct): + type = StrTypes.COLLECTION + + def __init__(self, name, logger=None, struct=None): + default_struct = {} + self.name = name + self.logger = logger + self._struct = struct or default_struct + + def export(self, collection, factories, dependencies): + return self._struct + + def build(self, factories, imported_structs): + with self.logger.add_fail("Build collection", f"Name: {self.name}"): + collection = bpy.data.collections.get(self.name) + if collection is None: + collection = bpy.data.collections.new(self.name) + bpy.context.scene.collection.children.link(collection) + imported_structs[(StrTypes.COLLECTION, '', self.name)] = collection.name + + class OldNewNames: # todo can't this be regular dictionary? """This class should solve problem of old new names, when created object with one name get another one""" Old, New, Owner = str, str, str From b538db567e4fb6711e342eb4f1b1ce94b9de685f Mon Sep 17 00:00:00 2001 From: durman Date: Mon, 31 May 2021 16:43:16 +0400 Subject: [PATCH 16/29] add new node presets --- ui/development.py | 4 +- ui/presets.py | 12 +++- utils/sv_json_export.py | 7 ++- utils/sv_json_import.py | 10 +++- utils/sv_json_struct.py | 119 +++++++++++++++++++++++++++++++++++++--- 5 files changed, 135 insertions(+), 17 deletions(-) diff --git a/ui/development.py b/ui/development.py index f360f021b9..4a9272dca8 100644 --- a/ui/development.py +++ b/ui/development.py @@ -277,7 +277,7 @@ def draw(self, context): save = layout.operator(SvSaveSelected.bl_idname, text="Save current settings as node preset", icon='SOLO_ON') save.id_tree = ntree.name save.category = node.bl_idname - save.save_defaults = True + def idname_draw(self, context): if not displaying_sverchok_nodes(context): @@ -306,7 +306,7 @@ def idname_draw(self, context): save = save_row.operator(SvSaveSelected.bl_idname, text="Save Node Preset", icon='SOLO_ON') save.id_tree = ntree.name save.category = node.bl_idname - save.save_defaults = True + save.is_node_preset = True selected_nodes = [node for node in ntree.nodes if node.select] save_row.enabled = len(selected_nodes) == 1 diff --git a/ui/presets.py b/ui/presets.py index 5d54a60014..5b6a4c2f09 100644 --- a/ui/presets.py +++ b/ui/presets.py @@ -406,7 +406,7 @@ class SvSaveSelected(bpy.types.Operator): preset_name: StringProperty(name="Name", description="Preset name") id_tree: StringProperty() category: StringProperty() - save_defaults : BoolProperty(default = False) + is_node_preset: BoolProperty() def execute(self, context): if not self.id_tree: @@ -430,11 +430,16 @@ def execute(self, context): self.report({'ERROR'}, msg) return {'CANCELLED'} - layout_dict = JSONExporter.get_tree_structure(ng, True) + # the operator can be used for both preset importing of a node and preset from a bunch of selected nodes + if self.is_node_preset: + layout_dict = JSONExporter.get_node_structure(nodes[0]) + else: + layout_dict = JSONExporter.get_tree_structure(ng, True) + preset = SvPreset(name=self.preset_name, category = self.category) preset.make_add_operator() destination_path = preset.path - json.dump(layout_dict, open(destination_path, 'w'), sort_keys=True, indent=2) + json.dump(layout_dict, open(destination_path, 'w'), indent=2) # sort keys is not expected by the exporter msg = 'exported to: ' + destination_path self.report({"INFO"}, msg) info(msg) @@ -898,6 +903,7 @@ def draw(self, context): op = row.operator('node.sv_save_selected', text="Save Preset", icon='SOLO_ON') op.id_tree = ntree.name op.category = panel_props.category + op.is_node_preset = False selected_nodes = [node for node in ntree.nodes if node.select] can_save_preset = len(selected_nodes) > 0 diff --git a/utils/sv_json_export.py b/utils/sv_json_export.py index dd40fe1965..e5ebc5d97c 100644 --- a/utils/sv_json_export.py +++ b/utils/sv_json_export.py @@ -14,7 +14,7 @@ from sverchok.utils.sv_node_utils import recursive_framed_location_finder from sverchok.utils.handle_blender_data import BPYProperty from sverchok.utils.sv_IO_monad_helpers import pack_monad -from sverchok.utils.sv_json_struct import FileStruct +from sverchok.utils.sv_json_struct import FileStruct, NodePresetFileStruct if TYPE_CHECKING: from sverchok.node_tree import SverchCustomTree, SverchCustomTreeNode @@ -36,6 +36,11 @@ def get_tree_structure(tree: SverchCustomTree, use_selection=False) -> dict: else: return FileStruct().export_tree(tree, use_selection) + @staticmethod + def get_node_structure(node) -> dict: + """For exporting node properties""" + return NodePresetFileStruct().export(node) + @staticmethod def _get_nodes_structure(nodes: List[SverchCustomTreeNode]) -> dict: """Generate structure of given nodes which can be saved into json format""" diff --git a/utils/sv_json_import.py b/utils/sv_json_import.py index 17a142b4d6..d7b65efb78 100644 --- a/utils/sv_json_import.py +++ b/utils/sv_json_import.py @@ -20,7 +20,7 @@ from sverchok.utils.logging import info, warning, getLogger, logging from sverchok.utils.handle_blender_data import BPYProperty, BPYNode from sverchok.utils.sv_IO_monad_helpers import unpack_monad -from sverchok.utils.sv_json_struct import FileStruct +from sverchok.utils.sv_json_struct import FileStruct, NodePresetFileStruct if TYPE_CHECKING: from sverchok.node_tree import SverchCustomTree, SverchCustomTreeNode @@ -63,7 +63,13 @@ def import_into_tree(self, tree: SverchCustomTree, print_log: bool = True): build_update_list(tree) process_tree(tree) - def import_node_settings(self, node: SverchCustomTreeNode): # todo should be done something for new importer + def import_node_settings(self, node: SverchCustomTreeNode): + if self.structure_version < 1.0: + self._old_import_node_settings(node) + else: + NodePresetFileStruct(logger=self._fails_log, structure=self._structure).build(node) + + def _old_import_node_settings(self, node: SverchCustomTreeNode): """ It takes first node from file and apply its settings to given node It is strange but it is how it was originally implemented diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 1f486f286e..6a6dd9a0b7 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -52,9 +52,6 @@ def __init__(self, factories: List[Type[Struct]]): if factory.type in self._factory_names: factory_name = self._factory_names[factory.type] setattr(self, factory_name, factory) - # probably we would never want using file structure from inside others - elif factory.type == StrTypes.FILE: - continue else: raise TypeError(f'Factory with type: {factory.type}' f' is not among supported: {self._factory_names.keys()}') @@ -67,7 +64,7 @@ def get_factory(self, struct_type: StrTypes) -> Type[Struct]: raise TypeError(f'Given struct type: {struct_type} is not among supported {self._factory_names.keys()}') @classmethod - def gram_from_module(cls) -> StructFactory: + def grab_from_module(cls) -> StructFactory: """Grab all factories in the module""" factory_classes = [] module_classes = inspect.getmembers(sys.modules[__name__], @@ -79,7 +76,6 @@ def gram_from_module(cls) -> StructFactory: class StrTypes(Enum): - FILE = auto() TREE = auto() NODE = auto() SOCK = auto() @@ -150,8 +146,6 @@ def read_collection(self, collection: dict): class FileStruct(Struct): - type = StrTypes.FILE - def __init__(self, name=None, logger: FailsLog = None, struct: dict = None): self._struct: Dict[str, Any] = struct or {"export_version": str(self.version)} self.logger: FailsLog = logger @@ -165,7 +159,7 @@ def export_tree(self, tree, use_selection=False): raise TypeError(f'Only exporting main trees is supported, {tree.bl_label} is given') self._struct["main_tree"] = dict() - struct_factories = StructFactory.gram_from_module() # todo to args? + struct_factories = StructFactory.grab_from_module() # todo to args? dependencies: List[Tuple[BPYPointers, str]] = [] # export main tree first @@ -204,7 +198,7 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no with tree.throttle_update(): # todo it is required only for current update system can be deleted later?? - factories = StructFactory.gram_from_module() + factories = StructFactory.grab_from_module() imported_structs = OldNewNames() trees_to_build = [] version, main_tree, data_blocks = self.read() @@ -251,6 +245,113 @@ def _data_blocks_reader(self): # todo add logger? yield StrTypes[struct_type_name], block_name, block_struct +class NodePresetFileStruct(Struct): + def __init__(self, name=None, logger=None, structure=None): + default_struct = { + "export_version": str(self.version), + "node": dict(), + } + self.logger = logger + self._struct = structure or default_struct + + def export(self, node): + factories = StructFactory.grab_from_module() + dependencies: List[Tuple[BPYPointers, str]] = [] + + struct = factories.node(node.name, self.logger) + self._struct["node"][node.name] = struct.export(node, factories, dependencies) + + while dependencies: + block_type, block_name = dependencies.pop() + struct_type = StrTypes.get_type(block_type) + if struct_type.name not in self._struct: + self._struct[struct_type.name] = dict() + if block_name not in self._struct[struct_type.name]: + factory = factories.get_factory(struct_type) + data_block = block_type.collection[block_name] + structure = factory(block_name, self.logger).export(data_block, factories, dependencies) + self._struct[struct_type.name][block_name] = structure + + return self._struct + + def build(self, node): + tree = node.id_data + with tree.throttle_update(), tree.init_tree(): # todo throttle can be deleted later + + factories = StructFactory.grab_from_module() + imported_structs = OldNewNames() + version, data_blocks = self.read() + trees_to_build = [] + + # initialize trees and build other data block types + for struct_type, block_name, raw_struct in data_blocks: + if struct_type == StrTypes.TREE: # in case it was group node + tree_struct = factories.tree(block_name, self.logger, raw_struct) + data_block = bpy.data.node_groups.new(block_name, tree_struct.read_bl_type()) + tree_struct.build_interface(data_block, factories, imported_structs) + imported_structs[(struct_type, '', block_name)] = data_block.name + trees_to_build.append(tree_struct) + else: + # all data block except node trees + block_struct = factories.get_factory(struct_type)(block_name, self.logger, raw_struct) + block_struct.build(factories, imported_structs) + + for tree_struct in trees_to_build: + new_name = imported_structs[StrTypes.TREE, '', tree_struct.name] + data_block = bpy.data.node_groups[new_name] + tree_struct.build(data_block, factories, imported_structs) + + # now it's time to update the node, we have to save its links first because they will be removed + links = [] + for link in _ordered_links(tree): + if link.from_node.name == node.name or link.to_node.name == node.name: + link_struct = factories.link(None, self.logger) + link_struct.export(link, factories, []) + links.append(link_struct) + + # recreate node from scratch, this need for resetting all its properties to default + node_name, raw_struct = next(iter(self._struct["node"].items())) + node_struct = factories.node(node_name, self.logger, raw_struct) + location = node.location[:] # without copying it looks like gives straight references to memory + tree.nodes.remove(node) + node = tree.nodes.new(node_struct.read_bl_type()) + node.name = node_name + node.select = True + tree.nodes.active = node + imported_structs[StrTypes.NODE, tree.name, node_name] = node.name + + # all nodes should be as if they was imported with new names before linking + for node in tree.nodes: + imported_structs[StrTypes.NODE, tree.name, node.name] = node.name + + # import the node and rebuild the links if possible + node_struct.build(node, factories, imported_structs) + node.location = location # return to initial position, it has to be after node build + for link_struct in links: + try: + link_struct.build(tree, factories, imported_structs) + except LookupError: # the node seems has different sockets + pass + # how it should work with group node links is not clear + # because they are bound to identifiers of the group tree input outputs + # for now breaking links will be considered as desired behaviour + + node.process_node(bpy.context) + + def read(self): + with self.logger.add_fail("Reading version of the file"): + version = float(self._struct["export_version"]) + + return version, self._data_blocks_reader() + + def _data_blocks_reader(self): # todo add logger? + struct_type: StrTypes + for struct_type_name, structures in self._struct.items(): + if struct_type_name in (it.name for it in StrTypes): + for block_name, block_struct in structures.items(): + yield StrTypes[struct_type_name], block_name, block_struct + + class TreeStruct(Struct): type = StrTypes.TREE From 214494a3f44dbd62b43b6b816472d44a61983ee3 Mon Sep 17 00:00:00 2001 From: durman Date: Tue, 1 Jun 2021 09:59:53 +0400 Subject: [PATCH 17/29] refactoring of the file structure and add properties to imported trees --- core/node_group.py | 13 ++-- utils/sv_json_struct.py | 132 ++++++++++++++++++++++++---------------- 2 files changed, 88 insertions(+), 57 deletions(-) diff --git a/core/node_group.py b/core/node_group.py index 4d90647309..eaaf5fd8a0 100644 --- a/core/node_group.py +++ b/core/node_group.py @@ -32,12 +32,17 @@ class SvGroupTree(bpy.types.NodeTree): handler = MainHandler - group_node_name: bpy.props.StringProperty() # should be updated by "Go to edit group tree" operator - tree_id_memory: bpy.props.StringProperty(default="") # identifier of the tree, should be used via `tree_id` - skip_tree_update: bpy.props.BoolProperty() # useless for API consistency # todo should be removed later + # should be updated by "Go to edit group tree" operator + group_node_name: bpy.props.StringProperty(options={'SKIP_SAVE'}) + + # identifier of the tree, should be used via `tree_id` + tree_id_memory: bpy.props.StringProperty(default="", options={'SKIP_SAVE'}) + + # useless for API consistency # todo should be removed later + skip_tree_update: bpy.props.BoolProperty(options={'SKIP_SAVE'}) # Always False, does not have sense to have for nested trees, sine of draft mode refactoring - sv_draft: bpy.props.BoolProperty() + sv_draft: bpy.props.BoolProperty(options={'SKIP_SAVE'}) @property def tree_id(self): diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index 6a6dd9a0b7..b78a463dc9 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -11,7 +11,6 @@ import sys from abc import abstractmethod, ABC from enum import Enum, auto -from itertools import chain from typing import Type, Generator, TYPE_CHECKING, Dict, Tuple, Optional, List, Any import bpy @@ -147,7 +146,14 @@ def read_collection(self, collection: dict): class FileStruct(Struct): def __init__(self, name=None, logger: FailsLog = None, struct: dict = None): - self._struct: Dict[str, Any] = struct or {"export_version": str(self.version)} + default_struct = { + "export_version": str(self.version), + "main_tree": { + "nodes": dict(), + "links": [] + } + } + self._struct: Dict[str, Any] = struct or default_struct self.logger: FailsLog = logger def export(self): @@ -158,18 +164,11 @@ def export_tree(self, tree, use_selection=False): if tree.bl_idname != 'SverchCustomTreeType': raise TypeError(f'Only exporting main trees is supported, {tree.bl_label} is given') - self._struct["main_tree"] = dict() struct_factories = StructFactory.grab_from_module() # todo to args? dependencies: List[Tuple[BPYPointers, str]] = [] # export main tree first - factory = struct_factories.get_factory(StrTypes.TREE) - struct = factory(tree.name, self.logger) - if use_selection: - raw_struct = struct.export_nodes([n for n in tree.nodes if n.select], struct_factories, dependencies) - else: - raw_struct = struct.export(tree, struct_factories, dependencies) - self._struct["main_tree"][tree.name] = raw_struct + self._export_nodes(tree, struct_factories, dependencies, use_selection) # it looks good place for exporting dependent data blocks because probably we do not always want to export them # from this place we have more control over it @@ -186,6 +185,19 @@ def export_tree(self, tree, use_selection=False): return self._struct + def _export_nodes(self, tree, factories, dependencies, use_selection=False): + """Structure of main tree""" + nodes = tree.nodes if not use_selection else [n for n in tree.nodes if n.select] + for node in nodes: + raw_struct = factories.node(node.name, self.logger).export(node, factories, dependencies) + self._struct["main_tree"]["nodes"][node.name] = raw_struct + + input_node_names = {node.name for node in nodes} + for link in _ordered_links(tree): + if link.from_node.name in input_node_names and link.to_node.name in input_node_names: + raw_struct = factories.link(None, self.logger).export(link, factories, dependencies) + self._struct["main_tree"]["links"].append(raw_struct) + def build(self, *_): raise NotImplementedError @@ -200,42 +212,65 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no factories = StructFactory.grab_from_module() imported_structs = OldNewNames() - trees_to_build = [] - version, main_tree, data_blocks = self.read() + version, data_blocks = self.read() # initialize trees and build other data block types - for struct_type, block_name, raw_struct in chain(main_tree, data_blocks): + trees_to_build = [] + for struct_type, block_name, raw_struct in data_blocks: if struct_type == StrTypes.TREE: tree_struct = factories.tree(block_name, self.logger, raw_struct) - if not trees_to_build: - # this is first tree and it should be main, does not need create anything - data_block = tree - else: - # this is node group tree, should be created? - data_block = bpy.data.node_groups.new(block_name, tree_struct.read_bl_type()) - # interface should be created before building all trees - tree_struct.build_interface(data_block, factories, imported_structs) + data_block = bpy.data.node_groups.new(block_name, tree_struct.read_bl_type()) + # interface should be created before building all trees + tree_struct.build_interface(data_block, factories, imported_structs) imported_structs[(struct_type, '', block_name)] = data_block.name trees_to_build.append(tree_struct) else: - # all data block except node trees block_struct = factories.get_factory(struct_type)(block_name, self.logger, raw_struct) block_struct.build(factories, imported_structs) + # build main tree nodes + self._build_nodes(tree, factories, imported_structs) + + # build group trees for tree_struct in trees_to_build: # build all trees new_name = imported_structs[StrTypes.TREE, '', tree_struct.name] data_block = bpy.data.node_groups[new_name] tree_struct.build(data_block, factories, imported_structs) + def _build_nodes(self, tree, factories, imported_structs): + """Build nodes of the main tree, other dependencies should be already initialized""" + with tree.init_tree(): + # first all nodes should be created without applying their inner data + # because some nodes can have `parent` property which points into another node + node_structs = [] + for node_name, raw_structure in self._struct["main_tree"]["nodes"].items(): + node_struct = factories.node(node_name, self.logger, raw_structure) + + # register optional node classes + if old_nodes.is_old(node_struct.read_bl_type()): + old_nodes.register_old(node_struct.read_bl_type()) + if dummy_nodes.is_dependent(node_struct.read_bl_type()): + dummy_nodes.register_dummy(node_struct.read_bl_type()) + + # add node an save its new name + node = tree.nodes.new(node_struct.read_bl_type()) + node.name = node_name + imported_structs[(StrTypes.NODE, tree.name, node_name)] = node.name + node_structs.append(node_struct) + + for node_struct in node_structs: + new_name = imported_structs[(StrTypes.NODE, tree.name, node_struct.name)] + node = tree.nodes[new_name] + node_struct.build(node, factories, imported_structs) + + for raw_struct in self._struct["main_tree"]["links"]: + factories.link(None, self.logger, raw_struct).build(tree, factories, imported_structs) + def read(self): with self.logger.add_fail("Reading version of the file"): version = float(self._struct["export_version"]) - - main_tree_struct = self._struct.get("main_tree") - main_tree_reader = ((StrTypes.TREE, name, raw_struct) for name, raw_struct in main_tree_struct.items()) - - return version, main_tree_reader, self._data_blocks_reader() + return version, self._data_blocks_reader() def _data_blocks_reader(self): # todo add logger? struct_type: StrTypes @@ -361,6 +396,7 @@ def __init__(self, name: str, logger: FailsLog, structure: dict = None): "links": [], "inputs": dict(), "outputs": dict(), + "properties": dict(), "bl_idname": "", } self._struct = structure or default_structure @@ -368,7 +404,6 @@ def __init__(self, name: str, logger: FailsLog, structure: dict = None): self.logger = logger def export(self, tree, factories: StructFactory, dependencies) -> dict: - # todo export tree properties, for all trees? only for group trees? for node in tree.nodes: raw_struct = factories.node(node.name, self.logger).export(node, factories, dependencies) self._struct['nodes'][node.name] = raw_struct @@ -384,36 +419,19 @@ def export(self, tree, factories: StructFactory, dependencies) -> dict: raw_struct = factories.interface(socket.name, self.logger).export(socket, factories, dependencies) self._struct["outputs"][socket.identifier] = raw_struct - self._struct["bl_idname"] = tree.bl_idname - return self._struct - - def export_nodes(self, nodes, factories: StructFactory, dependencies) -> dict: - tree = nodes[0].id_data - for node in nodes: - if node.id_data != tree: - raise TypeError(f"Given node from different trees: {tree.name} and {node.id_data.name}") - raw_struct = factories.node(node.name, self.logger).export(node, factories, dependencies) - self._struct["nodes"][node.name] = raw_struct - - input_node_names = {node.name for node in nodes} - for link in _ordered_links(tree): - if link.from_node.name in input_node_names and link.to_node.name in input_node_names: - self._struct["links"].append(factories.link(None, self.logger).export(link, factories, dependencies)) - - for socket in tree.inputs: - raw_struct = factories.interface(socket.name, self.logger).export(socket, factories, dependencies) - self._struct["inputs"][socket.identifier] = raw_struct - - for socket in tree.outputs: - raw_struct = factories.interface(socket.name, self.logger).export(socket, factories, dependencies) - self._struct["outputs"][socket.identifier] = raw_struct + for prop_name in tree.keys(): + prop = BPYProperty(tree, prop_name) + if prop.is_valid and prop.is_to_save: + raw_struct = factories.prop(prop.name, self.logger).export(prop, factories, dependencies) + if raw_struct is not None: + self._struct["properties"][prop.name] = raw_struct self._struct["bl_idname"] = tree.bl_idname return self._struct def build(self, tree, factories: StructFactory, imported_structs: OldNewNames): """Reads and generates nodes, links, dependent data blocks""" - nodes, links = self.read() + nodes, links, props = self.read() # first all nodes should be created without applying their inner data # because some nodes can have `parent` property which points into another node @@ -441,6 +459,10 @@ def build(self, tree, factories: StructFactory, imported_structs: OldNewNames): for raw_struct in links: factories.link(None, self.logger, raw_struct).build(tree, factories, imported_structs) + for prop_name, prop_value in props: + with self.logger.add_fail("Setting tree property", f'Tree: {node.id_data.name}, prop: {prop_name}'): + factories.prop(prop_name, self.logger, prop_value).build(tree, factories, imported_structs) + def read(self): with self.logger.add_fail("Reading nodes"): nodes_struct = self._struct["nodes"] @@ -450,7 +472,11 @@ def read(self): links_struct = self._struct["links"] links_reader = (link_struct for link_struct in links_struct) - return nodes_reader, links_reader + with self.logger.add_fail("Reading properties"): + prop_struct = self._struct["properties"] + prop_reader = self.read_collection(prop_struct) + + return nodes_reader, links_reader, prop_reader def build_interface(self, tree, factories, imported_structs): inputs, outputs = self.read_interface_data() From 6a5934f10f9336a3a904804e8dc9fdff5654ca9c Mon Sep 17 00:00:00 2001 From: durman Date: Tue, 1 Jun 2021 14:56:28 +0400 Subject: [PATCH 18/29] add more accurate logging and make more elements optional to make a JSON file shorter --- utils/sv_json_struct.py | 463 +++++++++++++++++----------------------- 1 file changed, 198 insertions(+), 265 deletions(-) diff --git a/utils/sv_json_struct.py b/utils/sv_json_struct.py index b78a463dc9..cb8f275f82 100644 --- a/utils/sv_json_struct.py +++ b/utils/sv_json_struct.py @@ -119,7 +119,7 @@ def is_supported_block(cls, block_type: BPYPointers) -> bool: class Struct(ABC): # I was trying to make API of all abstract method the same between child classes but it looks it's not possible # for example to build a node we can send to method a tree where the node should be duilded - version = 1.0 # override the property if necessary + version = 1.0 type: StrTypes = None # should be overridden @abstractmethod @@ -138,11 +138,6 @@ def read_bl_type(self) -> str: """typically should return bl_idname of the structure""" return None - def read_collection(self, collection: dict): - for name, structure in collection.items(): - with self.logger.add_fail("Reading collection"): - yield name, structure - class FileStruct(Struct): def __init__(self, name=None, logger: FailsLog = None, struct: dict = None): @@ -212,31 +207,32 @@ def build_into_tree(self, tree): # todo add protection from exporting inside no factories = StructFactory.grab_from_module() imported_structs = OldNewNames() - version, data_blocks = self.read() + data_blocks = self._data_blocks_reader() # initialize trees and build other data block types trees_to_build = [] for struct_type, block_name, raw_struct in data_blocks: - if struct_type == StrTypes.TREE: - tree_struct = factories.tree(block_name, self.logger, raw_struct) - data_block = bpy.data.node_groups.new(block_name, tree_struct.read_bl_type()) - # interface should be created before building all trees - tree_struct.build_interface(data_block, factories, imported_structs) - imported_structs[(struct_type, '', block_name)] = data_block.name - trees_to_build.append(tree_struct) - else: - block_struct = factories.get_factory(struct_type)(block_name, self.logger, raw_struct) - block_struct.build(factories, imported_structs) + with self.logger.add_fail("Initialize data block", f"Type: {struct_type.name}, Name: {block_name}"): + if struct_type == StrTypes.TREE: + tree_struct = factories.tree(block_name, self.logger, raw_struct) + data_block = bpy.data.node_groups.new(block_name, tree_struct.read_bl_type()) + # interface should be created before building all trees + tree_struct.build_interface(data_block, factories, imported_structs) + imported_structs[(struct_type, '', block_name)] = data_block.name + trees_to_build.append(tree_struct) + else: + block_struct = factories.get_factory(struct_type)(block_name, self.logger, raw_struct) + block_struct.build(factories, imported_structs) # build main tree nodes self._build_nodes(tree, factories, imported_structs) # build group trees for tree_struct in trees_to_build: - # build all trees - new_name = imported_structs[StrTypes.TREE, '', tree_struct.name] - data_block = bpy.data.node_groups[new_name] - tree_struct.build(data_block, factories, imported_structs) + with self.logger.add_fail("Build node group", f"Name: {tree_struct.name}"): + new_name = imported_structs[StrTypes.TREE, '', tree_struct.name] + data_block = bpy.data.node_groups[new_name] + tree_struct.build(data_block, factories, imported_structs) def _build_nodes(self, tree, factories, imported_structs): """Build nodes of the main tree, other dependencies should be already initialized""" @@ -245,39 +241,38 @@ def _build_nodes(self, tree, factories, imported_structs): # because some nodes can have `parent` property which points into another node node_structs = [] for node_name, raw_structure in self._struct["main_tree"]["nodes"].items(): - node_struct = factories.node(node_name, self.logger, raw_structure) + with self.logger.add_fail("Init node (main tree)", f"Name: {node_name}"): + node_struct = factories.node(node_name, self.logger, raw_structure) - # register optional node classes - if old_nodes.is_old(node_struct.read_bl_type()): - old_nodes.register_old(node_struct.read_bl_type()) - if dummy_nodes.is_dependent(node_struct.read_bl_type()): - dummy_nodes.register_dummy(node_struct.read_bl_type()) + # register optional node classes + if old_nodes.is_old(node_struct.read_bl_type()): + old_nodes.register_old(node_struct.read_bl_type()) + if dummy_nodes.is_dependent(node_struct.read_bl_type()): + dummy_nodes.register_dummy(node_struct.read_bl_type()) - # add node an save its new name - node = tree.nodes.new(node_struct.read_bl_type()) - node.name = node_name - imported_structs[(StrTypes.NODE, tree.name, node_name)] = node.name - node_structs.append(node_struct) + # add node an save its new name + node = tree.nodes.new(node_struct.read_bl_type()) + node.name = node_name + imported_structs[(StrTypes.NODE, tree.name, node_name)] = node.name + node_structs.append(node_struct) for node_struct in node_structs: - new_name = imported_structs[(StrTypes.NODE, tree.name, node_struct.name)] - node = tree.nodes[new_name] - node_struct.build(node, factories, imported_structs) + with self.logger.add_fail("Build node (main tree)", f"Name {node_struct.name}"): + new_name = imported_structs[(StrTypes.NODE, tree.name, node_struct.name)] + node = tree.nodes[new_name] + node_struct.build(node, factories, imported_structs) for raw_struct in self._struct["main_tree"]["links"]: - factories.link(None, self.logger, raw_struct).build(tree, factories, imported_structs) - - def read(self): - with self.logger.add_fail("Reading version of the file"): - version = float(self._struct["export_version"]) - return version, self._data_blocks_reader() + with self.logger.add_fail("Build link (main tree)", f"Struct: {raw_struct}"): + factories.link(None, self.logger, raw_struct).build(tree, factories, imported_structs) - def _data_blocks_reader(self): # todo add logger? + def _data_blocks_reader(self): struct_type: StrTypes for struct_type_name, structures in self._struct.items(): if struct_type_name in (it.name for it in StrTypes): - for block_name, block_struct in structures.items(): - yield StrTypes[struct_type_name], block_name, block_struct + with self.logger.add_fail("Reading data blocks", f"Type: {struct_type_name}"): + for block_name, block_struct in structures.items(): + yield StrTypes[struct_type_name], block_name, block_struct class NodePresetFileStruct(Struct): @@ -315,11 +310,10 @@ def build(self, node): factories = StructFactory.grab_from_module() imported_structs = OldNewNames() - version, data_blocks = self.read() trees_to_build = [] # initialize trees and build other data block types - for struct_type, block_name, raw_struct in data_blocks: + for struct_type, block_name, raw_struct in self._data_blocks_reader(): if struct_type == StrTypes.TREE: # in case it was group node tree_struct = factories.tree(block_name, self.logger, raw_struct) data_block = bpy.data.node_groups.new(block_name, tree_struct.read_bl_type()) @@ -373,18 +367,13 @@ def build(self, node): node.process_node(bpy.context) - def read(self): - with self.logger.add_fail("Reading version of the file"): - version = float(self._struct["export_version"]) - - return version, self._data_blocks_reader() - - def _data_blocks_reader(self): # todo add logger? + def _data_blocks_reader(self): struct_type: StrTypes for struct_type_name, structures in self._struct.items(): if struct_type_name in (it.name for it in StrTypes): - for block_name, block_struct in structures.items(): - yield StrTypes[struct_type_name], block_name, block_struct + with self.logger.add_fail("Reading data blocks", f"Type: {struct_type_name}"): + for block_name, block_struct in structures.items(): + yield StrTypes[struct_type_name], block_name, block_struct class TreeStruct(Struct): @@ -427,78 +416,57 @@ def export(self, tree, factories: StructFactory, dependencies) -> dict: self._struct["properties"][prop.name] = raw_struct self._struct["bl_idname"] = tree.bl_idname + + if not self._struct["properties"]: + del self._struct["properties"] + return self._struct def build(self, tree, factories: StructFactory, imported_structs: OldNewNames): """Reads and generates nodes, links, dependent data blocks""" - nodes, links, props = self.read() - - # first all nodes should be created without applying their inner data - # because some nodes can have `parent` property which points into another node - node_structs = [] - for node_name, raw_structure in nodes: - node_struct = factories.node(node_name, self.logger, raw_structure) - - # register optional node classes - if old_nodes.is_old(node_struct.read_bl_type()): - old_nodes.register_old(node_struct.read_bl_type()) - if dummy_nodes.is_dependent(node_struct.read_bl_type()): - dummy_nodes.register_dummy(node_struct.read_bl_type()) - - # add node an save its new name - node = tree.nodes.new(node_struct.read_bl_type()) - node.name = node_name - imported_structs[(StrTypes.NODE, tree.name, node_name)] = node.name - node_structs.append(node_struct) - - for node_struct in node_structs: - new_name = imported_structs[(StrTypes.NODE, tree.name, node_struct.name)] - node = tree.nodes[new_name] - node_struct.build(node, factories, imported_structs) - - for raw_struct in links: - factories.link(None, self.logger, raw_struct).build(tree, factories, imported_structs) - - for prop_name, prop_value in props: - with self.logger.add_fail("Setting tree property", f'Tree: {node.id_data.name}, prop: {prop_name}'): - factories.prop(prop_name, self.logger, prop_value).build(tree, factories, imported_structs) - - def read(self): - with self.logger.add_fail("Reading nodes"): - nodes_struct = self._struct["nodes"] - nodes_reader = self.read_collection(nodes_struct) + with tree.init_tree(): + # first all nodes should be created without applying their inner data + # because some nodes can have `parent` property which points into another node + node_structs = [] + for node_name, raw_structure in self._struct["nodes"].items(): + with self.logger.add_fail("Init node", f"Tree: {tree.name}, Node: {node_name}"): + node_struct = factories.node(node_name, self.logger, raw_structure) + + # register optional node classes + if old_nodes.is_old(node_struct.read_bl_type()): + old_nodes.register_old(node_struct.read_bl_type()) + if dummy_nodes.is_dependent(node_struct.read_bl_type()): + dummy_nodes.register_dummy(node_struct.read_bl_type()) + + # add node an save its new name + node = tree.nodes.new(node_struct.read_bl_type()) + node.name = node_name + imported_structs[(StrTypes.NODE, tree.name, node_name)] = node.name + node_structs.append(node_struct) - with self.logger.add_fail("Reading links"): - links_struct = self._struct["links"] - links_reader = (link_struct for link_struct in links_struct) + for node_struct in node_structs: + with self.logger.add_fail("Build node", f"Tree: {tree.name}, Node: {node_struct.name}"): + new_name = imported_structs[(StrTypes.NODE, tree.name, node_struct.name)] + node = tree.nodes[new_name] + node_struct.build(node, factories, imported_structs) - with self.logger.add_fail("Reading properties"): - prop_struct = self._struct["properties"] - prop_reader = self.read_collection(prop_struct) + for raw_struct in self._struct["links"]: + with self.logger.add_fail("Build link", f"Tree: {tree.name}, Struct: {raw_struct}"): + factories.link(None, self.logger, raw_struct).build(tree, factories, imported_structs) - return nodes_reader, links_reader, prop_reader + for prop_name, prop_value in self._struct.get("properties", []): + with self.logger.add_fail("Setting tree property", f'Tree: {node.id_data.name}, prop: {prop_name}'): + factories.prop(prop_name, self.logger, prop_value).build(tree, factories, imported_structs) def build_interface(self, tree, factories, imported_structs): - inputs, outputs = self.read_interface_data() - # create tree sockets - with self.logger.add_fail("Create tree socket"): - for sock_name, raw_struct in inputs: + for sock_name, raw_struct in self._struct["inputs"].items(): + with self.logger.add_fail("Create tree in socket", f"Tree: {tree.name}, Sock: {sock_name}"): factories.interface(sock_name, self.logger, raw_struct).build(tree.inputs, factories, imported_structs) - with self.logger.add_fail("Create tree socket"): - for sock_name, raw_struct in outputs: + for sock_name, raw_struct in self._struct["outputs"].items(): + with self.logger.add_fail("Create tree out socket", f"Tree: {tree.name}, Sock: {sock_name}"): factories.interface(sock_name, self.logger, raw_struct).build(tree.outputs, factories, imported_structs) - def read_interface_data(self): - with self.logger.add_fail("Reading inputs"): - input_struct = self._struct["inputs"] - input_reader = self.read_collection(input_struct) - - with self.logger.add_fail("Reading outpus"): - outputs_struct = self._struct["outputs"] - output_reader = self.read_collection(outputs_struct) - return input_reader, output_reader - def read_bl_type(self): return self._struct["bl_idname"] @@ -508,7 +476,16 @@ class NodeStruct(Struct): def __init__(self, name: str, logger: FailsLog, structure: dict = None): default_structure = { - "attributes": dict(), + "attributes": { + "location": (0, 0), + "height": None, + "width": None, + "label": '', + "hide": False, + "color": (0, 0, 0), + "use_custom_color": False, + "parent": None, + }, "properties": dict(), "inputs": dict(), "outputs": dict(), @@ -517,34 +494,24 @@ def __init__(self, name: str, logger: FailsLog, structure: dict = None): self._struct = structure or default_structure self.name = name self.logger = logger - self._attributes = { - "height": None, - "width": None, - "label": '', - "hide": False, - "location": (0, 0), - "color": (0, 0, 0), - "use_custom_color": False, - "parent": None - } def export(self, node, factories: StructFactory, dependencies) -> dict: # add_mandatory_attributes self._struct['bl_idname'] = node.bl_idname - self._struct["attributes"]['height'] = node.height - self._struct["attributes"]['width'] = node.width - self._struct["attributes"]['label'] = node.label - self._struct["attributes"]['hide'] = node.hide self._struct["attributes"]['location'] = recursive_framed_location_finder(node, node.location[:]) - # optional attributes - if node.use_custom_color: - self._struct["attributes"]['color'] = node.color[:] - self._struct["attributes"]['use_custom_color'] = True + _set_optional(self._struct["attributes"], 'height', node.height, node.height != 100.0) + _set_optional(self._struct["attributes"], 'width', node.width, node.width != 140.0) + _set_optional(self._struct["attributes"], "label", node.label) + _set_optional(self._struct["attributes"], "hide", node.hide) + _set_optional(self._struct["attributes"], "use_custom_color", node.use_custom_color) + _set_optional(self._struct["attributes"], "color", node.color[:], node.use_custom_color) if node.parent: # the node is inside of a frame node prop = BPYProperty(node, "parent") raw_struct = factories.prop("parent", self.logger).export(prop, factories, dependencies) self._struct["attributes"]["parent"] = raw_struct + else: + del self._struct["attributes"]["parent"] # add non default node properties for prop_name in node.keys(): @@ -554,6 +521,8 @@ def export(self, node, factories: StructFactory, dependencies) -> dict: if raw_struct is not None: # todo check every where self._struct["properties"][prop.name] = raw_struct + _set_optional(self._struct, "properties", self._struct["properties"]) + # all sockets should be kept in a file because it's possible to create UI # where sockets would be defined by pressing buttons for example like in the node group interface. # there is no sense of exporting information about sockets of group input and output nodes @@ -567,20 +536,21 @@ def export(self, node, factories: StructFactory, dependencies) -> dict: raw_struct = factories.sock(socket.identifier, self.logger).export(socket, factories, dependencies) self._struct["outputs"][socket.identifier] = raw_struct - if hasattr(node, 'save_to_json'): + _set_optional(self._struct, "inputs", self._struct["inputs"]) + _set_optional(self._struct, "outputs", self._struct["outputs"]) + + if hasattr(node, 'save_to_json'): # todo pass empty dictionary? node.save_to_json(self._struct) return self._struct def build(self, node, factories: StructFactory, imported_data: OldNewNames): - # todo it would be nice? to add context to logger of what is currently is being created (not only here) - attributes, properties, inputs, outputs = self.read() - for attr_name, attr_value in attributes: + for attr_name, attr_value in self._struct["attributes"].items(): with self.logger.add_fail("Setting node attribute", f'Tree: {node.id_data.name}, Node: {node.name}, attr: {attr_name}'): factories.prop(attr_name, self.logger, attr_value).build(node, factories, imported_data) - for prop_name, prop_value in properties: + for prop_name, prop_value in self._struct.get("properties", dict()).items(): with self.logger.add_fail("Setting node property", f'Tree: {node.id_data.name}, Node: {node.name}, prop: {prop_name}'): factories.prop(prop_name, self.logger, prop_value).build(node, factories, imported_data) @@ -594,13 +564,15 @@ def build(self, node, factories: StructFactory, imported_data: OldNewNames): # clearing and adding sockets of Group input and Group output nodes # immediately cause their rebuilding by Blender, so JSON file does not save information about their sockets. node.inputs.clear() - for sock_identifier, raw_struct in inputs: - with self.logger.add_fail(f"Add socket: {sock_identifier} to node {node.name}"): + for sock_identifier, raw_struct in self._struct.get("inputs", dict()).items(): + with self.logger.add_fail("Add in socket", + f"Tree: {node.id_data.name}, Node {node.name}, Sock: {sock_identifier}"): factories.sock(sock_identifier, self.logger, raw_struct).build(node.inputs, factories, imported_data) node.outputs.clear() - for sock_identifier, raw_struct in outputs: - with self.logger.add_fail(f"Add socket: {sock_identifier} ot node {node.name}"): + for sock_identifier, raw_struct in self._struct.get("outputs", dict()).items(): + with self.logger.add_fail("Add out socket", + f"Tree: {node.id_data.name}, Node {node.name}, Sock: {sock_identifier}"): factories.sock(sock_identifier, self.logger, raw_struct).build(node.outputs, factories, imported_data) if hasattr(node, 'load_from_json'): @@ -608,26 +580,6 @@ def build(self, node, factories: StructFactory, imported_data: OldNewNames): f'Tree: {node.id_data.name}, Node: {node.name}'): node.load_from_json(self._struct, self.version) - def read(self): - """Reads node attributes from node structure, returns (attr_name, value)""" - with self.logger.add_fail("Reading node attributes"): - attrs_struct = self._struct["attributes"] - attributes = self.read_collection(attrs_struct) - - with self.logger.add_fail("Reading node properties"): - props_struct = self._struct["properties"] - properties = self.read_collection(props_struct) - - with self.logger.add_fail("Reading input sockets"): - input_struct = self._struct["inputs"] - inputs = self.read_collection(input_struct) - - with self.logger.add_fail("Reading output sockets"): - output_struct = self._struct["outputs"] - outputs = self.read_collection(output_struct) - - return attributes, properties, inputs, outputs - def read_bl_type(self): return self._struct['bl_idname'] @@ -641,7 +593,9 @@ def __init__(self, identifier, logger: FailsLog, structure: dict = None): "bl_idname": "", "name": "", "tree": "", # util information for group nodes about the name of their node tree - "attributes": dict(), + "attributes": { + "hide": False, + }, "properties": dict(), } self.identifier = identifier @@ -651,11 +605,10 @@ def __init__(self, identifier, logger: FailsLog, structure: dict = None): def export(self, socket, factories, dependencies): self._struct['bl_idname'] = socket.bl_idname self._struct['name'] = socket.name - if hasattr(socket.node, 'node_tree'): - self._struct['tree'] = socket.node.node_tree.name - else: - del self._struct['tree'] - self._struct['attributes']['hide'] = socket.hide + + _set_optional(self._struct, 'tree', socket.node.node_tree.name if hasattr(socket.node, 'node_tree') else '') + _set_optional(self._struct["attributes"], 'hide', socket.hide) + _set_optional(self._struct, "attributes", self._struct["attributes"]) for prop_name in socket.keys(): prop = BPYProperty(socket, prop_name) @@ -663,11 +616,13 @@ def export(self, socket, factories, dependencies): raw_struct = factories.prop(prop.name, self.logger).export(prop, factories, dependencies) if raw_struct is not None: self._struct["properties"][prop.name] = raw_struct + _set_optional(self._struct, "properties", self._struct["properties"]) return self._struct def build(self, sockets, factories, imported_structs): - name, group_tree_name, attributes, properties = self.read() + name = self._struct['name'] + group_tree_name = self._struct.get('tree') # check whether the socket is of group tree if group_tree_name is not None: @@ -678,34 +633,21 @@ def build(self, sockets, factories, imported_structs): else: identifier = self.identifier - # create the socket in the method because identifier is hidden is shown only inside the class + # create the socket in the method because identifier(name) is hidden is shown only inside the class socket = sockets.new(self.read_bl_type(), name, identifier=identifier) - for attr_name, attr_value in attributes: + for attr_name, attr_value in self._struct.get("attributes", dict()).items(): with self.logger.add_fail( "Setting socket attribute", # socket.node can be None sometimes 0_o f'Tree: {socket.id_data.name}, socket: {socket.name}, attr: {attr_name}'): factories.prop(attr_name, self.logger, attr_value).build(socket, factories, imported_structs) - for prop_name, prop_value in properties: + for prop_name, prop_value in self._struct.get("properties", dict()).items(): with self.logger.add_fail( # I think when socket is just created socket.node is None due Blender limitation "Setting socket property", f'Tree: {socket.id_data.name}, socket: {socket.name}, prop: {prop_name}'): factories.prop(prop_name, self.logger, prop_value).build(socket, factories, imported_structs) - def read(self): - with self.logger.add_fail("Reading socket attributes"): - attrs_struct = self._struct["attributes"] - attributes = self.read_collection(attrs_struct) - name = self._struct['name'] - tree = self._struct.get('tree') - - with self.logger.add_fail("Reading socket properties"): - props_struct = self._struct["properties"] - properties = self.read_collection(props_struct) - - return name, tree, attributes, properties - def read_bl_type(self) -> str: with self.logger.add_fail("Reading socket bl_idname"): return self._struct['bl_idname'] @@ -719,7 +661,6 @@ def __init__(self, identifier, logger: FailsLog, structure=None): "bl_idname": "", "name": "", "properties": dict(), - "attributes": dict() } self.identifier = identifier self.logger = logger @@ -733,44 +674,26 @@ def export(self, socket, factories, dependencies): prop = BPYProperty(socket, prop_name) if prop.is_valid and prop.is_to_save: raw_struct = factories.prop(prop.name, self.logger).export(prop, factories, dependencies) - self._struct["properties"][prop.name] = raw_struct + if raw_struct is not None: + self._struct["properties"][prop.name] = raw_struct + _set_optional(self._struct, "properties", self._struct["properties"]) return self._struct def build(self, sockets, factories, imported_structs): - attributes, properties, name = self.read() - + name = self._struct["name"] # create the socket in the method because identifier is hidden is shown only inside the class - with self.logger.add_fail("Create interface socket"): - interface_class = bpy.types.NodeSocketInterface.bl_rna_get_subclass_py(self.read_bl_type()) - socket_type = interface_class.bl_socket_idname - socket = sockets.new(socket_type, name) # the method gives its own identifier - imported_structs[self.type, socket.id_data.name, self.identifier] = socket.identifier - - for attr_name, attr_value in attributes: - with self.logger.add_fail( - "Setting interface socket attribute", # socket.node can be None sometimes 0_o - f'Tree: {socket.id_data.name}, socket: {socket.name}, attr: {attr_name}'): - factories.prop(attr_name, self.logger, attr_value).build(socket, factories, imported_structs) + interface_class = bpy.types.NodeSocketInterface.bl_rna_get_subclass_py(self.read_bl_type()) + socket_type = interface_class.bl_socket_idname + socket = sockets.new(socket_type, name) # the method gives its own identifier + imported_structs[self.type, socket.id_data.name, self.identifier] = socket.identifier - for prop_name, prop_value in properties: + for prop_name, prop_value in self._struct.get("properties", dict()).items(): with self.logger.add_fail( "Setting interface socket property", f'Tree: {socket.id_data.name}, socket: {socket.name}, prop: {prop_name}'): factories.prop(prop_name, self.logger, prop_value).build(socket, factories, imported_structs) - def read(self): - with self.logger.add_fail("Reading interface socket attributes"): - attrs_struct = self._struct["attributes"] - attributes = self.read_collection(attrs_struct) - name = self._struct["name"] - - with self.logger.add_fail("Reading interface socket properties"): - props_struct = self._struct["properties"] - properties = self.read_collection(props_struct) - - return attributes, properties, name - def read_bl_type(self) -> str: with self.logger.add_fail("Reading interface socket bl_idname"): return self._struct['bl_idname'] @@ -783,8 +706,11 @@ def __init__(self, name=None, logger: FailsLog = None, structure: dict = None): default_struct = { "from_node": "", "from_socket": "", # identifier + "from_tree": "", "to_node": "", - "to_socket": ""} # identifier + "to_socket": "", # identifier + "to_tree": "", + } self._struct = structure or default_struct self.logger = logger @@ -792,19 +718,28 @@ def export(self, link, *_): self._struct["from_node"] = link.from_node.name self._struct["from_socket"] = link.from_socket.identifier if hasattr(link.from_node, 'node_tree'): - self._struct["from_tree"] = link.from_node.node_tree.name + _set_optional(self._struct, 'from_tree', link.from_node.node_tree.name) elif link.from_node.bl_idname == 'NodeGroupInput': - self._struct["from_tree"] = link.id_data.name + _set_optional(self._struct, "from_tree", link.id_data.name) + else: + _set_optional(self._struct, "from_tree", "") self._struct["to_node"] = link.to_node.name self._struct["to_socket"] = link.to_socket.identifier if hasattr(link.to_node, 'node_tree'): - self._struct["to_tree"] = link.to_node.node_tree.name + _set_optional(self._struct, "to_tree", link.to_node.node_tree.name) elif link.to_node.bl_idname == 'NodeGroupOutput': - self._struct["to_tree"] = link.id_data.name + _set_optional(self._struct, "to_tree", link.id_data.name) + else: + _set_optional(self._struct, "to_tree", "") return self._struct def build(self, tree, factories: StructFactory, imported_structs: OldNewNames): - from_node_name, from_sock_identifier, from_tree, to_node_name, to_sock_identifier, to_tree = self.read() + from_node_name = self._struct["from_node"] + from_sock_identifier = self._struct["from_socket"] + from_tree = self._struct.get("from_tree") + to_node_name = self._struct["to_node"] + to_sock_identifier = self._struct["to_socket"] + to_tree = self._struct.get("to_tree") # all nodes can has different names from_node_new_name = imported_structs[(factories.node.type, tree.name, from_node_name)] @@ -827,11 +762,6 @@ def build(self, tree, factories: StructFactory, imported_structs: OldNewNames): if from_socket and to_socket: tree.links.new(to_socket, from_socket) - def read(self): - with self.logger.add_fail("Read socket data"): - return self._struct["from_node"], self._struct["from_socket"], self._struct.get("from_tree"), \ - self._struct["to_node"], self._struct["to_socket"], self._struct.get("to_tree") - def _search_socket(self, node, socket_identifier: str, sock_type): with self.logger.add_fail(f"Building link, trying to find socket {socket_identifier}"): for sock in node.inputs if sock_type == "INPUT" else node.outputs: @@ -868,42 +798,40 @@ def export(self, prop: BPYProperty, _, dependencies): else: return None else: - return prop.value + # default values should be persistent per Sverchok releases - no need to save + if prop.value != prop.default_value: + return prop.value + else: + return None def build(self, obj, factories: StructFactory, imported_structs: OldNewNames): - with self.logger.add_fail("Assigning value", f"Name: {self.name}"): - prop = BPYProperty(obj, self.name) - - # this is structure (pointer property) - if isinstance(self._struct, dict): - pointer_type, old_obj_name = self.read() - if pointer_type == BPYPointers.NODE: - new_name = imported_structs[(StrTypes.get_type(pointer_type), obj.id_data.name, old_obj_name)] - # this should work in case obj is a node or socket - # but in other cases probably extra information should be kept in the property structure - data_block = obj.id_data.nodes[new_name] - else: - new_name = imported_structs[(StrTypes.get_type(pointer_type), '', old_obj_name)] - data_block = pointer_type.collection[new_name] - setattr(obj, self.name, data_block) + prop = BPYProperty(obj, self.name) - # this is collection property - elif prop.type == 'COLLECTION': - self._set_collection_values(obj, factories, imported_structs) + # this is structure (pointer property) + if isinstance(self._struct, dict): + pointer_type = BPYPointers[self._struct["type"]] + old_obj_name = self._struct["value"] + if pointer_type == BPYPointers.NODE: + new_name = imported_structs[(StrTypes.get_type(pointer_type), obj.id_data.name, old_obj_name)] + # this should work in case obj is a node or socket + # but in other cases probably extra information should be kept in the property structure + data_block = obj.id_data.nodes[new_name] + else: + new_name = imported_structs[(StrTypes.get_type(pointer_type), '', old_obj_name)] + data_block = pointer_type.collection[new_name] + setattr(obj, self.name, data_block) - # this is property - elif prop.is_valid: - prop.value = self._struct + # this is collection property + elif prop.type == 'COLLECTION': + self._set_collection_values(obj, factories, imported_structs) - # this is attribute - else: - setattr(obj, self.name, self._struct) + # this is property + elif prop.is_valid: + prop.value = self._struct - def read(self) -> Tuple[BPYPointers, str]: - with self.logger.add_fail(f"Reading property (value): {self.name}"): - pointer_type = BPYPointers[self._struct["type"]] - old_obj_name = self._struct["value"] - return pointer_type, old_obj_name + # this is attribute + else: + setattr(obj, self.name, self._struct) def _export_collection_values(self, col_prop, dependencies): collection = [] @@ -945,11 +873,10 @@ def export(self, mat, factories, dependencies): return self._struct def build(self, factories, imported_structs): - with self.logger.add_fail("Build material", f"Name: {self.name}"): - material = bpy.data.materials.get(self.name) - if material is None: - material = bpy.data.materials.new(self.name) - imported_structs[(StrTypes.MATERIAL, '', self.name)] = material.name + material = bpy.data.materials.get(self.name) + if material is None: + material = bpy.data.materials.new(self.name) + imported_structs[(StrTypes.MATERIAL, '', self.name)] = material.name class CollectionStruct(Struct): @@ -965,12 +892,11 @@ def export(self, collection, factories, dependencies): return self._struct def build(self, factories, imported_structs): - with self.logger.add_fail("Build collection", f"Name: {self.name}"): - collection = bpy.data.collections.get(self.name) - if collection is None: - collection = bpy.data.collections.new(self.name) - bpy.context.scene.collection.children.link(collection) - imported_structs[(StrTypes.COLLECTION, '', self.name)] = collection.name + collection = bpy.data.collections.get(self.name) + if collection is None: + collection = bpy.data.collections.new(self.name) + bpy.context.scene.collection.children.link(collection) + imported_structs[(StrTypes.COLLECTION, '', self.name)] = collection.name class OldNewNames: # todo can't this be regular dictionary? @@ -996,3 +922,10 @@ def _ordered_links(tree) -> Generator[bpy.types.NodeLink]: for input_socket in node.inputs: for link in input_socket.links: yield link + + +def _set_optional(data: dict, key, value, condition=None): + if condition if condition is not None else value: + data[key] = value + else: + del data[key] From 82bd855fe1cf9226249d6f6489838ddded651970 Mon Sep 17 00:00:00 2001 From: durman Date: Tue, 1 Jun 2021 15:29:57 +0400 Subject: [PATCH 19/29] add option for creating compact JSON files --- ui/sv_IO_panel.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/sv_IO_panel.py b/ui/sv_IO_panel.py index e44cbdc7e7..04fa7b37f7 100644 --- a/ui/sv_IO_panel.py +++ b/ui/sv_IO_panel.py @@ -117,6 +117,7 @@ class SvNodeTreeExporter(bpy.types.Operator): options={'HIDDEN'}) id_tree: bpy.props.StringProperty() + compact: bpy.props.BoolProperty(default=True, description="Compact representation of the JSON file") compress: bpy.props.BoolProperty() selected_only: bpy.props.BoolProperty(name="Selected only") @@ -141,7 +142,8 @@ def execute(self, context): warning(msg) return {'CANCELLED'} - json.dump(layout_dict, open(destination_path, 'w'), indent=2) # json_struct does not expect sort_keys = True + indent = None if self.compact else 2 + json.dump(layout_dict, open(destination_path, 'w'), indent=indent) # json_struct doesnt expect sort_keys = True msg = 'exported to: ' + destination_path self.report({"INFO"}, msg) info(msg) @@ -179,6 +181,7 @@ def draw(self, context): col = self.layout.column() # old syntax in <= 2.83 col.use_property_split = True + col.prop(self, 'compact') col.prop(self, 'selected_only') col.prop(self, 'compress', text="Create ZIP archive") From 03ca67d67a45db62119ee732685f89af7731bfe2 Mon Sep 17 00:00:00 2001 From: durman Date: Wed, 2 Jun 2021 16:36:17 +0400 Subject: [PATCH 20/29] replacing monads with node groups in tests and fixing related bugs --- nodes/scene/objects_in_lite.py | 7 +- old_nodes/__init__.py | 10 ++ tests/json_export_import_tests.py | 21 ++- ...port_test.py => node_group_import_test.py} | 4 +- tests/references/monad_1.json | 128 ------------------ tests/references/monad_1_ref.blend.gz | Bin 73567 -> 0 bytes tests/references/node_group_test.blend.gz | Bin 0 -> 836720 bytes tests/references/node_group_test.json.zip | Bin 0 -> 1762 bytes utils/sv_json_struct.py | 12 ++ utils/testing.py | 23 +++- 10 files changed, 58 insertions(+), 147 deletions(-) rename tests/{monad_import_test.py => node_group_import_test.py} (74%) delete mode 100644 tests/references/monad_1.json delete mode 100644 tests/references/monad_1_ref.blend.gz create mode 100644 tests/references/node_group_test.blend.gz create mode 100644 tests/references/node_group_test.json.zip diff --git a/nodes/scene/objects_in_lite.py b/nodes/scene/objects_in_lite.py index e34cb111a0..331fe45c85 100644 --- a/nodes/scene/objects_in_lite.py +++ b/nodes/scene/objects_in_lite.py @@ -146,11 +146,14 @@ def load_from_json(self, node_data: dict, import_version: float): if 'geom' not in node_data: return # looks like a node was empty when it was imported geom = node_data['geom'] - name = node_data['params']["obj_name"] + if import_version < 1.0: + name = node_data['params']["obj_name"] + else: + name = self.obj_name geom_dict = json.loads(geom) if not geom_dict: - print(self.name, 'contains no flatten geom') + self.debug(f'{self.name}, contains no flatten geom') return unrolled_geom = unflatten(geom_dict) diff --git a/old_nodes/__init__.py b/old_nodes/__init__.py index a684a2338f..5593f2cefd 100644 --- a/old_nodes/__init__.py +++ b/old_nodes/__init__.py @@ -128,6 +128,16 @@ def register_old(bl_id): error("Cannot find {} among old nodes".format(bl_id)) return None + +def register_all(): + """Register all old node classes""" + for bl_id in old_bl_idnames: + try: + register_old(bl_id) + except Exception as e: + print(e) + + def unregister_old(bl_id): global imported_mods mod = imported_mods.get(bl_id) diff --git a/tests/json_export_import_tests.py b/tests/json_export_import_tests.py index 7468b61adf..2401c1171e 100644 --- a/tests/json_export_import_tests.py +++ b/tests/json_export_import_tests.py @@ -58,26 +58,18 @@ def test_mesh_expr_export(self): raise(ImportError(importer.fail_massage)) -class MonadExportTest(ReferenceTreeTestCase): +class NodeGroupExportTest(ReferenceTreeTestCase): - reference_file_name = "monad_1_ref.blend.gz" + reference_file_name = "node_group_test.blend.gz" + reference_tree_name = "NodeGroupTest" - def setUp(self): - self.link_node_tree(tree_name="PulledCube") - super().setUp() - - @unittest.skip("Linking node tree with Monad node does not work correctly.") - def test_monad_export(self): + def test_node_group_export(self): export_result = JSONExporter.get_tree_structure(self.tree) importer = JSONImporter(export_result) importer.import_into_tree(self.tree, print_log=False) if importer.has_fails: raise(ImportError(importer.fail_massage)) - def tearDown(self): - remove_node_tree("PulledCube") - super().tearDown() - class ViewerTextExportTest(ReferenceTreeTestCase): @@ -101,3 +93,8 @@ def test_list_join_node_import(self): importer.import_into_tree(self.tree, print_log=False) if importer.has_fails: raise(ImportError(importer.fail_massage)) + + +if __name__ == '__main__': + import unittest + unittest.main(exit=False) diff --git a/tests/monad_import_test.py b/tests/node_group_import_test.py similarity index 74% rename from tests/monad_import_test.py rename to tests/node_group_import_test.py index 2d8f78f09e..ca154af051 100644 --- a/tests/monad_import_test.py +++ b/tests/node_group_import_test.py @@ -5,9 +5,9 @@ class MonadImportTest(SverchokTestCase): def test_monad_import(self): with self.temporary_node_tree("ImportedTree") as new_tree: - importer = JSONImporter.init_from_path(self.get_reference_file_path("monad_1.json")) + importer = JSONImporter.init_from_path(self.get_reference_file_path("node_group_test.json.zip")) importer.import_into_tree(new_tree, print_log=False) if importer.has_fails: raise (ImportError(importer.fail_massage)) - self.assert_node_input_equals("ImportedTree", "Monad", "Num X", [[4]]) + self.assert_node_input_equals("ImportedTree", "Group node (Alpha)", "Num X", [[4]]) diff --git a/tests/references/monad_1.json b/tests/references/monad_1.json deleted file mode 100644 index 0c91f5c655..0000000000 --- a/tests/references/monad_1.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "export_version": "0.10", - "framed_nodes": {}, - "groups": { - "Monad": "{\"export_version\": \"0.10\", \"framed_nodes\": {}, \"groups\": {}, \"nodes\": {\"Group Inputs Exp\": {\"bl_idname\": \"SvGroupInputsNodeExp\", \"height\": 100.0, \"width\": 140.0, \"label\": \"\", \"hide\": false, \"location\": [-25.839576721191406, 252.48233032226562], \"params\": {\"node_kind\": \"outputs\"}, \"custom_socket_props\": {}, \"color\": [0.8308190107345581, 0.911391019821167, 0.7545620203018188], \"use_custom_color\": true, \"outputs\": [[\"Num X\", \"SvStringsSocket\"]]}, \"Group Outputs Exp\": {\"bl_idname\": \"SvGroupOutputsNodeExp\", \"height\": 100.0, \"width\": 140.0, \"label\": \"\", \"hide\": false, \"location\": [584.160400390625, 252.48233032226562], \"params\": {\"node_kind\": \"inputs\"}, \"custom_socket_props\": {}, \"color\": [0.8308190107345581, 0.911391019821167, 0.7545620203018188], \"use_custom_color\": true, \"inputs\": [[\"vertices\", \"SvVerticesSocket\"], [\"polygons\", \"SvStringsSocket\"]]}, \"Plane MK2\": {\"bl_idname\": \"SvPlaneNodeMK2\", \"height\": 100.0, \"width\": 140.0, \"label\": \"\", \"hide\": false, \"location\": [184.16041564941406, 234.9845428466797], \"params\": {\"center\": true, \"numx\": 3, \"numy\": 3}, \"custom_socket_props\": {}, \"color\": [0.0, 0.5, 0.5], \"use_custom_color\": true}, \"Inset Special.001\": {\"bl_idname\": \"SvInsetSpecialMk2\", \"height\": 100.0, \"width\": 140.0, \"label\": \"\", \"hide\": false, \"location\": [404.1604309082031, 269.9801025390625], \"params\": {\"distance\": 1.46999990940094, \"inset\": 0.6700000166893005, \"make_inner\": 0}, \"custom_socket_props\": {\"0\": {\"is_mandatory\": true}, \"1\": {\"is_mandatory\": true, \"nesting_level\": 3}}}}, \"update_lists\": [[\"Inset Special.001\", 0, \"Group Outputs Exp\", 0], [\"Inset Special.001\", 1, \"Group Outputs Exp\", 1], [\"Group Inputs Exp\", 0, \"Plane MK2\", 0], [\"Group Inputs Exp\", 0, \"Plane MK2\", 1], [\"Plane MK2\", 0, \"Inset Special.001\", 0], [\"Plane MK2\", 2, \"Inset Special.001\", 1]], \"bl_idname\": \"SverchGroupTreeType\", \"cls_bl_idname\": \"SvGroupNodeMonad_140482759202207\"}" - }, - "nodes": { - "A Number": { - "bl_idname": "SvNumberNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 55.772071838378906, - 134.84036254882812 - ], - "params": { - "int_": 4, - "selected_mode": "int" - }, - "width": 140.0 - }, - "Monad": { - "bl_idname": "SvMonadGenericNode", - "color": [ - 0.8308190107345581, - 0.911391019821167, - 0.7545620203018188 - ], - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 374.74664306640625, - 261.3251953125 - ], - "params": { - "all_props": { - "cls_bl_idname": "SvGroupNodeMonad_140482759202207", - "float_props": {}, - "int_props": { - "ints_1_numx": { - "default": 3, - "description": "Number of vertices along X", - "min": 2, - "name": "N Verts X" - }, - "numx": { - "default": 3, - "description": "Number of vertices along X", - "min": 2, - "name": "N Verts X" - } - }, - "name": "Monad" - }, - "cls_dict": { - "cls_bl_idname": "SvGroupNodeMonad_140482759202207", - "input_template": [ - [ - "Num X", - "SvStringsSocket", - { - "prop_name": "ints_1_numx" - } - ] - ], - "output_template": [ - [ - "vertices", - "SvVerticesSocket" - ], - [ - "polygons", - "SvStringsSocket" - ] - ] - }, - "monad": "Monad" - }, - "use_custom_color": true, - "width": 140.0 - }, - "VD Experimental": { - "bl_idname": "SvViewerDrawMk4", - "color": [ - 1.0, - 0.30000001192092896, - 0.0 - ], - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 704.7466430664062, - 289.9302062988281 - ], - "params": { - "selected_draw_mode": "facet" - }, - "use_custom_color": true, - "width": 140.0 - } - }, - "update_lists": [ - [ - "A Number", - 0, - "Monad", - 0 - ], - [ - "Monad", - 0, - "VD Experimental", - 0 - ], - [ - "Monad", - 1, - "VD Experimental", - 2 - ] - ] -} \ No newline at end of file diff --git a/tests/references/monad_1_ref.blend.gz b/tests/references/monad_1_ref.blend.gz deleted file mode 100644 index 3713947c4e05c2439074d79ae6e7ce3107b36e5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73567 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 diff --git a/tests/references/node_group_test.blend.gz b/tests/references/node_group_test.blend.gz new file mode 100644 index 0000000000000000000000000000000000000000..06d609cbe33fe8f8c898ad9ad187b9ba3abe8648 GIT binary patch literal 836720 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

ASFKa{ch}le;#qNYLlZ__4$uNre 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 literal 0 HcmV?d00001 diff --git a/tests/references/node_group_test.json.zip b/tests/references/node_group_test.json.zip new file mode 100644 index 0000000000000000000000000000000000000000..2148ce475f396998aa7db0386758c1a91aa3a83b GIT binary patch literal 1762 zcmV<81|9iOO9KQH000080OLi%QYG$Q`B(-30ER9A02BZK0B&z&WnX7wI3e*XCw)Vr=%Tox^ z@(;X%*5YIVv%}wL)OE3 z9nGAX=A=!a8SZqZn>sK|SJO4%n$72bAO6@kUQ{<}vVmzHcIXnuxrY|pa?}h)Fv#J; zTO+vGD2Pn$VIIbtZO$^jMsHASX!R-b!bR+_3DdjX9kNlp%U!{`yOP;Hvb<&3$|IFti46*zhVw}hrH(65kM)`i}QSgx4t4ekgI>D z$#$b)KTv+Vh&C_&?=nulX&F;61FoUlnxoAei?WJtSX0Yz3}dES_RO$OKKM%vgYxeB zVg9}$wz#>yyz=h9U0pGowDY5FX)c%sFUd+F4Amj#?)4b7>}j%&7TQJSJM**zOshTe z3~n>%VG{H(Pq%Qw5G>%czm0OQ+){qhrJEyb>%S(^`!b1-Q*Bs`AW`5RvFi&H50}c-8`{Wl4ddL8WW)&DLCMc> zOxrM=nG0;4juEakbu}>49ow);5NXATw#|biN>Ww^%Kj8rFf{=A(KJ^Fu0C^Zh5~Q| zVP7fZb0rh|%=m~^CuJPw>&*P@2XJg|u8;27ePLWKBf#pAkrgs|F`#YftHp8%reQu* zF#AvO)NU+0EO2DbeZ=ZbEU};y2rQX##66%Bouta=(lG==37POHHaJSyKH{_0-m#gh zRcP*r?9$E0om-hEXz=@B13)BsH2m^d%Ctb6e7`Fg>Oe%haQ$>W!h zO;PSW+q0<+gdM#RMcHIil=~EcphPwx4J^}UyKbhoZME7=1Zz1bc5!{h}&H9%(tRz*G&owsXTTNxeexZgE6Lc2?OrRwv$ zqVSl+AsQSAV?Lb)2~?VB-(FzS3Ksn+8_S~%EbpVS+~sg1cVkM9r=rOaoAFyigiGC< zlA{bb;@#nRR?ozU+b56hl)HLmfx&ya+2ie9Fce7b?>Ubb+}U^X_k@x}k_4$5zvz95 zq)3sZs*;#WsuWqJ689V#vQWAQ*L9r1T)Er8AoQat0L*~+cyv{sPBDnH?U`!;Kx?b6{T1}WI41cQa8&C}rswteBSKx6 zd22sj_<54PceZBY$bD9qmII05fP3Q2;_ID5h@%oQA2v^;BGq2sswpXEGK; zO?bMdKj(DA8yp|ooCRv!K0o7p*f*zU)!nZg;Ku^@(Q8y)`Wk6nbQ z`fgf%44Pluf4lzv6BffCS(bJOsfGL~wGc`^PX-ftSQ;+F*pHMQ+MG-Y7lYhvs0tn2 zhw6hrb)HR~nz{HSX6(@JR5co01@dp;YiYW*pRJ_MO4^){Zq~>lEBlr9l)q-uxF^8O z<`t^DX$^weW@y3^cg&jGRLsP_98oe<{4u#QWMJR6mON&`omnA1HBk^{#_s Date: Wed, 2 Jun 2021 16:37:00 +0400 Subject: [PATCH 21/29] Replace monads with node groups in the examples --- json_examples/Advanced/Pineapple.json | 1929 --------- json_examples/Advanced/Pineapple.zip | Bin 0 -> 8930 bytes .../Architecture/Fasade_Complicated.json | 3830 ----------------- .../Architecture/Fasade_Complicated.zip | Bin 0 -> 15425 bytes 4 files changed, 5759 deletions(-) delete mode 100644 json_examples/Advanced/Pineapple.json create mode 100644 json_examples/Advanced/Pineapple.zip delete mode 100644 json_examples/Architecture/Fasade_Complicated.json create mode 100644 json_examples/Architecture/Fasade_Complicated.zip diff --git a/json_examples/Advanced/Pineapple.json b/json_examples/Advanced/Pineapple.json deleted file mode 100644 index 5a6a899204..0000000000 --- a/json_examples/Advanced/Pineapple.json +++ /dev/null @@ -1,1929 +0,0 @@ -{ - "export_version": "0.10", - "framed_nodes": { - "3pt Arc": "Frame.003", - "A Number": "Frame", - "A Number.001": "Frame", - "A Number.002": "Frame.003", - "A Number.003": "Frame.009", - "A Number.004": "Frame.009", - "A Number.005": "Frame.003", - "Arc 3pt (Curve)": "Frame.004", - "Area": "Frame", - "Color in": "Frame.010", - "Color in.001": "Frame.006", - "Component Analyzer": "Frame.002", - "Curve Mapper": "Frame.002", - "Curve Mapper.001": "Frame.007", - "Dual Mesh": "Frame.001", - "Evaluate Curve": "Frame.004", - "Frame.002": "Frame.010", - "Frame.007": "Frame.010", - "IcoSphere": "Frame.001", - "Inset Special.001": "Frame.001", - "Line": "Frame.003", - "List Join": "Frame.003", - "List Length": "Frame.008", - "List Length.001": "Frame.006", - "List Math": "Frame.007", - "List Split": "Frame.005", - "Logic functions": "Frame", - "Map Range": "Frame.007", - "Mask to Index": "Frame", - "Mesh viewer": "Frame.008", - "Mesh viewer.001": "Frame.006", - "Monad": "Frame.009", - "Monad.001": "Frame.009", - "Move": "Frame.003", - "Move.001": "Frame.003", - "Move.002": "Frame.003", - "MultiExtrude Alt from addons": "Frame.011", - "Number Range": "Frame.008", - "Number Range.001": "Frame.005", - "Number Range.002": "Frame.006", - "Number Range.003": "Frame.005", - "Object ID Out MK2": "Frame.006", - "Origins": "Frame.007", - "Polyline Viewer": "Frame.005", - "Polyline Viewer.001": "Frame.004", - "Reroute.001": "Frame.009", - "Reroute.002": "Frame.009", - "Scalar Math": "Frame.005", - "Scalar Math.001": "Frame.005", - "Scalar Math.002": "Frame.003", - "Scale": "Frame.001", - "Spiral": "Frame.003", - "Spiral.001": "Frame.003", - "Switch": "Frame", - "Vector in": "Frame.009", - "Vector in.001": "Frame.009", - "Vector in.002": "Frame.009", - "Vector in.003": "Frame.009", - "Vector out": "Frame.007", - "Vertex color mk3": "Frame.008", - "Vertex color mk3.001": "Frame.006" - }, - "groups": { - "Monad": "{\"export_version\": \"0.10\", \"framed_nodes\": {}, \"groups\": {}, \"nodes\": {\"Group Inputs Exp\": {\"bl_idname\": \"SvGroupInputsNodeExp\", \"height\": 100.0, \"width\": 140.0, \"label\": \"\", \"hide\": false, \"location\": [1028.47900390625, -0.3426551818847656], \"params\": {\"node_kind\": \"outputs\"}, \"custom_socket_props\": {}, \"color\": [0.8308190107345581, 0.911391019821167, 0.7545620203018188], \"use_custom_color\": true, \"outputs\": [[\"Vertices\", \"SvVerticesSocket\"], [\"Movement Vectors\", \"SvVerticesSocket\"], [\"Polygons\", \"SvStringsSocket\"], [\"Center\", \"SvVerticesSocket\"], [\"Radius\", \"SvStringsSocket\"]]}, \"Group Outputs Exp\": {\"bl_idname\": \"SvGroupOutputsNodeExp\", \"height\": 100.0, \"width\": 140.0, \"label\": \"\", \"hide\": false, \"location\": [1871.47509765625, -0.3426551818847656], \"params\": {\"node_kind\": \"inputs\"}, \"custom_socket_props\": {}, \"color\": [0.8308190107345581, 0.911391019821167, 0.7545620203018188], \"use_custom_color\": true, \"inputs\": [[\"Vertices\", \"SvVerticesSocket\"]]}, \"Move\": {\"bl_idname\": \"SvMoveNodeMk3\", \"height\": 100.0, \"width\": 140.0, \"label\": \"\", \"hide\": false, \"location\": [1691.47509765625, -18.33763885498047], \"params\": {\"movement_vectors\": [0.0, 0.0, 0.20000000298023224]}, \"custom_socket_props\": {\"1\": {\"expanded\": true}}}, \"Select mesh elements by location\": {\"bl_idname\": \"SvMeshSelectNodeMk2\", \"height\": 100.0, \"width\": 140.0, \"label\": \"\", \"hide\": false, \"location\": [1238.47900390625, 17.652328491210938], \"params\": {\"mode\": \"BySphere\", \"radius\": 0.6000000238418579, \"include_partial\": true}, \"custom_socket_props\": {\"0\": {\"is_mandatory\": true}, \"1\": {\"nesting_level\": 3}, \"2\": {\"nesting_level\": 3}, \"3\": {\"prop\": [0.0, 0.0, 1.0]}, \"4\": {\"prop\": [0.0, 0.0, -1.0], \"expanded\": true}}}, \"Proportional Edit Falloff\": {\"bl_idname\": \"SvProportionalEditNode\", \"height\": 100.0, \"width\": 166.3173828125, \"label\": \"\", \"hide\": false, \"location\": [1466.7452392578125, -14.141777992248535], \"params\": {\"radius\": 0.699999988079071, \"falloff_type\": \"sharp\"}, \"custom_socket_props\": {}}}, \"update_lists\": [[\"Move\", 0, \"Group Outputs Exp\", 0], [\"Group Inputs Exp\", 0, \"Move\", 0], [\"Group Inputs Exp\", 1, \"Move\", 1], [\"Proportional Edit Falloff\", 0, \"Move\", 2], [\"Group Inputs Exp\", 0, \"Select mesh elements by location\", 0], [\"Group Inputs Exp\", 2, \"Select mesh elements by location\", 2], [\"Group Inputs Exp\", 3, \"Select mesh elements by location\", 4], [\"Group Inputs Exp\", 4, \"Select mesh elements by location\", 6], [\"Group Inputs Exp\", 0, \"Proportional Edit Falloff\", 0], [\"Select mesh elements by location\", 0, \"Proportional Edit Falloff\", 1]], \"bl_idname\": \"SverchGroupTreeType\", \"cls_bl_idname\": \"SvGroupNodeMonad_140486095450048\"}" - }, - "nodes": { - "3pt Arc": { - "bl_idname": "svBasicArcNode", - "color": [ - 0.9200000166893005, - 0.9200000166893005, - 0.9200000166893005 - ], - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 4159.858367919922, - -80.02827548980713 - ], - "params": {}, - "use_custom_color": true, - "width": 140.0 - }, - "A Number": { - "bl_idname": "SvNumberNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": true, - "label": "", - "location": [ - 826.978271484375, - -233.7791290283203 - ], - "params": { - "int_": 1, - "selected_mode": "int" - }, - "width": 140.0 - }, - "A Number.001": { - "bl_idname": "SvNumberNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": true, - "label": "", - "location": [ - 826.978271484375, - -297.2085418701172 - ], - "params": { - "selected_mode": "int" - }, - "width": 140.0 - }, - "A Number.002": { - "bl_idname": "SvNumberNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3455.314910888672, - -158.1414270401001 - ], - "params": { - "int_": 8, - "selected_mode": "int" - }, - "width": 140.0 - }, - "A Number.003": { - "bl_idname": "SvNumberNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 1920.5932159423828, - -377.1818084716797 - ], - "params": { - "float_": 0.4000000059604645 - }, - "width": 140.0 - }, - "A Number.004": { - "bl_idname": "SvNumberNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2219.2947540283203, - -381.7967987060547 - ], - "params": { - "float_": 0.4000000059604645 - }, - "width": 140.0 - }, - "A Number.005": { - "bl_idname": "SvNumberNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3455.314910888672, - -235.46737003326416 - ], - "params": { - "int_": 5, - "selected_mode": "int" - }, - "width": 140.0 - }, - "Arc 3pt (Curve)": { - "bl_idname": "SvArc3ptCurveNode", - "custom_socket_props": { - "0": { - "expanded": true, - "prop": [ - -0.5, - 0.0, - 0.0 - ] - }, - "1": { - "expanded": true, - "prop": [ - 0.0, - 0.30000001192092896, - 0.0 - ] - }, - "2": { - "expanded": true, - "prop": [ - 0.5, - 0.0, - 0.0 - ] - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3151.761474609375, - 1608.2613525390625 - ], - "params": {}, - "width": 140.0 - }, - "Area": { - "bl_idname": "SvAreaNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 830.5361328125, - -70.78495788574219 - ], - "params": {}, - "width": 140.0 - }, - "Color in": { - "bl_idname": "SvColorsInNodeMK1", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2919.780397415161, - 609.8927307128906 - ], - "params": { - "g_": 0.699999988079071 - }, - "width": 110.0 - }, - "Color in.001": { - "bl_idname": "SvColorsInNodeMK1", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 4531.6654052734375, - 810.6104125976562 - ], - "params": { - "g_": 1.0, - "r_": 0.30000001192092896 - }, - "width": 110.0 - }, - "Component Analyzer": { - "bl_idname": "SvComponentAnalyzerNode", - "custom_socket_props": { - "0": { - "is_mandatory": true - }, - "1": { - "nesting_level": 3 - }, - "2": { - "is_mandatory": true, - "nesting_level": 3 - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2435.963991165161, - 1051.2159118652344 - ], - "params": { - "face_mode": "Perimeter" - }, - "width": 140.0 - }, - "Curve Mapper": { - "bl_idname": "SvCurveMapperNode", - "curve_data": "{\"group_name\": \"sverchok_helper_group\", \"bl_idname\": \"ShaderNodeRGBCurve\", \"data\": [[[\"AUTO\", [0.0, 0.0]], [\"AUTO\", [1.0, 1.0]]], [[\"AUTO\", [0.0, 0.0]], [\"AUTO\", [1.0, 1.0]]], [[\"AUTO\", [0.0, 0.0]], [\"AUTO\", [1.0, 1.0]]], [[\"AUTO\", [0.055555559694767, 0.11250001192092896]], [\"AUTO\", [0.21111096441745758, 0.581250011920929]], [\"AUTO\", [0.6222215294837952, 0.9875001907348633]], [\"AUTO\", [0.9444444179534912, 0.7124999165534973]]]]}", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2610.590944290161, - 1051.2159118652344 - ], - "params": {}, - "width": 200.0 - }, - "Curve Mapper.001": { - "bl_idname": "SvCurveMapperNode", - "curve_data": "{\"group_name\": \"sverchok_helper_group\", \"bl_idname\": \"ShaderNodeRGBCurve\", \"data\": [[[\"AUTO\", [0.0, 0.0]], [\"AUTO\", [1.0, 1.0]]], [[\"AUTO\", [0.0, 0.0]], [\"AUTO\", [1.0, 1.0]]], [[\"AUTO\", [0.0, 0.0]], [\"AUTO\", [1.0, 1.0]]], [[\"AUTO\", [0.17777776718139648, 0.8812499046325684]], [\"AUTO\", [0.48333311080932617, 0.4999999701976776]], [\"AUTO\", [0.9111111164093018, 0.9312501549720764]]]]}", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2502.5364475250244, - 612.1684875488281 - ], - "params": {}, - "width": 200.0 - }, - "Dual Mesh": { - "bl_idname": "SvDualMeshNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 1144.1441650390625, - 360.9542236328125 - ], - "params": {}, - "width": 140.0 - }, - "Evaluate Curve": { - "bl_idname": "SvExEvalCurveNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3353.988525390625, - 1610.490966796875 - ], - "params": { - "sample_size": 6 - }, - "width": 140.0 - }, - "Frame": { - "bl_idname": "NodeFrame", - "custom_socket_props": {}, - "height": 311.423583984375, - "hide": false, - "label": "mask small=green", - "location": [ - 1388.2783203125, - 166.85975646972656 - ], - "params": {}, - "width": 712.710205078125 - }, - "Frame.001": { - "bl_idname": "NodeFrame", - "custom_socket_props": {}, - "height": 451.6605529785156, - "hide": false, - "label": "init", - "location": [ - 1714.1441650390625, - 270.9542236328125 - ], - "params": {}, - "width": 708.5906982421875 - }, - "Frame.002": { - "bl_idname": "NodeFrame", - "color": [ - 0.6079999804496765, - 0.17259472608566284, - 0.12654002010822296 - ], - "custom_socket_props": {}, - "height": 424.2159423828125, - "hide": false, - "label": "rad", - "location": [ - 1955.2977313995361, - 1045.9466857910156 - ], - "params": {}, - "use_custom_color": true, - "width": 434.626953125 - }, - "Frame.003": { - "bl_idname": "NodeFrame", - "color": [ - 0.6079999804496765, - 0.6079999804496765, - 0.6079999804496765 - ], - "custom_socket_props": {}, - "height": 1019.98095703125, - "hide": false, - "label": "arcs", - "location": [ - 222.46994018554688, - -3.0290002822875977 - ], - "params": {}, - "use_custom_color": true, - "width": 923.622314453125 - }, - "Frame.004": { - "bl_idname": "NodeFrame", - "custom_socket_props": {}, - "height": 513.9317626953125, - "hide": false, - "label": "shape", - "location": [ - -1538.842529296875, - 1299.5205078125 - ], - "params": {}, - "width": 577.27880859375 - }, - "Frame.005": { - "bl_idname": "NodeFrame", - "custom_socket_props": {}, - "height": 519.2294921875, - "hide": false, - "label": "leafs", - "location": [ - -2082.97705078125, - 343.86724853515625 - ], - "params": {}, - "width": 813.14111328125 - }, - "Frame.006": { - "bl_idname": "NodeFrame", - "custom_socket_props": {}, - "height": 580.61376953125, - "hide": false, - "label": "meshing coloring", - "location": [ - -2000.0382080078125, - 470.0436706542969 - ], - "params": {}, - "width": 913.2109375 - }, - "Frame.007": { - "bl_idname": "NodeFrame", - "color": [ - 0.15021802484989166, - 0.6079999804496765, - 0.21782340109348297 - ], - "custom_socket_props": {}, - "height": 433.1684875488281, - "hide": false, - "label": "green", - "location": [ - 34.265207290649414, - -168.75625610351562 - ], - "params": {}, - "use_custom_color": true, - "width": 970.3638916015625 - }, - "Frame.008": { - "bl_idname": "NodeFrame", - "color": [ - 0.6079999804496765, - 0.6079999804496765, - 0.6079999804496765 - ], - "custom_socket_props": {}, - "height": 591.1061401367188, - "hide": false, - "label": "ananas-color", - "location": [ - -23.45445442199707, - -44.2061767578125 - ], - "params": {}, - "use_custom_color": true, - "width": 657.8564453125 - }, - "Frame.009": { - "bl_idname": "NodeFrame", - "custom_socket_props": {}, - "height": 586.7090454101562, - "hide": false, - "label": "smashing", - "location": [ - 133.9361114501953, - 131.7018585205078 - ], - "params": {}, - "width": 786.3482666015625 - }, - "Frame.010": { - "bl_idname": "NodeFrame", - "color": [ - 0.6079999804496765, - 0.6079999804496765, - 0.6079999804496765 - ], - "custom_socket_props": {}, - "height": 932.2159423828125, - "hide": false, - "label": "color", - "location": [ - -23.605833053588867, - 126.68020629882812 - ], - "params": {}, - "use_custom_color": true, - "width": 1150.6649169921875 - }, - "Frame.011": { - "bl_idname": "NodeFrame", - "custom_socket_props": {}, - "height": 714.8028564453125, - "hide": false, - "label": " ", - "location": [ - 8.582347869873047, - 41.7886848449707 - ], - "params": {}, - "width": 200.0 - }, - "IcoSphere": { - "bl_idname": "SvIcosphereNode", - "color": [ - 0.9200000166893005, - 0.9200000166893005, - 0.9200000166893005 - ], - "custom_socket_props": { - "0": { - "nesting_level": 1, - "pre_processing": "ONE_ITEM" - }, - "1": { - "nesting_level": 1, - "pre_processing": "ONE_ITEM" - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 804.1441650390625, - 360.9542236328125 - ], - "params": { - "subdivisions": 3 - }, - "use_custom_color": true, - "width": 140.0 - }, - "Inset Special.001": { - "bl_idname": "SvInsetSpecialMk2", - "custom_socket_props": { - "0": { - "is_mandatory": true - }, - "1": { - "is_mandatory": true, - "nesting_level": 3 - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 1312.73486328125, - 399.6605529785156 - ], - "params": { - "distance": 0.019999999552965164, - "inset": 0.8999999761581421 - }, - "width": 140.0 - }, - "Line": { - "bl_idname": "SvLineNodeMK4", - "color": [ - 0.9200000166893005, - 0.9200000166893005, - 0.9200000166893005 - ], - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3721.787567138672, - 440.98094844818115 - ], - "params": { - "direction": "Z", - "num": 25, - "size": 1.0 - }, - "use_custom_color": true, - "width": 140.0 - }, - "List Join": { - "bl_idname": "ListJoinNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 4178.936981201172, - 164.03279781341553 - ], - "params": { - "JoinLevel": 2, - "mix_check": true - }, - "width": 140.0 - }, - "List Length": { - "bl_idname": "ListLengthNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2758.098524093628, - -310.5350341796875 - ], - "params": {}, - "width": 140.0 - }, - "List Length.001": { - "bl_idname": "ListLengthNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 4186.7313232421875, - 753.499755859375 - ], - "params": {}, - "width": 140.0 - }, - "List Math": { - "bl_idname": "ListFuncNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2739.4793186187744, - 423.3693542480469 - ], - "params": { - "func_": "MAX" - }, - "width": 140.0 - }, - "List Split": { - "bl_idname": "SvListSplitNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3345.673828125, - 1073.198486328125 - ], - "params": {}, - "width": 140.0 - }, - "Logic functions": { - "bl_idname": "SvLogicNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 982.636962890625, - -70.78495788574219 - ], - "params": { - "i_y": 0.007000000216066837, - "items_": "BIG", - "prop_types": [ - true, - true - ] - }, - "width": 140.0 - }, - "Map Range": { - "bl_idname": "SvMapRangeNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2327.9180393218994, - 487.8794250488281 - ], - "params": { - "new_max": 1.0, - "old_max": 1.7100000381469727, - "old_min": -1.4800000190734863 - }, - "width": 140.0 - }, - "Mask to Index": { - "bl_idname": "SvMaskToIndexNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 1339.6884765625, - -70.78495788574219 - ], - "params": {}, - "width": 140.0 - }, - "Mesh viewer": { - "bl_idname": "SvMeshViewer", - "color": [ - 0.6284880042076111, - 0.931007981300354, - 1.0 - ], - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2961.895887374878, - 48.10614776611328 - ], - "params": {}, - "use_custom_color": true, - "width": 140.0 - }, - "Mesh viewer.001": { - "bl_idname": "SvMeshViewer", - "color": [ - 0.6284880042076111, - 0.931007981300354, - 1.0 - ], - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 4346.8465576171875, - 1101.6137390136719 - ], - "params": { - "base_data_name": "Delta", - "is_merge": true - }, - "use_custom_color": true, - "width": 140.0 - }, - "Monad": { - "bl_idname": "SvMonadGenericNode", - "color": [ - 0.8308190107345581, - 0.911391019821167, - 0.7545620203018188 - ], - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2084.380081176758, - 63.816627502441406 - ], - "params": { - "all_props": { - "cls_bl_idname": "SvGroupNodeMonad_140486095450048", - "float_props": { - "floats_1_radius": { - "default": 0.6000000238418579, - "min": 0.0, - "name": "Radius" - } - }, - "int_props": {}, - "name": "Monad" - }, - "cls_dict": { - "cls_bl_idname": "SvGroupNodeMonad_140486095450048", - "input_template": [ - [ - "Vertices", - "SvVerticesSocket", - {} - ], - [ - "Movement Vectors", - "SvVerticesSocket", - {} - ], - [ - "Polygons", - "SvStringsSocket", - {} - ], - [ - "Center", - "SvVerticesSocket", - {} - ], - [ - "Radius", - "SvStringsSocket", - { - "prop_name": "floats_1_radius" - } - ] - ], - "output_template": [ - [ - "Vertices", - "SvVerticesSocket" - ] - ] - }, - "monad": "Monad" - }, - "use_custom_color": true, - "width": 140.0 - }, - "Monad.001": { - "bl_idname": "SvMonadGenericNode", - "color": [ - 0.8308190107345581, - 0.911391019821167, - 0.7545620203018188 - ], - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2398.3426055908203, - 70.70904541015625 - ], - "params": { - "all_props": { - "cls_bl_idname": "SvGroupNodeMonad_140486095450048", - "float_props": { - "floats_1_radius": { - "default": 0.6000000238418579, - "min": 0.0, - "name": "Radius" - } - }, - "int_props": {}, - "name": "Monad" - }, - "cls_dict": { - "cls_bl_idname": "SvGroupNodeMonad_140486095450048", - "input_template": [ - [ - "Vertices", - "SvVerticesSocket", - {} - ], - [ - "Movement Vectors", - "SvVerticesSocket", - {} - ], - [ - "Polygons", - "SvStringsSocket", - {} - ], - [ - "Center", - "SvVerticesSocket", - {} - ], - [ - "Radius", - "SvStringsSocket", - { - "prop_name": "floats_1_radius" - } - ] - ], - "output_template": [ - [ - "Vertices", - "SvVerticesSocket" - ] - ] - }, - "monad": "Monad" - }, - "use_custom_color": true, - "width": 140.0 - }, - "Move": { - "bl_idname": "SvMoveNodeMk3", - "custom_socket_props": { - "1": { - "expanded": true - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3913.080535888672, - 205.84603023529053 - ], - "params": { - "movement_vectors": [ - 0.0, - 0.0, - 2.200000047683716 - ] - }, - "width": 140.0 - }, - "Move.001": { - "bl_idname": "SvMoveNodeMk3", - "custom_socket_props": { - "1": { - "expanded": true - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3919.724334716797, - -176.54200077056885 - ], - "params": { - "movement_vectors": [ - 0.0, - 0.0, - 2.5 - ] - }, - "width": 140.0 - }, - "Move.002": { - "bl_idname": "SvMoveNodeMk3", - "custom_socket_props": { - "1": { - "expanded": true - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3901.772430419922, - 428.88329219818115 - ], - "params": { - "movement_vectors": [ - 0.0, - 0.0, - 0.5 - ] - }, - "width": 140.0 - }, - "MultiExtrude Alt from addons": { - "bl_idname": "SvMultiExtrudeAlt", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 1633.909740447998, - 289.80282974243164 - ], - "params": { - "nrotx": 10.0, - "nroty": 10.0, - "num": 4, - "off": 0.029999999329447746, - "offz": 0.07999999821186066, - "sca": 0.5 - }, - "width": 140.0 - }, - "Note": { - "bl_idname": "NoteNode", - "color": [ - 1.0, - 0.8993440270423889, - 0.9742509722709656 - ], - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 873.7223510742188, - 597.8712158203125 - ], - "params": { - "n_id": "-9080898779879143203", - "text": "nikitron 2020 \\ pineapple" - }, - "use_custom_color": true, - "width": 400.0 - }, - "Number Range": { - "bl_idname": "SvGenNumberRange", - "custom_socket_props": { - "1": { - "label": "step" - }, - "2": { - "label": "count" - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2950.909559249878, - -311.7882385253906 - ], - "params": { - "current_mode": "RANGE_STEP", - "number_mode": "int", - "range_mode": "RANGE_STEP" - }, - "width": 140.0 - }, - "Number Range.001": { - "bl_idname": "SvGenNumberRange", - "custom_socket_props": { - "1": { - "label": "stop" - }, - "2": { - "label": "count" - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3130.8056640625, - 892.314453125 - ], - "params": { - "count_": 20, - "current_mode": "RANGE_COUNT", - "range_mode": "RANGE_COUNT", - "stop_float": 3.1415927410125732 - }, - "width": 140.0 - }, - "Number Range.002": { - "bl_idname": "SvGenNumberRange", - "custom_socket_props": { - "1": { - "label": "stop" - }, - "2": { - "label": "count" - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 4346.6141357421875, - 755.614501953125 - ], - "params": { - "count_": 8325, - "current_mode": "RANGE_COUNT", - "range_mode": "RANGE_COUNT", - "start_float": 1.0, - "stop_float": 0.41999998688697815 - }, - "width": 140.0 - }, - "Number Range.003": { - "bl_idname": "SvGenNumberRange", - "custom_socket_props": { - "1": { - "label": "stop" - }, - "2": { - "label": "count" - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3128.7529296875, - 1076.2294921875 - ], - "params": { - "count_": 20, - "current_mode": "RANGE_COUNT", - "range_mode": "RANGE_COUNT", - "start_float": 0.6399999260902405, - "stop_float": 1.5815927982330322 - }, - "width": 140.0 - }, - "Object ID Out MK2": { - "bl_idname": "SvObjectToMeshNodeMK2", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3985.8509521484375, - 922.445068359375 - ], - "params": { - "modifiers": true - }, - "width": 140.0 - }, - "Origins": { - "bl_idname": "SvOrigins", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 1969.115427017212, - 454.6520080566406 - ], - "params": { - "mode": "Faces" - }, - "width": 140.0 - }, - "Polyline Viewer": { - "bl_idname": "SvPolylineViewerNode", - "color": [ - 0.6284880042076111, - 0.931007981300354, - 1.0 - ], - "custom_socket_props": { - "2": { - "default_float_property": 0.20000000298023224 - }, - "4": { - "object_kinds": "CURVE" - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3741.89404296875, - 1070.5540771484375 - ], - "params": { - "base_data_name": "Zeta", - "is_merge": true, - "resolution": 1 - }, - "use_custom_color": true, - "width": 140.0 - }, - "Polyline Viewer.001": { - "bl_idname": "SvPolylineViewerNode", - "color": [ - 0.6284880042076111, - 0.931007981300354, - 1.0 - ], - "custom_socket_props": { - "2": { - "default_float_property": 1.0 - }, - "4": { - "object_kinds": "CURVE" - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3529.040283203125, - 1619.9317626953125 - ], - "params": { - "base_data_name": "Theta", - "bevel_depth": 0.0, - "curve_dimensions": "2D" - }, - "use_custom_color": true, - "width": 140.0 - }, - "Reroute.001": { - "bl_idname": "NodeReroute", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2619.8614044189453, - -46.028350830078125 - ], - "params": {}, - "width": 16.0 - }, - "Reroute.002": { - "bl_idname": "NodeReroute", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2638.9414825439453, - 3.9315948486328125 - ], - "params": {}, - "width": 16.0 - }, - "Scalar Math": { - "bl_idname": "SvScalarMathNodeMK4", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3353.6689453125, - 897.3230590820312 - ], - "params": { - "current_op": "SINE" - }, - "width": 140.0 - }, - "Scalar Math.001": { - "bl_idname": "SvScalarMathNodeMK4", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3556.45654296875, - 1003.8472900390625 - ], - "params": { - "current_op": "DIV", - "y_": 1.5 - }, - "width": 140.0 - }, - "Scalar Math.002": { - "bl_idname": "SvScalarMathNodeMK4", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3457.737518310547, - -363.9794397354126 - ], - "params": {}, - "width": 140.0 - }, - "Scale": { - "bl_idname": "SvScaleNodeMk3", - "custom_socket_props": { - "2": { - "expanded": true - } - }, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 964.1441650390625, - 360.9542236328125 - ], - "params": { - "scale": [ - 1.0, - 1.0, - 1.5 - ] - }, - "width": 140.0 - }, - "Spiral": { - "bl_idname": "SvSpiralNodeMK2", - "custom_socket_props": {}, - "height": 6.0, - "hide": false, - "label": "", - "location": [ - 3705.589324951172, - -175.07491397857666 - ], - "params": { - "eRadius": 0.6000000238418579, - "exponent": 0.0, - "height": 6.0, - "iRadius": 0.5, - "phase": 1.709999918937683, - "presets": " ", - "resolution": 5, - "sType": "SPHERICAL", - "scale": 1.5, - "turns": 5 - }, - "width": 170.0 - }, - "Spiral.001": { - "bl_idname": "SvSpiralNodeMK2", - "custom_socket_props": {}, - "height": 6.0, - "hide": false, - "label": "", - "location": [ - 3701.105682373047, - 213.3470220565796 - ], - "params": { - "eRadius": 0.4000000059604645, - "exponent": 1.0, - "height": 6.0, - "iRadius": 0.10000000149011612, - "presets": " ", - "resolution": 5, - "sType": "SPHERICAL", - "scale": 0.9399999976158142, - "turns": 5 - }, - "width": 170.0 - }, - "Switch": { - "bl_idname": "SvSwitchNodeMK2", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 1157.6025390625, - -70.78495788574219 - ], - "params": {}, - "width": 140.0 - }, - "Vector in": { - "bl_idname": "GenVectorsNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 1938.7565460205078, - -231.9490203857422 - ], - "params": { - "z_": -1.2999999523162842 - }, - "width": 100.0 - }, - "Vector in.001": { - "bl_idname": "GenVectorsNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 1943.7394561767578, - -86.71066284179688 - ], - "params": { - "z_": 0.20000000298023224 - }, - "width": 100.0 - }, - "Vector in.002": { - "bl_idname": "GenVectorsNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2237.4580841064453, - -236.56394958496094 - ], - "params": { - "z_": 2.0 - }, - "width": 100.0 - }, - "Vector in.003": { - "bl_idname": "GenVectorsNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2242.4409942626953, - -91.32559204101562 - ], - "params": { - "z_": 1.0 - }, - "width": 100.0 - }, - "Vector out": { - "bl_idname": "VectorsOutNode", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 2150.5113010406494, - 393.9389343261719 - ], - "params": {}, - "width": 100.0 - }, - "Vertex color mk3": { - "bl_idname": "SvVertexColorNodeMK3", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 3215.954969406128, - 4.958885192871094 - ], - "params": { - "mode": "polygons" - }, - "width": 140.0 - }, - "Vertex color mk3.001": { - "bl_idname": "SvVertexColorNodeMK3", - "custom_socket_props": {}, - "height": 100.0, - "hide": false, - "label": "", - "location": [ - 4699.0618896484375, - 970.0443115234375 - ], - "params": { - "mode": "polygons" - }, - "width": 140.0 - } - }, - "update_lists": [ - [ - "List Join", - 0, - "3pt Arc", - 1 - ], - [ - "Inset Special.001", - 0, - "Area", - 0 - ], - [ - "Inset Special.001", - 1, - "Area", - 1 - ], - [ - "Number Range.002", - 0, - "Color in.001", - 0 - ], - [ - "Scale", - 0, - "Dual Mesh", - 0 - ], - [ - "IcoSphere", - 2, - "Dual Mesh", - 2 - ], - [ - "Arc 3pt (Curve)", - 0, - "Evaluate Curve", - 0 - ], - [ - "Scalar Math.002", - 0, - "Line", - 0 - ], - [ - "Move.001", - 0, - "List Join", - 0 - ], - [ - "Move", - 0, - "List Join", - 1 - ], - [ - "Move.002", - 0, - "List Join", - 2 - ], - [ - "Reroute.001", - "Output", - "List Length", - "Data" - ], - [ - "Object ID Out MK2", - 3, - "List Length.001", - 0 - ], - [ - "Number Range.003", - 0, - "List Split", - 0 - ], - [ - "Area", - 0, - "Logic functions", - 0 - ], - [ - "Switch", - 0, - "Mask to Index", - 0 - ], - [ - "Reroute.002", - "Output", - "Mesh viewer", - "vertices" - ], - [ - "Reroute.001", - "Output", - "Mesh viewer", - "faces" - ], - [ - "Object ID Out MK2", - 0, - "Mesh viewer.001", - 0 - ], - [ - "Object ID Out MK2", - 3, - "Mesh viewer.001", - 2 - ], - [ - "MultiExtrude Alt from addons", - 0, - "Monad", - 0 - ], - [ - "Vector in.001", - 0, - "Monad", - 1 - ], - [ - "MultiExtrude Alt from addons", - 1, - "Monad", - 2 - ], - [ - "Vector in", - 0, - "Monad", - 3 - ], - [ - "A Number.003", - 0, - "Monad", - 4 - ], - [ - "Monad", - 0, - "Monad.001", - 0 - ], - [ - "Vector in.003", - 0, - "Monad.001", - 1 - ], - [ - "MultiExtrude Alt from addons", - 1, - "Monad.001", - 2 - ], - [ - "Vector in.002", - 0, - "Monad.001", - 3 - ], - [ - "A Number.004", - 0, - "Monad.001", - 4 - ], - [ - "Spiral.001", - 0, - "Move", - 0 - ], - [ - "Spiral", - 0, - "Move.001", - 0 - ], - [ - "Line", - 0, - "Move.002", - 0 - ], - [ - "Inset Special.001", - 0, - "MultiExtrude Alt from addons", - 0 - ], - [ - "Inset Special.001", - 1, - "MultiExtrude Alt from addons", - 1 - ], - [ - "Mask to Index", - 0, - "MultiExtrude Alt from addons", - 2 - ], - [ - "List Length", - 0, - "Number Range", - 2 - ], - [ - "List Length.001", - 0, - "Number Range.002", - 2 - ], - [ - "Polyline Viewer", - 0, - "Object ID Out MK2", - 0 - ], - [ - "3pt Arc", - 0, - "Polyline Viewer", - 0 - ], - [ - "Scalar Math.001", - 0, - "Polyline Viewer", - 2 - ], - [ - "Polyline Viewer.001", - 0, - "Polyline Viewer", - 4 - ], - [ - "Evaluate Curve", - 0, - "Polyline Viewer.001", - 0 - ], - [ - "MultiExtrude Alt from addons", - "faces", - "Reroute.001", - "Input" - ], - [ - "Monad.001", - "Vertices", - "Reroute.002", - "Input" - ], - [ - "Number Range.001", - 0, - "Scalar Math", - 0 - ], - [ - "Scalar Math", - 0, - "Scalar Math.001", - 0 - ], - [ - "List Split", - 0, - "Scalar Math.001", - 1 - ], - [ - "A Number.002", - 0, - "Scalar Math.002", - 0 - ], - [ - "A Number.005", - 0, - "Scalar Math.002", - 1 - ], - [ - "IcoSphere", - 0, - "Scale", - 0 - ], - [ - "A Number.002", - 0, - "Spiral", - 3 - ], - [ - "A Number.005", - 0, - "Spiral", - 4 - ], - [ - "A Number.002", - 0, - "Spiral.001", - 3 - ], - [ - "A Number.005", - 0, - "Spiral.001", - 4 - ], - [ - "Logic functions", - 0, - "Switch", - 0 - ], - [ - "A Number", - 0, - "Switch", - 1 - ], - [ - "A Number.001", - 0, - "Switch", - 2 - ], - [ - "Mesh viewer", - 0, - "Vertex color mk3", - 0 - ], - [ - "Number Range", - 0, - "Vertex color mk3", - 1 - ], - [ - "Color in", - 0, - "Vertex color mk3", - 2 - ], - [ - "Mesh viewer.001", - 0, - "Vertex color mk3.001", - 0 - ], - [ - "Color in.001", - 0, - "Vertex color mk3.001", - 2 - ], - [ - "Dual Mesh", - 0, - "Inset Special.001", - 0 - ], - [ - "Dual Mesh", - 1, - "Inset Special.001", - 1 - ], - [ - "Curve Mapper", - 0, - "Color in", - 0 - ], - [ - "Curve Mapper.001", - 0, - "Color in", - 1 - ], - [ - "MultiExtrude Alt from addons", - 0, - "Component Analyzer", - 0 - ], - [ - "MultiExtrude Alt from addons", - 1, - "Component Analyzer", - 2 - ], - [ - "Component Analyzer", - 0, - "Curve Mapper", - 0 - ], - [ - "Map Range", - 0, - "Curve Mapper.001", - 0 - ], - [ - "Vector out", - 2, - "List Math", - 0 - ], - [ - "Vector out", - 2, - "Map Range", - 0 - ], - [ - "MultiExtrude Alt from addons", - 0, - "Origins", - 0 - ], - [ - "MultiExtrude Alt from addons", - 1, - "Origins", - 2 - ], - [ - "Origins", - 0, - "Vector out", - 0 - ] - ] -} \ No newline at end of file diff --git a/json_examples/Advanced/Pineapple.zip b/json_examples/Advanced/Pineapple.zip new file mode 100644 index 0000000000000000000000000000000000000000..79677a7fe16b9498dd5e2617055b4207692f0982 GIT binary patch literal 8930 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%0AbN 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