181 lines
5.3 KiB
Python
181 lines
5.3 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
__author__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>"
|
|
__status__ = "production"
|
|
__version__ = "6.6"
|
|
__date__ = "22 Apr 2022"
|
|
|
|
import bpy
|
|
from bpy.props import BoolProperty
|
|
import bmesh
|
|
from mathutils import Vector
|
|
|
|
from .. import common
|
|
from ..utils.bl_class_registry import BlClassRegistry
|
|
from ..utils.property_class_registry import PropertyClassRegistry
|
|
|
|
|
|
def _is_valid_context(context):
|
|
# only 'VIEW_3D' space is allowed to execute
|
|
if not common.is_valid_space(context, ['VIEW_3D']):
|
|
return False
|
|
|
|
# Multiple objects editing mode is not supported in this feature.
|
|
objs = common.get_uv_editable_objects(context)
|
|
if len(objs) != 1:
|
|
return False
|
|
|
|
# only edit mode is allowed to execute
|
|
if context.object.mode != 'EDIT':
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
@PropertyClassRegistry()
|
|
class _Properties:
|
|
idname = "move_uv"
|
|
|
|
@classmethod
|
|
def init_props(cls, scene):
|
|
scene.muv_move_uv_enabled = BoolProperty(
|
|
name="Move UV Enabled",
|
|
description="Move UV is enabled",
|
|
default=False
|
|
)
|
|
|
|
@classmethod
|
|
def del_props(cls, scene):
|
|
del scene.muv_move_uv_enabled
|
|
|
|
|
|
@BlClassRegistry()
|
|
class MUV_OT_MoveUV(bpy.types.Operator):
|
|
"""
|
|
Operator class: Move UV
|
|
"""
|
|
|
|
bl_idname = "uv.muv_move_uv"
|
|
bl_label = "Move UV"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
__running = False
|
|
|
|
def __init__(self):
|
|
self.__topology_dict = []
|
|
self.__prev_mouse = Vector((0.0, 0.0))
|
|
self.__offset_uv = Vector((0.0, 0.0))
|
|
self.__prev_offset_uv = Vector((0.0, 0.0))
|
|
self.__first_time = True
|
|
self.__ini_uvs = []
|
|
self.__operating = False
|
|
|
|
# Creation of BMesh is high cost, so cache related objects.
|
|
self.__cache = {}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
# we can not get area/space/region from console
|
|
if common.is_console_mode():
|
|
return False
|
|
if cls.is_running(context):
|
|
return False
|
|
return _is_valid_context(context)
|
|
|
|
@classmethod
|
|
def is_running(cls, _):
|
|
return cls.__running
|
|
|
|
def _find_uv(self, bm, active_uv):
|
|
topology_dict = []
|
|
uvs = []
|
|
for fidx, f in enumerate(bm.faces):
|
|
for vidx, v in enumerate(f.verts):
|
|
if v.select:
|
|
uvs.append(f.loops[vidx][active_uv].uv.copy())
|
|
topology_dict.append([fidx, vidx])
|
|
|
|
return topology_dict, uvs
|
|
|
|
def modal(self, _, event):
|
|
if self.__first_time is True:
|
|
self.__prev_mouse = Vector((
|
|
event.mouse_region_x, event.mouse_region_y))
|
|
self.__first_time = False
|
|
return {'RUNNING_MODAL'}
|
|
|
|
# move UV
|
|
div = 10000
|
|
self.__offset_uv += Vector((
|
|
(event.mouse_region_x - self.__prev_mouse.x) / div,
|
|
(event.mouse_region_y - self.__prev_mouse.y) / div))
|
|
ouv = self.__offset_uv
|
|
pouv = self.__prev_offset_uv
|
|
vec = Vector((ouv.x - ouv.y, ouv.x + ouv.y))
|
|
dv = vec - pouv
|
|
self.__prev_offset_uv = vec
|
|
self.__prev_mouse = Vector((
|
|
event.mouse_region_x, event.mouse_region_y))
|
|
|
|
# check if operation is started
|
|
if not self.__operating:
|
|
if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
|
|
self.__operating = True
|
|
return {'RUNNING_MODAL'}
|
|
|
|
# update UV
|
|
obj = self.__cache["active_object"]
|
|
bm = self.__cache["bmesh"]
|
|
active_uv = self.__cache["active_uv"]
|
|
for uv in self.__cache["target_uv"]:
|
|
uv += dv
|
|
bmesh.update_edit_mesh(obj.data)
|
|
|
|
# check mouse preference
|
|
confirm_btn = 'LEFTMOUSE'
|
|
cancel_btn = 'RIGHTMOUSE'
|
|
|
|
# cancelled
|
|
if event.type == cancel_btn and event.value == 'PRESS':
|
|
for (fidx, vidx), uv in zip(self.__topology_dict, self.__ini_uvs):
|
|
bm.faces[fidx].loops[vidx][active_uv].uv = uv
|
|
MUV_OT_MoveUV.__running = False
|
|
self.__cache = {}
|
|
return {'FINISHED'}
|
|
# confirmed
|
|
if event.type == confirm_btn and event.value == 'PRESS':
|
|
MUV_OT_MoveUV.__running = False
|
|
self.__cache = {}
|
|
return {'FINISHED'}
|
|
|
|
return {'RUNNING_MODAL'}
|
|
|
|
def execute(self, context):
|
|
MUV_OT_MoveUV.__running = True
|
|
self.__operating = False
|
|
self.__first_time = True
|
|
|
|
context.window_manager.modal_handler_add(self)
|
|
|
|
objs = common.get_uv_editable_objects(context)
|
|
# poll() method ensures that only one object is selected.
|
|
obj = objs[0]
|
|
bm = bmesh.from_edit_mesh(obj.data)
|
|
active_uv = bm.loops.layers.uv.active
|
|
self.__topology_dict, self.__ini_uvs = self._find_uv(bm, active_uv)
|
|
|
|
# Optimization: Store temporary variables which cause heavy
|
|
# calculation.
|
|
self.__cache["active_object"] = obj
|
|
self.__cache["bmesh"] = bm
|
|
self.__cache["active_uv"] = active_uv
|
|
self.__cache["target_uv"] = []
|
|
for fidx, vidx in self.__topology_dict:
|
|
l = bm.faces[fidx].loops[vidx]
|
|
self.__cache["target_uv"].append(l[active_uv].uv)
|
|
|
|
if context.area:
|
|
context.area.tag_redraw()
|
|
|
|
return {'RUNNING_MODAL'}
|