From 0bddfea5e3802a7647ed4277155a4e4779c0503d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 4 Dec 2020 22:29:31 +0100 Subject: [PATCH] Move node replacement system into its own module --- blender/arm/logicnode/arm_nodes.py | 103 +--------- blender/arm/logicnode/replacement.py | 271 +++++++++++++++++++++++++++ blender/arm/nodes_logic.py | 164 +--------------- blender/arm/props.py | 3 +- blender/arm/props_ui.py | 15 +- 5 files changed, 290 insertions(+), 266 deletions(-) create mode 100644 blender/arm/logicnode/replacement.py diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index 62853418..64c23121 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -7,6 +7,8 @@ import bpy.types from bpy.props import * from nodeitems_utils import NodeItem +# Pass NodeReplacment forward to individual node modules that import arm_nodes +from arm.logicnode.replacement import NodeReplacement import arm.node_utils # When passed as a category to add_node(), this will use the capitalized @@ -65,10 +67,11 @@ class ArmLogicTreeNode(bpy.types.Node): note that the lowest 'defined' version should be 1. if the node's version is 0, it means that it has been saved before versioning was a thing. NODES OF VERSION 1 AND VERSION 0 SHOULD HAVE THE SAME CONTENTS """ - if self.arm_version==0 and type(self).arm_version == 1: - return NodeReplacement.Identity(self) # in case someone doesn't implement this function, but the node has version 0. + if self.arm_version == 0 and type(self).arm_version == 1: + # In case someone doesn't implement this function, but the node has version 0 + return NodeReplacement.Identity(self) else: - raise LookupError(f"the current node class, {repr(type(self)):s}, does not implement the getReplacementNode method, even though it has updated") + raise LookupError(f"the current node class {repr(type(self)):s} does not implement get_replacement_node() even though it has updated") def add_input(self, socket_type: str, socket_name: str, default_value: Any = None, is_var: bool = False) -> bpy.types.NodeSocket: """Adds a new input socket to the node. @@ -105,100 +108,6 @@ class ArmLogicTreeNode(bpy.types.Node): return socket -class NodeReplacement: - """ - Represents a simple replacement rule, this can replace nodes of one type to nodes of a second type. - However, it is fairly limited. For instance, it assumes there are no changes in the type of the inputs or outputs - Second, it also assumes that node properties (especially EnumProperties) keep the same possible values. - - - from_node, from_node_version: the type of node to be removed, and its version number - - to_node, to_node_version: the type of node which takes from_node's place, and its version number - - *_socket_mapping: a map which defines how the sockets of the old node shall be connected to the new node - {1: 2} means that anything connected to the socket with index 1 on the original node will be connected to the socket with index 2 on the new node - - property_mapping: the mapping used to transfer the values of the old node's properties to the new node's properties. - {"property0": "property1"} mean that the value of the new node's property1 should be the old node's property0's value. - - input_defaults: a mapping used to give default values to the inputs which aren't overridden otherwise. - - property_defaults: a mapping used to define the value of the new node's properties, when they aren't overridden otherwise. - """ - - def __init__(self, from_node: str, from_node_version: int, to_node: str, to_node_version: int, - in_socket_mapping: Dict[int, int], out_socket_mapping: Dict[int, int], property_mapping: Optional[Dict[str, str]] = None, - input_defaults: Optional[Dict[int, any]] = None, property_defaults: Optional[Dict[str, any]] = None): - self.from_node = from_node - self.to_node = to_node - self.from_node_version = from_node_version - self.to_node_version = to_node_version - - self.in_socket_mapping = in_socket_mapping - self.out_socket_mapping = out_socket_mapping - self.property_mapping = {} if property_mapping is None else property_mapping - - self.input_defaults = {} if input_defaults is None else input_defaults - self.property_defaults = {} if property_defaults is None else property_defaults - - @classmethod - def Identity(cls, node: ArmLogicTreeNode): - """returns a NodeReplacement that does nothing, while operating on a given node. - WARNING: it assumes that all node properties have names that start with "property" - """ - in_socks = {i:i for i in range(len(node.inputs))} - out_socks = {i:i for i in range(len(node.outputs))} - props = {} - i=0 - - # finding all the properties fo a node is not possible in a clean way for now. - # so, I'll assume their names start with "property", and list all the node's attributes that fulfill that condition. - # next, to check that those are indeed properties (in the blender sense), we need to check the class's type annotations. - # those annotations are not even instances of bpy.types.Property, but tuples, with the first element being a function accessible at bpy.props.XXXProperty - property_types = [] - for possible_prop_type in dir(bpy.props): - if possible_prop_type.endswith('Property'): - property_types.append( getattr(bpy.props, possible_prop_type) ) - possible_properties = [] - for attrname in dir(node): - if attrname.startswith('property'): - possible_properties.append(attrname) - for attrname in possible_properties: - if attrname not in node.__annotations__: - continue - if not isinstance(node.__annotations__[attrname], tuple): - continue - if node.__annotations__[attrname][0] in property_types: - props[attrname] = attrname - - return NodeReplacement( - node.bl_idname, node.arm_version, node.bl_idname, type(node).arm_version, - in_socket_mapping=in_socks, out_socket_mapping=out_socks, - property_mapping=props - ) - - def chain_with(self, other): - """modify the current NodeReplacement by "adding" a second replacement after it""" - if self.to_node != other.from_node or self.to_node_version != other.from_node_version: - raise TypeError('the given NodeReplacement-s could not be chained') - self.to_node = other.to_node - self.to_node_version = other.to_node_version - - for i1, i2 in self.in_socket_mapping.items(): - i3 = other.in_socket_mapping[i2] - self.in_socket_mapping[i1] = i3 - for i1, i2 in self.out_socket_mapping.items(): - i3 = other.out_socket_mapping[i2] - self.out_socket_mapping[i1] = i3 - for p1, p2 in self.property_mapping.items(): - p3 = other.property_mapping[p2] - self.property_mapping[p1] = p3 - - old_input_defaults = self.input_defaults - self.input_defaults = other.input_defaults.copy() - for i, x in old_input_defaults.items(): - self.input_defaults[ other.in_socket_mapping[i] ] = x - - old_property_defaults = self.property_defaults - self.property_defaults = other.property_defaults.copy() - for p, x in old_property_defaults.items(): - self.property_defaults[ other.property_mapping[p] ] = x - class ArmNodeAddInputButton(bpy.types.Operator): """Add a new input socket to the node set by node_index.""" bl_idname = 'arm.node_add_input' diff --git a/blender/arm/logicnode/replacement.py b/blender/arm/logicnode/replacement.py new file mode 100644 index 00000000..36712461 --- /dev/null +++ b/blender/arm/logicnode/replacement.py @@ -0,0 +1,271 @@ +""" +This module contains the functionality to replace nodes by other nodes +in order to keep files from older Armory versions backward compatible. + +Nodes can define custom update procedures which describe how the replacement +should look like. +""" +import os.path +import time +from typing import Dict, List, Optional, Tuple + +import bpy.props + +import arm.logicnode.arm_nodes as arm_nodes +import arm.logicnode.arm_sockets + +# List of errors that occurred during the replacement +# Format: (error identifier, node.bl_idname, tree name) +replacement_errors: List[Tuple[str, Optional[str], str]] + + +class NodeReplacement: + """ + Represents a simple replacement rule, this can replace nodes of one type to nodes of a second type. + However, it is fairly limited. For instance, it assumes there are no changes in the type of the inputs or outputs + Second, it also assumes that node properties (especially EnumProperties) keep the same possible values. + + - from_node, from_node_version: the type of node to be removed, and its version number + - to_node, to_node_version: the type of node which takes from_node's place, and its version number + - *_socket_mapping: a map which defines how the sockets of the old node shall be connected to the new node + {1: 2} means that anything connected to the socket with index 1 on the original node will be connected to the socket with index 2 on the new node + - property_mapping: the mapping used to transfer the values of the old node's properties to the new node's properties. + {"property0": "property1"} mean that the value of the new node's property1 should be the old node's property0's value. + - input_defaults: a mapping used to give default values to the inputs which aren't overridden otherwise. + - property_defaults: a mapping used to define the value of the new node's properties, when they aren't overridden otherwise. + """ + + def __init__(self, from_node: str, from_node_version: int, to_node: str, to_node_version: int, + in_socket_mapping: Dict[int, int], out_socket_mapping: Dict[int, int], property_mapping: Optional[Dict[str, str]] = None, + input_defaults: Optional[Dict[int, any]] = None, property_defaults: Optional[Dict[str, any]] = None): + self.from_node = from_node + self.to_node = to_node + self.from_node_version = from_node_version + self.to_node_version = to_node_version + + self.in_socket_mapping = in_socket_mapping + self.out_socket_mapping = out_socket_mapping + self.property_mapping = {} if property_mapping is None else property_mapping + + self.input_defaults = {} if input_defaults is None else input_defaults + self.property_defaults = {} if property_defaults is None else property_defaults + + @classmethod + def Identity(cls, node: 'ArmLogicTreeNode'): + """Returns a NodeReplacement that does nothing, while operating on a given node. + WARNING: it assumes that all node properties have names that start with "property" + """ + in_socks = {i: i for i in range(len(node.inputs))} + out_socks = {i: i for i in range(len(node.outputs))} + props = {} + i = 0 + + # finding all the properties fo a node is not possible in a clean way for now. + # so, I'll assume their names start with "property", and list all the node's attributes that fulfill that condition. + # next, to check that those are indeed properties (in the blender sense), we need to check the class's type annotations. + # those annotations are not even instances of bpy.types.Property, but tuples, with the first element being a function accessible at bpy.props.XXXProperty + property_types = [] + for possible_prop_type in dir(bpy.props): + if possible_prop_type.endswith('Property'): + property_types.append(getattr(bpy.props, possible_prop_type)) + possible_properties = [] + for attrname in dir(node): + if attrname.startswith('property'): + possible_properties.append(attrname) + for attrname in possible_properties: + if attrname not in node.__annotations__: + continue + if not isinstance(node.__annotations__[attrname], tuple): + continue + if node.__annotations__[attrname][0] in property_types: + props[attrname] = attrname + + return NodeReplacement( + node.bl_idname, node.arm_version, node.bl_idname, type(node).arm_version, + in_socket_mapping=in_socks, out_socket_mapping=out_socks, + property_mapping=props + ) + + def chain_with(self, other): + """Modify the current NodeReplacement by "adding" a second replacement after it""" + if self.to_node != other.from_node or self.to_node_version != other.from_node_version: + raise TypeError('the given NodeReplacement-s could not be chained') + self.to_node = other.to_node + self.to_node_version = other.to_node_version + + for i1, i2 in self.in_socket_mapping.items(): + i3 = other.in_socket_mapping[i2] + self.in_socket_mapping[i1] = i3 + for i1, i2 in self.out_socket_mapping.items(): + i3 = other.out_socket_mapping[i2] + self.out_socket_mapping[i1] = i3 + for p1, p2 in self.property_mapping.items(): + p3 = other.property_mapping[p2] + self.property_mapping[p1] = p3 + + old_input_defaults = self.input_defaults + self.input_defaults = other.input_defaults.copy() + for i, x in old_input_defaults.items(): + self.input_defaults[ other.in_socket_mapping[i] ] = x + + old_property_defaults = self.property_defaults + self.property_defaults = other.property_defaults.copy() + for p, x in old_property_defaults.items(): + self.property_defaults[ other.property_mapping[p] ] = x + + +def replace(tree: bpy.types.NodeTree, node: 'ArmLogicTreeNode'): + """Replaces the given node with its replacement.""" + + # the node can either return a NodeReplacement object (for simple replacements) + # or a brand new node, for more complex stuff. + response = node.get_replacement_node(tree) + + if isinstance(response, arm_nodes.ArmLogicTreeNode): + newnode = response + # some misc. properties + newnode.parent = node.parent + newnode.location = node.location + newnode.select = node.select + + elif isinstance(response, list): # a list of nodes: + for newnode in response: + newnode.parent = node.parent + newnode.location = node.location + newnode.select = node.select + + elif isinstance(response, NodeReplacement): + replacement = response + # if the returned object is a NodeReplacement, check that it corresponds to the node (also, create the new node) + if node.bl_idname != replacement.from_node or node.arm_version != replacement.from_node_version: + raise LookupError("The provided NodeReplacement doesn't seem to correspond to the node needing replacement") + + # Create the replacement node + newnode = tree.nodes.new(response.to_node) + if newnode.arm_version != replacement.to_node_version: + tree.nodes.remove(newnode) + raise LookupError("The provided NodeReplacement doesn't seem to correspond to the node needing replacement") + + # some misc. properties + newnode.parent = node.parent + newnode.location = node.location + newnode.select = node.select + + # now, use the `replacement` to hook up the new node correctly + # start by applying defaults + for prop_name, prop_value in replacement.property_defaults.items(): + setattr(newnode, prop_name, prop_value) + for input_id, input_value in replacement.input_defaults.items(): + input_socket = newnode.inputs[input_id] + if isinstance(input_socket, arm.logicnode.arm_sockets.ArmCustomSocket): + if input_socket.arm_socket_type != 'NONE': + input_socket.default_value_raw = input_value + elif input_socket.type != 'SHADER': + # note: shader-type sockets don't have a default value... + input_socket.default_value = input_value + + # map properties + for src_prop_name, dest_prop_name in replacement.property_mapping.items(): + setattr(newnode, dest_prop_name, getattr(node, src_prop_name)) + + # map inputs + for src_socket_id, dest_socket_id in replacement.in_socket_mapping.items(): + src_socket = node.inputs[src_socket_id] + dest_socket = newnode.inputs[dest_socket_id] + if src_socket.is_linked: + # an input socket only has one link + datasource_socket = src_socket.links[0].from_socket + tree.links.new(datasource_socket, dest_socket) + else: + if isinstance(dest_socket, arm.logicnode.arm_sockets.ArmCustomSocket): + if dest_socket.arm_socket_type != 'NONE': + dest_socket.default_value_raw = src_socket.default_value_raw + elif dest_socket.type != 'SHADER': + # note: shader-type sockets don't have a default value... + dest_socket.default_value = src_socket.default_value + + # map outputs + for src_socket_id, dest_socket_id in replacement.out_socket_mapping.items(): + dest_socket = newnode.outputs[dest_socket_id] + for link in node.outputs[src_socket_id].links: + tree.links.new(dest_socket, link.to_socket) + else: + print(response) + + tree.nodes.remove(node) + + +def replace_all(): + """Iterate through all logic node trees in the file and check for node updates/replacements to execute.""" + global replacement_errors + + list_of_errors: List[Tuple[str, Optional[str], str]] = list() + + for tree in bpy.data.node_groups: + if tree.bl_idname == "ArmLogicTreeType": + # Use list() to make a "static" copy. It's possible to iterate over it because nodes which get removed + # from the tree leave python objects in the list + for node in list(tree.nodes): + # Blender nodes (layout) + if not isinstance(node, arm_nodes.ArmLogicTreeNode): + continue + + # That node has been removed from the tree without replace() being called on it somehow + elif node.type == '': + continue + + # Node type deleted. That's unusual. Or it has been replaced for a looong time + elif not node.is_registered_node_type(): + list_of_errors.append(('unregistered', None, tree.name)) + + # Invalid version number + elif not isinstance(type(node).arm_version, int): + list_of_errors.append(('bad version', node.bl_idname, tree.name)) + + # Actual replacement + elif node.arm_version < type(node).arm_version: + try: + replace(tree, node) + except LookupError as err: + list_of_errors.append(('update failed', node.bl_idname, tree.name)) + except Exception as err: + list_of_errors.append(('misc.', node.bl_idname, tree.name)) + + # Node version is newer than supported by the class + elif node.arm_version > type(node).arm_version: + list_of_errors.append(('future version', node.bl_idname, tree.name)) + + # If possible, make a popup about the errors and write an error report into the .blend file's folder + if len(list_of_errors) > 0: + print('There were errors in node replacement') + basedir = os.path.dirname(bpy.data.filepath) + reportfile = os.path.join( + basedir, 'node_update_failure.{:s}.txt'.format( + time.strftime("%Y-%m-%dT%H-%M-%S%z") + ) + ) + + with open(reportfile, 'w') as reportf: + for error_type, node_class, tree_name in list_of_errors: + if error_type == 'unregistered': + print(f"A node whose class doesn't exist was found in node tree \"{tree_name}\"", file=reportf) + elif error_type == 'update failed': + print(f"A node of type {node_class} in tree \"{tree_name}\" failed to be updated, " + f"because there is no (longer?) an update routine for this version of the node.", file=reportf) + elif error_type == 'future version': + print(f"A node of type {node_class} in tree \"{tree_name}\" seemingly comes from a future version of armory. " + f"Please check whether your version of armory is up to date", file=reportf) + elif error_type == 'bad version': + print(f"A node of type {node_class} in tree \"{tree_name}\" doesn't have version information attached to it. " + f"If so, please check that the nodes in the file are compatible with the in-code node classes. " + f"If this nodes comes from an add-on, please check that it is compatible with this version of armory.", file=reportf) + elif error_type == 'misc.': + print(f"A node of type {node_class} in tree \"{tree_name}\" failed to be updated, " + f"because the node's update procedure itself failed.", file=reportf) + else: + print(f"Whoops, we don't know what this error type (\"{error_type}\") means. You might want to report a bug here. " + f"All we know is that it comes form a node of class {node_class} in the node tree called \"{tree_name}\".", file=reportf) + + replacement_errors = list_of_errors + bpy.ops.arm.show_node_update_errors() + replacement_errors = None diff --git a/blender/arm/nodes_logic.py b/blender/arm/nodes_logic.py index 65d67a17..f6f3820b 100755 --- a/blender/arm/nodes_logic.py +++ b/blender/arm/nodes_logic.py @@ -1,12 +1,11 @@ -import os.path -import time -from typing import Callable, Set, Tuple, Optional +from typing import Callable import webbrowser import bpy from bpy.props import BoolProperty, StringProperty import arm.logicnode.arm_nodes as arm_nodes +import arm.logicnode.replacement import arm.logicnode import arm.utils @@ -339,163 +338,6 @@ class ARMAddSetVarNode(bpy.types.Operator): return({'FINISHED'}) -def replace(tree: bpy.types.NodeTree, node: arm_nodes.ArmLogicTreeNode): - """Replaces the given node with its replacement.""" - - # the node can either return a NodeReplacement object (for simple replacements) - # or a brand new node, for more complex stuff. - response = node.get_replacement_node(tree) - - if isinstance(response, arm_nodes.ArmLogicTreeNode): - newnode = response - # some misc. properties - newnode.parent = node.parent - newnode.location = node.location - newnode.select = node.select - - elif isinstance(response, list): # a list of nodes: - for newnode in response: - newnode.parent = node.parent - newnode.location = node.location - newnode.select = node.select - - elif isinstance(response, arm_nodes.NodeReplacement): - replacement = response - # if the returned object is a NodeReplacement, check that it corresponds to the node (also, create the new node) - if node.bl_idname != replacement.from_node or node.arm_version != replacement.from_node_version: - raise LookupError("The provided NodeReplacement doesn't seem to correspond to the node needing replacement") - - # Create the replacement node - newnode = tree.nodes.new(response.to_node) - if newnode.arm_version != replacement.to_node_version: - tree.nodes.remove(newnode) - raise LookupError("The provided NodeReplacement doesn't seem to correspond to the node needing replacement") - - # some misc. properties - newnode.parent = node.parent - newnode.location = node.location - newnode.select = node.select - - # now, use the `replacement` to hook up the new node correctly - # start by applying defaults - for prop_name, prop_value in replacement.property_defaults.items(): - setattr(newnode, prop_name, prop_value) - for input_id, input_value in replacement.input_defaults.items(): - input_socket = newnode.inputs[input_id] - if isinstance(input_socket, arm.logicnode.arm_sockets.ArmCustomSocket): - if input_socket.arm_socket_type != 'NONE': - input_socket.default_value_raw = input_value - elif input_socket.type != 'SHADER': - # note: shader-type sockets don't have a default value... - input_socket.default_value = input_value - - # map properties - for src_prop_name, dest_prop_name in replacement.property_mapping.items(): - setattr(newnode, dest_prop_name, getattr(node, src_prop_name)) - - # map inputs - for src_socket_id, dest_socket_id in replacement.in_socket_mapping.items(): - src_socket = node.inputs[src_socket_id] - dest_socket = newnode.inputs[dest_socket_id] - if src_socket.is_linked: - # an input socket only has one link - datasource_socket = src_socket.links[0].from_socket - tree.links.new(datasource_socket, dest_socket) - else: - if isinstance(dest_socket, arm.logicnode.arm_sockets.ArmCustomSocket): - if dest_socket.arm_socket_type != 'NONE': - dest_socket.default_value_raw = src_socket.default_value_raw - elif dest_socket.type != 'SHADER': - # note: shader-type sockets don't have a default value... - dest_socket.default_value = src_socket.default_value - - # map outputs - for src_socket_id, dest_socket_id in replacement.out_socket_mapping.items(): - dest_socket = newnode.outputs[dest_socket_id] - for link in node.outputs[src_socket_id].links: - tree.links.new(dest_socket, link.to_socket) - else: - print(response) - tree.nodes.remove(node) - - -def replace_all(): - """Iterate through all logic node trees and check for node updates/replacements to execute.""" - global replacement_errors - - # Errors: (error identifier, node.bl_idname, tree name) - list_of_errors: Set[Tuple[str, Optional[str], str]] = set() - - for tree in bpy.data.node_groups: - if tree.bl_idname == "ArmLogicTreeType": - # Use list() to make a "static" copy. It's possible to iterate over it because nodes which get removed - # from the tree leave python objects in the list - for node in list(tree.nodes): - # Blender nodes (layout) - if not isinstance(node, arm_nodes.ArmLogicTreeNode): - continue - - # That node has been removed from the tree without replace() being called on it somehow - elif node.type == '': - continue - - # Node type deleted. That's unusual. Or it has been replaced for a looong time - elif not node.is_registered_node_type(): - list_of_errors.add(('unregistered', None, tree.name)) - - # Invalid version number - elif not isinstance(type(node).arm_version, int): - list_of_errors.add(('bad version', node.bl_idname, tree.name)) - - # Actual replacement - elif node.arm_version < type(node).arm_version: - try: - replace(tree, node) - except LookupError as err: - list_of_errors.add(('update failed', node.bl_idname, tree.name)) - except Exception as err: - list_of_errors.add(('misc.', node.bl_idname, tree.name)) - - # Node version is newer than supported by the class - elif node.arm_version > type(node).arm_version: - list_of_errors.add(('future version', node.bl_idname, tree.name)) - - # If possible, make a popup about the errors and write an error report into the .blend file's folder - if len(list_of_errors) > 0: - print('There were errors in node replacement') - basedir = os.path.dirname(bpy.data.filepath) - reportfile = os.path.join( - basedir, 'node_update_failure.{:s}.txt'.format( - time.strftime("%Y-%m-%dT%H-%M-%S%z") - ) - ) - - with open(reportfile, 'w') as reportf: - for error_type, node_class, tree_name in list_of_errors: - if error_type == 'unregistered': - print(f"A node whose class doesn't exist was found in node tree \"{tree_name}\"", file=reportf) - elif error_type == 'update failed': - print(f"A node of type {node_class} in tree \"{tree_name}\" failed to be updated, " - f"because there is no (longer?) an update routine for this version of the node.", file=reportf) - elif error_type == 'future version': - print(f"A node of type {node_class} in tree \"{tree_name}\" seemingly comes from a future version of armory. " - f"Please check whether your version of armory is up to date", file=reportf) - elif error_type == 'bad version': - print(f"A node of type {node_class} in tree \"{tree_name}\" doesn't have version information attached to it. " - f"If so, please check that the nodes in the file are compatible with the in-code node classes. " - f"If this nodes comes from an add-on, please check that it is compatible with this version of armory.", file=reportf) - elif error_type == 'misc.': - print(f"A node of type {node_class} in tree \"{tree_name}\" failed to be updated, " - f"because the node's update procedure itself failed.", file=reportf) - else: - print(f"Whoops, we don't know what this error type (\"{error_type}\") means. You might want to report a bug here. " - f"All we know is that it comes form a node of class {node_class} in the node tree called \"{tree_name}\".", file=reportf) - - replacement_errors = list_of_errors - bpy.ops.arm.show_node_update_errors() - replacement_errors = None - - class ReplaceNodesOperator(bpy.types.Operator): """Automatically replaces deprecated nodes.""" bl_idname = "node.replace" @@ -503,7 +345,7 @@ class ReplaceNodesOperator(bpy.types.Operator): bl_description = "Replace deprecated nodes" def execute(self, context): - replace_all() + arm.logicnode.replacement.replace_all() return {'FINISHED'} @classmethod diff --git a/blender/arm/props.py b/blender/arm/props.py index 1051d82f..ae6aa0a9 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -4,6 +4,7 @@ import re import multiprocessing import arm.assets as assets +import arm.logicnode.replacement import arm.make import arm.nodes_logic import arm.proxy @@ -488,7 +489,7 @@ def update_armory_world(): rp.rp_voxelao = True # Replace deprecated nodes - arm.nodes_logic.replace_all() + arm.logicnode.replacement.replace_all() print('Project updated to sdk v' + arm_version + ' (' + arm_commit + ')') wrd.arm_version = arm_version diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index b23f6c38..fe455192 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -7,6 +7,7 @@ from bpy.props import * import arm.api import arm.assets as assets import arm.log as log +import arm.logicnode.replacement import arm.make as make import arm.make_state as state import arm.props as props @@ -2313,7 +2314,7 @@ class ARM_OT_ShowNodeUpdateErrors(bpy.types.Operator): wrd = None # a helper internal variable def draw_message_box(self, context): - list_of_errors = arm.nodes_logic.replacement_errors.copy() + list_of_errors = arm.logicnode.replacement.replacement_errors.copy() # note: list_of_errors is a set of tuples: `(error_type, node_class, tree_name)` # where `error_type` can be "unregistered", "update failed", "future version", "bad version", or "misc." @@ -2322,8 +2323,8 @@ class ARM_OT_ShowNodeUpdateErrors(bpy.types.Operator): # this will help order versions better, somewhat. # note: this is NOT complete - current_version_2 = tuple( current_version.split('.') ) - file_version_2 = tuple( file_version.split('.') ) + current_version_2 = tuple(current_version.split('.')) + file_version_2 = tuple(file_version.split('.')) is_armory_upgrade = (current_version_2 > file_version_2) error_types = set() @@ -2338,8 +2339,8 @@ class ARM_OT_ShowNodeUpdateErrors(bpy.types.Operator): layout = layout.column(align=True) layout.alignment = 'EXPAND' - layout.label(text="Some nodes failed to be updated to the current armory version", icon="ERROR") - if current_version==file_version: + layout.label(text="Some nodes failed to be updated to the current Armory version", icon="ERROR") + if current_version == file_version: layout.label(text="(This might be because you are using a development snapshot, or a homemade version ;) )", icon='BLANK1') elif not is_armory_upgrade: layout.label(text="(Please note that it is not possible do downgrade nodes to a previous version either.", icon='BLANK1') @@ -2351,7 +2352,7 @@ class ARM_OT_ShowNodeUpdateErrors(bpy.types.Operator): if 'update failed' in error_types: layout.label(text="Some nodes do not have an update procedure to deal with the version saved in this file.", icon='BLANK1') - if current_version==file_version: + if current_version == file_version: layout.label(text="(if you are a developer, this might be because you didn't implement it yet.)", icon='BLANK1') if 'bad version' in error_types: layout.label(text="Some nodes do not have version information attached to them.", icon='BLANK1') @@ -2406,7 +2407,7 @@ class ARM_OT_UpdateFileSDK(bpy.types.Operator): rp.rp_voxelao = True # Replace deprecated nodes - arm.nodes_logic.replace_all() + arm.logicnode.replacement.replace_all() wrd.arm_version = props.arm_version wrd.arm_commit = props.arm_commit