# ##### 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 ##### # <pep8 compliant> bl_info = { "name": "Copy UV's from Joined", "description": "Copy UV coordinates from the active joined mesh", "author": "Sergey Sharybin", "version": (0, 1), "blender": (2, 63, 0), "location": "Object mode 'Make Links' menu", "wiki_url": "", "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", "category": "Object"} import bpy from bpy.types import Operator from mathutils import Vector FLT_MAX = 30000.0 KEY_PRECISION = 1 def MINMAX_INIT(): return (Vector((+FLT_MAX, +FLT_MAX, +FLT_MAX)), Vector((-FLT_MAX, -FLT_MAX, -FLT_MAX))) def MINMAX_DO(min, max, vec): for x in range(3): if vec[x] < min[x]: min[x] = vec[x] if vec[x] > max[x]: max[x] = vec[x] def getObjectAABB(obj): min, max = MINMAX_INIT() matrix = obj.matrix_world.copy() for vec in obj.bound_box: v = matrix * Vector(vec) MINMAX_DO(min, max, v) return min, max class OBJECT_OT_copy_uv_from_joined(Operator): """ Copy UVs from joined objects into originals """ bl_idname = "object.copy_uv_from_joined" bl_label = "Copy UVs from Joined" def _findTranslation(self, obact, objects): """ Find a translation from original objects to joined """ bb_joined = getObjectAABB(obact) bb_orig = MINMAX_INIT() for ob in objects: if ob != obact: bb = getObjectAABB(ob) MINMAX_DO(bb_orig[0], bb_orig[1], bb[0]) MINMAX_DO(bb_orig[0], bb_orig[1], bb[1]) return bb_joined[0] - bb_orig[0] def _getPolygonMedian(self, me, poly): median = Vector() verts = me.vertices for vert_index in poly.vertices: median += verts[vert_index].co median /= len(poly.vertices) return median def _getVertexLookupMap(self, obact, objects): """ Create a vertex lookup map from joined object space to original object """ uv_map = {} T = self._findTranslation(obact, objects) for obj in objects: if obj != obact: me = obj.data mat = obj.matrix_world.copy() uv_layer = me.uv_layers.active for poly in me.polygons: center = mat * self._getPolygonMedian(me, poly) + T center_key = center.to_tuple(KEY_PRECISION) for loop_index in poly.loop_indices: loop = me.loops[loop_index] vert = me.vertices[loop.vertex_index] vec = mat * vert.co + T key = (center_key, vec.to_tuple(KEY_PRECISION)) uv_map.setdefault(key, []).append((center, vec, (uv_layer, loop_index))) return uv_map def execute(self, context): obact = context.object # Check wether we're working with meshes # other object types are not supported if obact.type != 'MESH': self.report({'ERROR'}, "Only meshes are supported") return {'CANCELLED'} objects = context.selected_objects for obj in context.selected_objects: if obj.type != 'MESH': self.report({'ERROR'}, "Only meshes are supported") return {'CANCELLED'} uv_map = self._getVertexLookupMap(obact, objects) me = obact.data mat = obact.matrix_world.copy() uv_layer = me.uv_layers.active for poly in me.polygons: center = mat * self._getPolygonMedian(me, poly) center_key = center.to_tuple(KEY_PRECISION) for loop_index in poly.loop_indices: loop = me.loops[loop_index] vert = me.vertices[loop.vertex_index] vec = mat * vert.co key = (center_key, vec.to_tuple(KEY_PRECISION)) check_list = uv_map.get(key) if check_list is not None: new_uv = None closest_data = None dist = FLT_MAX for x in check_list: cur_center, cur_vec, data = x d1 = Vector(cur_center) - Vector(center) d2 = Vector(cur_vec) - Vector(vec) d = d1.length_squared + d2.length_squared if d < dist: closest_data = data dist = d if closest_data is not None: orig_uv_layer, orig_loop_index = closest_data new_uv = uv_layer.data[loop_index].uv orig_uv_layer.data[orig_loop_index].uv = new_uv else: print("Failed to lookup %r" % (key,)) return {'FINISHED'} def menu_func(self, context): self.layout.operator("OBJECT_OT_copy_uv_from_joined", text="Join as UVs (active to other selected)", icon="PLUGIN") def register(): bpy.utils.register_module(__name__) bpy.types.VIEW3D_MT_make_links.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.VIEW3D_MT_make_links.remove(menu_func) if __name__ == "__main__": register()