File headers: use SPDX license identifiers
[blender-addons.git] / magic_uv / op / move_uv.py
blob76022d12d7c2b9d51711c2f255173d896d92dc80
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # <pep8-80 compliant>
5 __author__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>"
6 __status__ = "production"
7 __version__ = "6.5"
8 __date__ = "6 Mar 2021"
10 import bpy
11 from bpy.props import BoolProperty
12 import bmesh
13 from mathutils import Vector
15 from .. import common
16 from ..utils.bl_class_registry import BlClassRegistry
17 from ..utils.property_class_registry import PropertyClassRegistry
20 def _is_valid_context(context):
21 # Multiple objects editing mode is not supported in this feature.
22 objs = common.get_uv_editable_objects(context)
23 if len(objs) != 1:
24 return False
26 # only edit mode is allowed to execute
27 if context.object.mode != 'EDIT':
28 return False
30 # only 'VIEW_3D' space is allowed to execute
31 if not common.is_valid_space(context, ['VIEW_3D']):
32 return False
34 return True
37 @PropertyClassRegistry()
38 class _Properties:
39 idname = "move_uv"
41 @classmethod
42 def init_props(cls, scene):
43 scene.muv_move_uv_enabled = BoolProperty(
44 name="Move UV Enabled",
45 description="Move UV is enabled",
46 default=False
49 @classmethod
50 def del_props(cls, scene):
51 del scene.muv_move_uv_enabled
54 @BlClassRegistry()
55 class MUV_OT_MoveUV(bpy.types.Operator):
56 """
57 Operator class: Move UV
58 """
60 bl_idname = "uv.muv_move_uv"
61 bl_label = "Move UV"
62 bl_options = {'REGISTER', 'UNDO'}
64 __running = False
66 def __init__(self):
67 self.__topology_dict = []
68 self.__prev_mouse = Vector((0.0, 0.0))
69 self.__offset_uv = Vector((0.0, 0.0))
70 self.__prev_offset_uv = Vector((0.0, 0.0))
71 self.__first_time = True
72 self.__ini_uvs = []
73 self.__operating = False
75 # Creation of BMesh is high cost, so cache related objects.
76 self.__cache = {}
78 @classmethod
79 def poll(cls, context):
80 # we can not get area/space/region from console
81 if common.is_console_mode():
82 return False
83 if cls.is_running(context):
84 return False
85 return _is_valid_context(context)
87 @classmethod
88 def is_running(cls, _):
89 return cls.__running
91 def _find_uv(self, bm, active_uv):
92 topology_dict = []
93 uvs = []
94 for fidx, f in enumerate(bm.faces):
95 for vidx, v in enumerate(f.verts):
96 if v.select:
97 uvs.append(f.loops[vidx][active_uv].uv.copy())
98 topology_dict.append([fidx, vidx])
100 return topology_dict, uvs
102 def modal(self, _, event):
103 if self.__first_time is True:
104 self.__prev_mouse = Vector((
105 event.mouse_region_x, event.mouse_region_y))
106 self.__first_time = False
107 return {'RUNNING_MODAL'}
109 # move UV
110 div = 10000
111 self.__offset_uv += Vector((
112 (event.mouse_region_x - self.__prev_mouse.x) / div,
113 (event.mouse_region_y - self.__prev_mouse.y) / div))
114 ouv = self.__offset_uv
115 pouv = self.__prev_offset_uv
116 vec = Vector((ouv.x - ouv.y, ouv.x + ouv.y))
117 dv = vec - pouv
118 self.__prev_offset_uv = vec
119 self.__prev_mouse = Vector((
120 event.mouse_region_x, event.mouse_region_y))
122 # check if operation is started
123 if not self.__operating:
124 if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
125 self.__operating = True
126 return {'RUNNING_MODAL'}
128 # update UV
129 obj = self.__cache["active_object"]
130 bm = self.__cache["bmesh"]
131 active_uv = self.__cache["active_uv"]
132 for uv in self.__cache["target_uv"]:
133 uv += dv
134 bmesh.update_edit_mesh(obj.data)
136 # check mouse preference
137 confirm_btn = 'LEFTMOUSE'
138 cancel_btn = 'RIGHTMOUSE'
140 # cancelled
141 if event.type == cancel_btn and event.value == 'PRESS':
142 for (fidx, vidx), uv in zip(self.__topology_dict, self.__ini_uvs):
143 bm.faces[fidx].loops[vidx][active_uv].uv = uv
144 MUV_OT_MoveUV.__running = False
145 self.__cache = {}
146 return {'FINISHED'}
147 # confirmed
148 if event.type == confirm_btn and event.value == 'PRESS':
149 MUV_OT_MoveUV.__running = False
150 self.__cache = {}
151 return {'FINISHED'}
153 return {'RUNNING_MODAL'}
155 def execute(self, context):
156 MUV_OT_MoveUV.__running = True
157 self.__operating = False
158 self.__first_time = True
160 context.window_manager.modal_handler_add(self)
162 objs = common.get_uv_editable_objects(context)
163 # poll() method ensures that only one object is selected.
164 obj = objs[0]
165 bm = bmesh.from_edit_mesh(obj.data)
166 active_uv = bm.loops.layers.uv.active
167 self.__topology_dict, self.__ini_uvs = self._find_uv(bm, active_uv)
169 # Optimization: Store temporary variables which cause heavy
170 # calculation.
171 self.__cache["active_object"] = obj
172 self.__cache["bmesh"] = bm
173 self.__cache["active_uv"] = active_uv
174 self.__cache["target_uv"] = []
175 for fidx, vidx in self.__topology_dict:
176 l = bm.faces[fidx].loops[vidx]
177 self.__cache["target_uv"].append(l[active_uv].uv)
179 if context.area:
180 context.area.tag_redraw()
182 return {'RUNNING_MODAL'}