Node Wrangler: create world output if the node tree is of type world
[blender-addons.git] / magic_uv / op / mirror_uv.py
blob2cca66b1b1c24541c4b24a4e0b9167fee63eab27
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 __author__ = "Keith (Wahooney) Boshoff, Nutti <nutti.metro@gmail.com>"
4 __status__ = "production"
5 __version__ = "6.6"
6 __date__ = "22 Apr 2022"
8 import bpy
9 from bpy.props import (
10 EnumProperty,
11 FloatProperty,
12 BoolProperty,
14 import bmesh
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
20 from .. import common
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']):
26 return False
28 objs = common.get_uv_editable_objects(context)
29 if not objs:
30 return False
32 # only edit mode is allowed to execute
33 if context.object.mode != 'EDIT':
34 return False
36 return True
39 def _is_vector_similar(v1, v2, error):
40 """
41 Check if two vectors are similar, within an error threshold
42 """
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):
51 """
52 Copy UV coordinates from one UV face to another
53 """
54 for sl in src.loops:
55 suv = sl[uv_layer].uv.copy()
56 svco = transformed[sl.vert].copy()
57 for dl in dst.loops:
58 dvco = transformed[dl.vert].copy()
59 if axis == 'X':
60 dvco.x = -dvco.x
61 elif axis == 'Y':
62 dvco.y = -dvco.y
63 elif axis == 'Z':
64 dvco.z = -dvco.z
66 if _is_vector_similar(svco, dvco, error):
67 dl[uv_layer].uv = suv.copy()
70 def _get_face_center(face, transformed):
71 """
72 Get center coordinate of the face
73 """
74 center = Vector((0.0, 0.0, 0.0))
75 for v in face.verts:
76 tv = transformed[v]
77 center = center + tv
79 return center / len(face.verts)
82 @PropertyClassRegistry()
83 class _Properties:
84 idname = "mirror_uv"
86 @classmethod
87 def init_props(cls, scene):
88 scene.muv_mirror_uv_enabled = BoolProperty(
89 name="Mirror UV Enabled",
90 description="Mirror UV is enabled",
91 default=False
93 scene.muv_mirror_uv_axis = EnumProperty(
94 items=[
95 ('X', "X", "Mirror Along X axis"),
96 ('Y', "Y", "Mirror Along Y axis"),
97 ('Z', "Z", "Mirror Along Z axis")
99 name="Axis",
100 description="Mirror Axis",
101 default='X'
103 scene.muv_mirror_uv_origin = EnumProperty(
104 items=(
105 ('WORLD', "World", "World"),
106 ("GLOBAL", "Global", "Global"),
107 ('LOCAL', "Local", "Local"),
109 name="Origin",
110 description="Origin of the mirror operation",
111 default='LOCAL'
114 @classmethod
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
121 @BlClassRegistry()
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'}
132 axis = EnumProperty(
133 items=(
134 ('X', "X", "Mirror Along X axis"),
135 ('Y', "Y", "Mirror Along Y axis"),
136 ('Z', "Z", "Mirror Along Z axis")
138 name="Axis",
139 description="Mirror Axis",
140 default='X'
142 error = FloatProperty(
143 name="Error",
144 description="Error threshold",
145 default=0.001,
146 min=0.0,
147 max=100.0,
148 soft_min=0.0,
149 soft_max=1.0
151 origin = EnumProperty(
152 items=(
153 ('WORLD', "World", "World"),
154 ("GLOBAL", "Global", "Global"),
155 ('LOCAL', "Local", "Local"),
157 name="Origin",
158 description="Origin of the mirror operation",
159 default='LOCAL'
162 @classmethod
163 def poll(cls, context):
164 # we can not get area/space/region from console
165 if common.is_console_mode():
166 return True
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.
174 transformed = {}
175 for v in bm.verts:
176 transformed[v] = compat.matmul(world_orientation_mat, v.co)
178 return transformed
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))
187 for v in bm.verts:
188 center_location += v.co
189 center_location /= len(bm.verts)
191 # Move to local to global.
192 transformed = {}
193 for v in bm.verts:
194 transformed[v] = compat.matmul(rotation_mat, v.co)
195 transformed[v] -= center_location
197 return transformed
199 def _get_local_vertices(self, _, bm):
200 transformed = {}
202 # Get center location of all vertices.
203 center_location = Vector((0.0, 0.0, 0.0))
204 for v in bm.verts:
205 center_location += v.co
206 center_location /= len(bm.verts)
208 for v in bm.verts:
209 transformed[v] = v.co.copy()
210 transformed[v] -= center_location
212 return transformed
214 def execute(self, context):
215 objs = common.get_uv_editable_objects(context)
217 for obj in objs:
218 bm = bmesh.from_edit_mesh(obj.data)
220 error = self.error
221 axis = self.axis
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"
228 .format(obj.name))
229 return {'CANCELLED'}
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]
240 for f_dst in faces:
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:
245 continue
246 if count != len(f_src.verts):
247 continue
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)
253 # invert source axis
254 if axis == 'X':
255 if ((dst.x > 0 and src.x > 0) or
256 (dst.x < 0 and src.x < 0)):
257 continue
258 src.x = -src.x
259 elif axis == 'Y':
260 if ((dst.y > 0 and src.y > 0) or
261 (dst.y < 0 and src.y < 0)):
262 continue
263 src.y = -src.y
264 elif axis == 'Z':
265 if ((dst.z > 0 and src.z > 0) or
266 (dst.z < 0 and src.z < 0)):
267 continue
268 src.z = -src.z
270 # do mirror UV
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)
277 return {'FINISHED'}