# ##### 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 ##### # import bpy from bpy.types import Operator from bpy.props import ( EnumProperty, IntProperty, ) class MeshMirrorUV(Operator): """Copy mirror UV coordinates on the X axis based on a mirrored mesh""" bl_idname = "mesh.faces_mirror_uv" bl_label = "Copy Mirrored UV Coords" bl_options = {'REGISTER', 'UNDO'} direction: EnumProperty( name="Axis Direction", items=( ('POSITIVE', "Positive", ""), ('NEGATIVE', "Negative", ""), ), ) precision: IntProperty( name="Precision", description=("Tolerance for finding vertex duplicates"), min=1, max=16, soft_min=1, soft_max=16, default=3, ) # Returns has_active_UV_layer, double_warn. def do_mesh_mirror_UV(self, mesh, DIR): precision = self.precision double_warn = 0 if not mesh.uv_layers.active: # has_active_UV_layer, double_warn return False, 0 # mirror lookups mirror_gt = {} mirror_lt = {} vcos = (v.co.to_tuple(precision) for v in mesh.vertices) for i, co in enumerate(vcos): if co[0] >= 0.0: double_warn += co in mirror_gt mirror_gt[co] = i if co[0] <= 0.0: double_warn += co in mirror_lt mirror_lt[co] = i vmap = {} for mirror_a, mirror_b in ((mirror_gt, mirror_lt), (mirror_lt, mirror_gt)): for co, i in mirror_a.items(): nco = (-co[0], co[1], co[2]) j = mirror_b.get(nco) if j is not None: vmap[i] = j polys = mesh.polygons loops = mesh.loops uv_loops = mesh.uv_layers.active.data nbr_polys = len(polys) mirror_pm = {} pmap = {} puvs = [None] * nbr_polys puvs_cpy = [None] * nbr_polys puvsel = [None] * nbr_polys pcents = [None] * nbr_polys vidxs = [None] * nbr_polys for i, p in enumerate(polys): lstart = lend = p.loop_start lend += p.loop_total puvs[i] = tuple(uv.uv for uv in uv_loops[lstart:lend]) puvs_cpy[i] = tuple(uv.copy() for uv in puvs[i]) puvsel[i] = (False not in (uv.select for uv in uv_loops[lstart:lend])) # Vert idx of the poly. vidxs[i] = tuple(l.vertex_index for l in loops[lstart:lend]) pcents[i] = p.center # Preparing next step finding matching polys. mirror_pm[tuple(sorted(vidxs[i]))] = i for i in range(nbr_polys): # Find matching mirror poly. tvidxs = [vmap.get(j) for j in vidxs[i]] if None not in tvidxs: tvidxs.sort() j = mirror_pm.get(tuple(tvidxs)) if j is not None: pmap[i] = j for i, j in pmap.items(): if not puvsel[i] or not puvsel[j]: continue elif DIR == 0 and pcents[i][0] < 0.0: continue elif DIR == 1 and pcents[i][0] > 0.0: continue # copy UVs uv1 = puvs[i] uv2 = puvs_cpy[j] # get the correct rotation v1 = vidxs[j] v2 = tuple(vmap[k] for k in vidxs[i]) if len(v1) == len(v2): for k in range(len(v1)): k_map = v1.index(v2[k]) uv1[k].xy = - (uv2[k_map].x - 0.5) + 0.5, uv2[k_map].y # has_active_UV_layer, double_warn return True, double_warn @classmethod def poll(cls, context): obj = context.view_layer.objects.active return (obj and obj.type == 'MESH') def execute(self, context): DIR = (self.direction == 'NEGATIVE') total_no_active_UV = 0 total_duplicates = 0 meshes_with_duplicates = 0 ob = context.view_layer.objects.active is_editmode = (ob.mode == 'EDIT') if is_editmode: bpy.ops.object.mode_set(mode='OBJECT', toggle=False) meshes = [ob.data for ob in context.view_layer.objects.selected if ob.type == 'MESH' and ob.data.library is None] for mesh in meshes: mesh.tag = False for mesh in meshes: if mesh.tag: continue mesh.tag = True has_active_UV_layer, double_warn = self.do_mesh_mirror_UV(mesh, DIR) if not has_active_UV_layer: total_no_active_UV = total_no_active_UV + 1 elif double_warn: total_duplicates += double_warn meshes_with_duplicates = meshes_with_duplicates + 1 if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False) if total_duplicates and total_no_active_UV: self.report({'WARNING'}, "%d %s with no active UV layer. " "%d duplicates found in %d %s, mirror may be incomplete." % (total_no_active_UV, "mesh" if total_no_active_UV == 1 else "meshes", total_duplicates, meshes_with_duplicates, "mesh" if meshes_with_duplicates == 1 else "meshes")) elif total_no_active_UV: self.report({'WARNING'}, "%d %s with no active UV layer." % (total_no_active_UV, "mesh" if total_no_active_UV == 1 else "meshes")) elif total_duplicates: self.report({'WARNING'}, "%d duplicates found in %d %s," " mirror may be incomplete." % (total_duplicates, meshes_with_duplicates, "mesh" if meshes_with_duplicates == 1 else "meshes")) return {'FINISHED'} class MeshSelectNext(Operator): """Select the next element (using selection order)""" bl_idname = "mesh.select_next_item" bl_label = "Select Next Element" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): return (context.mode == 'EDIT_MESH') def execute(self, context): import bmesh from .bmesh import find_adjacent obj = context.active_object me = obj.data bm = bmesh.from_edit_mesh(me) if find_adjacent.select_next(bm, self.report): bm.select_flush_mode() bmesh.update_edit_mesh(me, False) return {'FINISHED'} class MeshSelectPrev(Operator): """Select the previous element (using selection order)""" bl_idname = "mesh.select_prev_item" bl_label = "Select Previous Element" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): return (context.mode == 'EDIT_MESH') def execute(self, context): import bmesh from .bmesh import find_adjacent obj = context.active_object me = obj.data bm = bmesh.from_edit_mesh(me) if find_adjacent.select_prev(bm, self.report): bm.select_flush_mode() bmesh.update_edit_mesh(me, False) return {'FINISHED'} classes = ( MeshMirrorUV, MeshSelectNext, MeshSelectPrev, )