diff options
Diffstat (limited to 'uv_magic_uv/op/uv_bounding_box.py')
-rw-r--r-- | uv_magic_uv/op/uv_bounding_box.py | 374 |
1 files changed, 237 insertions, 137 deletions
diff --git a/uv_magic_uv/op/uv_bounding_box.py b/uv_magic_uv/op/uv_bounding_box.py index 9ebc76c4..4aa8874b 100644 --- a/uv_magic_uv/op/uv_bounding_box.py +++ b/uv_magic_uv/op/uv_bounding_box.py @@ -20,8 +20,8 @@ __author__ = "Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "5.1" -__date__ = "24 Feb 2018" +__version__ = "5.2" +__date__ = "17 Nov 2018" from enum import IntEnum import math @@ -30,14 +30,101 @@ import bpy import bgl import mathutils import bmesh +from bpy.props import BoolProperty, EnumProperty from .. import common +__all__ = [ + 'Properties', + 'Operator', +] + + MAX_VALUE = 100000.0 -class MUV_UVBBCmd(): +def is_valid_context(context): + obj = context.object + + # only edit mode is allowed to execute + if obj is None: + return False + if obj.type != 'MESH': + return False + if context.object.mode != 'EDIT': + return False + + # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute. + # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf + # after the execution + for space in context.area.spaces: + if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'): + break + else: + return False + + return True + + +class Properties: + @classmethod + def init_props(cls, scene): + class Props(): + uv_info_ini = [] + ctrl_points_ini = [] + ctrl_points = [] + + scene.muv_props.uv_bounding_box = Props() + + def get_func(_): + return Operator.is_running(bpy.context) + + def set_func(_, __): + pass + + def update_func(_, __): + bpy.ops.uv.muv_uv_bounding_box_operator('INVOKE_REGION_WIN') + + scene.muv_uv_bounding_box_enabled = BoolProperty( + name="UV Bounding Box Enabled", + description="UV Bounding Box is enabled", + default=False + ) + scene.muv_uv_bounding_box_show = BoolProperty( + name="UV Bounding Box Showed", + description="UV Bounding Box is showed", + default=False, + get=get_func, + set=set_func, + update=update_func + ) + scene.muv_uv_bounding_box_uniform_scaling = BoolProperty( + name="Uniform Scaling", + description="Enable Uniform Scaling", + default=False + ) + scene.muv_uv_bounding_box_boundary = EnumProperty( + name="Boundary", + description="Boundary", + default='UV_SEL', + items=[ + ('UV', "UV", "Boundary is decided by UV"), + ('UV_SEL', "UV (Selected)", + "Boundary is decided by Selected UV") + ] + ) + + @classmethod + def del_props(cls, scene): + del scene.muv_props.uv_bounding_box + del scene.muv_uv_bounding_box_enabled + del scene.muv_uv_bounding_box_show + del scene.muv_uv_bounding_box_uniform_scaling + del scene.muv_uv_bounding_box_boundary + + +class CommandBase(): """ Custom class: Base class of command """ @@ -52,7 +139,7 @@ class MUV_UVBBCmd(): return mat -class MUV_UVBBTranslationCmd(MUV_UVBBCmd): +class TranslationCommand(CommandBase): """ Custom class: Translation operation """ @@ -76,7 +163,7 @@ class MUV_UVBBTranslationCmd(MUV_UVBBCmd): self.__y = y -class MUV_UVBBRotationCmd(MUV_UVBBCmd): +class RotationCommand(CommandBase): """ Custom class: Rotation operation """ @@ -107,7 +194,7 @@ class MUV_UVBBRotationCmd(MUV_UVBBCmd): self.__y = y -class MUV_UVBBScalingCmd(MUV_UVBBCmd): +class ScalingCommand(CommandBase): """ Custom class: Scaling operation """ @@ -158,7 +245,7 @@ class MUV_UVBBScalingCmd(MUV_UVBBCmd): self.__y = y -class MUV_UVBBUniformScalingCmd(MUV_UVBBCmd): +class UniformScalingCommand(CommandBase): """ Custom class: Uniform Scaling operation """ @@ -222,7 +309,7 @@ class MUV_UVBBUniformScalingCmd(MUV_UVBBCmd): self.__y = y -class MUV_UVBBCmdExecuter(): +class CommandExecuter(): """ Custom class: manage command history and execute command """ @@ -288,67 +375,7 @@ class MUV_UVBBCmdExecuter(): self.__cmd_list.append(cmd) -class MUV_UVBBRenderer(bpy.types.Operator): - """ - Operation class: Render UV bounding box - """ - - bl_idname = "uv.muv_uvbb_renderer" - bl_label = "UV Bounding Box Renderer" - bl_description = "Bounding Box Renderer about UV in Image Editor" - - __handle = None - - @staticmethod - def handle_add(obj, context): - if MUV_UVBBRenderer.__handle is None: - sie = bpy.types.SpaceImageEditor - MUV_UVBBRenderer.__handle = sie.draw_handler_add( - MUV_UVBBRenderer.draw_bb, - (obj, context), "WINDOW", "POST_PIXEL") - - @staticmethod - def handle_remove(): - if MUV_UVBBRenderer.__handle is not None: - sie = bpy.types.SpaceImageEditor - sie.draw_handler_remove( - MUV_UVBBRenderer.__handle, "WINDOW") - MUV_UVBBRenderer.__handle = None - - @staticmethod - def __draw_ctrl_point(context, pos): - """ - Draw control point - """ - prefs = context.user_preferences.addons["uv_magic_uv"].preferences - cp_size = prefs.uvbb_cp_size - offset = cp_size / 2 - verts = [ - [pos.x - offset, pos.y - offset], - [pos.x - offset, pos.y + offset], - [pos.x + offset, pos.y + offset], - [pos.x + offset, pos.y - offset] - ] - bgl.glEnable(bgl.GL_BLEND) - bgl.glBegin(bgl.GL_QUADS) - bgl.glColor4f(1.0, 1.0, 1.0, 1.0) - for (x, y) in verts: - bgl.glVertex2f(x, y) - bgl.glEnd() - - @staticmethod - def draw_bb(_, context): - """ - Draw bounding box - """ - props = context.scene.muv_props.uvbb - for cp in props.ctrl_points: - MUV_UVBBRenderer.__draw_ctrl_point( - context, mathutils.Vector( - context.region.view2d.view_to_region(cp.x, cp.y))) - - -class MUV_UVBBState(IntEnum): +class State(IntEnum): """ Enum: State definition used by MUV_UVBBStateMgr """ @@ -369,7 +396,7 @@ class MUV_UVBBState(IntEnum): UNIFORM_SCALING_4 = 14 -class MUV_UVBBStateBase(): +class StateBase(): """ Custom class: Base class of state """ @@ -381,7 +408,7 @@ class MUV_UVBBStateBase(): raise NotImplementedError -class MUV_UVBBStateNone(MUV_UVBBStateBase): +class StateNone(StateBase): """ Custom class: No state @@ -397,8 +424,8 @@ class MUV_UVBBStateNone(MUV_UVBBStateBase): Update state """ prefs = context.user_preferences.addons["uv_magic_uv"].preferences - cp_react_size = prefs.uvbb_cp_react_size - is_uscaling = context.scene.muv_uvbb_uniform_scaling + cp_react_size = prefs.uv_bounding_box_cp_react_size + is_uscaling = context.scene.muv_uv_bounding_box_uniform_scaling if (event.type == 'LEFTMOUSE') and (event.value == 'PRESS'): x, y = context.region.view2d.view_to_region( mouse_view.x, mouse_view.y) @@ -413,16 +440,16 @@ class MUV_UVBBStateNone(MUV_UVBBStateBase): arr = [1, 3, 6, 8] if i in arr: return ( - MUV_UVBBState.UNIFORM_SCALING_1 + + State.UNIFORM_SCALING_1 + arr.index(i) ) else: - return MUV_UVBBState.TRANSLATING + i + return State.TRANSLATING + i - return MUV_UVBBState.NONE + return State.NONE -class MUV_UVBBStateTranslating(MUV_UVBBStateBase): +class StateTranslating(StateBase): """ Custom class: Translating state """ @@ -431,19 +458,19 @@ class MUV_UVBBStateTranslating(MUV_UVBBStateBase): super().__init__() self.__cmd_exec = cmd_exec ix, iy = ctrl_points[0].x, ctrl_points[0].y - self.__cmd_exec.append(MUV_UVBBTranslationCmd(ix, iy)) + self.__cmd_exec.append(TranslationCommand(ix, iy)) def update(self, context, event, ctrl_points, mouse_view): if event.type == 'LEFTMOUSE': if event.value == 'RELEASE': - return MUV_UVBBState.NONE + return State.NONE if event.type == 'MOUSEMOVE': x, y = mouse_view.x, mouse_view.y self.__cmd_exec.top().set(x, y) - return MUV_UVBBState.TRANSLATING + return State.TRANSLATING -class MUV_UVBBStateScaling(MUV_UVBBStateBase): +class StateScaling(StateBase): """ Custom class: Scaling state """ @@ -460,19 +487,19 @@ class MUV_UVBBStateScaling(MUV_UVBBStateBase): dir_x, dir_y = dir_x_list[idx], dir_y_list[idx] mat = self.__cmd_exec.execute(end=self.__cmd_exec.undo_size()) self.__cmd_exec.append( - MUV_UVBBScalingCmd(ix, iy, ox, oy, dir_x, dir_y, mat.inverted())) + ScalingCommand(ix, iy, ox, oy, dir_x, dir_y, mat.inverted())) def update(self, context, event, ctrl_points, mouse_view): if event.type == 'LEFTMOUSE': if event.value == 'RELEASE': - return MUV_UVBBState.NONE + return State.NONE if event.type == 'MOUSEMOVE': x, y = mouse_view.x, mouse_view.y self.__cmd_exec.top().set(x, y) return self.__state -class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase): +class StateUniformScaling(StateBase): """ Custom class: Uniform Scaling state """ @@ -483,17 +510,17 @@ class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase): self.__cmd_exec = cmd_exec icp_idx = [1, 3, 6, 8] ocp_idx = [8, 6, 3, 1] - idx = state - MUV_UVBBState.UNIFORM_SCALING_1 + idx = state - State.UNIFORM_SCALING_1 ix, iy = ctrl_points[icp_idx[idx]].x, ctrl_points[icp_idx[idx]].y ox, oy = ctrl_points[ocp_idx[idx]].x, ctrl_points[ocp_idx[idx]].y mat = self.__cmd_exec.execute(end=self.__cmd_exec.undo_size()) - self.__cmd_exec.append(MUV_UVBBUniformScalingCmd( + self.__cmd_exec.append(UniformScalingCommand( ix, iy, ox, oy, mat.inverted())) def update(self, context, event, ctrl_points, mouse_view): if event.type == 'LEFTMOUSE': if event.value == 'RELEASE': - return MUV_UVBBState.NONE + return State.NONE if event.type == 'MOUSEMOVE': x, y = mouse_view.x, mouse_view.y self.__cmd_exec.top().set(x, y) @@ -501,7 +528,7 @@ class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase): return self.__state -class MUV_UVBBStateRotating(MUV_UVBBStateBase): +class StateRotating(StateBase): """ Custom class: Rotating state """ @@ -511,27 +538,27 @@ class MUV_UVBBStateRotating(MUV_UVBBStateBase): self.__cmd_exec = cmd_exec ix, iy = ctrl_points[9].x, ctrl_points[9].y ox, oy = ctrl_points[0].x, ctrl_points[0].y - self.__cmd_exec.append(MUV_UVBBRotationCmd(ix, iy, ox, oy)) + self.__cmd_exec.append(RotationCommand(ix, iy, ox, oy)) def update(self, context, event, ctrl_points, mouse_view): if event.type == 'LEFTMOUSE': if event.value == 'RELEASE': - return MUV_UVBBState.NONE + return State.NONE if event.type == 'MOUSEMOVE': x, y = mouse_view.x, mouse_view.y self.__cmd_exec.top().set(x, y) - return MUV_UVBBState.ROTATING + return State.ROTATING -class MUV_UVBBStateMgr(): +class StateManager(): """ Custom class: Manage state about this feature """ def __init__(self, cmd_exec): self.__cmd_exec = cmd_exec # command executer - self.__state = MUV_UVBBState.NONE # current state - self.__state_obj = MUV_UVBBStateNone(self.__cmd_exec) + self.__state = State.NONE # current state + self.__state_obj = StateNone(self.__cmd_exec) def __update_state(self, next_state, ctrl_points): """ @@ -541,18 +568,18 @@ class MUV_UVBBStateMgr(): if next_state == self.__state: return obj = None - if next_state == MUV_UVBBState.TRANSLATING: - obj = MUV_UVBBStateTranslating(self.__cmd_exec, ctrl_points) - elif MUV_UVBBState.SCALING_1 <= next_state <= MUV_UVBBState.SCALING_8: - obj = MUV_UVBBStateScaling( + if next_state == State.TRANSLATING: + obj = StateTranslating(self.__cmd_exec, ctrl_points) + elif State.SCALING_1 <= next_state <= State.SCALING_8: + obj = StateScaling( self.__cmd_exec, next_state, ctrl_points) - elif next_state == MUV_UVBBState.ROTATING: - obj = MUV_UVBBStateRotating(self.__cmd_exec, ctrl_points) - elif next_state == MUV_UVBBState.NONE: - obj = MUV_UVBBStateNone(self.__cmd_exec) - elif (MUV_UVBBState.UNIFORM_SCALING_1 <= next_state <= - MUV_UVBBState.UNIFORM_SCALING_4): - obj = MUV_UVBBStateUniformScaling( + elif next_state == State.ROTATING: + obj = StateRotating(self.__cmd_exec, ctrl_points) + elif next_state == State.NONE: + obj = StateNone(self.__cmd_exec) + elif (State.UNIFORM_SCALING_1 <= next_state <= + State.UNIFORM_SCALING_4): + obj = StateUniformScaling( self.__cmd_exec, next_state, ctrl_points) if obj is not None: @@ -569,34 +596,97 @@ class MUV_UVBBStateMgr(): context, event, ctrl_points, mouse_view) self.__update_state(next_state, ctrl_points) + return self.__state + -class MUV_UVBBUpdater(bpy.types.Operator): +class Operator(bpy.types.Operator): """ - Operation class: Update state and handle event by modal function + Operation class: UV Bounding Box """ - bl_idname = "uv.muv_uvbb_updater" - bl_label = "UV Bounding Box Updater" - bl_description = "Update UV Bounding Box" + bl_idname = "uv.muv_uv_bounding_box_operator" + bl_label = "UV Bounding Box" + bl_description = "Internal operation for UV Bounding Box" bl_options = {'REGISTER', 'UNDO'} def __init__(self): self.__timer = None - self.__cmd_exec = MUV_UVBBCmdExecuter() # Command executer - self.__state_mgr = MUV_UVBBStateMgr(self.__cmd_exec) # State Manager + self.__cmd_exec = CommandExecuter() # Command executor + self.__state_mgr = StateManager(self.__cmd_exec) # State Manager - def __handle_add(self, context): - if self.__timer is None: - self.__timer = context.window_manager.event_timer_add( + __handle = None + __timer = None + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return False + return is_valid_context(context) + + @classmethod + def is_running(cls, _): + return 1 if cls.__handle else 0 + + @classmethod + def handle_add(cls, obj, context): + if cls.__handle is None: + sie = bpy.types.SpaceImageEditor + cls.__handle = sie.draw_handler_add( + cls.draw_bb, (obj, context), "WINDOW", "POST_PIXEL") + if cls.__timer is None: + cls.__timer = context.window_manager.event_timer_add( 0.1, context.window) - context.window_manager.modal_handler_add(self) - MUV_UVBBRenderer.handle_add(self, context) + context.window_manager.modal_handler_add(obj) - def __handle_remove(self, context): - MUV_UVBBRenderer.handle_remove() - if self.__timer is not None: - context.window_manager.event_timer_remove(self.__timer) - self.__timer = None + @classmethod + def handle_remove(cls, context): + if cls.__handle is not None: + sie = bpy.types.SpaceImageEditor + sie.draw_handler_remove(cls.__handle, "WINDOW") + cls.__handle = None + if cls.__timer is not None: + context.window_manager.event_timer_remove(cls.__timer) + cls.__timer = None + + @classmethod + def __draw_ctrl_point(cls, context, pos): + """ + Draw control point + """ + prefs = context.user_preferences.addons["uv_magic_uv"].preferences + cp_size = prefs.uv_bounding_box_cp_size + offset = cp_size / 2 + verts = [ + [pos.x - offset, pos.y - offset], + [pos.x - offset, pos.y + offset], + [pos.x + offset, pos.y + offset], + [pos.x + offset, pos.y - offset] + ] + bgl.glEnable(bgl.GL_BLEND) + bgl.glBegin(bgl.GL_QUADS) + bgl.glColor4f(1.0, 1.0, 1.0, 1.0) + for (x, y) in verts: + bgl.glVertex2f(x, y) + bgl.glEnd() + + @classmethod + def draw_bb(cls, _, context): + """ + Draw bounding box + """ + props = context.scene.muv_props.uv_bounding_box + + if not Operator.is_running(context): + return + + if not is_valid_context(context): + return + + for cp in props.ctrl_points: + cls.__draw_ctrl_point( + context, mathutils.Vector( + context.region.view2d.view_to_region(cp.x, cp.y))) def __get_uv_info(self, context): """ @@ -615,10 +705,10 @@ class MUV_UVBBUpdater(bpy.types.Operator): if not f.select: continue for i, l in enumerate(f.loops): - if sc.muv_uvbb_boundary == 'UV_SEL': + if sc.muv_uv_bounding_box_boundary == 'UV_SEL': if l[uv_layer].select: uv_info.append((f.index, i, l[uv_layer].uv.copy())) - elif sc.muv_uvbb_boundary == 'UV': + elif sc.muv_uv_bounding_box_boundary == 'UV': uv_info.append((f.index, i, l[uv_layer].uv.copy())) if not uv_info: return None @@ -688,16 +778,23 @@ class MUV_UVBBUpdater(bpy.types.Operator): return [trans_mat * cp for cp in ctrl_points_ini] def modal(self, context, event): - props = context.scene.muv_props.uvbb + props = context.scene.muv_props.uv_bounding_box common.redraw_all_areas() - if props.running is False: - self.__handle_remove(context) + + if not Operator.is_running(context): return {'FINISHED'} - area, _, _ = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D') + if not is_valid_context(context): + Operator.handle_remove(context) + return {'FINISHED'} - if event.mouse_region_x < 0 or event.mouse_region_x > area.width or \ - event.mouse_region_y < 0 or event.mouse_region_y > area.height: + region_types = [ + 'HEADER', + 'UI', + 'TOOLS', + ] + if not common.mouse_on_area(event, 'IMAGE_EDITOR') or \ + common.mouse_on_regions(event, 'IMAGE_EDITOR', region_types): return {'PASS_THROUGH'} if event.type == 'TIMER': @@ -706,27 +803,30 @@ class MUV_UVBBUpdater(bpy.types.Operator): props.ctrl_points = self.__update_ctrl_point( props.ctrl_points_ini, trans_mat) - self.__state_mgr.update(context, props.ctrl_points, event) + state = self.__state_mgr.update(context, props.ctrl_points, event) + if state == State.NONE: + return {'PASS_THROUGH'} return {'RUNNING_MODAL'} - def execute(self, context): - props = context.scene.muv_props.uvbb + def invoke(self, context, _): + props = context.scene.muv_props.uv_bounding_box - if props.running is True: - props.running = False + if Operator.is_running(context): + Operator.handle_remove(context) return {'FINISHED'} props.uv_info_ini = self.__get_uv_info(context) if props.uv_info_ini is None: return {'CANCELLED'} + + Operator.handle_add(self, context) + props.ctrl_points_ini = self.__get_ctrl_point(props.uv_info_ini) trans_mat = self.__cmd_exec.execute() # Update is needed in order to display control point self.__update_uvs(context, props.uv_info_ini, trans_mat) props.ctrl_points = self.__update_ctrl_point( props.ctrl_points_ini, trans_mat) - self.__handle_add(context) - props.running = True return {'RUNNING_MODAL'} |