[Bf-extensions-cvs] [760364b] master: Node Wrangler: Rewrite 'Align Nodes' function

Greg Zaal noreply at git.blender.org
Tue Apr 14 16:30:55 CEST 2015


Commit: 760364b233192a43cf0d300a33900a9aa3029441
Author: Greg Zaal
Date:   Tue Apr 14 15:58:23 2015 +0200
Branches: master
https://developer.blender.org/rBA760364b233192a43cf0d300a33900a9aa3029441

Node Wrangler: Rewrite 'Align Nodes' function

The previous behaviour was flawed:
1. Repeatedly aligning the same selection of nodes would space them further and further apart each time
2. The user had to choose between "Horizontal" and "Vertical", which gave the opposite of intended behavior

The new behavior:
1. Nodes are spaced evenly and consistently apart
2. Whether the nodes are aligned vertically or horizontally is now determined automatically

===================================================================

M	node_wrangler.py

===================================================================

diff --git a/node_wrangler.py b/node_wrangler.py
index 7926d67..7a1b310 100644
--- a/node_wrangler.py
+++ b/node_wrangler.py
@@ -2671,98 +2671,63 @@ class NWLinkActiveToSelected(Operator, NWBase):
 
 
 class NWAlignNodes(Operator, NWBase):
+    '''Align the selected nodes neatly in a row/column'''
     bl_idname = "node.nw_align_nodes"
-    bl_label = "Align nodes"
+    bl_label = "Align Nodes"
     bl_options = {'REGISTER', 'UNDO'}
 
-    # option: 'Vertically', 'Horizontally'
-    option = EnumProperty(
-        name="option",
-        description="Direction",
-        items=(
-            ('AXIS_X', "Align Vertically", 'Align Vertically'),
-            ('AXIS_Y', "Aligh Horizontally", 'Aligh Horizontally'),
-        )
-    )
-
     def execute(self, context):
+        # TODO prop: lock active (arrange everything without moving active node)
         nodes, links = get_nodes_links(context)
-        selected = []  # entry = [index, loc.x, loc.y, width, height]
-        frames_reselect = []  # entry = frame node. will be used to reselect all selected frames
-        active = nodes.active
-        for i, node in enumerate(nodes):
-            total_w = 0.0  # total width of all nodes. Will be calculated later.
-            total_h = 0.0  # total height of all nodes. Will be calculated later
-            if node.select:
-                if node.type == 'FRAME':
-                    node.select = False
-                    frames_reselect.append(i)
-                else:
-                    locx = node.location.x
-                    locy = node.location.y
-                    width = node.dimensions[0]
-                    height = node.dimensions[1]
-                    total_w += width  # add nodes[i] width to total width of all nodes
-                    total_h += height  # add nodes[i] height to total height of all nodes
-                    # calculate relative locations
-                    parent = node.parent
-                    while parent is not None:
-                        locx += parent.location.x
-                        locy += parent.location.y
-                        parent = parent.parent
-                    selected.append([i, locx, locy, width, height])
-        count = len(selected)
-        if count > 1:  # aligning makes sense only if at least 2 nodes are selected
-            selected_sorted_x = sorted(selected, key=lambda k: (k[1], -k[2]))
-            selected_sorted_y = sorted(selected, key=lambda k: (-k[2], k[1]))
-            min_x = selected_sorted_x[0][1]  # min loc.x
-            min_x_loc_y = selected_sorted_x[0][2]  # loc y of node with min loc x
-            min_x_w = selected_sorted_x[0][3]  # width of node with max loc x
-            max_x = selected_sorted_x[count - 1][1]  # max loc.x
-            max_x_loc_y = selected_sorted_x[count - 1][2]  # loc y of node with max loc.x
-            max_x_w = selected_sorted_x[count - 1][3]  # width of node with max loc.x
-            min_y = selected_sorted_y[0][2]  # min loc.y
-            min_y_loc_x = selected_sorted_y[0][1]  # loc.x of node with min loc.y
-            min_y_h = selected_sorted_y[0][4]  # height of node with min loc.y
-            min_y_w = selected_sorted_y[0][3]  # width of node with min loc.y
-            max_y = selected_sorted_y[count - 1][2]  # max loc.y
-            max_y_loc_x = selected_sorted_y[count - 1][1]  # loc x of node with max loc.y
-            max_y_w = selected_sorted_y[count - 1][3]  # width of node with max loc.y
-            max_y_h = selected_sorted_y[count - 1][4]  # height of node with max loc.y
-
-            if self.option == 'AXIS_Y':  # Horizontally. Equivelent of s -> x -> 0 with even spacing.
-                loc_x = min_x
-                #loc_y = (max_x_loc_y + min_x_loc_y) / 2.0
-                loc_y = (max_y - max_y_h / 2.0 + min_y - min_y_h / 2.0) / 2.0
-                offset_x = (max_x - min_x - total_w + max_x_w) / (count - 1)
-                for i, x, y, w, h in selected_sorted_x:
-                    nodes[i].location.x = loc_x
-                    nodes[i].location.y = loc_y + h / 2.0
-                    parent = nodes[i].parent
-                    while parent is not None:
-                        nodes[i].location.x -= parent.location.x
-                        nodes[i].location.y -= parent.location.y
-                        parent = parent.parent
-                    loc_x += offset_x + w
-            else:  # if self.option == 'AXIS_Y'
-                loc_x = (max_x + max_x_w / 2.0 + min_x + min_x_w / 2.0) / 2.0
-                loc_y = min_y
-                offset_y = (max_y - min_y + total_h - min_y_h) / (count - 1)
-                for i, x, y, w, h in selected_sorted_y:
-                    nodes[i].location.x = loc_x - w / 2.0
-                    nodes[i].location.y = loc_y
-                    parent = nodes[i].parent
-                    while parent is not None:
-                        nodes[i].location.x -= parent.location.x
-                        nodes[i].location.y -= parent.location.y
-                        parent = parent.parent
-                    loc_y += offset_y - h
-
-            # reselect selected frames
-            for i in frames_reselect:
-                nodes[i].select = True
-            # restore active node
-            nodes.active = active
+        margin = 80
+        
+        selection = []
+        for node in nodes:
+            if node.select and node.type != 'FRAME':
+                selection.append(node)
+
+        # If no nodes are selected, align all nodes
+        if not selection:
+            selection = nodes
+
+        # Check if nodes should be layed out horizontally or vertically
+        x_locs = [n.location.x + (n.dimensions.x / 2) for n in selection]  # use dimension to get center of node, not corner
+        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 / 2 if node.hide else current_margin  # use a smaller margin for hidden nodes
+
+            if horizontal:
+                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 = current_pos
+                current_pos -= (current_margin / 2) + node.dimensions.y  # use half-margin for vertical alignment
+                node.location.x = mid_x - (node.dimensions.x / 2)
+
+        # 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'}
 
