Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move default socket properties to update system #4515

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions core/group_update_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ def update(self, node: 'GrNode'):

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:
Expand Down Expand Up @@ -130,10 +130,10 @@ def _walk(self) -> tuple[Node, list[NodeSocket]]:
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:
Expand Down
7 changes: 2 additions & 5 deletions core/socket_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
34 changes: 11 additions & 23 deletions core/sockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,36 +403,24 @@ def sv_get(self, default=..., deepcopy=True):
"""
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"""
Expand Down
138 changes: 92 additions & 46 deletions core/update_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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,
Expand Down Expand Up @@ -154,15 +155,6 @@ def previous_sockets(self, node: 'SvNode') -> list[Optional[NodeSocket]]:
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":
Expand Down Expand Up @@ -340,10 +332,11 @@ def get(cls, tree: "SvTree", refresh_tree=False) -> "UpdateTree":
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:
Expand Down Expand Up @@ -381,10 +374,10 @@ def main_update(cls, tree: NodeTree, update_nodes=True, update_interface=True) -
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
Expand All @@ -396,6 +389,15 @@ def main_update(cls, tree: NodeTree, update_nodes=True, update_interface=True) -
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"""
Expand All @@ -420,6 +422,9 @@ def add_outdated(self, nodes: Iterable):
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
Expand All @@ -439,16 +444,19 @@ def __init__(self, tree: NodeTree):
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',
'is_scene_updated',
'_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()
Expand Down Expand Up @@ -486,19 +494,18 @@ def _walk(self) -> tuple[Node, list[NodeSocket]]:
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:
node[UPDATE_KEY] = False

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
Expand Down Expand Up @@ -528,7 +535,7 @@ def __sort_nodes(self,
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']:
Expand All @@ -543,12 +550,73 @@ def _update_difference(self, old: 'UpdateTree') -> set['SvNode']:
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"""
Expand Down Expand Up @@ -626,28 +694,6 @@ def __exit__(self, exc_type, exc_val, exc_tb):
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"""
Expand Down
8 changes: 5 additions & 3 deletions tests/socket_conversion_tests.py
Original file line number Diff line number Diff line change
@@ -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 *
Expand All @@ -18,8 +18,10 @@ def test_vertices_to_matrices(self):
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()]

Expand Down