forked from franMarz/TexTools-Blender
-
Notifications
You must be signed in to change notification settings - Fork 0
/
op_island_align_sort.py
171 lines (121 loc) · 4.91 KB
/
op_island_align_sort.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import bpy
import bmesh
import operator
import math
from mathutils import Vector
from collections import defaultdict
from . import utilities_uv
import imp
imp.reload(utilities_uv)
class op(bpy.types.Operator):
bl_idname = "uv.textools_island_align_sort"
bl_label = "Align & Sort"
bl_description = "Rotates UV islands to minimal bounds and sorts them horizontal or vertical"
bl_options = {'REGISTER', 'UNDO'}
is_vertical : bpy.props.BoolProperty(description="Vertical or Horizontal orientation", default=True)
padding : bpy.props.FloatProperty(description="Padding between UV islands", default=0.05)
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False #self.report({'WARNING'}, "Object must have more than one UV map")
#Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
return True
def execute(self, context):
main(context, self.is_vertical, self.padding)
return {'FINISHED'}
def main(context, isVertical, padding):
print("Executing IslandsAlignSort main {}".format(padding))
#Store selection
utilities_uv.selection_store()
if bpy.context.tool_settings.transform_pivot_point != 'CURSOR':
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
#Only in Face or Island mode
if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND':
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
boundsAll = utilities_uv.getSelectionBBox()
islands = utilities_uv.getSelectionIslands()
allSizes = {} #https://stackoverflow.com/questions/613183/sort-a-python-dictionary-by-value
allBounds = {}
print("Islands: "+str(len(islands))+"x")
bpy.context.window_manager.progress_begin(0, len(islands))
#Rotate to minimal bounds
for i in range(0, len(islands)):
alignIslandMinimalBounds(uv_layers, islands[i])
# Collect BBox sizes
bounds = utilities_uv.getSelectionBBox()
allSizes[i] = max(bounds['width'], bounds['height']) + i*0.000001;#Make each size unique
allBounds[i] = bounds;
print("Rotate compact: "+str(allSizes[i]))
bpy.context.window_manager.progress_update(i)
bpy.context.window_manager.progress_end()
#Position by sorted size in row
sortedSizes = sorted(allSizes.items(), key=operator.itemgetter(1))#Sort by values, store tuples
sortedSizes.reverse()
offset = 0.0
for sortedSize in sortedSizes:
index = sortedSize[0]
island = islands[index]
bounds = allBounds[index]
#Select Island
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(island)
#Offset Island
if(isVertical):
delta = Vector((boundsAll['min'].x - bounds['min'].x, boundsAll['max'].y - bounds['max'].y));
bpy.ops.transform.translate(value=(delta.x, delta.y-offset, 0))
offset += bounds['height']+padding
else:
print("Horizontal")
delta = Vector((boundsAll['min'].x - bounds['min'].x, boundsAll['max'].y - bounds['max'].y));
bpy.ops.transform.translate(value=(delta.x+offset, delta.y, 0))
offset += bounds['width']+padding
#Restore selection
utilities_uv.selection_restore()
def alignIslandMinimalBounds(uv_layers, faces):
# Select Island
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(faces)
steps = 8
angle = 45; # Starting Angle, half each step
bboxPrevious = utilities_uv.getSelectionBBox()
for i in range(0, steps):
# Rotate right
bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z')
bbox = utilities_uv.getSelectionBBox()
if i == 0:
sizeA = bboxPrevious['width'] * bboxPrevious['height']
sizeB = bbox['width'] * bbox['height']
if abs(bbox['width'] - bbox['height']) <= 0.0001 and sizeA < sizeB:
# print("Already squared")
bpy.ops.transform.rotate(value=(-angle * math.pi / 180), orient_axis='Z')
break;
if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success
else:
# Rotate Left
bpy.ops.transform.rotate(value=(-angle*2 * math.pi / 180), orient_axis='Z')
bbox = utilities_uv.getSelectionBBox()
if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success
else:
# Restore angle of this iteration
bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z')
angle = angle / 2
if bboxPrevious['width'] < bboxPrevious['height']:
bpy.ops.transform.rotate(value=(90 * math.pi / 180), orient_axis='Z')
bpy.utils.register_class(op)