diff --git a/__init__.py b/__init__.py index 439bd0aeee521a98ba87619b69036887a3ac2b2d..901a4408e2597635efcb3ac522f53f502d5d041e 100755 --- a/__init__.py +++ b/__init__.py @@ -77,7 +77,7 @@ node_list = make_node_list(nodes) if "bpy" in locals(): reload_event = True - node_list = handle_reload_event(nodes, imported_modules, old_nodes) + node_list = handle_reload_event(nodes, imported_modules) import bpy diff --git a/core/__init__.py b/core/__init__.py index bed6c63aaedc8636b124f1d0b8b2b6283eabf340..0a48ae48c7ecacf62479abed9f4cd2b31b5b75a0 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -13,7 +13,7 @@ root_modules = [ core_modules = [ "sv_custom_exceptions", "node_id_dict", "links", "sockets", - "handlers", "update_system", "upgrade_nodes", + "handlers", "update_system", "events", "node_group", "group_handlers" ] @@ -44,16 +44,13 @@ sv_registration_utils.register_all = sv_register_modules sv_registration_utils.unregister_all = sv_unregister_modules -def reload_all(imported_modules, node_list, old_nodes): +def reload_all(imported_modules, node_list): # reload base modules _ = [importlib.reload(im) for im in imported_modules] # reload nodes _ = [importlib.reload(node) for node in node_list] - old_nodes.reload_old() - - def make_node_list(nodes): node_list = [] @@ -70,21 +67,9 @@ def import_modules(modules, base, im_list): im_list.append(im) -def handle_reload_event(nodes, imported_modules, old_nodes): +def handle_reload_event(nodes, imported_modules): node_list = make_node_list(nodes) - reload_all(imported_modules, node_list, old_nodes) - - try: - from sverchok.old_nodes import old_bl_idnames - debug('Known old_bl_idnames after reload: %s', len(old_bl_idnames)) - except Exception as err: - exception(err) - try: - from sverchok.utils import dummy_nodes - debug('Known dummy_bl_idnames after reload: %s', len(dummy_nodes.dummy_nodes_dict)) - except Exception as err: - exception(err) - + reload_all(imported_modules, node_list) return node_list diff --git a/core/handlers.py b/core/handlers.py index 17bccac9d2b39054a54f4fdc3f730421b8f7f35e..2738eaa18d58979e31f7a4f6b0c6620afd0dd78c 100644 --- a/core/handlers.py +++ b/core/handlers.py @@ -1,17 +1,15 @@ -import traceback - import bpy from bpy.app.handlers import persistent from sverchok import old_nodes from sverchok import data_structure -from sverchok.core import upgrade_nodes, undo_handler_node_count from sverchok.core.update_system import ( - set_first_run, clear_system_cache, reset_timing_graphs) + set_first_run, clear_system_cache, reset_timing_graphs, build_update_list, process_tree) from sverchok.ui import bgl_callback_nodeview, bgl_callback_3dview from sverchok.utils import app_handler_ops -from sverchok.utils.logging import debug +from sverchok.utils.handle_blender_data import BlTrees from sverchok.utils import dummy_nodes +from sverchok.utils.logging import catch_log_error _state = {'frame': None} @@ -46,22 +44,6 @@ def get_all_sverchok_affiliated_trees(): return list(ng for ng in bpy.data.node_groups if ng.bl_idname in sv_types and ng.nodes) -def ensure_all_encountered_nodes_are_valid(sv_trees): - for ng in sv_trees: - with ng.throttle_update(): - try: - old_nodes.load_old(ng) - except: - traceback.print_exc() - try: - dummy_nodes.load_dummy(ng) - except: - traceback.print_exc() - try: - upgrade_nodes.upgrade_nodes(ng) - except: - traceback.print_exc() - def has_frame_changed(scene): last_frame = _state['frame'] _state['frame'] = scene.frame_current @@ -169,6 +151,15 @@ def sv_clean(scene): @persistent def sv_pre_load(scene): + """ + This method is called whenever new file is opening + THe update order is next: + 1. pre_load handler + 2. update methods of trees in a file + 3. post_load handler + Because Sverchok does not fully initialize itself during its initialization + it requires throttling of update method of loaded trees + """ clear_system_cache() sv_clean(scene) @@ -183,22 +174,35 @@ def sv_pre_load(scene): def sv_post_load(scene): """ Upgrade nodes, apply preferences and do an update. + THe update order is next: + 1. pre_load handler + 2. update methods of trees in a file + 3. post_load handler + post_load handler is also called when Blender is first ran + The method should remove throttling trees made in pre_load event, + initialize Sverchok parts which are required by loaded tree + and update all Sverchok trees """ - - set_first_run(False) from sverchok import node_tree, settings - + # ensure current nodeview view scale / location parameters reflect users' system settings node_tree.SverchCustomTree.update_gl_scale_info(None, "sv_post_load") - sv_trees = get_all_sverchok_affiliated_trees() - ensure_all_encountered_nodes_are_valid(sv_trees) + # register and mark old and dependent nodes + with catch_log_error(): + if any(not n.is_registered_node_type() for ng in BlTrees().sv_trees for n in ng.nodes): + old_nodes.register_all() + old_nodes.mark_all() + dummy_nodes.register_all() + dummy_nodes.mark_all() - settings.apply_theme_if_necessary() + with catch_log_error(): + settings.apply_theme_if_necessary() - for ng in sv_trees: - if ng.bl_idname == 'SverchCustomTreeType' and ng.nodes: - ng.update() + # release all trees and update them + set_first_run(False) + build_update_list() + process_tree() def set_frame_change(mode): diff --git a/core/upgrade_nodes.py b/core/upgrade_nodes.py deleted file mode 100644 index 529202c706604f378057510692d39dd7ba009c7d..0000000000000000000000000000000000000000 --- a/core/upgrade_nodes.py +++ /dev/null @@ -1,195 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### -import bpy -# dict for nodes be upgraded to -# compact node layout. Format -# bl_idname : [[socket_name0, prop_name0], -# [socket_name1, prop_name1]], - -from sverchok import old_nodes - -upgrade_dict = { - 'SphereNode': - [['Radius', 'rad_'], - ['U', 'U_'], - ['V', 'V_']], - 'CylinderNode': - [['RadTop', 'radTop_'], - ['RadBot', 'radBot_'], - ['Vertices', 'vert_'], - ['Height', 'height_'], - ['Subdivisions', 'subd_']], - 'RandomNode': - [['Count', 'count_inner'], - ['Seed', 'seed']], - 'PlaneNode': - [["Nº Vertices X", 'int_X'], - ["Nº Vertices Y", 'int_Y'], - ["Step X", "step_X"], - ["Step Y", "step_Y"]], - 'ListSliceNode': - [['Start', 'start'], - ['Stop', 'stop']], - 'LineNode': - [["Nº Vertices", 'int_'], - ["Step", 'step_']], - 'FloatNode': - [['Float', 'float_']], - 'IntegerNode': - [['Integer', 'int_']], - 'HilbertNode': - [["Level", 'level_'], - ["Size", 'size_']], - 'HilbertImageNode': - [["Level", 'level_'], - ["Size", 'size_'], - ["Sensitivity", 'sensitivity_']], - 'VectorMoveNode': - [["multiplier", 'mult_']], - 'EvaluateLine': - [["Factor", 'factor_']], - 'SvBoxNode': - [["Size", 'Size'], - ["Divx", 'Divx'], - ["Divy", 'Divy'], - ["Divz", 'Divz']], - 'ImageNode': - [["vecs X", 'Xvecs'], - ["vecs Y", 'Yvecs'], - ["Step X", 'Xstep'], - ["Step Y", 'Ystep']], - 'GenVectorsNode': - [['X', 'x_'], - ['Y', 'y_'], - ['Z', 'z_']], - 'MatrixInterpolationNode': - [['Factor', 'factor_']], - 'MatrixShearNode': - [['Factor1', 'factor1_'], - ['Factor2', 'factor2_']], - 'RandomVectorNode': - [["Count", 'count_inner'], - ["Seed", "seed"]], - 'ListRepeaterNode': - [["Number", "number"]], - 'ListItem2Node': - [["Item", "item"]], - 'SvWireframeNode': - [['thickness', 'thickness'], - ['Offset', 'offset']], - 'SvSolidifyNode': - [['thickness', 'thickness']], - 'SvRemoveDoublesNode': - [['Distance', 'distance']], - 'ScalarMathNode': - [['X', 'x'], - ['Y', 'y']] - } - - -# for new style vertices socket ~ 0.5 -# bl_idname: -# socket_name, prop_name, boolean use_prop , default 3 tuple -# two distinct scenarios -# use prop_name of node -# or use_prop and set default - -vertices_socket_upgrade = { - 'MatrixGenNode' : - [['Location', '', True, (0,0,0)], - ['Scale', '', True, (1,1,1)], - ['Rotation', '', True, (0,0,1)]], -} - -# new sockets -# format -# bl_idname : [[new_socket0],[newsocket1]], -# new_socket [inputs/outputs,type,name,position] - -new_socket_dict = { - 'SvWireframeNode': - [['inputs', 'SvStringsSocket', 'thickness', 0], - ['inputs', 'SvStringsSocket', 'Offset', 1]], - 'SvSolidifyNode': - [['inputs', 'SvStringsSocket', 'thickness', 0]], - 'SvRemoveDoublesNode': - [['inputs', 'SvStringsSocket', 'Distance', 0]], - 'MaskListNode': - [['outputs', 'SvStringsSocket', 'mask', 0], - ['outputs', 'SvStringsSocket', 'ind_true', 1], - ['outputs', 'SvStringsSocket', 'ind_false', 2]], - 'ListFLNode': - [['outputs', 'SvStringsSocket', 'Middl', 0]], - 'CentersPolsNode': - [['outputs', 'SvVerticesSocket', "Norm_abs", 1], - ['outputs', 'SvVerticesSocket', "Origins", 2]], - 'SvSolidifyNode': - [['outputs', 'SvStringsSocket', 'newpols', 3]], - 'IndexViewerNode': - [['inputs', 'SvStringsSocket', 'text', 4]], - 'SvIterateNode': - [['outputs', 'SvMatrixSocket', 'Matrices', 3]], - 'SvBmeshViewerNodeMK2': - [['outputs', 'SvObjectSocket', 'Objects', 0]], - } - -upgrade_pointer_dict = { - 'SvProfileNodeMK3': "set_pointer_from_filename", - 'SvTextOutNodeMK2': "set_pointer_from_filename", - 'SvTextInNodeMK2': "set_pointer_from_filename", - 'SvTextureViewerNodeLite': "set_pointer_from_filename" -} - -def upgrade_nodes(ng): - ''' - Apply prop_name for nodes in the node group ng for - upgrade to compact ui and create nodes that we add to - ''' - old_nodes.load_old(ng) - - for node in [n for n in ng.nodes if n.bl_idname in new_socket_dict]: - print(node.name) - - for in_out, s_type, name, pos in new_socket_dict[node.bl_idname]: - s_list = getattr(node, in_out) - if name not in s_list: - s_list.new(s_type, name) - s_list.move(len(s_list)-1, pos) - - for node in [node for node in ng.nodes if node.bl_idname in upgrade_dict]: - for s_name, p_name in upgrade_dict[node.bl_idname]: - socket = node.inputs.get(s_name) - if socket and not socket.prop_name: - socket.prop_name = p_name - - for n in [n for n in ng.nodes if n.bl_idname in vertices_socket_upgrade]: - for s_name, p_name, use_prop, default in vertices_socket_upgrade[n.bl_idname]: - socket = n.inputs.get(s_name) - if socket: - if p_name and not socket.prop_name: - socket.prop_name = p_name - elif use_prop and not socket.use_prop: - socket.prop = default - socket.use_prop = True - socket.prop_name = "" - else: - pass - - # this dict can call a function on the node, during the execution of postload handler - for node in [node for node in ng.nodes if node.bl_idname in upgrade_pointer_dict]: - getattr(node, upgrade_pointer_dict[node.bl_idname])() \ No newline at end of file diff --git a/json_examples/Architecture/Fasade_Complicated.zip b/json_examples/Architecture/Fasade_Complicated.zip index b9fea61431ef2deeb7347d93abe6de630740bf1d..679c8a643930bbff1f0b96865c5573915a33c812 100644 Binary files a/json_examples/Architecture/Fasade_Complicated.zip and b/json_examples/Architecture/Fasade_Complicated.zip differ diff --git a/nodes/script/profile_mk3.py b/nodes/script/profile_mk3.py index 942739bb39ecd03858980ab7bc10ee975c4fb8ef..8301834667e876fbc0c8b57164ede3487eba5f93 100644 --- a/nodes/script/profile_mk3.py +++ b/nodes/script/profile_mk3.py @@ -614,6 +614,13 @@ class SvProfileNodeMK3(bpy.types.Node, SverchCustomTreeNode, SvAnimatableNode): return result def process(self): + + # upgrades older versions of ProfileMK3 to the version that has self.file_pointer + if self.filename and not self.file_pointer: + text = self.get_bpy_data_from_name(self.filename, bpy.data.texts) + if text: + self.file_pointer = text + if not any(o.is_linked for o in self.outputs): return @@ -710,12 +717,6 @@ class SvProfileNodeMK3(bpy.types.Node, SverchCustomTreeNode, SvAnimatableNode): def set_filename_to_match_file_pointer(self): self.file_pointer = self.file_pointer - def set_pointer_from_filename(self): - """ this function upgrades older versions of ProfileMK3 to the version that has self.file_pointer """ - if hasattr(self, "file_pointer") and not self.file_pointer: - text = self.get_bpy_data_from_name(self.filename, bpy.data.texts) - if text: - self.file_pointer = text classes = [ SvProfileImportMenu, diff --git a/nodes/text/text_in_mk2.py b/nodes/text/text_in_mk2.py index ceb655e0110c870cb1e4e12ed610751834cc7752..c568b8311a3910c1dda1fcf81ab2e1486ce4ab52 100644 --- a/nodes/text/text_in_mk2.py +++ b/nodes/text/text_in_mk2.py @@ -245,8 +245,14 @@ class SvTextInNodeMK2(bpy.types.Node, SverchCustomTreeNode, SvAnimatableNode): # if we turn on reload on update we need a safety check for this to work. updateNode(self, None) - def process(self): # dispatch based on mode + + # upgrades older versions of ProfileMK3 to the version that has self.file_pointer + if self.text and not self.file_pointer: + text = self.get_bpy_data_from_name(self.text, bpy.data.texts) + if text: + self.file_pointer = text + if not self.current_text: return if self.textmode == 'CSV': @@ -605,13 +611,6 @@ class SvTextInNodeMK2(bpy.types.Node, SverchCustomTreeNode, SvAnimatableNode): # load data into selected socket self.outputs[0].sv_set(self.list_data[n_id]) - def set_pointer_from_filename(self): - """ this function upgrades older versions of ProfileMK3 to the version that has self.file_pointer """ - if hasattr(self, "file_pointer") and not self.file_pointer: - text = self.get_bpy_data_from_name(self.text, bpy.data.texts) - if text: - self.file_pointer = text - def save_to_json(self, node_data: dict): if not self.text: return # empty node, nothing to do diff --git a/nodes/text/text_out_mk2.py b/nodes/text/text_out_mk2.py index e158ccf5301978586d71b9e256d2d020483c0369..b5945529f0ea94156f4a329f3780e795e8f78fe7 100644 --- a/nodes/text/text_out_mk2.py +++ b/nodes/text/text_out_mk2.py @@ -201,8 +201,14 @@ class SvTextOutNodeMK2(bpy.types.Node, SverchCustomTreeNode): row.operator(TEXT_IO_CALLBACK, text='D U M P').fn_name = 'dump' col2.prop(self, 'append', text="Append") - def process(self): + + # upgrades older versions of ProfileMK3 to the version that has self.file_pointer + if self.text and not self.file_pointer: + text = self.get_bpy_data_from_name(self.text, bpy.data.texts) + if text: + self.file_pointer = text + if self.text_mode in {'CSV', 'JSON'}: multi_socket(self, min=1) @@ -238,13 +244,6 @@ class SvTextOutNodeMK2(bpy.types.Node, SverchCustomTreeNode): out = get_text_data(node=self) return out - def set_pointer_from_filename(self): - """ this function upgrades older versions of ProfileMK3 to the version that has self.file_pointer """ - if hasattr(self, "file_pointer") and not self.file_pointer: - text = self.get_bpy_data_from_name(self.text, bpy.data.texts) - if text: - self.file_pointer = text - def register(): bpy.utils.register_class(SvTextOutNodeMK2) diff --git a/nodes/viz/viewer_texture_lite.py b/nodes/viz/viewer_texture_lite.py index 188504dabbd4c8087da2d21179c2e7537af79b5f..7091663f4f175e6d9acd7165a2d85824e8fd9a41 100644 --- a/nodes/viz/viewer_texture_lite.py +++ b/nodes/viz/viewer_texture_lite.py @@ -96,6 +96,13 @@ class SvTextureViewerNodeLite(bpy.types.Node, SverchCustomTreeNode): bgl.glDeleteTextures(1, names) def process(self): + + # upgrades older versions of ProfileMK3 to the version that has self.file_pointer + if self.image and not self.image_pointer: + image = self.get_bpy_data_from_name(self.image, bpy.data.images) + if image: + self.image_pointer = image + n_id = node_id(self) self.delete_texture() nvBGL2.callback_disable(n_id) @@ -158,12 +165,6 @@ class SvTextureViewerNodeLite(bpy.types.Node, SverchCustomTreeNode): # reset n_id on copy self.n_id = '' - def set_pointer_from_filename(self): - """ this function upgrades older versions of ProfileMK3 to the version that has self.file_pointer """ - if hasattr(self, "image_pointer") and not self.image_pointer: - image = self.get_bpy_data_from_name(self.image, bpy.data.images) - if image: - self.image_pointer = image classes = [SvTextureViewerNodeLite,] register, unregister = bpy.utils.register_classes_factory(classes) diff --git a/old_nodes/__init__.py b/old_nodes/__init__.py index 2d0a6beaf02a93a2384d28497b17b11cabd3156d..67177d5c11182588bf61d648367e9718a655cf63 100644 --- a/old_nodes/__init__.py +++ b/old_nodes/__init__.py @@ -20,39 +20,34 @@ import os import importlib import inspect -import traceback +from typing import Union import bpy - -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, debug +from sverchok.utils.logging import error +from sverchok.utils.handle_blender_data import BlTrees -imported_mods = {} +imported_mods = {} old_bl_idnames = get_old_node_bl_idnames(path=os.path.dirname(__file__)) -def is_old(node_info): - ''' +def is_old(node_info: Union[str, bpy.types.Node]): + """ Check if node or node.bl_idname is among the old nodes - ''' + """ if isinstance(node_info, str): # assumes bl_idname return node_info in old_bl_idnames elif isinstance(node_info, bpy.types.Node): return node_info.bl_idname in old_bl_idnames - else: - return False + raise TypeError(f"String or Node is expected, {node_info} is given") + -def scan_for_old(ng): - nodes = [n for n in ng.nodes if n.bl_idname in old_bl_idnames] - for node in nodes: - mark_old(node) - def mark_old(node): + """Create a frame node around given one with deprecated label""" if node.parent and node.parent.label == "Deprecated node!": return ng = node.id_data @@ -65,68 +60,27 @@ def mark_old(node): frame.color = (.8, 0, 0) frame.shrink = True -def reload_old(ng=False): - if ng: - bl_idnames = {n.bl_idname for n in ng.nodes if n.bl_idname in old_bl_idnames} - for bl_id in bl_idnames: - mod = register_old(bl_id) - if mod: - importlib.reload(mod) - else: - print("Couldn't reload {}".format(bl_id)) - else: - for ng in bpy.data.node_groups: - reload_old(ng) - #if ng.bl_idname in { 'SverchCustomTreeType', }: - # reload_old(ng) - -def load_old(ng): - - """ - This approach didn't work, bl_idname of undefined node isn't as I expected - bl_idnames = {n.bl_idname for n in ng.nodes} - old_bl_ids = bl_idnames.intersection(old_bl_idnames) - if old_bl_ids: - - """ - not_reged_nodes = list(n for n in ng.nodes if not n.is_registered_node_type()) - if not_reged_nodes: - for bl_id in old_bl_idnames: - register_old(bl_id) - nodes = [n for n in ng.nodes if n.bl_idname == bl_id] - if nodes: - for node in nodes: - mark_old(node) - not_reged_nodes = list(n for n in ng.nodes if not n.is_registered_node_type()) - node_count = len(not_reged_nodes) - print(f"Loaded {bl_id}. {node_count} nodes are left unregistered.") - if node_count == 0: - return - else: # didn't help remove - unregister_old(bl_id) - + +def mark_all(): + """Add frames with deprecated label to all deprecated nodes if necessary""" + for node in (n for t in BlTrees().sv_trees for n in t.nodes): + if node.bl_idname in old_bl_idnames: + mark_old(node) + + def register_old(bl_id): + """Register old node class""" if bl_id in old_bl_idnames: mod = importlib.import_module(".{}".format(old_bl_idnames[bl_id]), __name__) res = inspect.getmembers(mod) - # print('mod/res:', mod, res) for name, cls in res: if inspect.isclass(cls): if issubclass(cls, bpy.types.Node) and cls.bl_idname == bl_id: if bl_id not in imported_mods: - try: - mod.register() - except Exception as err: - print('failed mod register') - exception(err) - + mod.register() imported_mods[bl_id] = mod - return mod - else: - return imported_mods[bl_id] - - error("Cannot find {} among old nodes".format(bl_id)) - return None + else: + raise LookupError(f"Cannot find {bl_id} among old nodes") def register_all(): @@ -135,22 +89,24 @@ def register_all(): try: register_old(bl_id) except Exception as e: - print(e) + # when a code of an old node is copied to old folder + # it can be copied with other classes (property groups) + # which does not change it version to MK2, so we have the error + error(e) + + +def register(): + + import sverchok + # This part is called upon scrip.reload (F8), because old nodes will be unregistered again + # There is no way to say which old node classes should be registered without registering them all + if sverchok.reload_event: + register_all() -def unregister_old(bl_id): - global imported_mods - mod = imported_mods.get(bl_id) - if mod: - #print("Unloaded old node type {}".format(bl_id)) - mod.unregister() - del imported_mods[bl_id] - def unregister(): - global imported_mods - for key, mod in imported_mods.items(): - if hasattr(bpy.types, key): + for mod in imported_mods.values(): + try: mod.unregister() - else: - debug(f'{key} was not registered, did not unregister') - imported_mods = {} + except Exception as e: + error(e) diff --git a/tests/json_export_import_tests.py b/tests/json_export_import_tests.py index 2401c1171e024adc060186bcd3d208bbc0423430..76556dd08bf743ed250483ec6ef0db5a9dae6c10 100644 --- a/tests/json_export_import_tests.py +++ b/tests/json_export_import_tests.py @@ -1,6 +1,3 @@ -import traceback - -from sverchok.core import upgrade_nodes from sverchok.utils.testing import * from sverchok.utils.sv_json_export import JSONExporter @@ -28,12 +25,6 @@ class ProfileExportTest(ReferenceTreeTestCase): super().setUp() def test_profile_export(self): - - try: - upgrade_nodes.upgrade_nodes(self.tree) - except: - traceback.print_exc() - export_result = JSONExporter.get_tree_structure(self.tree) importer = JSONImporter(export_result) importer.import_into_tree(self.tree, print_log=False) diff --git a/tests/json_import_tests.py b/tests/json_import_tests.py index 49be0078afc73349f9efa23b6f90928ebdd23bb6..1748cfd63bb37c71c20d589713a585ce9882a1b9 100644 --- a/tests/json_import_tests.py +++ b/tests/json_import_tests.py @@ -1,4 +1,4 @@ -from sverchok.utils.dummy_nodes import is_dummy +from sverchok.utils.dummy_nodes import is_dependent from sverchok.utils.testing import * from sverchok.ui.sv_examples_menu import example_categories_names @@ -91,7 +91,7 @@ class ExamplesImportTest(SverchokTestCase): if is_old(node): error_format = "This example contains deprecated node `{}' ({}). Please upgrade the example file." self.fail(error_format.format(node.name, node.bl_idname)) - if is_dummy(node): + if is_dependent(node.bl_idname): self.skipTest("Some dependencies was not installed") if importer.has_fails: raise ImportError(importer.fail_massage) diff --git a/utils/__init__.py b/utils/__init__.py index 1eb879bf96f4558051a2f5dc9f0e8e09cd2f1236..36817375eb129db346eed344332f23f5e721d7ef 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -131,7 +131,7 @@ utils_modules = [ "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', "sv_json_struct", - "nodeview_time_graph_drawing", "modules.shader_utils", + "nodeview_time_graph_drawing", "modules.shader_utils", "dummy_nodes", # UI text editor ui "text_editor_submenu", "text_editor_plugins", # UI operators and tools diff --git a/utils/dummy_nodes.py b/utils/dummy_nodes.py index 181d1182d9aec776088d425979021f6306097796..c7ec1478d08be1ebe6e1b5d43fd0427f620e462a 100644 --- a/utils/dummy_nodes.py +++ b/utils/dummy_nodes.py @@ -5,75 +5,54 @@ # SPDX-License-Identifier: GPL3 # License-Filename: LICENSE - -import os -import importlib -import inspect -import traceback - import bpy -from bpy.props import StringProperty - -from sverchok.utils.sv_oldnodes_parser import get_old_node_bl_idnames -from sverchok.utils.logging import error, exception +from sverchok.utils.logging import error +from sverchok.utils.handle_blender_data import BlTrees -imported_dummys = {} +imported_dummys = {} # dummy node classes +dummy_nodes_dict = {} # bl_idnames of nodes which was not registered due some dependencies missing -dummy_nodes_dict = {} -''' -Usage example: +def add_dummy(bl_id, name, dependecy): + """ + Usage example: -from sverchok.dependencies import FreeCAD -from sverchok.utils.dummy_nodes import add_dummy + from sverchok.dependencies import FreeCAD + from sverchok.utils.dummy_nodes import add_dummy -if FreeCAD is None: - bl_idname bl_label dependecy - add_dummy('SvBoxSolidNode', 'Box (Solid)', 'FreeCAD') -else: - class SvBoxSolidNode(...): - ... .... + if FreeCAD is None: + bl_idname bl_label dependecy + add_dummy('SvBoxSolidNode', 'Box (Solid)', 'FreeCAD') + else: + class SvBoxSolidNode(...): + ... .... -this will create a dummy node if needed -''' -def add_dummy(bl_id, name, dependecy): + this will create a dummy node if needed + """ dummy_nodes_dict[bl_id] = [name, dependecy] -class SvDummyNode(): - ''' - This mixin is used to in nodes that have external dependencies that are not loaded. - ''' +class SvDummyNode: + """This mixin is used to in nodes that have external dependencies that are not loaded.""" bl_label = 'Dummy Node' bl_idname = 'SvDummyNode' def draw_buttons(self, context, layout): box = layout.box() - box.label(text=self.missing_dependecy+" module") + box.label(text=self.missing_dependecy + " module") box.label(text="not found. Check ") box.label(text="extra-nodes in preferences") + def is_dependent(bl_id): + """True if the class was not registered due some dependencies missing""" return bl_id in dummy_nodes_dict -def is_dummy(node): - ''' - Check if node or node.bl_idname is among - the old nodes - ''' - if isinstance(node, bpy.types.Node): - return hasattr(node, 'missing_dependecy') - else: - return False - -def scan_for_dummy(ng): - nodes = [n for n in ng.nodes if is_dummy(n)] - for node in nodes: - mark_dummy(node) def mark_dummy(node): + """Create frame node around the node with Missing dependencies label""" if node.parent and node.parent.label == "Missing dependencies!": return ng = node.id_data @@ -86,72 +65,64 @@ def mark_dummy(node): frame.color = (.8, 0, 0) frame.shrink = True -def reload_dummy(ng=False): - print("Reloading dummy") - if ng: - load_dummy(ng) - else: - for ng in bpy.data.node_groups: - - load_dummy(ng) def create_dummy_class(bl_id): + """Create a dummy node class with given bl_idname""" from sverchok.node_tree import SverchCustomTreeNode - node = dummy_nodes_dict[bl_id] - cls = type(bl_id, - (bpy.types.Node, SverchCustomTreeNode, SvDummyNode), - {'bl_idname': bl_id, - 'bl_label': node[0], - # "missing_dependecy": StringProperty(name="Missing Dependency", default=node[1]), - "missing_dependecy":node[1], - }) - # cls['missing_dependecy'].default = node[1] - return cls -def load_dummy(ng): - - not_reged_nodes = list(n for n in ng.nodes if not n.is_registered_node_type()) - if not_reged_nodes: - for bl_id in dummy_nodes_dict: - register_dummy(bl_id) - nodes = [n for n in ng.nodes if n.bl_idname == bl_id] - if nodes: - for node in nodes: - mark_dummy(node) - not_reged_nodes = list(n for n in ng.nodes if not n.is_registered_node_type()) - node_count = len(not_reged_nodes) - print(f"Loaded {bl_id}. {node_count} nodes are left unregistered.") - if node_count == 0: - return - else: # didn't help remove - unregister_dummy(bl_id) + label, dependency_name = dummy_nodes_dict[bl_id] + return type( + bl_id, + (bpy.types.Node, SverchCustomTreeNode, SvDummyNode), + { + 'bl_idname': bl_id, + 'bl_label': label, + "missing_dependecy": dependency_name, + } + ) def register_dummy(bl_id): - + """Register dummy class if was not registered yet""" if bl_id in dummy_nodes_dict: - if bl_id not in imported_dummys: cls = create_dummy_class(bl_id) bpy.utils.register_class(cls) imported_dummys[bl_id] = cls - return cls - else: - return imported_dummys[bl_id] - error("Cannot find {} among old nodes".format(bl_id)) - return None + else: + raise LookupError("Cannot find {} among dummy nodes".format(bl_id)) + def unregister_dummy(bl_id): - global imported_dummys cls = imported_dummys.get(bl_id) if cls: - #print("Unloaded old node type {}".format(bl_id)) bpy.utils.unregister_class(cls) - del imported_dummys[bl_id] + +def mark_all(): + """Add frames with deprecated label to all deprecated nodes if necessary""" + for node in (n for t in BlTrees().sv_trees for n in t.nodes): + if node.bl_idname in dummy_nodes_dict: + mark_dummy(node) + + +def register_all(): + """Register all dummies""" + for bl_idname in dummy_nodes_dict: + try: + register_dummy(bl_idname) + except Exception as e: + error(e) + + +def register(): + import sverchok + # this part is called only when Sverchok is reloading + # we can't say to which class a node with unregistered class is belonging without registering it + if sverchok.reload_event: + register_all() + + def unregister(): - global imported_dummys - print(imported_dummys) for cls in imported_dummys.values(): bpy.utils.unregister_class(cls) - imported_dummys = {} diff --git a/utils/handle_blender_data.py b/utils/handle_blender_data.py index 51f1f9b6617efd2492ead64dfa68d07d442b9c6c..f4e308caeb11f3be0aed054c1a99053458b0a3c0 100644 --- a/utils/handle_blender_data.py +++ b/utils/handle_blender_data.py @@ -101,6 +101,26 @@ def get_sv_trees(): # ~~~~ encapsulation Blender objects ~~~~ +class BlTrees: + """Wrapping around Blender tree, use with care + it can crash if other containers are modified a lot + https://docs.blender.org/api/current/info_gotcha.html#help-my-script-crashes-blender + All this is True and about Blender class itself""" + + def __init__(self, node_groups=None): + self._trees = node_groups + + @property + def sv_trees(self): + trees = self._trees or bpy.data.node_groups + return (t for t in trees if t.bl_idname in {'SverchCustomTreeType', 'SvGroupTree'}) + + @property + def sv_main_trees(self): + trees = self._trees or bpy.data.node_groups + return (t for t in trees if t.bl_idname == 'SverchCustomTreeType') + + class BPYNode: """Wrapping around ordinary node for extracting some its information""" def __init__(self, node): diff --git a/utils/handling_nodes.py b/utils/handling_nodes.py index 6771a93231222ad2e01ce98378e675d1e74f68c5..1f334861e706a5c3e7b68cbf4729cada9053146b 100644 --- a/utils/handling_nodes.py +++ b/utils/handling_nodes.py @@ -129,6 +129,9 @@ class WrapNode: node_classes = [] # to register +# not to register, they will be registered by old_nodes.__init__.register or register_all +old_nodes = ['SvSplitEdgesMk2Node', 'SvSplitEdgesNode'] + def initialize_node(wrap_node: WrapNode): # class decorator for automatization sockets and property creation @@ -140,7 +143,8 @@ def initialize_node(wrap_node: WrapNode): wrap_node.set_sv_init_method(node_class) if hasattr(node_class, 'process'): wrap_node.decorate_process_method(node_class) - node_classes.append(node_class) # auto registration classes + if node_class.__name__ not in old_nodes: + node_classes.append(node_class) # auto registration classes return node_class return wrapper diff --git a/utils/logging.py b/utils/logging.py index f2128608e68fa0b29e965decdb1361141e7cb565..9d87b79e46116495cbbfd77c3a7e92304b6890bf 100644 --- a/utils/logging.py +++ b/utils/logging.py @@ -5,6 +5,7 @@ import inspect import traceback import logging import logging.handlers +from contextlib import contextmanager import sverchok from sverchok.utils.development import get_version_string @@ -20,6 +21,20 @@ file_initialized = False # Whether logging is initialized initialized = False + +@contextmanager +def catch_log_error(): + """Catch logging errors""" + try: + yield + except Exception as e: + frame, _, line, *_ = inspect.stack()[2] # third frame is where the context manager was used + module = inspect.getmodule(frame) + name = module.__name__ or "" + try_initialize() + logging.getLogger(f'{name} {line}').error(e) + + def get_log_buffer(log_buffer_name): """ Get internal blender text buffer for logging. @@ -153,14 +168,11 @@ def with_module_logger(method_name): Logger name is obtained from caller module name. """ def wrapper(*args, **kwargs): - frame = inspect.stack()[1] - module = inspect.getmodule(frame[0]) - if module is None: - name = "" - else: - name = module.__name__ + frame, _, line, *_ = inspect.stack()[1] + module = inspect.getmodule(frame) + name = module.__name__ or "" try_initialize() - logger = logging.getLogger(name) + logger = logging.getLogger(f'{name} {line}') method = getattr(logger, method_name) return method(*args, **kwargs) @@ -181,9 +193,9 @@ def getLogger(name=None): If name is None, then logger name is obtained from caller module name. """ if name is None: - frame = inspect.stack()[1] - module = inspect.getmodule(frame[0]) - name = module.__name__ + frame, _, line, *_ = inspect.stack()[1] + module = inspect.getmodule(frame) + name = f'{module.__name__} {line}' try_initialize() return logging.getLogger(name)