mesh.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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. # <pep8-80 compliant>
  19. import bpy
  20. from bpy.types import Operator
  21. from bpy.props import (
  22. EnumProperty,
  23. IntProperty,
  24. )
  25. class MeshMirrorUV(Operator):
  26. """Copy mirror UV coordinates on the X axis based on a mirrored mesh"""
  27. bl_idname = "mesh.faces_mirror_uv"
  28. bl_label = "Copy Mirrored UV Coords"
  29. bl_options = {'REGISTER', 'UNDO'}
  30. direction: EnumProperty(
  31. name="Axis Direction",
  32. items=(
  33. ('POSITIVE', "Positive", ""),
  34. ('NEGATIVE', "Negative", ""),
  35. ),
  36. )
  37. precision: IntProperty(
  38. name="Precision",
  39. description=("Tolerance for finding vertex duplicates"),
  40. min=1, max=16,
  41. soft_min=1, soft_max=16,
  42. default=3,
  43. )
  44. # Returns has_active_UV_layer, double_warn.
  45. def do_mesh_mirror_UV(self, mesh, DIR):
  46. precision = self.precision
  47. double_warn = 0
  48. if not mesh.uv_layers.active:
  49. # has_active_UV_layer, double_warn
  50. return False, 0
  51. # mirror lookups
  52. mirror_gt = {}
  53. mirror_lt = {}
  54. vcos = (v.co.to_tuple(precision) for v in mesh.vertices)
  55. for i, co in enumerate(vcos):
  56. if co[0] >= 0.0:
  57. double_warn += co in mirror_gt
  58. mirror_gt[co] = i
  59. if co[0] <= 0.0:
  60. double_warn += co in mirror_lt
  61. mirror_lt[co] = i
  62. vmap = {}
  63. for mirror_a, mirror_b in ((mirror_gt, mirror_lt),
  64. (mirror_lt, mirror_gt)):
  65. for co, i in mirror_a.items():
  66. nco = (-co[0], co[1], co[2])
  67. j = mirror_b.get(nco)
  68. if j is not None:
  69. vmap[i] = j
  70. polys = mesh.polygons
  71. loops = mesh.loops
  72. uv_loops = mesh.uv_layers.active.data
  73. nbr_polys = len(polys)
  74. mirror_pm = {}
  75. pmap = {}
  76. puvs = [None] * nbr_polys
  77. puvs_cpy = [None] * nbr_polys
  78. puvsel = [None] * nbr_polys
  79. pcents = [None] * nbr_polys
  80. vidxs = [None] * nbr_polys
  81. for i, p in enumerate(polys):
  82. lstart = lend = p.loop_start
  83. lend += p.loop_total
  84. puvs[i] = tuple(uv.uv for uv in uv_loops[lstart:lend])
  85. puvs_cpy[i] = tuple(uv.copy() for uv in puvs[i])
  86. puvsel[i] = (False not in
  87. (uv.select for uv in uv_loops[lstart:lend]))
  88. # Vert idx of the poly.
  89. vidxs[i] = tuple(l.vertex_index for l in loops[lstart:lend])
  90. pcents[i] = p.center
  91. # Preparing next step finding matching polys.
  92. mirror_pm[tuple(sorted(vidxs[i]))] = i
  93. for i in range(nbr_polys):
  94. # Find matching mirror poly.
  95. tvidxs = [vmap.get(j) for j in vidxs[i]]
  96. if None not in tvidxs:
  97. tvidxs.sort()
  98. j = mirror_pm.get(tuple(tvidxs))
  99. if j is not None:
  100. pmap[i] = j
  101. for i, j in pmap.items():
  102. if not puvsel[i] or not puvsel[j]:
  103. continue
  104. elif DIR == 0 and pcents[i][0] < 0.0:
  105. continue
  106. elif DIR == 1 and pcents[i][0] > 0.0:
  107. continue
  108. # copy UVs
  109. uv1 = puvs[i]
  110. uv2 = puvs_cpy[j]
  111. # get the correct rotation
  112. v1 = vidxs[j]
  113. v2 = tuple(vmap[k] for k in vidxs[i])
  114. if len(v1) == len(v2):
  115. for k in range(len(v1)):
  116. k_map = v1.index(v2[k])
  117. uv1[k].xy = - (uv2[k_map].x - 0.5) + 0.5, uv2[k_map].y
  118. # has_active_UV_layer, double_warn
  119. return True, double_warn
  120. @classmethod
  121. def poll(cls, context):
  122. obj = context.view_layer.objects.active
  123. return (obj and obj.type == 'MESH')
  124. def execute(self, context):
  125. DIR = (self.direction == 'NEGATIVE')
  126. total_no_active_UV = 0
  127. total_duplicates = 0
  128. meshes_with_duplicates = 0
  129. ob = context.view_layer.objects.active
  130. is_editmode = (ob.mode == 'EDIT')
  131. if is_editmode:
  132. bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
  133. meshes = [ob.data for ob in context.view_layer.objects.selected
  134. if ob.type == 'MESH' and ob.data.library is None]
  135. for mesh in meshes:
  136. mesh.tag = False
  137. for mesh in meshes:
  138. if mesh.tag:
  139. continue
  140. mesh.tag = True
  141. has_active_UV_layer, double_warn = self.do_mesh_mirror_UV(mesh, DIR)
  142. if not has_active_UV_layer:
  143. total_no_active_UV = total_no_active_UV + 1
  144. elif double_warn:
  145. total_duplicates += double_warn
  146. meshes_with_duplicates = meshes_with_duplicates + 1
  147. if is_editmode:
  148. bpy.ops.object.mode_set(mode='EDIT', toggle=False)
  149. if total_duplicates and total_no_active_UV:
  150. self.report({'WARNING'}, "%d %s with no active UV layer. "
  151. "%d duplicates found in %d %s, mirror may be incomplete."
  152. % (total_no_active_UV,
  153. "mesh" if total_no_active_UV == 1 else "meshes",
  154. total_duplicates,
  155. meshes_with_duplicates,
  156. "mesh" if meshes_with_duplicates == 1 else "meshes"))
  157. elif total_no_active_UV:
  158. self.report({'WARNING'}, "%d %s with no active UV layer."
  159. % (total_no_active_UV,
  160. "mesh" if total_no_active_UV == 1 else "meshes"))
  161. elif total_duplicates:
  162. self.report({'WARNING'}, "%d duplicates found in %d %s,"
  163. " mirror may be incomplete."
  164. % (total_duplicates,
  165. meshes_with_duplicates,
  166. "mesh" if meshes_with_duplicates == 1 else "meshes"))
  167. return {'FINISHED'}
  168. class MeshSelectNext(Operator):
  169. """Select the next element (using selection order)"""
  170. bl_idname = "mesh.select_next_item"
  171. bl_label = "Select Next Element"
  172. bl_options = {'REGISTER', 'UNDO'}
  173. @classmethod
  174. def poll(cls, context):
  175. return (context.mode == 'EDIT_MESH')
  176. def execute(self, context):
  177. import bmesh
  178. from .bmesh import find_adjacent
  179. obj = context.active_object
  180. me = obj.data
  181. bm = bmesh.from_edit_mesh(me)
  182. if find_adjacent.select_next(bm, self.report):
  183. bm.select_flush_mode()
  184. bmesh.update_edit_mesh(me, False)
  185. return {'FINISHED'}
  186. class MeshSelectPrev(Operator):
  187. """Select the previous element (using selection order)"""
  188. bl_idname = "mesh.select_prev_item"
  189. bl_label = "Select Previous Element"
  190. bl_options = {'REGISTER', 'UNDO'}
  191. @classmethod
  192. def poll(cls, context):
  193. return (context.mode == 'EDIT_MESH')
  194. def execute(self, context):
  195. import bmesh
  196. from .bmesh import find_adjacent
  197. obj = context.active_object
  198. me = obj.data
  199. bm = bmesh.from_edit_mesh(me)
  200. if find_adjacent.select_prev(bm, self.report):
  201. bm.select_flush_mode()
  202. bmesh.update_edit_mesh(me, False)
  203. return {'FINISHED'}
  204. classes = (
  205. MeshMirrorUV,
  206. MeshSelectNext,
  207. MeshSelectPrev,
  208. )