1# <pep8-80 compliant> 2 3# ##### BEGIN GPL LICENSE BLOCK ##### 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License 7# as published by the Free Software Foundation; either version 2 8# of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software Foundation, 17# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18# 19# ##### END GPL LICENSE BLOCK ##### 20 21__author__ = "Nutti <nutti.metro@gmail.com>" 22__status__ = "production" 23__version__ = "6.3" 24__date__ = "10 Aug 2020" 25 26import bpy 27from mathutils import Vector 28from bpy.props import EnumProperty, BoolProperty, FloatVectorProperty 29import bmesh 30 31from .. import common 32from ..utils.bl_class_registry import BlClassRegistry 33from ..utils.property_class_registry import PropertyClassRegistry 34from ..utils import compatibility as compat 35 36 37def _is_valid_context(context): 38 # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute. 39 # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf 40 # after the execution 41 for space in context.area.spaces: 42 if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'): 43 break 44 else: 45 return False 46 47 return True 48 49 50@PropertyClassRegistry() 51class _Properties: 52 idname = "align_uv_cursor" 53 54 @classmethod 55 def init_props(cls, scene): 56 def auvc_get_cursor_loc(self): 57 area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW', 58 'IMAGE_EDITOR') 59 if compat.check_version(2, 80, 0) < 0: 60 bd_size = common.get_uvimg_editor_board_size(area) 61 else: 62 bd_size = [1.0, 1.0] 63 loc = space.cursor_location 64 65 if bd_size[0] < 0.000001: 66 cx = 0.0 67 else: 68 cx = loc[0] / bd_size[0] 69 if bd_size[1] < 0.000001: 70 cy = 0.0 71 else: 72 cy = loc[1] / bd_size[1] 73 74 self['muv_align_uv_cursor_cursor_loc'] = Vector((cx, cy)) 75 return self.get('muv_align_uv_cursor_cursor_loc', (0.0, 0.0)) 76 77 def auvc_set_cursor_loc(self, value): 78 self['muv_align_uv_cursor_cursor_loc'] = value 79 area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW', 80 'IMAGE_EDITOR') 81 if compat.check_version(2, 80, 0) < 0: 82 bd_size = common.get_uvimg_editor_board_size(area) 83 else: 84 bd_size = [1.0, 1.0] 85 cx = bd_size[0] * value[0] 86 cy = bd_size[1] * value[1] 87 space.cursor_location = Vector((cx, cy)) 88 89 scene.muv_align_uv_cursor_enabled = BoolProperty( 90 name="Align UV Cursor Enabled", 91 description="Align UV Cursor is enabled", 92 default=False 93 ) 94 95 scene.muv_align_uv_cursor_cursor_loc = FloatVectorProperty( 96 name="UV Cursor Location", 97 size=2, 98 precision=4, 99 soft_min=-1.0, 100 soft_max=1.0, 101 step=1, 102 default=(0.000, 0.000), 103 get=auvc_get_cursor_loc, 104 set=auvc_set_cursor_loc 105 ) 106 scene.muv_align_uv_cursor_align_method = EnumProperty( 107 name="Align Method", 108 description="Align Method", 109 default='TEXTURE', 110 items=[ 111 ('TEXTURE', "Texture", "Align to texture"), 112 ('UV', "UV", "Align to UV"), 113 ('UV_SEL', "UV (Selected)", "Align to Selected UV") 114 ] 115 ) 116 117 scene.muv_uv_cursor_location_enabled = BoolProperty( 118 name="UV Cursor Location Enabled", 119 description="UV Cursor Location is enabled", 120 default=False 121 ) 122 123 @classmethod 124 def del_props(cls, scene): 125 del scene.muv_align_uv_cursor_enabled 126 del scene.muv_align_uv_cursor_cursor_loc 127 del scene.muv_align_uv_cursor_align_method 128 129 del scene.muv_uv_cursor_location_enabled 130 131 132@BlClassRegistry() 133@compat.make_annotations 134class MUV_OT_AlignUVCursor(bpy.types.Operator): 135 136 bl_idname = "uv.muv_align_uv_cursor" 137 bl_label = "Align UV Cursor" 138 bl_description = "Align cursor to the center of UV island" 139 bl_options = {'REGISTER', 'UNDO'} 140 141 position = EnumProperty( 142 items=( 143 ('CENTER', "Center", "Align to Center"), 144 ('LEFT_TOP', "Left Top", "Align to Left Top"), 145 ('LEFT_MIDDLE', "Left Middle", "Align to Left Middle"), 146 ('LEFT_BOTTOM', "Left Bottom", "Align to Left Bottom"), 147 ('MIDDLE_TOP', "Middle Top", "Align to Middle Top"), 148 ('MIDDLE_BOTTOM', "Middle Bottom", "Align to Middle Bottom"), 149 ('RIGHT_TOP', "Right Top", "Align to Right Top"), 150 ('RIGHT_MIDDLE', "Right Middle", "Align to Right Middle"), 151 ('RIGHT_BOTTOM', "Right Bottom", "Align to Right Bottom") 152 ), 153 name="Position", 154 description="Align position", 155 default='CENTER' 156 ) 157 base = EnumProperty( 158 items=( 159 ('TEXTURE', "Texture", "Align based on Texture"), 160 ('UV', "UV", "Align to UV"), 161 ('UV_SEL', "UV (Selected)", "Align to Selected UV") 162 ), 163 name="Base", 164 description="Align base", 165 default='TEXTURE' 166 ) 167 168 @classmethod 169 def poll(cls, context): 170 # we can not get area/space/region from console 171 if common.is_console_mode(): 172 return True 173 return _is_valid_context(context) 174 175 def execute(self, context): 176 area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW', 177 'IMAGE_EDITOR') 178 if compat.check_version(2, 80, 0) < 0: 179 bd_size = common.get_uvimg_editor_board_size(area) 180 else: 181 bd_size = [1.0, 1.0] 182 183 if self.base == 'UV': 184 obj = context.active_object 185 bm = bmesh.from_edit_mesh(obj.data) 186 if not bm.loops.layers.uv: 187 return None 188 uv_layer = bm.loops.layers.uv.verify() 189 190 max_ = Vector((-10000000.0, -10000000.0)) 191 min_ = Vector((10000000.0, 10000000.0)) 192 for f in bm.faces: 193 if not f.select: 194 continue 195 for l in f.loops: 196 uv = l[uv_layer].uv 197 max_.x = max(max_.x, uv.x) 198 max_.y = max(max_.y, uv.y) 199 min_.x = min(min_.x, uv.x) 200 min_.y = min(min_.y, uv.y) 201 center = Vector(((max_.x + min_.x) / 2.0, (max_.y + min_.y) / 2.0)) 202 203 elif self.base == 'UV_SEL': 204 obj = context.active_object 205 bm = bmesh.from_edit_mesh(obj.data) 206 if not bm.loops.layers.uv: 207 return None 208 uv_layer = bm.loops.layers.uv.verify() 209 210 max_ = Vector((-10000000.0, -10000000.0)) 211 min_ = Vector((10000000.0, 10000000.0)) 212 for f in bm.faces: 213 if not f.select: 214 continue 215 for l in f.loops: 216 if not l[uv_layer].select: 217 continue 218 uv = l[uv_layer].uv 219 max_.x = max(max_.x, uv.x) 220 max_.y = max(max_.y, uv.y) 221 min_.x = min(min_.x, uv.x) 222 min_.y = min(min_.y, uv.y) 223 center = Vector(((max_.x + min_.x) / 2.0, (max_.y + min_.y) / 2.0)) 224 225 elif self.base == 'TEXTURE': 226 min_ = Vector((0.0, 0.0)) 227 max_ = Vector((1.0, 1.0)) 228 center = Vector((0.5, 0.5)) 229 else: 230 self.report({'ERROR'}, "Unknown Operation") 231 return {'CANCELLED'} 232 233 if self.position == 'CENTER': 234 cx = center.x 235 cy = center.y 236 elif self.position == 'LEFT_TOP': 237 cx = min_.x 238 cy = max_.y 239 elif self.position == 'LEFT_MIDDLE': 240 cx = min_.x 241 cy = center.y 242 elif self.position == 'LEFT_BOTTOM': 243 cx = min_.x 244 cy = min_.y 245 elif self.position == 'MIDDLE_TOP': 246 cx = center.x 247 cy = max_.y 248 elif self.position == 'MIDDLE_BOTTOM': 249 cx = center.x 250 cy = min_.y 251 elif self.position == 'RIGHT_TOP': 252 cx = max_.x 253 cy = max_.y 254 elif self.position == 'RIGHT_MIDDLE': 255 cx = max_.x 256 cy = center.y 257 elif self.position == 'RIGHT_BOTTOM': 258 cx = max_.x 259 cy = min_.y 260 else: 261 self.report({'ERROR'}, "Unknown Operation") 262 return {'CANCELLED'} 263 264 cx = cx * bd_size[0] 265 cy = cy * bd_size[1] 266 267 space.cursor_location = Vector((cx, cy)) 268 269 return {'FINISHED'} 270