260 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			260 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # ##### 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-80 compliant>
 | |
| 
 | |
| 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,
 | |
| )
 | 
