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