Bug fix
[blender-addons-contrib.git] / mesh_copy_uvs_from_joined.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; either version 2
6 #  of the License, or (at your option) any later version.
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 # <pep8 compliant>
20
21 bl_info = {
22     "name": "Copy UV's from Joined",
23     "description": "Copy UV coordinates from the active joined mesh",
24     "author": "Sergey Sharybin",
25     "version": (0, 1),
26     "blender": (2, 63, 14),
27     "location": "Object mode 'Make Links' menu",
28     "wiki_url": "",
29     "tracker_url": "",
30     "category": "Object"}
31
32
33 import bpy
34 from bpy.types import Operator
35 from mathutils import Vector
36
37 FLT_MAX = 30000.0
38 KEY_PRECISION = 1
39
40
41 def MINMAX_INIT():
42     return (Vector((+FLT_MAX, +FLT_MAX, +FLT_MAX)),
43             Vector((-FLT_MAX, -FLT_MAX, -FLT_MAX)))
44
45
46 def MINMAX_DO(min, max, vec):
47     for x in range(3):
48         if vec[x] < min[x]:
49             min[x] = vec[x]
50
51         if vec[x] > max[x]:
52             max[x] = vec[x]
53
54
55 def getObjectAABB(obj):
56     min, max = MINMAX_INIT()
57
58     matrix = obj.matrix_world.copy()
59     for vec in obj.bound_box:
60         v = matrix * Vector(vec)
61         MINMAX_DO(min, max, v)
62
63     return min, max
64
65
66 class  OBJECT_OT_copy_uv_from_joined(Operator):
67     """
68     Copy UVs from joined objects into originals
69     """
70
71     bl_idname = "object.copy_uv_from_joined"
72     bl_label = "Copy UVs from Joined"
73
74     def _findTranslation(self, obact, objects):
75         """
76         Find a translation from original objects to joined
77         """
78
79         bb_joined = getObjectAABB(obact)
80         bb_orig = MINMAX_INIT()
81
82         for ob in objects:
83             if ob != obact:
84                 bb = getObjectAABB(ob)
85                 MINMAX_DO(bb_orig[0], bb_orig[1], bb[0])
86                 MINMAX_DO(bb_orig[0], bb_orig[1], bb[1])
87
88         return bb_joined[0] - bb_orig[0]
89
90     def _getPolygonMedian(self, me, poly):
91         median = Vector()
92         verts = me.vertices
93
94         for vert_index in poly.vertices:
95             median += verts[vert_index].co
96
97         median /= len(poly.vertices)
98
99         return median
100
101     def _getVertexLookupMap(self, obact, objects):
102         """
103         Create a vertex lookup map from joined object space to original object
104         """
105
106         uv_map = {}
107
108         T = self._findTranslation(obact, objects)
109
110         for obj in objects:
111             if obj != obact:
112                 me = obj.data
113                 mat = obj.matrix_world.copy()
114                 uv_layer = me.uv_layers.active
115
116                 for poly in me.polygons:
117                     center = mat * self._getPolygonMedian(me, poly) + T
118                     center_key = center.to_tuple(KEY_PRECISION)
119
120                     for loop_index in poly.loop_indices:
121                         loop = me.loops[loop_index]
122                         vert = me.vertices[loop.vertex_index]
123                         vec = mat * vert.co + T
124
125                         key = (center_key, vec.to_tuple(KEY_PRECISION))
126
127                         uv_map.setdefault(key, []).append((center, vec, (uv_layer, loop_index)))
128
129         return uv_map
130
131     def execute(self, context):
132         obact = context.object
133
134         # Check wether we're working with meshes
135         # other object types are not supported
136         if obact.type != 'MESH':
137             self.report({'ERROR'}, "Only meshes are supported")
138             return {'CANCELLED'}
139
140         objects = context.selected_objects
141
142         for obj in context.selected_objects:
143             if obj.type != 'MESH':
144                 self.report({'ERROR'}, "Only meshes are supported")
145                 return {'CANCELLED'}
146
147         uv_map = self._getVertexLookupMap(obact, objects)
148
149         me = obact.data
150         mat = obact.matrix_world.copy()
151         uv_layer = me.uv_layers.active
152
153         for poly in me.polygons:
154             center = mat * self._getPolygonMedian(me, poly)
155             center_key = center.to_tuple(KEY_PRECISION)
156
157             for loop_index in poly.loop_indices:
158                 loop = me.loops[loop_index]
159                 vert = me.vertices[loop.vertex_index]
160                 vec = mat * vert.co
161
162                 key = (center_key, vec.to_tuple(KEY_PRECISION))
163                 check_list = uv_map.get(key)
164
165                 if check_list is not None:
166                     new_uv = None
167                     closest_data = None
168
169                     dist = FLT_MAX
170                     for x in check_list:
171                         cur_center, cur_vec, data = x
172
173                         d1 = Vector(cur_center) - Vector(center)
174                         d2 = Vector(cur_vec) - Vector(vec)
175
176                         d = d1.length_squared + d2.length_squared
177
178                         if d < dist:
179                             closest_data = data
180                             dist = d
181
182                     if closest_data is not None:
183                         orig_uv_layer, orig_loop_index = closest_data
184                         new_uv = uv_layer.data[loop_index].uv
185                         orig_uv_layer.data[orig_loop_index].uv = new_uv
186                 else:
187                     print("Failed to lookup %r" % key)
188
189         return {'FINISHED'}
190
191
192 def menu_func(self, context):
193     self.layout.operator("OBJECT_OT_copy_uv_from_joined",
194                          text="Join as UVs (active to other selected)",
195                          icon="PLUGIN")
196
197
198 def register():
199     bpy.utils.register_module(__name__)
200     
201     bpy.types.VIEW3D_MT_make_links.append(menu_func)
202     
203
204 def unregister():
205     bpy.utils.unregister_module(__name__)
206
207     bpy.types.VIEW3D_MT_make_links.remove(menu_func)
208
209
210 if __name__ == "__main__":
211     register()