Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2019-09-05 03:31:18 +0300
committermeta-androcto <meta.androcto1@gmail.com>2019-09-05 03:31:18 +0300
commit2a98d83b4bcb37e3b8de5ccc72c283a5135708ed (patch)
tree7088d3294d60206820a1dcae5c727a7573f4ca3e /node_arrange.py
parent734f7eb12bb9651e6327131594e6da414d2cccd7 (diff)
Node Arrange: 2.81, Thanks JuhaW: T66410
Diffstat (limited to 'node_arrange.py')
-rw-r--r--node_arrange.py520
1 files changed, 520 insertions, 0 deletions
diff --git a/node_arrange.py b/node_arrange.py
new file mode 100644
index 00000000..370cbeff
--- /dev/null
+++ b/node_arrange.py
@@ -0,0 +1,520 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Node Arrange",
+ "author": "JuhaW",
+ "version": (0, 2, 1),
+ "blender": (2, 80, 4),
+ "location": "Node Editor > Properties > Trees",
+ "description": "Node Tree Arrangement Tools",
+ "warning": "",
+ "wiki_url": "",
+ "tracker_url": "https://github.com/JuhaW/NodeArrange/issues",
+ "category": "Node"
+}
+
+
+import sys
+import bpy
+from collections import OrderedDict
+from itertools import repeat
+import pprint
+import pdb
+from bpy.types import Operator, Panel
+from bpy.props import (
+ IntProperty,
+)
+from copy import copy
+
+
+#From Node Wrangler
+def get_nodes_linked(context):
+ tree = context.space_data.node_tree
+
+ # Get nodes from currently edited tree.
+ # If user is editing a group, space_data.node_tree is still the base level (outside group).
+ # context.active_node is in the group though, so if space_data.node_tree.nodes.active is not
+ # the same as context.active_node, the user is in a group.
+ # Check recursively until we find the real active node_tree:
+ if tree.nodes.active:
+ while tree.nodes.active != context.active_node:
+ tree = tree.nodes.active.node_tree
+
+ return tree.nodes, tree.links
+
+class NA_OT_AlignNodes(Operator):
+ '''Align the selected nodes/Tidy loose nodes'''
+ bl_idname = "node.na_align_nodes"
+ bl_label = "Align Nodes"
+ bl_options = {'REGISTER', 'UNDO'}
+ margin: IntProperty(name='Margin', default=50, description='The amount of space between nodes')
+
+ def execute(self, context):
+ nodes, links = get_nodes_linked(context)
+ margin = self.margin
+
+ selection = []
+ for node in nodes:
+ if node.select and node.type != 'FRAME':
+ selection.append(node)
+
+ # If no nodes are selected, align all nodes
+ active_loc = None
+ if not selection:
+ selection = nodes
+ elif nodes.active in selection:
+ active_loc = copy(nodes.active.location) # make a copy, not a reference
+
+ # Check if nodes should be laid 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 * 0.5 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 * 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 - nodes.active.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'}
+
+class values():
+ average_y = 0
+ x_last = 0
+ margin_x = 100
+ mat_name = ""
+ margin_y = 20
+
+
+class NA_PT_NodePanel(Panel):
+ bl_label = "Node Arrange"
+ bl_space_type = "NODE_EDITOR"
+ bl_region_type = "UI"
+ bl_category = "Arrange"
+
+ def draw(self, context):
+ if context.active_node is not None:
+ layout = self.layout
+ row = layout.row()
+ col = layout.column
+ row.operator('node.button')
+
+ row = layout.row()
+ row.prop(bpy.context.scene, 'nodemargin_x', text="Margin x")
+ row = layout.row()
+ row.prop(bpy.context.scene, 'nodemargin_y', text="Margin y")
+ row = layout.row()
+ row.prop(context.scene, 'node_center', text="Center nodes")
+
+ row = layout.row()
+ row.operator('node.na_align_nodes', text="Align to Selected")
+
+ row = layout.row()
+ node = context.space_data.node_tree.nodes.active
+ if node and node.select:
+ row.prop(node, 'location', text = "Node X", index = 0)
+ row.prop(node, 'location', text = "Node Y", index = 1)
+ row = layout.row()
+ row.prop(node, 'width', text = "Node width")
+
+ row = layout.row()
+ row.operator('node.button_odd')
+
+class NA_OT_NodeButton(Operator):
+
+ '''Arrange Connected Nodes/Arrange All Nodes'''
+ bl_idname = 'node.button'
+ bl_label = 'Arrange All Nodes'
+
+ def execute(self, context):
+ nodemargin(self, context)
+ bpy.context.space_data.node_tree.nodes.update()
+ bpy.ops.node.view_all()
+
+ return {'FINISHED'}
+
+ # not sure this is doing what you expect.
+ # blender.org/api/blender_python_api_current/bpy.types.Operator.html#invoke
+ def invoke(self, context, value):
+ values.mat_name = bpy.context.space_data.node_tree
+ nodemargin(self, context)
+ return {'FINISHED'}
+
+
+class NA_OT_NodeButtonOdd(Operator):
+
+ 'Show the nodes for this material'
+ bl_idname = 'node.button_odd'
+ bl_label = 'Select Unlinked'
+
+ def execute(self, context):
+ values.mat_name = bpy.context.space_data.node_tree
+ #mat = bpy.context.object.active_material
+ nodes_iterate(context.space_data.node_tree, False)
+ return {'FINISHED'}
+
+
+class NA_OT_NodeButtonCenter(Operator):
+
+ 'Show the nodes for this material'
+ bl_idname = 'node.button_center'
+ bl_label = 'Center nodes (0,0)'
+
+ def execute(self, context):
+ values.mat_name = "" # reset
+ mat = bpy.context.object.active_material
+ nodes_center(mat)
+ return {'FINISHED'}
+
+
+def nodemargin(self, context):
+
+ values.margin_x = context.scene.nodemargin_x
+ values.margin_y = context.scene.nodemargin_y
+
+ ntree = context.space_data.node_tree
+
+ nodes_iterate(ntree)
+
+ # arrange nodes + this center nodes together
+ if context.scene.node_center:
+ nodes_center(ntree)
+
+
+class NA_OT_ArrangeNodesOp(bpy.types.Operator):
+ bl_idname = 'node.arrange_nodetree'
+ bl_label = 'Nodes Private Op'
+
+ mat_name : bpy.props.StringProperty()
+ margin_x : bpy.props.IntProperty(default=120)
+ margin_y : bpy.props.IntProperty(default=120)
+
+ def nodemargin2(self, context):
+ mat = None
+ mat_found = bpy.data.materials.get(self.mat_name)
+ if self.mat_name and mat_found:
+ mat = mat_found
+ #print(mat)
+
+ if not mat:
+ return
+ else:
+ values.mat_name = self.mat_name
+ scn = context.scene
+ scn.nodemargin_x = self.margin_x
+ scn.nodemargin_y = self.margin_y
+ nodes_iterate(mat)
+ if scn.node_center:
+ nodes_center(mat)
+
+ def execute(self, context):
+ self.nodemargin2(context)
+ return {'FINISHED'}
+
+
+def outputnode_search(ntree): # return node/None
+
+ outputnodes = []
+ for node in bpy.context.space_data.node_tree.nodes:
+ if not node.outputs:
+ for input in node.inputs:
+ if input.is_linked:
+ outputnodes.append(node)
+ break
+
+ if not outputnodes:
+ print("No output node found")
+ return None
+ return outputnodes
+
+
+###############################################################
+def nodes_iterate(ntree, arrange=True):
+
+ nodeoutput = outputnode_search(ntree)
+ if nodeoutput is None:
+ #print ("nodeoutput is None")
+ return None
+ a = []
+ a.append([])
+ for i in nodeoutput:
+ a[0].append(i)
+
+
+ level = 0
+
+ while a[level]:
+ a.append([])
+
+ for node in a[level]:
+ inputlist = [i for i in node.inputs if i.is_linked]
+
+ if inputlist:
+
+ for input in inputlist:
+ for nlinks in input.links:
+ node1 = nlinks.from_node
+ a[level + 1].append(node1)
+
+ else:
+ pass
+
+ level += 1
+
+ del a[level]
+ level -= 1
+
+ #remove duplicate nodes at the same level, first wins
+ for x, nodes in enumerate(a):
+ a[x] = list(OrderedDict(zip(a[x], repeat(None))))
+
+ #remove duplicate nodes in all levels, last wins
+ top = level
+ for row1 in range(top, 1, -1):
+ for col1 in a[row1]:
+ for row2 in range(row1-1, 0, -1):
+ for col2 in a[row2]:
+ if col1 == col2:
+ a[row2].remove(col2)
+ break
+
+ """
+ for x, i in enumerate(a):
+ print (x)
+ for j in i:
+ print (j)
+ #print()
+ """
+ """
+ #add node frames to nodelist
+ frames = []
+ print ("Frames:")
+ print ("level:", level)
+ print ("a:",a)
+ for row in range(level, 0, -1):
+
+ for i, node in enumerate(a[row]):
+ if node.parent:
+ print ("Frame found:", node.parent, node)
+ #if frame already added to the list ?
+ frame = node.parent
+ #remove node
+ del a[row][i]
+ if frame not in frames:
+ frames.append(frame)
+ #add frame to the same place than node was
+ a[row].insert(i, frame)
+
+ pprint.pprint(a)
+ """
+ #return None
+ ########################################
+
+
+
+ if not arrange:
+ nodelist = [j for i in a for j in i]
+ nodes_odd(ntree, nodelist=nodelist)
+ return None
+
+ ########################################
+
+ levelmax = level + 1
+ level = 0
+ values.x_last = 0
+
+ while level < levelmax:
+
+ values.average_y = 0
+ nodes = [x for x in a[level]]
+ #print ("level, nodes:", level, nodes)
+ nodes_arrange(nodes, level)
+
+ level = level + 1
+
+ return None
+
+
+###############################################################
+def nodes_odd(ntree, nodelist):
+
+ nodes = ntree.nodes
+ for i in nodes:
+ i.select = False
+
+ a = [x for x in nodes if x not in nodelist]
+ # print ("odd nodes:",a)
+ for i in a:
+ i.select = True
+
+
+def nodes_arrange(nodelist, level):
+
+ parents = []
+ for node in nodelist:
+ parents.append(node.parent)
+ node.parent = None
+ bpy.context.space_data.node_tree.nodes.update()
+
+
+ #print ("nodes arrange def")
+ # node x positions
+
+ widthmax = max([x.dimensions.x for x in nodelist])
+ xpos = values.x_last - (widthmax + values.margin_x) if level != 0 else 0
+ #print ("nodelist, xpos", nodelist,xpos)
+ values.x_last = xpos
+
+ # node y positions
+ x = 0
+ y = 0
+
+ for node in nodelist:
+
+ if node.hide:
+ hidey = (node.dimensions.y / 2) - 8
+ y = y - hidey
+ else:
+ hidey = 0
+
+ node.location.y = y
+ y = y - values.margin_y - node.dimensions.y + hidey
+
+ node.location.x = xpos #if node.type != "FRAME" else xpos + 1200
+
+ y = y + values.margin_y
+
+ center = (0 + y) / 2
+ values.average_y = center - values.average_y
+
+ #for node in nodelist:
+
+ #node.location.y -= values.average_y
+
+ for i, node in enumerate(nodelist):
+ node.parent = parents[i]
+
+def nodetree_get(mat):
+
+ return mat.node_tree.nodes
+
+
+def nodes_center(ntree):
+
+ bboxminx = []
+ bboxmaxx = []
+ bboxmaxy = []
+ bboxminy = []
+
+ for node in ntree.nodes:
+ if not node.parent:
+ bboxminx.append(node.location.x)
+ bboxmaxx.append(node.location.x + node.dimensions.x)
+ bboxmaxy.append(node.location.y)
+ bboxminy.append(node.location.y - node.dimensions.y)
+
+ # print ("bboxminy:",bboxminy)
+ bboxminx = min(bboxminx)
+ bboxmaxx = max(bboxmaxx)
+ bboxminy = min(bboxminy)
+ bboxmaxy = max(bboxmaxy)
+ center_x = (bboxminx + bboxmaxx) / 2
+ center_y = (bboxminy + bboxmaxy) / 2
+ '''
+ print ("minx:",bboxminx)
+ print ("maxx:",bboxmaxx)
+ print ("miny:",bboxminy)
+ print ("maxy:",bboxmaxy)
+
+ print ("bboxes:", bboxminx, bboxmaxx, bboxmaxy, bboxminy)
+ print ("center x:",center_x)
+ print ("center y:",center_y)
+ '''
+
+ x = 0
+ y = 0
+
+ for node in ntree.nodes:
+
+ if not node.parent:
+ node.location.x -= center_x
+ node.location.y += -center_y
+
+classes = [
+ NA_PT_NodePanel,
+ NA_OT_NodeButton,
+ NA_OT_NodeButtonOdd,
+ NA_OT_NodeButtonCenter,
+ NA_OT_ArrangeNodesOp,
+ NA_OT_AlignNodes
+]
+
+def register():
+ for c in classes:
+ bpy.utils.register_class(c)
+
+ bpy.types.Scene.nodemargin_x = bpy.props.IntProperty(default=100, update=nodemargin)
+ bpy.types.Scene.nodemargin_y = bpy.props.IntProperty(default=20, update=nodemargin)
+ bpy.types.Scene.node_center = bpy.props.BoolProperty(default=True, update=nodemargin)
+
+
+
+def unregister():
+ for c in classes:
+ bpy.utils.unregister_class(c)
+
+ del bpy.types.Scene.nodemargin_x
+ del bpy.types.Scene.nodemargin_y
+ del bpy.types.Scene.node_center
+
+if __name__ == "__main__":
+ register()