Node Wrangler: Improved accuracy on Align Nodes operator #104551
@ -26,7 +26,7 @@ from .utils.draw import draw_callback_nodeoutline
|
||||
from .utils.paths import match_files_to_socket_names, split_into_components
|
||||
from .utils.nodes import (node_mid_pt, autolink, node_at_pos, get_active_tree, get_nodes_links, is_viewer_socket,
|
||||
is_viewer_link, get_group_output_node, get_output_location, force_update, get_internal_socket,
|
||||
nw_check, NWBase, get_first_enabled_output, is_visible_socket, viewer_socket_name)
|
||||
nw_check, NWBase, get_first_enabled_output, is_visible_socket, temporary_unframe, viewer_socket_name)
|
||||
|
||||
|
||||
class NWLazyMix(Operator, NWBase):
|
||||
@ -2352,55 +2352,56 @@ class NWAlignNodes(Operator, NWBase):
|
||||
active_node = context.active_node
|
||||
margin = self.margin
|
||||
|
||||
active_loc = None
|
||||
if active_node in selection:
|
||||
active_loc = copy(active_node.location) # make a copy, not a reference
|
||||
with temporary_unframe(nodes=selection):
|
||||
active_loc = None
|
||||
if active_node in selection:
|
||||
active_loc = copy(active_node.location) # make a copy, not a reference
|
||||
|
||||
# Check if nodes should be laid out horizontally or vertically
|
||||
# use dimension to get center of node, not corner
|
||||
x_locs = [n.location.x + (n.dimensions.x / 2) for n in selection]
|
||||
y_locs = [n.location.y - (n.dimensions.y / 2) for n in selection]
|
||||
x_range = max(x_locs) - min(x_locs)
|
||||
y_range = max(y_locs) - min(y_locs)
|
||||
mid_x = (max(x_locs) + min(x_locs)) / 2
|
||||
mid_y = (max(y_locs) + min(y_locs)) / 2
|
||||
horizontal = x_range > y_range
|
||||
|
||||
# Sort selection by location of node mid-point
|
||||
if horizontal:
|
||||
selection = sorted(selection, key=lambda n: n.location.x + (n.dimensions.x / 2))
|
||||
else:
|
||||
selection = sorted(selection, key=lambda n: n.location.y - (n.dimensions.y / 2), reverse=True)
|
||||
|
||||
# Alignment
|
||||
current_pos = 0
|
||||
for node in selection:
|
||||
current_margin = margin
|
||||
current_margin = current_margin * 0.5 if node.hide else current_margin # use a smaller margin for hidden nodes
|
||||
# Check if nodes should be laid out horizontally or vertically
|
||||
# use dimension to get center of node, not corner
|
||||
x_locs = [n.location.x + (n.dimensions.x / 2) for n in selection]
|
||||
y_locs = [n.location.y - (n.dimensions.y / 2) for n in selection]
|
||||
x_range = max(x_locs) - min(x_locs)
|
||||
y_range = max(y_locs) - min(y_locs)
|
||||
mid_x = (max(x_locs) + min(x_locs)) / 2
|
||||
mid_y = (max(y_locs) + min(y_locs)) / 2
|
||||
horizontal = x_range > y_range
|
||||
|
||||
# Sort selection by location of node mid-point
|
||||
if horizontal:
|
||||
node.location.x = current_pos
|
||||
current_pos += current_margin + node.dimensions.x
|
||||
node.location.y = mid_y + (node.dimensions.y / 2)
|
||||
selection = sorted(selection, key=lambda n: n.location.x + (n.dimensions.x / 2))
|
||||
else:
|
||||
node.location.y = current_pos
|
||||
current_pos -= (current_margin * 0.3) + node.dimensions.y # use half-margin for vertical alignment
|
||||
node.location.x = mid_x - (node.dimensions.x / 2)
|
||||
selection = sorted(selection, key=lambda n: n.location.y - (n.dimensions.y / 2), reverse=True)
|
||||
|
||||
# If active node is selected, center nodes around it
|
||||
if active_loc is not None:
|
||||
active_loc_diff = active_loc - active_node.location
|
||||
for node in selection:
|
||||
node.location += active_loc_diff
|
||||
else: # Position nodes centered around where they used to be
|
||||
locs = ([n.location.x + (n.dimensions.x / 2) for n in selection]
|
||||
) if horizontal else ([n.location.y - (n.dimensions.y / 2) for n in selection])
|
||||
new_mid = (max(locs) + min(locs)) / 2
|
||||
# Alignment
|
||||
current_pos = 0
|
||||
for node in selection:
|
||||
current_margin = margin
|
||||
current_margin = current_margin * 0.5 if node.hide else current_margin # use a smaller margin for hidden nodes
|
||||
|
||||
if horizontal:
|
||||
node.location.x += (mid_x - new_mid)
|
||||
node.location.x = current_pos
|
||||
current_pos += current_margin + node.dimensions.x
|
||||
node.location.y = mid_y + (node.dimensions.y / 2)
|
||||
else:
|
||||
node.location.y += (mid_y - new_mid)
|
||||
node.location.y = current_pos
|
||||
current_pos -= (current_margin * 0.3) + node.dimensions.y # use half-margin for vertical alignment
|
||||
node.location.x = mid_x - (node.dimensions.x / 2)
|
||||
|
||||
# If active node is selected, center nodes around it
|
||||
if active_loc is not None:
|
||||
active_loc_diff = active_loc - active_node.location
|
||||
for node in selection:
|
||||
node.location += active_loc_diff
|
||||
else: # Position nodes centered around where they used to be
|
||||
locs = ([n.location.x + (n.dimensions.x / 2) for n in selection]
|
||||
) if horizontal else ([n.location.y - (n.dimensions.y / 2) for n in selection])
|
||||
new_mid = (max(locs) + min(locs)) / 2
|
||||
for node in selection:
|
||||
if horizontal:
|
||||
node.location.x += (mid_x - new_mid)
|
||||
else:
|
||||
node.location.y += (mid_y - new_mid)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
@ -250,6 +250,22 @@ def is_visible_socket(socket):
|
||||
return not socket.hide and socket.enabled and socket.type != 'CUSTOM'
|
||||
|
||||
|
||||
class temporary_unframe(): # Context manager for temporarily unparenting nodes from their frames
|
||||
def __init__(self, nodes):
|
||||
self.parent_dict = {}
|
||||
for node in nodes:
|
||||
if node.parent is not None:
|
||||
self.parent_dict[node] = node.parent
|
||||
node.parent = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
for node, parent in self.parent_dict.items():
|
||||
node.parent = parent
|
||||
|
||||
|
||||
class NWBase:
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
Loading…
Reference in New Issue
Block a user