diff --git a/core/group_update_system.py b/core/group_update_system.py index 5bf89a00334fed385f30c520ae4010a2e5e31d1b..8bf0f6cd9692f884da3f30eacc4cc839fde21efd 100644 --- a/core/group_update_system.py +++ b/core/group_update_system.py @@ -75,9 +75,9 @@ class GroupUpdateTree(us.UpdateTree): walker = self._walk() # walker = self._debug_color(walker) - for node, prev_socks in walker: + for node in walker: with us.AddStatistic(node): - us.prepare_input_data(prev_socks, node.inputs) + self._fill_input(node) node.process() if is_opened_tree: @@ -130,10 +130,10 @@ class GroupUpdateTree(us.UpdateTree): self._outdated_nodes.clear() self._viewer_nodes.clear() - for node, other_socks in self._sort_nodes(outdated, viewers): + for node in self._sort_nodes(outdated, viewers): # execute node only if all previous nodes are updated - if all(n.get(us.UPDATE_KEY, True) for sock in other_socks if (n := self._sock_node.get(sock))): - yield node, other_socks + if all(n.get(us.UPDATE_KEY, True) for n in self._from_nodes[node]): + yield node if node.get(us.ERROR_KEY, False): self._outdated_nodes.add(node) else: diff --git a/core/socket_data.py b/core/socket_data.py index af3d64fc4e117c26acf5645edd6bd90e66240fc3..af8c4e02b582d8e1f65dddaef83dab2845a85575 100644 --- a/core/socket_data.py +++ b/core/socket_data.py @@ -181,11 +181,8 @@ def sv_get_socket(socket, deepcopy=True): to increase performance if the node doesn't mutate input set to False and increase performance substanstilly """ - data = socket_data_cache.get(socket.socket_id) - if data is not None: - return sv_deep_copy(data) if deepcopy else data - else: - raise SvNoDataError(socket) + data = socket_data_cache[socket.socket_id] + return sv_deep_copy(data) if deepcopy else data def get_output_socket_data(node, output_socket_name): diff --git a/core/sockets.py b/core/sockets.py index b27d4cea319f17ba1e5633d1f7e39a30e85effd6..d8e07ca9a1be1b806e6e6460e4cd98ab79b4da7f 100644 --- a/core/sockets.py +++ b/core/sockets.py @@ -403,36 +403,24 @@ class SvSocketCommon(SvSocketProcessing): """ The method is used for getting socket data In most cases the method should not be overridden - Also a socket can use its default_property Order of getting data (if available): - 1. written socket data - 2. node default property - 3. socket default property - 4. script default property - 5. Raise no data error + 1. written socket data which includes default socket values + 2. script default property + 3. Raise no data error :param default: script default property - :param deepcopy: in most cases should be False for efficiency but not in cases if input data will be modified + :param deepcopy: in most cases should be False for efficiency but not + in cases if input data will be modified :return: data bound to the socket """ if self.is_output: - return sv_get_socket(self, False) + deepcopy = False - if self.is_linked: + try: return sv_get_socket(self, deepcopy) - - prop_name = self.get_prop_name() - if prop_name: - prop = getattr(self.node, prop_name) - return format_bpy_property(prop) - - if self.use_prop and hasattr(self, 'default_property') and self.default_property is not None: - default_property = self.default_property - return format_bpy_property(default_property) - - if default is not ...: - return default - - raise SvNoDataError(self) + except KeyError: + if default is not ...: + return default + raise SvNoDataError(self) def sv_set(self, data): """Set data, provide context in case the node can be evaluated several times in different context""" diff --git a/core/update_system.py b/core/update_system.py index a576527ff4da18056ae8cbfde63ab18d343b848d..1f8f7e69e29df1be6b74eebdb19031b67f5bf4f3 100644 --- a/core/update_system.py +++ b/core/update_system.py @@ -14,6 +14,7 @@ from sverchok.core.socket_conversions import conversions from sverchok.utils.profile import profile from sverchok.utils.logging import log_error from sverchok.utils.tree_walk import bfs_walk +from sverchok.utils.socket_utils import format_bpy_property if TYPE_CHECKING: from sverchok.node_tree import (SverchCustomTreeNode as SvNode, @@ -154,15 +155,6 @@ class SearchTree: If input socket is not linked the output socket will be None""" return [self._from_sock.get(s) for s in node.inputs] - def update_node(self, node: 'SvNode', suppress=True): - """Fetches data from previous node, makes data conversion if connected - sockets have different types, calls process method of the given node - records nodes statistics - If suppress is True an error during node execution will be suppressed""" - with AddStatistic(node, suppress): - prepare_input_data(self.previous_sockets(node), node.inputs) - node.process() - def _remove_reroutes(self): for r in self._tree.nodes: if r.bl_idname != "NodeReroute": @@ -340,10 +332,11 @@ class UpdateTree(SearchTree): changed_nodes = _tree._update_difference(old) # disconnected input sockets can remember previous data + _tree._update_default_values(changed_nodes) # a node can be laizy and don't recalculate output for node in changed_nodes: - for in_s in chain(node.inputs, node.outputs): - in_s.sv_forget() + for out_s in node.outputs: + out_s.sv_forget() _tree._outdated_nodes.update(changed_nodes) if not _tree.is_animation_updated: @@ -381,10 +374,10 @@ class UpdateTree(SearchTree): walker = up_tree._walk() # walker = up_tree._debug_color(walker) try: - for node, prev_socks in walker: + for node in walker: with AddStatistic(node): yield node - prepare_input_data(prev_socks, node.inputs) + up_tree._fill_input(node) node.process() except CancelError: pass @@ -396,6 +389,15 @@ class UpdateTree(SearchTree): times = None update_ui(tree, times) + def update_node(self, node: 'SvNode', suppress=True): + """Fetches data from previous node, makes data conversion if connected + sockets have different types, calls process method of the given node + records nodes statistics + If suppress is True an error during node execution will be suppressed""" + with AddStatistic(node, suppress): + self._fill_input(node) + node.process() + @classmethod def reset_tree(cls, tree: NodeTree = None): """Remove tree data or data of all trees from the cache""" @@ -420,6 +422,9 @@ class UpdateTree(SearchTree): if self._outdated_nodes is not None: self._outdated_nodes.update(nodes) + # todo is it best place? + self._update_default_values(n for n in nodes if n in self._from_nodes) + def __init__(self, tree: NodeTree): """Should not use be used directly, only via the get class method :is_updated: Should be False if topology of the tree was changed @@ -439,9 +444,6 @@ class UpdateTree(SearchTree): self.is_scene_updated = True self._outdated_nodes: Optional[set[SvNode]] = None # None means outdated all - # https://stackoverflow.com/a/68550238 - self._sort_nodes = lru_cache(maxsize=1)(self.__sort_nodes) - self._copy_attrs = [ 'is_updated', 'is_animation_updated', @@ -449,6 +451,12 @@ class UpdateTree(SearchTree): '_outdated_nodes', ] + # https://stackoverflow.com/a/68550238 + self.previous_sockets = lru_cache(maxsize=None)(self.previous_sockets) + self._sort_nodes = lru_cache(maxsize=1)(self.__sort_nodes) + + self._update_default_values(self._from_nodes.keys()) + def _animation_nodes(self) -> set['SvNode']: """Returns nodes which are animation dependent""" an_nodes = set() @@ -486,10 +494,10 @@ class UpdateTree(SearchTree): outdated = frozenset(self._outdated_nodes) self._outdated_nodes.clear() - for node, other_socks in self._sort_nodes(outdated): + for node in self._sort_nodes(outdated): # execute node only if all previous nodes are updated - if all(n.get(UPDATE_KEY, True) for sock in other_socks if (n := self._sock_node.get(sock))): - yield node, other_socks + if all(n.get(UPDATE_KEY, True) for n in self._from_nodes[node]): + yield node if node.get(ERROR_KEY, False): self._outdated_nodes.add(node) else: @@ -497,8 +505,7 @@ class UpdateTree(SearchTree): def __sort_nodes(self, from_nodes: frozenset['SvNode'] = None, - to_nodes: frozenset['SvNode'] = None)\ - -> list[tuple['SvNode', list[NodeSocket]]]: + to_nodes: frozenset['SvNode'] = None) -> list['SvNode']: """Sort nodes of the tree in proper execution order. Whe all given parameters are None it uses all tree nodes :from_nodes: if given it sorts only next nodes from given ones @@ -528,7 +535,7 @@ class UpdateTree(SearchTree): nodes = [] if walk_structure: for node in TopologicalSorter(walk_structure).static_order(): - nodes.append((node, [self._from_sock.get(s) for s in node.inputs])) + nodes.append(node) return nodes def _update_difference(self, old: 'UpdateTree') -> set['SvNode']: @@ -543,12 +550,73 @@ class UpdateTree(SearchTree): nodes_to_update.add(self._sock_node[from_sock]) else: nodes_to_update.add(self._sock_node[to_sock]) + for to_sock in self._disconnected_inputs(old): + nodes_to_update.add(self._sock_node[to_sock]) + return nodes_to_update + + def _disconnected_inputs(self, old: 'UpdateTree') -> set[NodeSocket]: + inputs = set() removed_links = old._links - self._links for from_sock, to_sock in removed_links: if to_sock not in self._sock_node: continue # the link was removed together with the node - nodes_to_update.add(self._sock_node[to_sock]) - return nodes_to_update + inputs.add(to_sock) + return inputs + + def _fill_input(self, node: Node): + for ps, ns in zip(self.previous_sockets(node), node.inputs): + + # default values already should be in socket_data cache dictionary + if ps is None: + continue + + # extract data from connected socket + else: + try: + data = ps.sv_get() + except SvNoDataError: + # let to the node handle No Data error + ns.sv_forget() + else: + # cast data + if ps.bl_idname != ns.bl_idname: + implicit_conversion = conversions[ns.default_conversion_name] + data = implicit_conversion.convert(ns, ps, data) + + ns.sv_set(data) + + def _update_default_values(self, nodes: Iterable[Node]): + for node in nodes: + for from_s, in_s in zip(self.previous_sockets(node), node.inputs): + if from_s is not None: + continue + elif (default := self._search_default_value(in_s)) is not None: + in_s.sv_set(default) + else: + in_s.sv_forget() + + def _search_default_value(self, socket: NodeSocket) -> Optional[list]: + node = self._sock_node[socket] + if hasattr(node, 'missing_dependency'): + prop_name = None + elif node.id_data.sv_draft: + draft = None + if hasattr(node, 'draft_properties_mapping'): + draft = node.draft_properties_mapping.get(socket.prop_name, None) + if draft is not None: + prop_name = draft + else: + prop_name = socket.prop_name + else: + prop_name = socket.prop_name + + if prop_name: + prop = getattr(node, prop_name) + return format_bpy_property(prop) + + elif socket.use_prop: + default_property = socket.default_property + return format_bpy_property(default_property) def _calc_cam_update_time(self) -> Iterable['SvNode']: """Return cumulative update time in order of node_group.nodes collection""" @@ -626,28 +694,6 @@ class AddStatistic: return issubclass(exc_type, Exception) -def prepare_input_data(prev_socks: list[Optional[NodeSocket]], - input_socks: list[NodeSocket]): - """Reads data from given outputs socket make it conversion if necessary and - put data into input given socket""" - # this can be a socket method - for ps, ns in zip(prev_socks, input_socks): - if ps is None: - continue - try: - data = ps.sv_get() - except SvNoDataError: - # let to the node handle No Data error - ns.sv_forget() - else: - # cast data - if ps.bl_idname != ns.bl_idname: - implicit_conversion = conversions[ns.default_conversion_name] - data = implicit_conversion.convert(ns, ps, data) - - ns.sv_set(data) - - def update_ui(tree: NodeTree, times: Iterable[float] = None): """Updates UI of the given tree :times: optional node timing in order of group_tree.nodes collection""" diff --git a/tests/socket_conversion_tests.py b/tests/socket_conversion_tests.py index 22da3d21ed26ca05e03873e1032b8c90f4f01671..427b8db9168daae1adcde7b78d25b0c3846b4ff5 100644 --- a/tests/socket_conversion_tests.py +++ b/tests/socket_conversion_tests.py @@ -1,4 +1,4 @@ -from sverchok.core.update_system import prepare_input_data +from sverchok.core.update_system import UpdateTree from mathutils import Matrix from sverchok.core.sv_custom_exceptions import ImplicitConversionProhibited from sverchok.utils.testing import * @@ -18,8 +18,10 @@ class SocketConversionTests(EmptyTreeTestCase): self.tree.links.new(ngon.outputs['Vertices'], matrix_apply.inputs['Matrixes']) # Trigger processing of NGon node - ngon.process() - prepare_input_data([ngon.outputs['Vertices']], [matrix_apply.inputs['Matrixes']]) + tree = UpdateTree.get(ngon.id_data) + tree.update_node(ngon) + tree.update_node(matrix_apply) + UpdateTree.reset_tree() # Read what MatrixApply node sees data =[[v[:] for v in m] for m in matrix_apply.inputs['Matrixes'].sv_get()]