@@ -3177,6 +3142,10 @@ def drawlayout(context, layout, mode='non-panel'):
     col.separator()
 
     col = layout.column(align=True)
+    col.operator(NWAlignNodes.bl_idname, icon='ALIGN')
+    col.separator()
+
+    col = layout.column(align=True)
     col.operator(NWDeleteUnused.bl_idname, icon='CANCEL')
     col.separator()
 
@@ -3432,16 +3401,6 @@ class NWLinkUseOutputsNamesMenu(Menu, NWBase):
         props.use_outputs_names = True
 
 
-class NWNodeAlignMenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_node_align_menu"
-    bl_label = "Align Nodes"
-
-    def draw(self, context):
-        layout = self.layout
-        layout.operator(NWAlignNodes.bl_idname, text="Horizontally").option = 'AXIS_X'
-        layout.operator(NWAlignNodes.bl_idname, text="Vertically").option = 'AXIS_Y'
-
-
 class NWVertColMenu(bpy.types.Menu):
     bl_idname = "NODE_MT_nw_node_vertex_color_menu"
     bl_label = "Vertex Colors"
@@ -4071,11 +4030,12 @@ kmi_defs = (
     (NWLazyConnect.bl_idname, 'RIGHTMOUSE', 'PRESS', True, True, False, (('with_menu', True),), "Lazy Connect with Socket Menu"),
     # Viewer Tile Center
     (NWViewerFocus.bl_idname, 'LEFTMOUSE', 'DOUBLE_CLICK', False, False, False, None, "Set Viewers Tile Center"),
+    # Align Nodes
+    (NWAlignNodes.bl_idname, 'EQUAL', 'PRESS', False, True, False, None, "Align selected nodes neatly in a row/column"),
     # MENUS
     ('wm.call_menu', 'SPACE', 'PRESS', True, False, False, (('name', NodeWranglerMenu.bl_idname),), "Node Wranger menu"),
     ('wm.call_menu', 'SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"),
     ('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"),
-    ('wm.call_menu', 'EQUAL', 'PRESS', False, True, False, (('name', NWNodeAlignMenu.bl_idname),), "Node alignment menu"),
     ('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False, (('name', NWLinkActiveToSelectedMenu.bl_idname),), "Link active to selected (menu)"),
     ('wm.call_menu', 'C', 'PRESS', False, True, False, (('name', NWCopyToSelectedMenu.bl_idname),), "Copy to selected (menu)"),
     ('wm.call_menu', 'S', 'PRESS', False, True, False, (('name', NWSwitchNodeTypeMenu.bl_idname),), "Switch node type menu"),
@@ -4133,8 +4093,6 @@ def unregister():
     del bpy.types.Scene.NWLazyTarget
     del bpy.types.Scene.NWSourceSocket
 
-    bpy.utils.unregister_module(__name__)
-
     # keymaps
     for km, kmi in addon_keymaps:
         km.keymap_items.remove(kmi)
@@ -4150,5 +4108,7 @@ def unregister():
     bpy.types.NODE_MT_category_CMP_INPUT.r

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list