1 # SPDX-License-Identifier: GPL-2.0-or-later
3 __author__
= "Keith (Wahooney) Boshoff, Nutti <nutti.metro@gmail.com>"
4 __status__
= "production"
6 __date__
= "22 Apr 2022"
9 from bpy
.props
import (
15 from mathutils
import Vector
, Euler
17 from ..utils
.bl_class_registry
import BlClassRegistry
18 from ..utils
.property_class_registry
import PropertyClassRegistry
19 from ..utils
import compatibility
as compat
23 def _is_valid_context(context
):
24 # only 'VIEW_3D' space is allowed to execute
25 if not common
.is_valid_space(context
, ['VIEW_3D']):
28 objs
= common
.get_uv_editable_objects(context
)
32 # only edit mode is allowed to execute
33 if context
.object.mode
!= 'EDIT':
39 def _is_vector_similar(v1
, v2
, error
):
41 Check if two vectors are similar, within an error threshold
43 within_err_x
= abs(v2
.x
- v1
.x
) < error
44 within_err_y
= abs(v2
.y
- v1
.y
) < error
45 within_err_z
= abs(v2
.z
- v1
.z
) < error
47 return within_err_x
and within_err_y
and within_err_z
50 def _mirror_uvs(uv_layer
, src
, dst
, axis
, error
, transformed
):
52 Copy UV coordinates from one UV face to another
55 suv
= sl
[uv_layer
].uv
.copy()
56 svco
= transformed
[sl
.vert
].copy()
58 dvco
= transformed
[dl
.vert
].copy()
66 if _is_vector_similar(svco
, dvco
, error
):
67 dl
[uv_layer
].uv
= suv
.copy()
70 def _get_face_center(face
, transformed
):
72 Get center coordinate of the face
74 center
= Vector((0.0, 0.0, 0.0))
79 return center
/ len(face
.verts
)
82 @PropertyClassRegistry()
87 def init_props(cls
, scene
):
88 scene
.muv_mirror_uv_enabled
= BoolProperty(
89 name
="Mirror UV Enabled",
90 description
="Mirror UV is enabled",
93 scene
.muv_mirror_uv_axis
= EnumProperty(
95 ('X', "X", "Mirror Along X axis"),
96 ('Y', "Y", "Mirror Along Y axis"),
97 ('Z', "Z", "Mirror Along Z axis")
100 description
="Mirror Axis",
103 scene
.muv_mirror_uv_origin
= EnumProperty(
105 ('WORLD', "World", "World"),
106 ("GLOBAL", "Global", "Global"),
107 ('LOCAL', "Local", "Local"),
110 description
="Origin of the mirror operation",
115 def del_props(cls
, scene
):
116 del scene
.muv_mirror_uv_enabled
117 del scene
.muv_mirror_uv_axis
118 del scene
.muv_mirror_uv_origin
122 @compat.make_annotations
123 class MUV_OT_MirrorUV(bpy
.types
.Operator
):
125 Operation class: Mirror UV
128 bl_idname
= "uv.muv_mirror_uv"
129 bl_label
= "Mirror UV"
130 bl_options
= {'REGISTER', 'UNDO'}
134 ('X', "X", "Mirror Along X axis"),
135 ('Y', "Y", "Mirror Along Y axis"),
136 ('Z', "Z", "Mirror Along Z axis")
139 description
="Mirror Axis",
142 error
= FloatProperty(
144 description
="Error threshold",
151 origin
= EnumProperty(
153 ('WORLD', "World", "World"),
154 ("GLOBAL", "Global", "Global"),
155 ('LOCAL', "Local", "Local"),
158 description
="Origin of the mirror operation",
163 def poll(cls
, context
):
164 # we can not get area/space/region from console
165 if common
.is_console_mode():
167 return _is_valid_context(context
)
169 def _get_world_vertices(self
, obj
, bm
):
170 # Get world orientation matrix.
171 world_orientation_mat
= obj
.matrix_world
173 # Move to local to world.
176 transformed
[v
] = compat
.matmul(world_orientation_mat
, v
.co
)
180 def _get_global_vertices(self
, obj
, bm
):
181 # Get world rotation matrix.
182 eular
= Euler(obj
.rotation_euler
)
183 rotation_mat
= eular
.to_matrix()
185 # Get center location of all vertices.
186 center_location
= Vector((0.0, 0.0, 0.0))
188 center_location
+= v
.co
189 center_location
/= len(bm
.verts
)
191 # Move to local to global.
194 transformed
[v
] = compat
.matmul(rotation_mat
, v
.co
)
195 transformed
[v
] -= center_location
199 def _get_local_vertices(self
, _
, bm
):
202 # Get center location of all vertices.
203 center_location
= Vector((0.0, 0.0, 0.0))
205 center_location
+= v
.co
206 center_location
/= len(bm
.verts
)
209 transformed
[v
] = v
.co
.copy()
210 transformed
[v
] -= center_location
214 def execute(self
, context
):
215 objs
= common
.get_uv_editable_objects(context
)
218 bm
= bmesh
.from_edit_mesh(obj
.data
)
223 if common
.check_version(2, 73, 0) >= 0:
224 bm
.faces
.ensure_lookup_table()
225 if not bm
.loops
.layers
.uv
:
226 self
.report({'WARNING'},
227 "Object {} must have more than one UV map"
230 uv_layer
= bm
.loops
.layers
.uv
.verify()
232 if self
.origin
== 'WORLD':
233 transformed_verts
= self
._get
_world
_vertices
(obj
, bm
)
234 elif self
.origin
== 'GLOBAL':
235 transformed_verts
= self
._get
_global
_vertices
(obj
, bm
)
236 elif self
.origin
== 'LOCAL':
237 transformed_verts
= self
._get
_local
_vertices
(obj
, bm
)
239 faces
= [f
for f
in bm
.faces
if f
.select
]
241 count
= len(f_dst
.verts
)
242 for f_src
in bm
.faces
:
243 # check if this is a candidate to do mirror UV
244 if f_src
.index
== f_dst
.index
:
246 if count
!= len(f_src
.verts
):
249 # test if the vertices x values are the same sign
250 dst
= _get_face_center(f_dst
, transformed_verts
)
251 src
= _get_face_center(f_src
, transformed_verts
)
255 if ((dst
.x
> 0 and src
.x
> 0) or
256 (dst
.x
< 0 and src
.x
< 0)):
260 if ((dst
.y
> 0 and src
.y
> 0) or
261 (dst
.y
< 0 and src
.y
< 0)):
265 if ((dst
.z
> 0 and src
.z
> 0) or
266 (dst
.z
< 0 and src
.z
< 0)):
271 if _is_vector_similar(dst
, src
, error
):
272 _mirror_uvs(uv_layer
, f_src
, f_dst
,
273 self
.axis
, self
.error
, transformed_verts
)
275 bmesh
.update_edit_mesh(obj
.data
)