Import Export Vertex Groups rewritten
[blender-addons-contrib.git] / uv_align_distribute.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; version 2
6 #  of the License.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 bl_info = {
20     "name": "UV Align/Distribute",
21     "author": "Rebellion (Luca Carella)",
22     "version": (1, 1),
23     "blender": (2, 7, 1),
24     "location": "UV/Image editor > Tool Panel, UV/Image editor UVs > menu",
25     "description": "Set of tools to help UV alignment\distribution",
26     "warning": "",
27     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/UV/UV_Align_Distribution",
28     "category": "UV"}
29
30 import math
31 from collections import defaultdict
32
33 import bmesh
34 import bpy
35 import mathutils
36 from bpy.props import EnumProperty, BoolProperty, FloatProperty
37
38
39 # Globals:
40 bpy.types.Scene.relativeItems = EnumProperty(
41     items=[
42         ('UV_SPACE', 'Uv Space', 'Align to UV space'),
43         ('ACTIVE', 'Active Face', 'Align to active face\island'),
44         ('CURSOR', 'Cursor', 'Align to cursor')],
45     name="Relative to")
46
47 bpy.types.Scene.selectionAsGroup = BoolProperty(
48     name="Selection as group",
49     description="Treat selection as group",
50     default=False)
51
52 bm = None
53 uvlayer = None
54
55
56 def InitBMesh():
57     global bm
58     global uvlayer
59     bm = bmesh.from_edit_mesh(bpy.context.edit_object.data)
60     uvlayer = bm.loops.layers.uv.active
61
62
63 def update():
64     bmesh.update_edit_mesh(bpy.context.edit_object.data, False, False)
65     # bm.to_mesh(bpy.context.object.data)
66     # bm.free()
67
68
69 def GBBox(islands):
70     minX = minY = 1000
71     maxX = maxY = -1000
72     for island in islands:
73         for face_id in island:
74             face = bm.faces[face_id]
75             for loop in face.loops:
76                 u, v = loop[uvlayer].uv
77                 minX = min(u, minX)
78                 minY = min(v, minY)
79                 maxX = max(u, maxX)
80                 maxY = max(v, maxY)
81
82     return mathutils.Vector((minX, minY)), mathutils.Vector((maxX, maxY))
83
84
85 def GBBoxCenter(islands):
86     minX = minY = 1000
87     maxX = maxY = -1000
88     for island in islands:
89         for face_id in island:
90             face = bm.faces[face_id]
91             for loop in face.loops:
92                 u, v = loop[uvlayer].uv
93                 minX = min(u, minX)
94                 minY = min(v, minY)
95                 maxX = max(u, maxX)
96                 maxY = max(v, maxY)
97
98     return (mathutils.Vector((minX, minY)) +
99             mathutils.Vector((maxX, maxY))) / 2
100
101
102 def BBox(island):
103     minX = minY = 1000
104     maxX = maxY = -1000
105     # for island in islands:
106     # print(island)
107     for face_id in island:
108         face = bm.faces[face_id]
109         for loop in face.loops:
110             u, v = loop[uvlayer].uv
111             minX = min(u, minX)
112             minY = min(v, minY)
113             maxX = max(u, maxX)
114             maxY = max(v, maxY)
115
116     return mathutils.Vector((minX, minY)), mathutils.Vector((maxX, maxY))
117
118
119 def BBoxCenter(island):
120     minX = minY = 1000
121     maxX = maxY = -1000
122     # for island in islands:
123     for face_id in island:
124         face = bm.faces[face_id]
125         for loop in face.loops:
126             u, v = loop[uvlayer].uv
127             minX = min(u, minX)
128             minY = min(v, minY)
129             maxX = max(u, maxX)
130             maxY = max(v, maxY)
131
132     return (mathutils.Vector((minX, minY)) +
133             mathutils.Vector((maxX, maxY))) / 2
134
135
136 def islandAngle(island):
137     uvList = []
138     for face_id in island:
139         face = bm.faces[face_id]
140         for loop in face.loops:
141             uv = loop[bm.loops.layers.uv.active].uv
142             uvList.append(uv)
143
144     angle = math.degrees(mathutils.geometry.box_fit_2d(uvList))
145     return angle
146
147
148 def moveIslands(vector, island):
149     for face_id in island:
150         face = bm.faces[face_id]
151         for loop in face.loops:
152             loop[bm.loops.layers.uv.active].uv += vector
153
154
155 def rotateIsland(island, angle):
156     rad = math.radians(angle)
157     center = BBoxCenter(island)
158
159     for face_id in island:
160         face = bm.faces[face_id]
161         for loop in face.loops:
162             uv_act = bm.loops.layers.uv.active
163             x, y = loop[uv_act].uv
164             xt = x - center.x
165             yt = y - center.y
166             xr = (xt * math.cos(rad)) - (yt * math.sin(rad))
167             yr = (xt * math.sin(rad)) + (yt * math.cos(rad))
168             # loop[bm.loops.layers.uv.active].uv = trans
169             loop[bm.loops.layers.uv.active].uv.x = xr + center.x
170             loop[bm.loops.layers.uv.active].uv.y = yr + center.y
171             # print('fired')
172
173
174 def scaleIsland(island, scaleX, scaleY):
175     scale = mathutils.Vector((scaleX, scaleY))
176     center = BBoxCenter(island)
177
178     for face_id in island:
179         face = bm.faces[face_id]
180         for loop in face.loops:
181             x = loop[bm.loops.layers.uv.active].uv.x
182             y = loop[bm.loops.layers.uv.active].uv.y
183             xt = x - center.x
184             yt = y - center.y
185             xs = xt * scaleX
186             ys = yt * scaleY
187             loop[bm.loops.layers.uv.active].uv.x = xs + center.x
188             loop[bm.loops.layers.uv.active].uv.y = ys + center.y
189
190
191 def vectorDistance(vector1, vector2):
192     return math.sqrt(
193         math.pow((vector2.x - vector1.x), 2) +
194         math.pow((vector2.y - vector1.y), 2))
195
196
197 def matchIsland(active, thresold, island):
198     for active_face_id in active:
199         active_face = bm.faces[active_face_id]
200
201         for active_loop in active_face.loops:
202             activeUVvert = active_loop[bm.loops.layers.uv.active].uv
203
204             for face_id in island:
205                 face = bm.faces[face_id]
206
207                 for loop in face.loops:
208                     selectedUVvert = loop[bm.loops.layers.uv.active].uv
209                     dist = vectorDistance(selectedUVvert, activeUVvert)
210
211                     if dist <= thresold:
212                         loop[bm.loops.layers.uv.active].uv = activeUVvert
213
214
215 def getTargetPoint(context, islands):
216     if context.scene.relativeItems == 'UV_SPACE':
217         return mathutils.Vector((0.0, 0.0)), mathutils.Vector((1.0, 1.0))
218     elif context.scene.relativeItems == 'ACTIVE':
219         activeIsland = islands.activeIsland()
220         if not activeIsland:
221             return None
222         else:
223             return BBox(activeIsland)
224     elif context.scene.relativeItems == 'CURSOR':
225         return context.space_data.cursor_location,\
226             context.space_data.cursor_location
227
228
229 def IslandSpatialSortX(islands):
230     spatialSort = []
231     for island in islands:
232         spatialSort.append((BBoxCenter(island).x, island))
233     spatialSort.sort()
234     return spatialSort
235
236
237 def IslandSpatialSortY(islands):
238     spatialSort = []
239     for island in islands:
240         spatialSort.append((BBoxCenter(island).y, island))
241     spatialSort.sort()
242     return spatialSort
243
244
245 def averageIslandDist(islands):
246     distX = 0
247     distY = 0
248     counter = 0
249
250     for i in range(len(islands)):
251         elem1 = BBox(islands[i][1])[1]
252         try:
253             elem2 = BBox(islands[i + 1][1])[0]
254             counter += 1
255         except:
256             break
257
258         distX += elem2.x - elem1.x
259         distY += elem2.y - elem1.y
260
261     avgDistX = distX / counter
262     avgDistY = distY / counter
263     return mathutils.Vector((avgDistX, avgDistY))
264
265
266 def islandSize(island):
267     bbox = BBox(island)
268     sizeX = bbox[1].x - bbox[0].x
269     sizeY = bbox[1].y - bbox[0].y
270
271     return sizeX, sizeY
272
273
274 class MakeIslands():
275
276     def __init__(self):
277         InitBMesh()
278         global bm
279         global uvlayer
280
281         self.face_to_verts = defaultdict(set)
282         self.vert_to_faces = defaultdict(set)
283         self.selectedIsland = set()
284
285         for face in bm.faces:
286             for loop in face.loops:
287                 id = loop[uvlayer].uv.to_tuple(5), loop.vert.index
288                 self.face_to_verts[face.index].add(id)
289                 self.vert_to_faces[id].add(face.index)
290                 if face.select:
291                     if loop[uvlayer].select:
292                         self.selectedIsland.add(face.index)
293
294     def addToIsland(self, face_id):
295         if face_id in self.faces_left:
296             # add the face itself
297             self.current_island.append(face_id)
298             self.faces_left.remove(face_id)
299             # and add all faces that share uvs with this face
300             verts = self.face_to_verts[face_id]
301             for vert in verts:
302                 # print('looking at vert {}'.format(vert))
303                 connected_faces = self.vert_to_faces[vert]
304                 if connected_faces:
305                     for face in connected_faces:
306                         self.addToIsland(face)
307
308     def getIslands(self):
309         self.islands = []
310         self.faces_left = set(self.face_to_verts.keys())
311         while len(self.faces_left) > 0:
312             face_id = list(self.faces_left)[0]
313             self.current_island = []
314             self.addToIsland(face_id)
315             self.islands.append(self.current_island)
316
317         return self.islands
318
319     def activeIsland(self):
320         for island in self.islands:
321             try:
322                 if bm.faces.active.index in island:
323                     return island
324             except:
325                 return None
326
327     def selectedIslands(self):
328         _selectedIslands = []
329         for island in self.islands:
330             if not self.selectedIsland.isdisjoint(island):
331                 _selectedIslands.append(island)
332         return _selectedIslands
333
334
335 # #####################
336 # OPERATOR
337 # #####################
338
339 class OperatorTemplate(bpy.types.Operator):
340
341     @classmethod
342     def poll(cls, context):
343         return not (context.scene.tool_settings.use_uv_select_sync)
344
345
346 #####################
347 # ALIGN
348 #####################
349
350 class AlignSXMargin(OperatorTemplate):
351
352     """Align left margin"""
353     bl_idname = "uv.align_left_margin"
354     bl_label = "Align left margin"
355     bl_options = {'REGISTER', 'UNDO'}
356
357     def execute(self, context):
358
359         makeIslands = MakeIslands()
360         islands = makeIslands.getIslands()
361         selectedIslands = makeIslands.selectedIslands()
362
363         targetElement = getTargetPoint(context, makeIslands)
364         if not targetElement:
365             self.report({"ERROR"}, "No active face")
366             return {"CANCELLED"}
367
368         if context.scene.selectionAsGroup:
369             groupBox = GBBox(selectedIslands)
370             if context.scene.relativeItems == 'ACTIVE':
371                 selectedIslands.remove(makeIslands.activeIsland())
372             for island in selectedIslands:
373                 vector = mathutils.Vector((targetElement[0].x - groupBox[0].x,
374                                            0.0))
375                 moveIslands(vector, island)
376
377         else:
378             for island in selectedIslands:
379                 vector = mathutils.Vector(
380                     (targetElement[0].x - BBox(island)[0].x, 0.0))
381                 moveIslands(vector, island)
382
383         update()
384         return {'FINISHED'}
385
386
387 class AlignRxMargin(OperatorTemplate):
388
389     """Align right margin"""
390     bl_idname = "uv.align_right_margin"
391     bl_label = "Align right margin"
392     bl_options = {'REGISTER', 'UNDO'}
393
394     def execute(self, context):
395         makeIslands = MakeIslands()
396         islands = makeIslands.getIslands()
397         selectedIslands = makeIslands.selectedIslands()
398
399         targetElement = getTargetPoint(context, makeIslands)
400         if not targetElement:
401             self.report({"ERROR"}, "No active face")
402             return {"CANCELLED"}
403
404         if context.scene.selectionAsGroup:
405             groupBox = GBBox(selectedIslands)
406             if context.scene.relativeItems == 'ACTIVE':
407                 selectedIslands.remove(makeIslands.activeIsland())
408             for island in selectedIslands:
409                 vector = mathutils.Vector((targetElement[1].x - groupBox[1].x,
410                                            0.0))
411                 moveIslands(vector, island)
412
413         else:
414             for island in selectedIslands:
415                 vector = mathutils.Vector(
416                     (targetElement[1].x - BBox(island)[1].x, 0.0))
417                 moveIslands(vector, island)
418
419         update()
420         return {'FINISHED'}
421
422
423 class AlignVAxis(OperatorTemplate):
424
425     """Align vertical axis"""
426     bl_idname = "uv.align_vertical_axis"
427     bl_label = "Align vertical axis"
428     bl_options = {'REGISTER', 'UNDO'}
429
430     def execute(self, context):
431         makeIslands = MakeIslands()
432         islands = makeIslands.getIslands()
433         selectedIslands = makeIslands.selectedIslands()
434
435         targetElement = getTargetPoint(context, makeIslands)
436         if not targetElement:
437             self.report({"ERROR"}, "No active face")
438             return {"CANCELLED"}
439         targetCenter = (targetElement[0] + targetElement[1]) / 2
440         if context.scene.selectionAsGroup:
441             groupBoxCenter = GBBoxCenter(selectedIslands)
442             if context.scene.relativeItems == 'ACTIVE':
443                 selectedIslands.remove(makeIslands.activeIsland())
444             for island in selectedIslands:
445                 vector = mathutils.Vector(
446                     (targetCenter.x - groupBoxCenter.x, 0.0))
447                 moveIslands(vector, island)
448
449         else:
450             for island in selectedIslands:
451                 vector = mathutils.Vector(
452                     (targetCenter.x - BBoxCenter(island).x, 0.0))
453                 moveIslands(vector, island)
454
455         update()
456         return {'FINISHED'}
457
458
459 ##################################################
460 class AlignTopMargin(OperatorTemplate):
461
462     """Align top margin"""
463     bl_idname = "uv.align_top_margin"
464     bl_label = "Align top margin"
465     bl_options = {'REGISTER', 'UNDO'}
466
467     def execute(self, context):
468
469         makeIslands = MakeIslands()
470         islands = makeIslands.getIslands()
471         selectedIslands = makeIslands.selectedIslands()
472
473         targetElement = getTargetPoint(context, makeIslands)
474         if not targetElement:
475             self.report({"ERROR"}, "No active face")
476             return {"CANCELLED"}
477         if context.scene.selectionAsGroup:
478             groupBox = GBBox(selectedIslands)
479             if context.scene.relativeItems == 'ACTIVE':
480                 selectedIslands.remove(makeIslands.activeIsland())
481             for island in selectedIslands:
482                 vector = mathutils.Vector(
483                     (0.0, targetElement[1].y - groupBox[1].y))
484                 moveIslands(vector, island)
485
486         else:
487             for island in selectedIslands:
488                 vector = mathutils.Vector(
489                     (0.0, targetElement[1].y - BBox(island)[1].y))
490                 moveIslands(vector, island)
491
492         update()
493         return {'FINISHED'}
494
495
496 class AlignLowMargin(OperatorTemplate):
497
498     """Align low margin"""
499     bl_idname = "uv.align_low_margin"
500     bl_label = "Align low margin"
501     bl_options = {'REGISTER', 'UNDO'}
502
503     def execute(self, context):
504         makeIslands = MakeIslands()
505         islands = makeIslands.getIslands()
506         selectedIslands = makeIslands.selectedIslands()
507
508         targetElement = getTargetPoint(context, makeIslands)
509         if not targetElement:
510             self.report({"ERROR"}, "No active face")
511             return {"CANCELLED"}
512         if context.scene.selectionAsGroup:
513             groupBox = GBBox(selectedIslands)
514             if context.scene.relativeItems == 'ACTIVE':
515                 selectedIslands.remove(makeIslands.activeIsland())
516             for island in selectedIslands:
517                 vector = mathutils.Vector(
518                     (0.0, targetElement[0].y - groupBox[0].y))
519                 moveIslands(vector, island)
520
521         else:
522             for island in selectedIslands:
523                 vector = mathutils.Vector(
524                     (0.0, targetElement[0].y - BBox(island)[0].y))
525                 moveIslands(vector, island)
526
527         update()
528         return {'FINISHED'}
529
530
531 class AlignHAxis(OperatorTemplate):
532
533     """Align horizontal axis"""
534     bl_idname = "uv.align_horizontal_axis"
535     bl_label = "Align horizontal axis"
536     bl_options = {'REGISTER', 'UNDO'}
537
538     def execute(self, context):
539         makeIslands = MakeIslands()
540         islands = makeIslands.getIslands()
541         selectedIslands = makeIslands.selectedIslands()
542
543         targetElement = getTargetPoint(context, makeIslands)
544         if not targetElement:
545             self.report({"ERROR"}, "No active face")
546             return {"CANCELLED"}
547         targetCenter = (targetElement[0] + targetElement[1]) / 2
548
549         if context.scene.selectionAsGroup:
550             groupBoxCenter = GBBoxCenter(selectedIslands)
551             if context.scene.relativeItems == 'ACTIVE':
552                 selectedIslands.remove(makeIslands.activeIsland())
553             for island in selectedIslands:
554                 vector = mathutils.Vector(
555                     (0.0, targetCenter.y - groupBoxCenter.y))
556                 moveIslands(vector, island)
557
558         else:
559             for island in selectedIslands:
560                 vector = mathutils.Vector(
561                     (0.0, targetCenter.y - BBoxCenter(island).y))
562                 moveIslands(vector, island)
563
564         update()
565         return {'FINISHED'}
566
567
568 #########################################
569 class AlignRotation(OperatorTemplate):
570
571     """Align island rotation """
572     bl_idname = "uv.align_rotation"
573     bl_label = "Align island rotation"
574     bl_options = {'REGISTER', 'UNDO'}
575
576     def execute(self, context):
577         makeIslands = MakeIslands()
578         islands = makeIslands.getIslands()
579         selectedIslands = makeIslands.selectedIslands()
580         activeIsland = makeIslands.activeIsland()
581         if not activeIsland:
582             self.report({"ERROR"}, "No active face")
583             return {"CANCELLED"}
584         activeAngle = islandAngle(activeIsland)
585
586         for island in selectedIslands:
587             uvAngle = islandAngle(island)
588             deltaAngle = activeAngle - uvAngle
589             deltaAngle = round(-deltaAngle, 5)
590             rotateIsland(island, deltaAngle)
591
592         update()
593         return {'FINISHED'}
594
595
596 class EqualizeScale(OperatorTemplate):
597
598     """Equalize the islands scale to the active one"""
599     bl_idname = "uv.equalize_scale"
600     bl_label = "Equalize Scale"
601     bl_options = {'REGISTER', 'UNDO'}
602
603     def execute(self, context):
604         makeIslands = MakeIslands()
605         islands = makeIslands.getIslands()
606         selectedIslands = makeIslands.selectedIslands()
607         activeIsland = makeIslands.activeIsland()
608
609         if not activeIsland:
610             self.report({"ERROR"}, "No active face")
611             return {"CANCELLED"}
612
613         activeSize = islandSize(activeIsland)
614         selectedIslands.remove(activeIsland)
615
616         for island in selectedIslands:
617             size = islandSize(island)
618             scaleX = activeSize[0] / size[0]
619             scaleY = activeSize[1] / size[1]
620             scaleIsland(island, scaleX, scaleY)
621
622         update()
623         return {"FINISHED"}
624
625
626 ############################
627 # DISTRIBUTION
628 ############################
629 class DistributeLEdgesH(OperatorTemplate):
630
631     """Distribute left edges equidistantly horizontally"""
632     bl_idname = "uv.distribute_ledges_horizontally"
633     bl_label = "Distribute Left Edges Horizontally"
634     bl_options = {'REGISTER', 'UNDO'}
635
636     def execute(self, context):
637         makeIslands = MakeIslands()
638         islands = makeIslands.getIslands()
639         selectedIslands = makeIslands.selectedIslands()
640
641         if len(selectedIslands) < 3:
642             return {'CANCELLED'}
643
644         islandSpatialSort = IslandSpatialSortX(selectedIslands)
645         uvFirstX = BBox(islandSpatialSort[0][1])[0].x
646         uvLastX = BBox(islandSpatialSort[-1][1])[0].x
647
648         distX = uvLastX - uvFirstX
649
650         deltaDist = distX / (len(selectedIslands) - 1)
651
652         islandSpatialSort.pop(0)
653         islandSpatialSort.pop(-1)
654
655         pos = uvFirstX + deltaDist
656
657         for island in islandSpatialSort:
658             vec = mathutils.Vector((pos - BBox(island[1])[0].x, 0.0))
659             pos += deltaDist
660             moveIslands(vec, island[1])
661         update()
662         return {"FINISHED"}
663
664
665 class DistributeCentersH(OperatorTemplate):
666
667     """Distribute centers equidistantly horizontally"""
668     bl_idname = "uv.distribute_center_horizontally"
669     bl_label = "Distribute Centers Horizontally"
670     bl_options = {'REGISTER', 'UNDO'}
671
672     def execute(self, context):
673         makeIslands = MakeIslands()
674         islands = makeIslands.getIslands()
675         selectedIslands = makeIslands.selectedIslands()
676
677         if len(selectedIslands) < 3:
678             return {'CANCELLED'}
679
680         islandSpatialSort = IslandSpatialSortX(selectedIslands)
681         uvFirstX = min(islandSpatialSort)
682         uvLastX = max(islandSpatialSort)
683
684         distX = uvLastX[0] - uvFirstX[0]
685
686         deltaDist = distX / (len(selectedIslands) - 1)
687
688         islandSpatialSort.pop(0)
689         islandSpatialSort.pop(-1)
690
691         pos = uvFirstX[0] + deltaDist
692
693         for island in islandSpatialSort:
694             vec = mathutils.Vector((pos - BBoxCenter(island[1]).x, 0.0))
695             pos += deltaDist
696             moveIslands(vec, island[1])
697         update()
698         return {"FINISHED"}
699
700
701 class DistributeREdgesH(OperatorTemplate):
702
703     """Distribute right edges equidistantly horizontally"""
704     bl_idname = "uv.distribute_redges_horizontally"
705     bl_label = "Distribute Right Edges Horizontally"
706     bl_options = {'REGISTER', 'UNDO'}
707
708     def execute(self, context):
709         makeIslands = MakeIslands()
710         islands = makeIslands.getIslands()
711         selectedIslands = makeIslands.selectedIslands()
712
713         if len(selectedIslands) < 3:
714             return {'CANCELLED'}
715
716         islandSpatialSort = IslandSpatialSortX(selectedIslands)
717         uvFirstX = BBox(islandSpatialSort[0][1])[1].x
718         uvLastX = BBox(islandSpatialSort[-1][1])[1].x
719
720         distX = uvLastX - uvFirstX
721
722         deltaDist = distX / (len(selectedIslands) - 1)
723
724         islandSpatialSort.pop(0)
725         islandSpatialSort.pop(-1)
726
727         pos = uvFirstX + deltaDist
728
729         for island in islandSpatialSort:
730             vec = mathutils.Vector((pos - BBox(island[1])[1].x, 0.0))
731             pos += deltaDist
732             moveIslands(vec, island[1])
733         update()
734         return {"FINISHED"}
735
736
737 class DistributeTEdgesV(OperatorTemplate):
738
739     """Distribute top edges equidistantly vertically"""
740     bl_idname = "uv.distribute_tedges_vertically"
741     bl_label = "Distribute Top Edges Vertically"
742     bl_options = {'REGISTER', 'UNDO'}
743
744     def execute(self, context):
745         makeIslands = MakeIslands()
746         islands = makeIslands.getIslands()
747         selectedIslands = makeIslands.selectedIslands()
748
749         if len(selectedIslands) < 3:
750             return {'CANCELLED'}
751
752         islandSpatialSort = IslandSpatialSortY(selectedIslands)
753         uvFirstX = BBox(islandSpatialSort[0][1])[1].y
754         uvLastX = BBox(islandSpatialSort[-1][1])[1].y
755
756         distX = uvLastX - uvFirstX
757
758         deltaDist = distX / (len(selectedIslands) - 1)
759
760         islandSpatialSort.pop(0)
761         islandSpatialSort.pop(-1)
762
763         pos = uvFirstX + deltaDist
764
765         for island in islandSpatialSort:
766             vec = mathutils.Vector((0.0, pos - BBox(island[1])[1].y))
767             pos += deltaDist
768             moveIslands(vec, island[1])
769         update()
770         return {"FINISHED"}
771
772
773 class DistributeCentersV(OperatorTemplate):
774
775     """Distribute centers equidistantly vertically"""
776     bl_idname = "uv.distribute_center_vertically"
777     bl_label = "Distribute Centers Vertically"
778     bl_options = {'REGISTER', 'UNDO'}
779
780     def execute(self, context):
781         makeIslands = MakeIslands()
782         islands = makeIslands.getIslands()
783         selectedIslands = makeIslands.selectedIslands()
784
785         if len(selectedIslands) < 3:
786             return {'CANCELLED'}
787
788         islandSpatialSort = IslandSpatialSortY(selectedIslands)
789         uvFirst = BBoxCenter(islandSpatialSort[0][1]).y
790         uvLast = BBoxCenter(islandSpatialSort[-1][1]).y
791
792         dist = uvLast - uvFirst
793
794         deltaDist = dist / (len(selectedIslands) - 1)
795
796         islandSpatialSort.pop(0)
797         islandSpatialSort.pop(-1)
798
799         pos = uvFirst + deltaDist
800
801         for island in islandSpatialSort:
802             vec = mathutils.Vector((0.0, pos - BBoxCenter(island[1]).y))
803             pos += deltaDist
804             moveIslands(vec, island[1])
805         update()
806         return {"FINISHED"}
807
808
809 class DistributeBEdgesV(OperatorTemplate):
810
811     """Distribute bottom edges equidistantly vertically"""
812     bl_idname = "uv.distribute_bedges_vertically"
813     bl_label = "Distribute Bottom Edges Vertically"
814     bl_options = {'REGISTER', 'UNDO'}
815
816     def execute(self, context):
817         makeIslands = MakeIslands()
818         islands = makeIslands.getIslands()
819         selectedIslands = makeIslands.selectedIslands()
820
821         if len(selectedIslands) < 3:
822             return {'CANCELLED'}
823
824         islandSpatialSort = IslandSpatialSortY(selectedIslands)
825         uvFirst = BBox(islandSpatialSort[0][1])[0].y
826         uvLast = BBox(islandSpatialSort[-1][1])[0].y
827
828         dist = uvLast - uvFirst
829
830         deltaDist = dist / (len(selectedIslands) - 1)
831
832         islandSpatialSort.pop(0)
833         islandSpatialSort.pop(-1)
834
835         pos = uvFirst + deltaDist
836
837         for island in islandSpatialSort:
838             vec = mathutils.Vector((0.0, pos - BBox(island[1])[0].y))
839             pos += deltaDist
840             moveIslands(vec, island[1])
841         update()
842         return {"FINISHED"}
843
844
845 class EqualizeHGap(OperatorTemplate):
846
847     """Equalize horizontal gap between island"""
848     bl_idname = "uv.equalize_horizontal_gap"
849     bl_label = "Equalize Horizontal Gap"
850     bl_options = {'REGISTER', 'UNDO'}
851
852     def execute(self, context):
853         makeIslands = MakeIslands()
854         islands = makeIslands.getIslands()
855         selectedIslands = makeIslands.selectedIslands()
856
857         if len(selectedIslands) < 3:
858             return {'CANCELLED'}
859
860         islandSpatialSort = IslandSpatialSortX(selectedIslands)
861
862         averageDist = averageIslandDist(islandSpatialSort)
863
864         for i in range(len(islandSpatialSort)):
865             if islandSpatialSort.index(islandSpatialSort[i + 1]) == \
866                     islandSpatialSort.index(islandSpatialSort[-1]):
867                 break
868             elem1 = BBox(islandSpatialSort[i][1])[1].x
869             elem2 = BBox(islandSpatialSort[i + 1][1])[0].x
870
871             dist = elem2 - elem1
872             increment = averageDist.x - dist
873
874             vec = mathutils.Vector((increment, 0.0))
875             island = islandSpatialSort[i + 1][1]
876             moveIslands(vec, islandSpatialSort[i + 1][1])
877         update()
878         return {"FINISHED"}
879
880
881 class EqualizeVGap(OperatorTemplate):
882
883     """Equalize vertical gap between island"""
884     bl_idname = "uv.equalize_vertical_gap"
885     bl_label = "Equalize Vertical Gap"
886     bl_options = {'REGISTER', 'UNDO'}
887
888     def execute(self, context):
889         makeIslands = MakeIslands()
890         islands = makeIslands.getIslands()
891         selectedIslands = makeIslands.selectedIslands()
892
893         if len(selectedIslands) < 3:
894             return {'CANCELLED'}
895
896         islandSpatialSort = IslandSpatialSortY(selectedIslands)
897
898         averageDist = averageIslandDist(islandSpatialSort)
899
900         for i in range(len(islandSpatialSort)):
901             if islandSpatialSort.index(islandSpatialSort[i + 1]) ==\
902                     islandSpatialSort.index(islandSpatialSort[-1]):
903                 break
904             elem1 = BBox(islandSpatialSort[i][1])[1].y
905             elem2 = BBox(islandSpatialSort[i + 1][1])[0].y
906
907             dist = elem2 - elem1
908
909             increment = averageDist.y - dist
910
911             vec = mathutils.Vector((0.0, increment))
912             island = islandSpatialSort[i + 1][1]
913
914             moveIslands(vec, islandSpatialSort[i + 1][1])
915         update()
916         return {"FINISHED"}
917
918 ##############
919 # SPECIALS
920 ##############
921
922
923 class MatchIsland(OperatorTemplate):
924
925     """Match UV Island by moving their vertex"""
926     bl_idname = "uv.match_island"
927     bl_label = "Match Island"
928     bl_options = {'REGISTER', 'UNDO'}
929
930     thresold = FloatProperty(
931         name="Thresold",
932         description="Thresold for island matching",
933         default=0.1,
934         min=0,
935         max=1,
936         soft_min=0.01,
937         soft_max=1,
938         step=1,
939         precision=2)
940
941     def execute(self, context):
942         makeIslands = MakeIslands()
943         islands = makeIslands.getIslands()
944         selectedIslands = makeIslands.selectedIslands()
945         activeIsland = makeIslands.activeIsland()
946
947         if len(selectedIslands) < 2:
948             return {'CANCELLED'}
949
950         selectedIslands.remove(activeIsland)
951
952         for island in selectedIslands:
953             matchIsland(activeIsland, self.thresold, island)
954
955         update()
956         return{'FINISHED'}
957
958
959 ##############
960 #   UI
961 ##############
962 class IMAGE_PT_align_distribute(bpy.types.Panel):
963     bl_label = "Align\Distribute"
964     bl_space_type = 'IMAGE_EDITOR'
965     bl_region_type = 'TOOLS'
966     bl_category = "Tools"
967
968     @classmethod
969     def poll(cls, context):
970         sima = context.space_data
971         return sima.show_uvedit and \
972             not (context.tool_settings.use_uv_sculpt
973                  or context.scene.tool_settings.use_uv_select_sync)
974
975     def draw(self, context):
976         scn = context.scene
977         layout = self.layout
978         layout.prop(scn, "relativeItems")
979         layout.prop(scn, "selectionAsGroup")
980
981         layout.separator()
982         layout.label(text="Align:")
983
984         box = layout.box()
985         row = box.row(True)
986         row.operator("uv.align_left_margin", "Align Left")
987         row.operator("uv.align_vertical_axis", "Align VAxis")
988         row.operator("uv.align_right_margin", "Align Right")
989         row = box.row(True)
990         row.operator("uv.align_top_margin", "Align Top")
991         row.operator("uv.align_horizontal_axis", "Align HAxis")
992         row.operator("uv.align_low_margin", "Align Low")
993
994         row = layout.row()
995         row.operator("uv.align_rotation", "Align rotation")
996         row.operator("uv.equalize_scale", "Equalize scale")
997
998         layout.separator()
999         # Another Panel??
1000         layout.label(text="Distribute:")
1001
1002         box = layout.box()
1003
1004         row = box.row(True)
1005         row.operator("uv.distribute_ledges_horizontally", "Distribute LEdges")
1006
1007         row.operator("uv.distribute_center_horizontally",
1008                      "Distribute HCenters")
1009
1010         row.operator("uv.distribute_redges_horizontally",
1011                      "Distribute RCenters")
1012
1013         row = box.row(True)
1014         row.operator("uv.distribute_tedges_vertically", "Distribute TEdges")
1015         row.operator("uv.distribute_center_vertically", "Distribute VCenters")
1016         row.operator("uv.distribute_bedges_vertically", "Distribute BEdges")
1017
1018         row = layout.row(True)
1019         row.operator("uv.equalize_horizontal_gap", "Equalize HGap")
1020         row.operator("uv.equalize_vertical_gap", "Equalize VGap")
1021
1022         layout.separator()
1023         layout.label("Others:")
1024         row = layout.row()
1025         layout.operator("uv.match_island")
1026
1027
1028 # Registration
1029 classes = (
1030     IMAGE_PT_align_distribute,
1031     AlignSXMargin,
1032     AlignRxMargin,
1033     AlignVAxis,
1034     AlignTopMargin,
1035     AlignLowMargin,
1036     AlignHAxis,
1037     AlignRotation,
1038     DistributeLEdgesH,
1039     DistributeCentersH,
1040     DistributeREdgesH,
1041     DistributeTEdgesV,
1042     DistributeCentersV,
1043     DistributeBEdgesV,
1044     EqualizeHGap,
1045     EqualizeVGap,
1046     EqualizeScale,
1047     MatchIsland)
1048
1049
1050 def register():
1051     for item in classes:
1052         bpy.utils.register_class(item)
1053     # bpy.utils.register_manual_map(add_object_manual_map)
1054     # bpy.types.INFO_MT_mesh_add.append(add_object_button)
1055
1056
1057 def unregister():
1058     for item in classes:
1059         bpy.utils.unregister_class(item)
1060     # bpy.utils.unregister_manual_map(add_object_manual_map)
1061     # bpy.types.INFO_MT_mesh_add.remove(add_object_button)
1062
1063
1064 if __name__ == "__main__":
1065     register()
1066
1067