diff options
Diffstat (limited to 'magic_uv/op/clip_uv.py')
-rw-r--r-- | magic_uv/op/clip_uv.py | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/magic_uv/op/clip_uv.py b/magic_uv/op/clip_uv.py new file mode 100644 index 00000000..c6f006e2 --- /dev/null +++ b/magic_uv/op/clip_uv.py @@ -0,0 +1,227 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Dusan Stevanovic, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "6.3" +__date__ = "10 Aug 2020" + + +import math + +import bpy +import bmesh +from mathutils import Vector +from bpy.props import BoolProperty, FloatVectorProperty + +from .. import common +from ..utils.bl_class_registry import BlClassRegistry +from ..utils.property_class_registry import PropertyClassRegistry +from ..utils import compatibility as compat + + +def _is_valid_context(context): + # '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 + + +def round_clip_uv_range(v): + sign = 1 if v >= 0.0 else -1 + return int((math.fabs(v) + 0.25) / 0.5) * 0.5 * sign + + +def get_clip_uv_range_max(self): + return self.get('muv_clip_uv_range_max', (0.5, 0.5)) + + +def set_clip_uv_range_max(self, value): + u = round_clip_uv_range(value[0]) + u = 0.5 if u <= 0.5 else u + v = round_clip_uv_range(value[1]) + v = 0.5 if v <= 0.5 else v + self['muv_clip_uv_range_max'] = (u, v) + + +def get_clip_uv_range_min(self): + return self.get('muv_clip_uv_range_min', (-0.5, -0.5)) + + +def set_clip_uv_range_min(self, value): + u = round_clip_uv_range(value[0]) + u = -0.5 if u >= -0.5 else u + v = round_clip_uv_range(value[1]) + v = -0.5 if v >= -0.5 else v + self['muv_clip_uv_range_min'] = (u, v) + + +@PropertyClassRegistry() +class _Properties: + idname = "clip_uv" + + @classmethod + def init_props(cls, scene): + scene.muv_clip_uv_enabled = BoolProperty( + name="Clip UV Enabled", + description="Clip UV is enabled", + default=False + ) + + scene.muv_clip_uv_range_max = FloatVectorProperty( + name="Range Max", + description="Max UV coordinates of the range to be clipped", + size=2, + default=(0.5, 0.5), + min=0.5, + step=50, + get=get_clip_uv_range_max, + set=set_clip_uv_range_max, + ) + + scene.muv_clip_uv_range_min = FloatVectorProperty( + name="Range Min", + description="Min UV coordinates of the range to be clipped", + size=2, + default=(-0.5, -0.5), + max=-0.5, + step=50, + get=get_clip_uv_range_min, + set=set_clip_uv_range_min, + ) + + # TODO: add option to preserve UV island + + @classmethod + def del_props(cls, scene): + del scene.muv_clip_uv_range_max + del scene.muv_clip_uv_range_min + + +@BlClassRegistry() +@compat.make_annotations +class MUV_OT_ClipUV(bpy.types.Operator): + + bl_idname = "uv.muv_clip_uv" + bl_label = "Clip UV" + bl_description = "Clip selected UV in the specified range" + bl_options = {'REGISTER', 'UNDO'} + + clip_uv_range_max = FloatVectorProperty( + name="Range Max", + description="Max UV coordinates of the range to be clipped", + size=2, + default=(0.5, 0.5), + min=0.5, + step=50, + ) + + clip_uv_range_min = FloatVectorProperty( + name="Range Min", + description="Min UV coordinates of the range to be clipped", + size=2, + default=(-0.5, -0.5), + max=-0.5, + step=50, + ) + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + return _is_valid_context(context) + + def execute(self, context): + obj = context.active_object + bm = common.create_bmesh(obj) + + if not bm.loops.layers.uv: + self.report({'WARNING'}, "Object must have more than one UV map") + return {'CANCELLED'} + + uv_layer = bm.loops.layers.uv.verify() + + for face in bm.faces: + if not face.select: + continue + + selected_loops = [l for l in face.loops + if l[uv_layer].select or + context.scene.tool_settings.use_uv_select_sync] + if not selected_loops: + continue + + # average of UV coordinates on the face + max_uv = Vector((-10000000.0, -10000000.0)) + min_uv = Vector((10000000.0, 10000000.0)) + for l in selected_loops: + uv = l[uv_layer].uv + max_uv.x = max(max_uv.x, uv.x) + max_uv.y = max(max_uv.y, uv.y) + min_uv.x = min(min_uv.x, uv.x) + min_uv.y = min(min_uv.y, uv.y) + + # clip + move_uv = Vector((0.0, 0.0)) + clip_size = Vector(self.clip_uv_range_max) - \ + Vector(self.clip_uv_range_min) + if max_uv.x > self.clip_uv_range_max[0]: + target_x = math.fmod(max_uv.x - self.clip_uv_range_min[0], + clip_size.x) + if target_x < 0.0: + target_x += clip_size.x + target_x += self.clip_uv_range_min[0] + move_uv.x = target_x - max_uv.x + if min_uv.x < self.clip_uv_range_min[0]: + target_x = math.fmod(min_uv.x - self.clip_uv_range_min[0], + clip_size.x) + if target_x < 0.0: + target_x += clip_size.x + target_x += self.clip_uv_range_min[0] + move_uv.x = target_x - min_uv.x + if max_uv.y > self.clip_uv_range_max[1]: + target_y = math.fmod(max_uv.y - self.clip_uv_range_min[1], + clip_size.y) + if target_y < 0.0: + target_y += clip_size.y + target_y += self.clip_uv_range_min[1] + move_uv.y = target_y - max_uv.y + if min_uv.y < self.clip_uv_range_min[1]: + target_y = math.fmod(min_uv.y - self.clip_uv_range_min[1], + clip_size.y) + if target_y < 0.0: + target_y += clip_size.y + target_y += self.clip_uv_range_min[1] + move_uv.y = target_y - min_uv.y + + # update UV + for l in selected_loops: + l[uv_layer].uv = l[uv_layer].uv + move_uv + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} |