diff --git a/uv_magic_uv/__init__.py b/uv_magic_uv/__init__.py index 20709e79250400212495338d007deccc0501eeb3..63591526db6e2001725105393dd30f47ef8c0a96 100644 --- a/uv_magic_uv/__init__.py +++ b/uv_magic_uv/__init__.py @@ -28,8 +28,8 @@ bl_info = { "name": "Magic UV", "author": "Nutti, Mifth, Jace Priester, kgeogeo, mem, imdjs" "Keith (Wahooney) Boshoff, McBuff, MaxRobinot, Alexander Milovsky", - "version": (5, 2, 0), - "blender": (2, 79, 0), + "version": (5, 3, 0), + "blender": (2, 80, 0), "location": "See Add-ons Preferences", "description": "UV Toolset. See Add-ons Preferences for details", "warning": "", @@ -40,43 +40,80 @@ bl_info = { "category": "UV" } +def check_version(major, minor, _): + """ + Check blender version + """ + + if bpy.app.version[0] == major and bpy.app.version[1] == minor: + return 0 + if bpy.app.version[0] > major: + return 1 + if bpy.app.version[1] > minor: + return 1 + return -1 + + if "bpy" in locals(): import importlib - importlib.reload(op) - importlib.reload(ui) importlib.reload(common) - importlib.reload(preferences) - importlib.reload(properites) - importlib.reload(addon_updater_ops) - importlib.reload(addon_updater) + importlib.reload(utils) + utils.bl_class_registry.BlClassRegistry.cleanup() + if check_version(2, 80, 0) >= 0: + importlib.reload(op) + importlib.reload(ui) + importlib.reload(properites) + importlib.reload(preferences) + importlib.reload(addon_updater_ops) + importlib.reload(addon_updater) + else: + importlib.reload(legacy) else: - from . import op - from . import ui + import bpy from . import common - from . import preferences - from . import properites - from . import addon_updater_ops - from . import addon_updater + from . import utils + if check_version(2, 80, 0) >= 0: + from . import op + from . import ui + from . import properites + from . import preferences + from . import addon_updater_ops + from . import addon_updater + else: + from . import legacy + import bpy def register(): - if not common.is_console_mode(): - addon_updater_ops.register(bl_info) - properites.init_props(bpy.types.Scene) - bpy.utils.register_module(__name__) - if preferences.Preferences.enable_builtin_menu: - preferences.add_builtin_menu() + if common.check_version(2, 80, 0) >= 0: + utils.bl_class_registry.BlClassRegistry.register() + properites.init_props(bpy.types.Scene) + if preferences.Preferences.enable_builtin_menu: + preferences.add_builtin_menu() + else: + utils.bl_class_registry.BlClassRegistry.register() + legacy.properites.init_props(bpy.types.Scene) + if legacy.preferences.Preferences.enable_builtin_menu: + legacy.preferences.add_builtin_menu() + if not common.is_console_mode(): + addon_updater_ops.register(bl_info) def unregister(): - if preferences.Preferences.enable_builtin_menu: - preferences.remove_builtin_menu() - bpy.utils.unregister_module(__name__) - properites.clear_props(bpy.types.Scene) - if not common.is_console_mode(): - addon_updater_ops.unregister() + if common.check_version(2, 80, 0) >= 0: + if preferences.Preferences.enable_builtin_menu: + preferences.remove_builtin_menu() + properites.clear_props(bpy.types.Scene) + utils.bl_class_registry.BlClassRegistry.unregister() + else: + if not common.is_console_mode(): + addon_updater_ops.unregister() + if legacy.preferences.Preferences.enable_builtin_menu: + legacy.preferences.remove_builtin_menu() + legacy.properites.clear_props(bpy.types.Scene) + utils.bl_class_registry.BlClassRegistry.unregister() if __name__ == "__main__": diff --git a/uv_magic_uv/common.py b/uv_magic_uv/common.py index b0c4306e9497b0d8e8c4e79b54f51fb45ea9c5b6..bad88167743bedafa944cff34b2c94ffdfaa808c 100644 --- a/uv_magic_uv/common.py +++ b/uv_magic_uv/common.py @@ -35,10 +35,16 @@ import bmesh __all__ = [ 'is_console_mode', + 'is_debug_mode', + 'enable_debugg_mode', + 'disable_debug_mode', 'debug_print', 'check_version', 'redraw_all_areas', 'get_space', + 'mouse_on_region', + 'mouse_on_area', + 'mouse_on_regions', 'create_bmesh', 'create_new_uv_map', 'get_island_info', @@ -51,10 +57,12 @@ __all__ = [ 'measure_uv_area', 'diff_point_to_segment', 'get_loop_sequences', + 'get_overlapped_uv_info', + 'get_flipped_uv_info', ] -__DEBUG_MODE = False +__DEBUG_MODE = True def is_console_mode(): diff --git a/uv_magic_uv/impl/__init__.py b/uv_magic_uv/impl/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/uv_magic_uv/impl/copy_paste_uv_impl.py b/uv_magic_uv/impl/copy_paste_uv_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..ed44637b31b1b054010754a477e0f98bf5416662 --- /dev/null +++ b/uv_magic_uv/impl/copy_paste_uv_impl.py @@ -0,0 +1,271 @@ +# <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__ = "imdjs, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + + +import bmesh + +from .. import common + + +__all__ = [ + 'is_valid_context', + 'get_copy_uv_layers', + 'get_paste_uv_layers', + 'get_src_face_info', + 'get_dest_face_info', + 'get_select_history_src_face_info', + 'get_select_history_dest_face_info', + 'paste_uv', +] + + +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 + + # only 'VIEW_3D' space is allowed to execute + for space in context.area.spaces: + if space.type == 'VIEW_3D': + break + else: + return False + + return True + + +def get_copy_uv_layers(ops_obj, bm, uv_map): + uv_layers = [] + if uv_map == "__default": + if not bm.loops.layers.uv: + ops_obj.report( + {'WARNING'}, "Object must have more than one UV map") + return None + uv_layers.append(bm.loops.layers.uv.verify()) + ops_obj.report({'INFO'}, "Copy UV coordinate") + elif uv_map == "__all": + for uv in bm.loops.layers.uv.keys(): + uv_layers.append(bm.loops.layers.uv[uv]) + ops_obj.report({'INFO'}, "Copy UV coordinate (UV map: ALL)") + else: + uv_layers.append(bm.loops.layers.uv[uv_map]) + ops_obj.report( + {'INFO'}, "Copy UV coordinate (UV map:{})".format(uv_map)) + + return uv_layers + + +def get_paste_uv_layers(ops_obj, obj, bm, src_info, uv_map): + uv_layers = [] + if uv_map == "__default": + if not bm.loops.layers.uv: + ops_obj.report( + {'WARNING'}, "Object must have more than one UV map") + return None + uv_layers.append(bm.loops.layers.uv.verify()) + ops_obj.report({'INFO'}, "Paste UV coordinate") + elif uv_map == "__new": + new_uv_map = common.create_new_uv_map(obj) + if not new_uv_map: + ops_obj.report({'WARNING'}, + "Reached to the maximum number of UV map") + return None + uv_layers.append(bm.loops.layers.uv[new_uv_map.name]) + ops_obj.report( + {'INFO'}, "Paste UV coordinate (UV map:{})".format(new_uv_map)) + elif uv_map == "__all": + for src_layer in src_info.keys(): + if src_layer not in bm.loops.layers.uv.keys(): + new_uv_map = common.create_new_uv_map(obj, src_layer) + if not new_uv_map: + ops_obj.report({'WARNING'}, + "Reached to the maximum number of UV map") + return None + uv_layers.append(bm.loops.layers.uv[src_layer]) + ops_obj.report({'INFO'}, "Paste UV coordinate (UV map: ALL)") + else: + uv_layers.append(bm.loops.layers.uv[uv_map]) + ops_obj.report( + {'INFO'}, "Paste UV coordinate (UV map:{})".format(uv_map)) + + return uv_layers + + +def get_src_face_info(ops_obj, bm, uv_layers, only_select=False): + src_info = {} + for layer in uv_layers: + face_info = [] + for face in bm.faces: + if not only_select or face.select: + info = { + "index": face.index, + "uvs": [l[layer].uv.copy() for l in face.loops], + "pin_uvs": [l[layer].pin_uv for l in face.loops], + "seams": [l.edge.seam for l in face.loops], + } + face_info.append(info) + if not face_info: + ops_obj.report({'WARNING'}, "No faces are selected") + return None + src_info[layer.name] = face_info + + return src_info + + +def get_dest_face_info(ops_obj, bm, uv_layers, src_info, strategy, + only_select=False): + dest_info = {} + for layer in uv_layers: + face_info = [] + for face in bm.faces: + if not only_select or face.select: + info = { + "index": face.index, + "uvs": [l[layer].uv.copy() for l in face.loops], + } + face_info.append(info) + if not face_info: + ops_obj.report({'WARNING'}, "No faces are selected") + return None + key = list(src_info.keys())[0] + src_face_count = len(src_info[key]) + dest_face_count = len(face_info) + if strategy == 'N_N' and src_face_count != dest_face_count: + ops_obj.report( + {'WARNING'}, + "Number of selected faces is different from copied" + + "(src:{}, dest:{})" + .format(src_face_count, dest_face_count)) + return None + dest_info[layer.name] = face_info + + return dest_info + + +def get_select_history_src_face_info(ops_obj, bm, uv_layers): + src_info = {} + for layer in uv_layers: + face_info = [] + for hist in bm.select_history: + if isinstance(hist, bmesh.types.BMFace) and hist.select: + info = { + "index": hist.index, + "uvs": [l[layer].uv.copy() for l in hist.loops], + "pin_uvs": [l[layer].pin_uv for l in hist.loops], + "seams": [l.edge.seam for l in hist.loops], + } + face_info.append(info) + if not face_info: + ops_obj.report({'WARNING'}, "No faces are selected") + return None + src_info[layer.name] = face_info + + return src_info + + +def get_select_history_dest_face_info(ops_obj, bm, uv_layers, src_info, + strategy): + dest_info = {} + for layer in uv_layers: + face_info = [] + for hist in bm.select_history: + if isinstance(hist, bmesh.types.BMFace) and hist.select: + info = { + "index": hist.index, + "uvs": [l[layer].uv.copy() for l in hist.loops], + } + face_info.append(info) + if not face_info: + ops_obj.report({'WARNING'}, "No faces are selected") + return None + key = list(src_info.keys())[0] + src_face_count = len(src_info[key]) + dest_face_count = len(face_info) + if strategy == 'N_N' and src_face_count != dest_face_count: + ops_obj.report( + {'WARNING'}, + "Number of selected faces is different from copied" + + "(src:{}, dest:{})" + .format(src_face_count, dest_face_count)) + return None + dest_info[layer.name] = face_info + + return dest_info + + +def paste_uv(ops_obj, bm, src_info, dest_info, uv_layers, strategy, flip, + rotate, copy_seams): + for slayer_name, dlayer in zip(src_info.keys(), uv_layers): + src_faces = src_info[slayer_name] + dest_faces = dest_info[dlayer.name] + + for idx, dinfo in enumerate(dest_faces): + sinfo = None + if strategy == 'N_N': + sinfo = src_faces[idx] + elif strategy == 'N_M': + sinfo = src_faces[idx % len(src_faces)] + + suv = sinfo["uvs"] + spuv = sinfo["pin_uvs"] + ss = sinfo["seams"] + if len(sinfo["uvs"]) != len(dinfo["uvs"]): + ops_obj.report({'WARNING'}, "Some faces are different size") + return -1 + + suvs_fr = [uv for uv in suv] + spuvs_fr = [pin_uv for pin_uv in spuv] + ss_fr = [s for s in ss] + + # flip UVs + if flip is True: + suvs_fr.reverse() + spuvs_fr.reverse() + ss_fr.reverse() + + # rotate UVs + for _ in range(rotate): + uv = suvs_fr.pop() + pin_uv = spuvs_fr.pop() + s = ss_fr.pop() + suvs_fr.insert(0, uv) + spuvs_fr.insert(0, pin_uv) + ss_fr.insert(0, s) + + # paste UVs + for l, suv, spuv, ss in zip(bm.faces[dinfo["index"]].loops, + suvs_fr, spuvs_fr, ss_fr): + l[dlayer].uv = suv + l[dlayer].pin_uv = spuv + if copy_seams is True: + l.edge.seam = ss + + return 0 diff --git a/uv_magic_uv/impl/copy_paste_uv_uvedit_impl.py b/uv_magic_uv/impl/copy_paste_uv_uvedit_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..f14a70d63c9c81fedc297ee0d107c24b9d708548 --- /dev/null +++ b/uv_magic_uv/impl/copy_paste_uv_uvedit_impl.py @@ -0,0 +1,166 @@ +# <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__ = "imdjs, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import math +from math import atan2, sin, cos + +import bmesh +from mathutils import Vector + +from .. import common + + +__all__ = [ + 'is_valid_context', + 'CopyUVImpl', + 'PasteUVImpl', +] + + +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 CopyUVImpl: + @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): + props = context.scene.muv_props.copy_paste_uv_uvedit + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + uv_layer = bm.loops.layers.uv.verify() + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + props.src_uvs = [] + for face in bm.faces: + if not face.select: + continue + skip = False + for l in face.loops: + if not l[uv_layer].select: + skip = True + break + if skip: + continue + props.src_uvs.append([l[uv_layer].uv.copy() for l in face.loops]) + + return {'FINISHED'} + + +class PasteUVImpl: + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + sc = context.scene + props = sc.muv_props.copy_paste_uv_uvedit + if not props.src_uvs: + return False + return is_valid_context(context) + + def execute(self, _, context): + props = context.scene.muv_props.copy_paste_uv_uvedit + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + uv_layer = bm.loops.layers.uv.verify() + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + dest_uvs = [] + dest_face_indices = [] + for face in bm.faces: + if not face.select: + continue + skip = False + for l in face.loops: + if not l[uv_layer].select: + skip = True + break + if skip: + continue + dest_face_indices.append(face.index) + uvs = [l[uv_layer].uv.copy() for l in face.loops] + dest_uvs.append(uvs) + + for suvs, duvs in zip(props.src_uvs, dest_uvs): + src_diff = suvs[1] - suvs[0] + dest_diff = duvs[1] - duvs[0] + + src_base = suvs[0] + dest_base = duvs[0] + + src_rad = atan2(src_diff.y, src_diff.x) + dest_rad = atan2(dest_diff.y, dest_diff.x) + if src_rad < dest_rad: + radian = dest_rad - src_rad + elif src_rad > dest_rad: + radian = math.pi * 2 - (src_rad - dest_rad) + else: # src_rad == dest_rad + radian = 0.0 + + ratio = dest_diff.length / src_diff.length + break + + for suvs, fidx in zip(props.src_uvs, dest_face_indices): + for l, suv in zip(bm.faces[fidx].loops, suvs): + base = suv - src_base + radian_ref = atan2(base.y, base.x) + radian_fin = (radian + radian_ref) + length = base.length + turn = Vector((length * cos(radian_fin), + length * sin(radian_fin))) + target_uv = Vector((turn.x * ratio, turn.y * ratio)) + \ + dest_base + l[uv_layer].uv = target_uv + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} diff --git a/uv_magic_uv/impl/flip_rotate_impl.py b/uv_magic_uv/impl/flip_rotate_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..f74bc256a6ac39c2958fc3ff50df3ae3299ce9ec --- /dev/null +++ b/uv_magic_uv/impl/flip_rotate_impl.py @@ -0,0 +1,133 @@ +# <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__ = "imdjs, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + + +__all__ = [ + 'is_valid_context', + 'get_uv_layer', + 'get_src_face_info', +] + + +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 + + # only 'VIEW_3D' space is allowed to execute + for space in context.area.spaces: + if space.type == 'VIEW_3D': + break + else: + return False + + return True + + +def get_uv_layer(ops_obj, bm): + # get UV layer + if not bm.loops.layers.uv: + ops_obj.report({'WARNING'}, "Object must have more than one UV map") + return None + uv_layer = bm.loops.layers.uv.verify() + + return uv_layer + + +def get_src_face_info(ops_obj, bm, uv_layers, only_select=False): + src_info = {} + for layer in uv_layers: + face_info = [] + for face in bm.faces: + if not only_select or face.select: + info = { + "index": face.index, + "uvs": [l[layer].uv.copy() for l in face.loops], + "pin_uvs": [l[layer].pin_uv for l in face.loops], + "seams": [l.edge.seam for l in face.loops], + } + face_info.append(info) + if not face_info: + ops_obj.report({'WARNING'}, "No faces are selected") + return None + src_info[layer.name] = face_info + + return src_info + + +def paste_uv(ops_obj, bm, src_info, dest_info, uv_layers, strategy, flip, + rotate, copy_seams): + for slayer_name, dlayer in zip(src_info.keys(), uv_layers): + src_faces = src_info[slayer_name] + dest_faces = dest_info[dlayer.name] + + for idx, dinfo in enumerate(dest_faces): + sinfo = None + if strategy == 'N_N': + sinfo = src_faces[idx] + elif strategy == 'N_M': + sinfo = src_faces[idx % len(src_faces)] + + suv = sinfo["uvs"] + spuv = sinfo["pin_uvs"] + ss = sinfo["seams"] + if len(sinfo["uvs"]) != len(dinfo["uvs"]): + ops_obj.report({'WARNING'}, "Some faces are different size") + return -1 + + suvs_fr = [uv for uv in suv] + spuvs_fr = [pin_uv for pin_uv in spuv] + ss_fr = [s for s in ss] + + # flip UVs + if flip is True: + suvs_fr.reverse() + spuvs_fr.reverse() + ss_fr.reverse() + + # rotate UVs + for _ in range(rotate): + uv = suvs_fr.pop() + pin_uv = spuvs_fr.pop() + s = ss_fr.pop() + suvs_fr.insert(0, uv) + spuvs_fr.insert(0, pin_uv) + ss_fr.insert(0, s) + + # paste UVs + for l, suv, spuv, ss in zip(bm.faces[dinfo["index"]].loops, + suvs_fr, spuvs_fr, ss_fr): + l[dlayer].uv = suv + l[dlayer].pin_uv = spuv + if copy_seams is True: + l.edge.seam = ss + + return 0 diff --git a/uv_magic_uv/impl/mirror_uv_impl.py b/uv_magic_uv/impl/mirror_uv_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..e79fbc2ce2d7c97ab5c65532e96133e19536bee6 --- /dev/null +++ b/uv_magic_uv/impl/mirror_uv_impl.py @@ -0,0 +1,158 @@ +# <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__ = "Keith (Wahooney) Boshoff, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bmesh +from mathutils import Vector + +from .. import common + + +__all__ = [ + 'is_valid_context', + 'is_vector_similar', + 'mirror_uvs', + 'get_face_center', + 'MirrorUVImpl', +] + + +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 + + # only 'VIEW_3D' space is allowed to execute + for space in context.area.spaces: + if space.type == 'VIEW_3D': + break + else: + return False + + return True + + +def is_vector_similar(v1, v2, error): + """ + Check if two vectors are similar, within an error threshold + """ + within_err_x = abs(v2.x - v1.x) < error + within_err_y = abs(v2.y - v1.y) < error + within_err_z = abs(v2.z - v1.z) < error + + return within_err_x and within_err_y and within_err_z + + +def mirror_uvs(uv_layer, src, dst, axis, error): + """ + Copy UV coordinates from one UV face to another + """ + for sl in src.loops: + suv = sl[uv_layer].uv.copy() + svco = sl.vert.co.copy() + for dl in dst.loops: + dvco = dl.vert.co.copy() + if axis == 'X': + dvco.x = -dvco.x + elif axis == 'Y': + dvco.y = -dvco.y + elif axis == 'Z': + dvco.z = -dvco.z + + if is_vector_similar(svco, dvco, error): + dl[uv_layer].uv = suv.copy() + + +def get_face_center(face): + """ + Get center coordinate of the face + """ + center = Vector((0.0, 0.0, 0.0)) + for v in face.verts: + center = center + v.co + + return center / len(face.verts) + + +class MirrorUVImpl: + @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, ops_obj, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + + error = ops_obj.error + axis = ops_obj.axis + + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + if not bm.loops.layers.uv: + ops_obj.report({'WARNING'}, + "Object must have more than one UV map") + return {'CANCELLED'} + uv_layer = bm.loops.layers.uv.verify() + + faces = [f for f in bm.faces if f.select] + for f_dst in faces: + count = len(f_dst.verts) + for f_src in bm.faces: + # check if this is a candidate to do mirror UV + if f_src.index == f_dst.index: + continue + if count != len(f_src.verts): + continue + + # test if the vertices x values are the same sign + dst = get_face_center(f_dst) + src = get_face_center(f_src) + if (dst.x > 0 and src.x > 0) or (dst.x < 0 and src.x < 0): + continue + + # invert source axis + if axis == 'X': + src.x = -src.x + elif axis == 'Y': + src.y = -src.z + elif axis == 'Z': + src.z = -src.z + + # do mirror UV + if is_vector_similar(dst, src, error): + mirror_uvs( + uv_layer, f_src, f_dst, ops_obj.axis, ops_obj.error) + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} diff --git a/uv_magic_uv/impl/move_uv_impl.py b/uv_magic_uv/impl/move_uv_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..ce507fbab165c8ef75fc80e5a554a7344e62650c --- /dev/null +++ b/uv_magic_uv/impl/move_uv_impl.py @@ -0,0 +1,166 @@ +# <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__ = "imdjs, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bmesh +from mathutils import Vector + +from .. import common + + +__all__ = [ + 'is_valid_context', + 'find_uv', + 'MoveUVImpl', +] + + +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 + + # only 'VIEW_3D' space is allowed to execute + for space in context.area.spaces: + if space.type == 'VIEW_3D': + break + else: + return False + + return True + + +def find_uv(context): + bm = bmesh.from_edit_mesh(context.object.data) + topology_dict = [] + uvs = [] + active_uv = bm.loops.layers.uv.active + 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 + + +class MoveUVImpl(): + __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 + + @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 modal(self, _, context, 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 = context.object + bm = bmesh.from_edit_mesh(obj.data) + active_uv = bm.loops.layers.uv.active + for fidx, vidx in self.__topology_dict: + l = bm.faces[fidx].loops[vidx] + l[active_uv].uv = l[active_uv].uv + dv + bmesh.update_edit_mesh(obj.data) + + # check mouse preference + if context.user_preferences.inputs.select_mouse == 'RIGHT': + confirm_btn = 'LEFTMOUSE' + cancel_btn = 'RIGHTMOUSE' + else: + confirm_btn = 'RIGHTMOUSE' + cancel_btn = 'LEFTMOUSE' + + # 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 + MoveUVImpl.__running = False + return {'FINISHED'} + # confirmed + if event.type == confirm_btn and event.value == 'PRESS': + MoveUVImpl.__running = False + return {'FINISHED'} + + return {'RUNNING_MODAL'} + + def execute(self, ops_obj, context): + MoveUVImpl.__running = True + self.__operating = False + self.__first_time = True + + context.window_manager.modal_handler_add(ops_obj) + self.__topology_dict, self.__ini_uvs = find_uv(context) + + if context.area: + context.area.tag_redraw() + + return {'RUNNING_MODAL'} diff --git a/uv_magic_uv/impl/transfer_uv_impl.py b/uv_magic_uv/impl/transfer_uv_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..adc973527f9d6dcd1a49c8a81150e8206917cc18 --- /dev/null +++ b/uv_magic_uv/impl/transfer_uv_impl.py @@ -0,0 +1,330 @@ +# <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__ = "imdjs, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +from collections import OrderedDict + +import bpy +import bmesh + +from .. import common + + +__all__ = [ + 'is_valid_context', + 'get_uv_layer', + 'main_parse', + 'parse_faces', + 'get_new_shared_faces', + 'get_other_verts_edges', + 'get_selected_src_faces', + 'paste_uv', +] + + +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 + + # only 'VIEW_3D' space is allowed to execute + for space in context.area.spaces: + if space.type == 'VIEW_3D': + break + else: + return False + + return True + + +def get_uv_layer(ops_obj, bm): + # get UV layer + if not bm.loops.layers.uv: + ops_obj.report({'WARNING'}, "Object must have more than one UV map") + return None + uv_layer = bm.loops.layers.uv.verify() + + return uv_layer + + +def main_parse(ops_obj, uv_layer, sel_faces, active_face, active_face_nor): + all_sorted_faces = OrderedDict() # This is the main stuff + + used_verts = set() + used_edges = set() + + faces_to_parse = [] + + # get shared edge of two faces + cross_edges = [] + for edge in active_face.edges: + if edge in sel_faces[0].edges and edge in sel_faces[1].edges: + cross_edges.append(edge) + + # parse two selected faces + if cross_edges and len(cross_edges) == 1: + shared_edge = cross_edges[0] + vert1 = None + vert2 = None + + dot_n = active_face_nor.normalized() + edge_vec_1 = (shared_edge.verts[1].co - shared_edge.verts[0].co) + edge_vec_len = edge_vec_1.length + edge_vec_1 = edge_vec_1.normalized() + + af_center = active_face.calc_center_median() + af_vec = shared_edge.verts[0].co + (edge_vec_1 * (edge_vec_len * 0.5)) + af_vec = (af_vec - af_center).normalized() + + if af_vec.cross(edge_vec_1).dot(dot_n) > 0: + vert1 = shared_edge.verts[0] + vert2 = shared_edge.verts[1] + else: + vert1 = shared_edge.verts[1] + vert2 = shared_edge.verts[0] + + # get active face stuff and uvs + face_stuff = get_other_verts_edges( + active_face, vert1, vert2, shared_edge, uv_layer) + all_sorted_faces[active_face] = face_stuff + used_verts.update(active_face.verts) + used_edges.update(active_face.edges) + + # get first selected face stuff and uvs as they share shared_edge + second_face = sel_faces[0] + if second_face is active_face: + second_face = sel_faces[1] + face_stuff = get_other_verts_edges( + second_face, vert1, vert2, shared_edge, uv_layer) + all_sorted_faces[second_face] = face_stuff + used_verts.update(second_face.verts) + used_edges.update(second_face.edges) + + # first Grow + faces_to_parse.append(active_face) + faces_to_parse.append(second_face) + + else: + ops_obj.report({'WARNING'}, "Two faces should share one edge") + return None + + # parse all faces + while True: + new_parsed_faces = [] + if not faces_to_parse: + break + for face in faces_to_parse: + face_stuff = all_sorted_faces.get(face) + new_faces = parse_faces(face, face_stuff, used_verts, used_edges, + all_sorted_faces, uv_layer) + if new_faces is None: + ops_obj.report({'WARNING'}, "More than 2 faces share edge") + return None + + new_parsed_faces += new_faces + faces_to_parse = new_parsed_faces + + return all_sorted_faces + + +def parse_faces(check_face, face_stuff, used_verts, used_edges, + all_sorted_faces, uv_layer): + """recurse faces around the new_grow only""" + + new_shared_faces = [] + for sorted_edge in face_stuff[1]: + shared_faces = sorted_edge.link_faces + if shared_faces: + if len(shared_faces) > 2: + bpy.ops.mesh.select_all(action='DESELECT') + for face_sel in shared_faces: + face_sel.select = True + shared_faces = [] + return None + + clear_shared_faces = get_new_shared_faces( + check_face, sorted_edge, shared_faces, all_sorted_faces.keys()) + if clear_shared_faces: + shared_face = clear_shared_faces[0] + # get vertices of the edge + vert1 = sorted_edge.verts[0] + vert2 = sorted_edge.verts[1] + + common.debug_print(face_stuff[0], vert1, vert2) + if face_stuff[0].index(vert1) > face_stuff[0].index(vert2): + vert1 = sorted_edge.verts[1] + vert2 = sorted_edge.verts[0] + + common.debug_print(shared_face.verts, vert1, vert2) + new_face_stuff = get_other_verts_edges( + shared_face, vert1, vert2, sorted_edge, uv_layer) + all_sorted_faces[shared_face] = new_face_stuff + used_verts.update(shared_face.verts) + used_edges.update(shared_face.edges) + + if common.is_debug_mode(): + shared_face.select = True # test which faces are parsed + + new_shared_faces.append(shared_face) + + return new_shared_faces + + +def get_new_shared_faces(orig_face, shared_edge, check_faces, used_faces): + shared_faces = [] + + for face in check_faces: + is_shared_edge = shared_edge in face.edges + not_used = face not in used_faces + not_orig = face is not orig_face + not_hide = face.hide is False + if is_shared_edge and not_used and not_orig and not_hide: + shared_faces.append(face) + + return shared_faces + + +def get_other_verts_edges(face, vert1, vert2, first_edge, uv_layer): + face_edges = [first_edge] + face_verts = [vert1, vert2] + face_loops = [] + + other_edges = [edge for edge in face.edges if edge not in face_edges] + + for _ in range(len(other_edges)): + found_edge = None + # get sorted verts and edges + for edge in other_edges: + if face_verts[-1] in edge.verts: + other_vert = edge.other_vert(face_verts[-1]) + + if other_vert not in face_verts: + face_verts.append(other_vert) + + found_edge = edge + if found_edge not in face_edges: + face_edges.append(edge) + break + + other_edges.remove(found_edge) + + # get sorted uvs + for vert in face_verts: + for loop in face.loops: + if loop.vert is vert: + face_loops.append(loop[uv_layer]) + break + + return [face_verts, face_edges, face_loops] + + +def get_selected_src_faces(ops_obj, bm, uv_layer): + topology_copied = [] + + # get selected faces + active_face = bm.faces.active + sel_faces = [face for face in bm.faces if face.select] + if len(sel_faces) != 2: + ops_obj.report({'WARNING'}, "Two faces must be selected") + return None + if not active_face or active_face not in sel_faces: + ops_obj.report({'WARNING'}, "Two faces must be active") + return None + + # parse all faces according to selection + active_face_nor = active_face.normal.copy() + all_sorted_faces = main_parse(ops_obj, uv_layer, sel_faces, active_face, + active_face_nor) + + if all_sorted_faces: + for face_data in all_sorted_faces.values(): + edges = face_data[1] + uv_loops = face_data[2] + uvs = [l.uv.copy() for l in uv_loops] + pin_uvs = [l.pin_uv for l in uv_loops] + seams = [e.seam for e in edges] + topology_copied.append([uvs, pin_uvs, seams]) + else: + return None + + return topology_copied + + +def paste_uv(ops_obj, bm, uv_layer, src_faces, invert_normals, copy_seams): + # get selection history + all_sel_faces = [e for e in bm.select_history + if isinstance(e, bmesh.types.BMFace) and e.select] + if len(all_sel_faces) % 2 != 0: + ops_obj.report({'WARNING'}, "Two faces must be selected") + return -1 + + # parse selection history + for i, _ in enumerate(all_sel_faces): + if (i == 0) or (i % 2 == 0): + continue + sel_faces = [all_sel_faces[i - 1], all_sel_faces[i]] + active_face = all_sel_faces[i] + + # parse all faces according to selection history + active_face_nor = active_face.normal.copy() + if invert_normals: + active_face_nor.negate() + all_sorted_faces = main_parse(ops_obj, uv_layer, sel_faces, + active_face, active_face_nor) + + if all_sorted_faces: + # check amount of copied/pasted faces + if len(all_sorted_faces) != len(src_faces): + ops_obj.report({'WARNING'}, + "Mesh has different amount of faces") + return -1 + + for j, face_data in enumerate(all_sorted_faces.values()): + copied_data = src_faces[j] + + # check amount of copied/pasted verts + if len(copied_data[0]) != len(face_data[2]): + bpy.ops.mesh.select_all(action='DESELECT') + # select problematic face + list(all_sorted_faces.keys())[j].select = True + ops_obj.report({'WARNING'}, + "Face have different amount of vertices") + return 0 + + for k, (edge, uvloop) in enumerate(zip(face_data[1], + face_data[2])): + uvloop.uv = copied_data[0][k] + uvloop.pin_uv = copied_data[1][k] + if copy_seams: + edge.seam = copied_data[2][k] + else: + return -1 + + return 0 diff --git a/uv_magic_uv/impl/uvw_impl.py b/uv_magic_uv/impl/uvw_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..e815f54fcc95ef89959d2a66aaa5e40ade1f81fa --- /dev/null +++ b/uv_magic_uv/impl/uvw_impl.py @@ -0,0 +1,154 @@ +# <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__ = "imdjs, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + + +from math import sin, cos, pi + +from mathutils import Vector + + +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 + + # only 'VIEW_3D' space is allowed to execute + for space in context.area.spaces: + if space.type == 'VIEW_3D': + break + else: + return False + + return True + + +def get_uv_layer(ops_obj, bm, assign_uvmap): + # get UV layer + if not bm.loops.layers.uv: + if assign_uvmap: + bm.loops.layers.uv.new() + else: + ops_obj.report({'WARNING'}, + "Object must have more than one UV map") + return None + uv_layer = bm.loops.layers.uv.verify() + + return uv_layer + + +def apply_box_map(bm, uv_layer, size, offset, rotation, tex_aspect): + scale = 1.0 / size + + sx = 1.0 * scale + sy = 1.0 * scale + sz = 1.0 * scale + ofx = offset[0] + ofy = offset[1] + ofz = offset[2] + rx = rotation[0] * pi / 180.0 + ry = rotation[1] * pi / 180.0 + rz = rotation[2] * pi / 180.0 + aspect = tex_aspect + + sel_faces = [f for f in bm.faces if f.select] + + # update UV coordinate + for f in sel_faces: + n = f.normal + for l in f.loops: + co = l.vert.co + x = co.x * sx + y = co.y * sy + z = co.z * sz + + # X-plane + if abs(n[0]) >= abs(n[1]) and abs(n[0]) >= abs(n[2]): + if n[0] >= 0.0: + u = (y - ofy) * cos(rx) + (z - ofz) * sin(rx) + v = -(y * aspect - ofy) * sin(rx) + \ + (z * aspect - ofz) * cos(rx) + else: + u = -(y - ofy) * cos(rx) + (z - ofz) * sin(rx) + v = (y * aspect - ofy) * sin(rx) + \ + (z * aspect - ofz) * cos(rx) + # Y-plane + elif abs(n[1]) >= abs(n[0]) and abs(n[1]) >= abs(n[2]): + if n[1] >= 0.0: + u = -(x - ofx) * cos(ry) + (z - ofz) * sin(ry) + v = (x * aspect - ofx) * sin(ry) + \ + (z * aspect - ofz) * cos(ry) + else: + u = (x - ofx) * cos(ry) + (z - ofz) * sin(ry) + v = -(x * aspect - ofx) * sin(ry) + \ + (z * aspect - ofz) * cos(ry) + # Z-plane + else: + if n[2] >= 0.0: + u = (x - ofx) * cos(rz) + (y - ofy) * sin(rz) + v = -(x * aspect - ofx) * sin(rz) + \ + (y * aspect - ofy) * cos(rz) + else: + u = -(x - ofx) * cos(rz) - (y + ofy) * sin(rz) + v = -(x * aspect + ofx) * sin(rz) + \ + (y * aspect - ofy) * cos(rz) + + l[uv_layer].uv = Vector((u, v)) + + +def apply_planer_map(bm, uv_layer, size, offset, rotation, tex_aspect): + scale = 1.0 / size + + sx = 1.0 * scale + sy = 1.0 * scale + ofx = offset[0] + ofy = offset[1] + rz = rotation * pi / 180.0 + aspect = tex_aspect + + sel_faces = [f for f in bm.faces if f.select] + + # calculate average of normal + n_ave = Vector((0.0, 0.0, 0.0)) + for f in sel_faces: + n_ave = n_ave + f.normal + q = n_ave.rotation_difference(Vector((0.0, 0.0, 1.0))) + + # update UV coordinate + for f in sel_faces: + for l in f.loops: + co = q @ l.vert.co + x = co.x * sx + y = co.y * sy + + u = x * cos(rz) - y * sin(rz) + ofx + v = -x * aspect * sin(rz) - y * aspect * cos(rz) + ofy + + l[uv_layer].uv = Vector((u, v)) diff --git a/uv_magic_uv/legacy/__init__.py b/uv_magic_uv/legacy/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..794d02bc0c0c9f380584bc1f290d13395ed65e1b --- /dev/null +++ b/uv_magic_uv/legacy/__init__.py @@ -0,0 +1,38 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +if "bpy" in locals(): + import importlib + importlib.reload(op) + importlib.reload(ui) + importlib.reload(properites) + importlib.reload(preferences) +else: + from . import op + from . import ui + from . import properites + from . import preferences + +import bpy diff --git a/uv_magic_uv/legacy/op/__init__.py b/uv_magic_uv/legacy/op/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9535b76d094b148741f5563ad34f356f810cad5a --- /dev/null +++ b/uv_magic_uv/legacy/op/__init__.py @@ -0,0 +1,74 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +if "bpy" in locals(): + import importlib + importlib.reload(align_uv) + importlib.reload(align_uv_cursor) + importlib.reload(copy_paste_uv) + importlib.reload(copy_paste_uv_object) + importlib.reload(copy_paste_uv_uvedit) + importlib.reload(flip_rotate_uv) + importlib.reload(mirror_uv) + importlib.reload(move_uv) + importlib.reload(pack_uv) + importlib.reload(preserve_uv_aspect) + importlib.reload(select_uv) + importlib.reload(smooth_uv) + importlib.reload(texture_lock) + importlib.reload(texture_projection) + importlib.reload(texture_wrap) + importlib.reload(transfer_uv) + importlib.reload(unwrap_constraint) + importlib.reload(uv_bounding_box) + importlib.reload(uv_inspection) + importlib.reload(uv_sculpt) + importlib.reload(uvw) + importlib.reload(world_scale_uv) +else: + from . import align_uv + from . import align_uv_cursor + from . import copy_paste_uv + from . import copy_paste_uv_object + from . import copy_paste_uv_uvedit + from . import flip_rotate_uv + from . import mirror_uv + from . import move_uv + from . import pack_uv + from . import preserve_uv_aspect + from . import select_uv + from . import smooth_uv + from . import texture_lock + from . import texture_projection + from . import texture_wrap + from . import transfer_uv + from . import unwrap_constraint + from . import uv_bounding_box + from . import uv_inspection + from . import uv_sculpt + from . import uvw + from . import world_scale_uv + +import bpy diff --git a/uv_magic_uv/op/align_uv.py b/uv_magic_uv/legacy/op/align_uv.py similarity index 98% rename from uv_magic_uv/op/align_uv.py rename to uv_magic_uv/legacy/op/align_uv.py index 90168a56a0126f2e21403fe9bbb4837201d2a767..9d0ff5f40b9764e389594801abeb6b69c457bd8f 100644 --- a/uv_magic_uv/op/align_uv.py +++ b/uv_magic_uv/legacy/op/align_uv.py @@ -31,14 +31,16 @@ import bmesh from mathutils import Vector from bpy.props import EnumProperty, BoolProperty, FloatProperty -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'OperatorCircle', - 'OperatorStraighten', - 'OperatorAxis', + 'MUV_OT_AlignUV_Circle', + 'MUV_OT_AlignUV_Straighten', + 'MUV_OT_AlignUV_Axis', ] @@ -65,59 +67,10 @@ def is_valid_context(context): return True -# get sum vertex length of loop sequences -def get_loop_vert_len(loops): - length = 0 - for l1, l2 in zip(loops[:-1], loops[1:]): - diff = l2.vert.co - l1.vert.co - length = length + abs(diff.length) - - return length - - -# get sum uv length of loop sequences -def get_loop_uv_len(loops, uv_layer): - length = 0 - for l1, l2 in zip(loops[:-1], loops[1:]): - diff = l2[uv_layer].uv - l1[uv_layer].uv - length = length + abs(diff.length) - - return length - - -# get center/radius of circle by 3 vertices -def get_circle(v): - alpha = atan2((v[0].y - v[1].y), (v[0].x - v[1].x)) + math.pi / 2 - beta = atan2((v[1].y - v[2].y), (v[1].x - v[2].x)) + math.pi / 2 - ex = (v[0].x + v[1].x) / 2.0 - ey = (v[0].y + v[1].y) / 2.0 - fx = (v[1].x + v[2].x) / 2.0 - fy = (v[1].y + v[2].y) / 2.0 - cx = (ey - fy - ex * tan(alpha) + fx * tan(beta)) / \ - (tan(beta) - tan(alpha)) - cy = ey - (ex - cx) * tan(alpha) - center = Vector((cx, cy)) - - r = v[0] - center - radian = r.length - - return center, radian - - -# get position on circle with same arc length -def calc_v_on_circle(v, center, radius): - base = v[0] - theta = atan2(base.y - center.y, base.x - center.x) - new_v = [] - for i in range(len(v)): - angle = theta + i * 2 * math.pi / len(v) - new_v.append(Vector((center.x + radius * sin(angle), - center.y + radius * cos(angle)))) - - return new_v - - +@PropertyClassRegistry(legacy=True) class Properties: + idname = "align_uv" + @classmethod def init_props(cls, scene): scene.muv_align_uv_enabled = BoolProperty( @@ -176,7 +129,60 @@ class Properties: del scene.muv_align_uv_location -class OperatorCircle(bpy.types.Operator): +# get sum vertex length of loop sequences +def get_loop_vert_len(loops): + length = 0 + for l1, l2 in zip(loops[:-1], loops[1:]): + diff = l2.vert.co - l1.vert.co + length = length + abs(diff.length) + + return length + + +# get sum uv length of loop sequences +def get_loop_uv_len(loops, uv_layer): + length = 0 + for l1, l2 in zip(loops[:-1], loops[1:]): + diff = l2[uv_layer].uv - l1[uv_layer].uv + length = length + abs(diff.length) + + return length + + +# get center/radius of circle by 3 vertices +def get_circle(v): + alpha = atan2((v[0].y - v[1].y), (v[0].x - v[1].x)) + math.pi / 2 + beta = atan2((v[1].y - v[2].y), (v[1].x - v[2].x)) + math.pi / 2 + ex = (v[0].x + v[1].x) / 2.0 + ey = (v[0].y + v[1].y) / 2.0 + fx = (v[1].x + v[2].x) / 2.0 + fy = (v[1].y + v[2].y) / 2.0 + cx = (ey - fy - ex * tan(alpha) + fx * tan(beta)) / \ + (tan(beta) - tan(alpha)) + cy = ey - (ex - cx) * tan(alpha) + center = Vector((cx, cy)) + + r = v[0] - center + radian = r.length + + return center, radian + + +# get position on circle with same arc length +def calc_v_on_circle(v, center, radius): + base = v[0] + theta = atan2(base.y - center.y, base.x - center.x) + new_v = [] + for i in range(len(v)): + angle = theta + i * 2 * math.pi / len(v) + new_v.append(Vector((center.x + radius * sin(angle), + center.y + radius * cos(angle)))) + + return new_v + + +@BlClassRegistry(legacy=True) +class MUV_OT_AlignUV_Circle(bpy.types.Operator): bl_idname = "uv.muv_align_uv_operator_circle" bl_label = "Align UV (Circle)" @@ -437,7 +443,8 @@ def get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx): return int((vidx + 1) / 2) * v_uv / (len(hseq) / 2) -class OperatorStraighten(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_AlignUV_Straighten(bpy.types.Operator): bl_idname = "uv.muv_align_uv_operator_straighten" bl_label = "Align UV (Straighten)" @@ -587,7 +594,8 @@ class OperatorStraighten(bpy.types.Operator): return {'FINISHED'} -class OperatorAxis(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_AlignUV_Axis(bpy.types.Operator): bl_idname = "uv.muv_align_uv_operator_axis" bl_label = "Align UV (XY-Axis)" diff --git a/uv_magic_uv/op/align_uv_cursor.py b/uv_magic_uv/legacy/op/align_uv_cursor.py similarity index 96% rename from uv_magic_uv/op/align_uv_cursor.py rename to uv_magic_uv/legacy/op/align_uv_cursor.py index d787bde941be21b16d4761e16a9b05e5b0626d80..ec3e703609ba9ab2180dcb15a2676ae97ebcd471 100644 --- a/uv_magic_uv/op/align_uv_cursor.py +++ b/uv_magic_uv/legacy/op/align_uv_cursor.py @@ -28,12 +28,14 @@ from mathutils import Vector from bpy.props import EnumProperty, BoolProperty, FloatVectorProperty import bmesh -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'Operator', + 'MUV_OT_AlignUVCursor', ] @@ -50,7 +52,10 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "align_uv_cursor" + @classmethod def init_props(cls, scene): def auvc_get_cursor_loc(self): @@ -121,7 +126,8 @@ class Properties: del scene.muv_uv_cursor_location_enabled -class Operator(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_AlignUVCursor(bpy.types.Operator): bl_idname = "uv.muv_align_uv_cursor_operator" bl_label = "Align UV Cursor" diff --git a/uv_magic_uv/legacy/op/copy_paste_uv.py b/uv_magic_uv/legacy/op/copy_paste_uv.py new file mode 100644 index 0000000000000000000000000000000000000000..a8aef017fb2e3c587be00cba29ea169b5775ddff --- /dev/null +++ b/uv_magic_uv/legacy/op/copy_paste_uv.py @@ -0,0 +1,531 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>, Jace Priester" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + + +import bmesh +import bpy.utils +from bpy.props import ( + StringProperty, + BoolProperty, + IntProperty, + EnumProperty, +) + +from ...impl import copy_paste_uv_impl as impl +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry + +__all__ = [ + 'Properties', + 'MUV_OT_CopyPasteUV_CopyUV', + 'MUV_MT_CopyPasteUV_CopyUV', + 'MUV_OT_CopyPasteUV_PasteUV', + 'MUV_MT_CopyPasteUV_PasteUV', + 'MUV_OT_CopyPasteUV_SelSeqCopyUV', + 'MUV_MT_CopyPasteUV_SelSeqCopyUV', + 'MUV_OT_CopyPasteUV_SelSeqPasteUV', + 'MUV_MT_CopyPasteUV_SelSeqPasteUV', +] + + +@PropertyClassRegistry(legacy=True) +class Properties: + idname = "copy_paste_uv" + + @classmethod + def init_props(cls, scene): + class Props(): + src_info = None + + scene.muv_props.copy_paste_uv = Props() + scene.muv_props.copy_paste_uv_selseq = Props() + + scene.muv_copy_paste_uv_enabled = BoolProperty( + name="Copy/Paste UV Enabled", + description="Copy/Paste UV is enabled", + default=False + ) + scene.muv_copy_paste_uv_copy_seams = BoolProperty( + name="Seams", + description="Copy Seams", + default=True + ) + scene.muv_copy_paste_uv_mode = EnumProperty( + items=[ + ('DEFAULT', "Default", "Default Mode"), + ('SEL_SEQ', "Selection Sequence", "Selection Sequence Mode") + ], + name="Copy/Paste UV Mode", + description="Copy/Paste UV Mode", + default='DEFAULT' + ) + scene.muv_copy_paste_uv_strategy = EnumProperty( + name="Strategy", + description="Paste Strategy", + items=[ + ('N_N', 'N:N', 'Number of faces must be equal to source'), + ('N_M', 'N:M', 'Number of faces must not be equal to source') + ], + default='N_M' + ) + + @classmethod + def del_props(cls, scene): + del scene.muv_props.copy_paste_uv + del scene.muv_props.copy_paste_uv_selseq + del scene.muv_copy_paste_uv_enabled + del scene.muv_copy_paste_uv_copy_seams + del scene.muv_copy_paste_uv_mode + del scene.muv_copy_paste_uv_strategy + + +@BlClassRegistry(legacy=True) +class MUV_OT_CopyPasteUV_CopyUV(bpy.types.Operator): + """ + Operation class: Copy UV coordinate + """ + + bl_idname = "uv.muv_copy_paste_uv_operator_copy_uv" + bl_label = "Copy UV" + bl_description = "Copy UV coordinate" + bl_options = {'REGISTER', 'UNDO'} + + uv_map = StringProperty(default="__default", options={'HIDDEN'}) + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + return impl.is_valid_context(context) + + def execute(self, context): + props = context.scene.muv_props.copy_paste_uv + obj = context.active_object + bm = common.create_bmesh(obj) + + # get UV layer + uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map) + if not uv_layers: + return {'CANCELLED'} + + # get selected face + src_info = impl.get_src_face_info(self, bm, uv_layers, True) + if src_info is None: + return {'CANCELLED'} + props.src_info = src_info + + face_count = len(props.src_info[list(props.src_info.keys())[0]]) + self.report({'INFO'}, "{} face(s) are copied".format(face_count)) + + return {'FINISHED'} + + +@BlClassRegistry(legacy=True) +class MUV_MT_CopyPasteUV_CopyUV(bpy.types.Menu): + """ + Menu class: Copy UV coordinate + """ + + bl_idname = "uv.muv_copy_paste_uv_menu_copy_uv" + bl_label = "Copy UV (Menu)" + bl_description = "Menu of Copy UV coordinate" + + @classmethod + def poll(cls, context): + return impl.is_valid_context(context) + + def draw(self, context): + layout = self.layout + # create sub menu + obj = context.active_object + bm = common.create_bmesh(obj) + uv_maps = bm.loops.layers.uv.keys() + + ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname, + text="[Default]") + ops.uv_map = "__default" + + ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname, + text="[All]") + ops.uv_map = "__all" + + for m in uv_maps: + ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname, text=m) + ops.uv_map = m + + +@BlClassRegistry(legacy=True) +class MUV_OT_CopyPasteUV_PasteUV(bpy.types.Operator): + """ + Operation class: Paste UV coordinate + """ + + bl_idname = "uv.muv_copy_paste_uv_operator_paste_uv" + bl_label = "Paste UV" + bl_description = "Paste UV coordinate" + bl_options = {'REGISTER', 'UNDO'} + + uv_map = StringProperty(default="__default", options={'HIDDEN'}) + strategy = EnumProperty( + name="Strategy", + description="Paste Strategy", + items=[ + ('N_N', 'N:N', 'Number of faces must be equal to source'), + ('N_M', 'N:M', 'Number of faces must not be equal to source') + ], + default="N_M" + ) + flip_copied_uv = BoolProperty( + name="Flip Copied UV", + description="Flip Copied UV...", + default=False + ) + rotate_copied_uv = IntProperty( + default=0, + name="Rotate Copied UV", + min=0, + max=30 + ) + copy_seams = BoolProperty( + name="Seams", + description="Copy Seams", + default=True + ) + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + sc = context.scene + props = sc.muv_props.copy_paste_uv + if not props.src_info: + return False + return impl.is_valid_context(context) + + def execute(self, context): + props = context.scene.muv_props.copy_paste_uv + if not props.src_info: + self.report({'WARNING'}, "Need copy UV at first") + return {'CANCELLED'} + obj = context.active_object + bm = common.create_bmesh(obj) + + # get UV layer + uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info, + self.uv_map) + if not uv_layers: + return {'CANCELLED'} + + # get selected face + dest_info = impl.get_dest_face_info(self, bm, uv_layers, + props.src_info, self.strategy, + True) + if dest_info is None: + return {'CANCELLED'} + + # paste + ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers, + self.strategy, self.flip_copied_uv, + self.rotate_copied_uv, self.copy_seams) + if ret: + return {'CANCELLED'} + + face_count = len(props.src_info[list(dest_info.keys())[0]]) + self.report({'INFO'}, "{} face(s) are pasted".format(face_count)) + + bmesh.update_edit_mesh(obj.data) + if self.copy_seams is True: + obj.data.show_edge_seams = True + + return {'FINISHED'} + + +@BlClassRegistry(legacy=True) +class MUV_MT_CopyPasteUV_PasteUV(bpy.types.Menu): + """ + Menu class: Paste UV coordinate + """ + + bl_idname = "uv.muv_copy_paste_uv_menu_paste_uv" + bl_label = "Paste UV (Menu)" + bl_description = "Menu of Paste UV coordinate" + + @classmethod + def poll(cls, context): + sc = context.scene + props = sc.muv_props.copy_paste_uv + if not props.src_info: + return False + return impl.is_valid_context(context) + + def draw(self, context): + sc = context.scene + layout = self.layout + # create sub menu + obj = context.active_object + bm = common.create_bmesh(obj) + uv_maps = bm.loops.layers.uv.keys() + + ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname, + text="[Default]") + ops.uv_map = "__default" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy + + ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname, + text="[New]") + ops.uv_map = "__new" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy + + ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname, + text="[All]") + ops.uv_map = "__all" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy + + for m in uv_maps: + ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname, text=m) + ops.uv_map = m + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy + + +@BlClassRegistry(legacy=True) +class MUV_OT_CopyPasteUV_SelSeqCopyUV(bpy.types.Operator): + """ + Operation class: Copy UV coordinate by selection sequence + """ + + bl_idname = "uv.muv_copy_paste_uv_operator_selseq_copy_uv" + bl_label = "Copy UV (Selection Sequence)" + bl_description = "Copy UV data by selection sequence" + bl_options = {'REGISTER', 'UNDO'} + + uv_map = StringProperty(default="__default", options={'HIDDEN'}) + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + return impl.is_valid_context(context) + + def execute(self, context): + props = context.scene.muv_props.copy_paste_uv_selseq + obj = context.active_object + bm = common.create_bmesh(obj) + + # get UV layer + uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map) + if not uv_layers: + return {'CANCELLED'} + + # get selected face + src_info = impl.get_select_history_src_face_info(self, bm, uv_layers) + if src_info is None: + return {'CANCELLED'} + props.src_info = src_info + + face_count = len(props.src_info[list(props.src_info.keys())[0]]) + self.report({'INFO'}, "{} face(s) are selected".format(face_count)) + + return {'FINISHED'} + + +@BlClassRegistry(legacy=True) +class MUV_MT_CopyPasteUV_SelSeqCopyUV(bpy.types.Menu): + """ + Menu class: Copy UV coordinate by selection sequence + """ + + bl_idname = "uv.muv_copy_paste_uv_menu_selseq_copy_uv" + bl_label = "Copy UV (Selection Sequence) (Menu)" + bl_description = "Menu of Copy UV coordinate by selection sequence" + + @classmethod + def poll(cls, context): + return impl.is_valid_context(context) + + def draw(self, context): + layout = self.layout + obj = context.active_object + bm = common.create_bmesh(obj) + uv_maps = bm.loops.layers.uv.keys() + + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname, + text="[Default]") + ops.uv_map = "__default" + + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname, + text="[All]") + ops.uv_map = "__all" + + for m in uv_maps: + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname, + text=m) + ops.uv_map = m + + +@BlClassRegistry(legacy=True) +class MUV_OT_CopyPasteUV_SelSeqPasteUV(bpy.types.Operator): + """ + Operation class: Paste UV coordinate by selection sequence + """ + + bl_idname = "uv.muv_copy_paste_uv_operator_selseq_paste_uv" + bl_label = "Paste UV (Selection Sequence)" + bl_description = "Paste UV coordinate by selection sequence" + bl_options = {'REGISTER', 'UNDO'} + + uv_map = StringProperty(default="__default", options={'HIDDEN'}) + strategy = EnumProperty( + name="Strategy", + description="Paste Strategy", + items=[ + ('N_N', 'N:N', 'Number of faces must be equal to source'), + ('N_M', 'N:M', 'Number of faces must not be equal to source') + ], + default="N_M" + ) + flip_copied_uv = BoolProperty( + name="Flip Copied UV", + description="Flip Copied UV...", + default=False + ) + rotate_copied_uv = IntProperty( + default=0, + name="Rotate Copied UV", + min=0, + max=30 + ) + copy_seams = BoolProperty( + name="Seams", + description="Copy Seams", + default=True + ) + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + sc = context.scene + props = sc.muv_props.copy_paste_uv_selseq + if not props.src_info: + return False + return impl.is_valid_context(context) + + def execute(self, context): + props = context.scene.muv_props.copy_paste_uv_selseq + if not props.src_info: + self.report({'WARNING'}, "Need copy UV at first") + return {'CANCELLED'} + obj = context.active_object + bm = common.create_bmesh(obj) + + # get UV layer + uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info, + self.uv_map) + if not uv_layers: + return {'CANCELLED'} + + # get selected face + dest_info = impl.get_select_history_dest_face_info(self, bm, uv_layers, + props.src_info, + self.strategy) + if dest_info is None: + return {'CANCELLED'} + + # paste + ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers, + self.strategy, self.flip_copied_uv, + self.rotate_copied_uv, self.copy_seams) + if ret: + return {'CANCELLED'} + + face_count = len(props.src_info[list(dest_info.keys())[0]]) + self.report({'INFO'}, "{} face(s) are pasted".format(face_count)) + + bmesh.update_edit_mesh(obj.data) + if self.copy_seams is True: + obj.data.show_edge_seams = True + + return {'FINISHED'} + + +@BlClassRegistry(legacy=True) +class MUV_MT_CopyPasteUV_SelSeqPasteUV(bpy.types.Menu): + """ + Menu class: Paste UV coordinate by selection sequence + """ + + bl_idname = "uv.muv_copy_paste_uv_menu_selseq_paste_uv" + bl_label = "Paste UV (Selection Sequence) (Menu)" + bl_description = "Menu of Paste UV coordinate by selection sequence" + + @classmethod + def poll(cls, context): + sc = context.scene + props = sc.muv_props.copy_paste_uv_selseq + if not props.src_uvs or not props.src_pin_uvs: + return False + return impl.is_valid_context(context) + + def draw(self, context): + sc = context.scene + layout = self.layout + # create sub menu + obj = context.active_object + bm = common.create_bmesh(obj) + uv_maps = bm.loops.layers.uv.keys() + + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname, + text="[Default]") + ops.uv_map = "__default" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy + + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname, + text="[New]") + ops.uv_map = "__new" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy + + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname, + text="[All]") + ops.uv_map = "__all" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy + + for m in uv_maps: + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname, + text=m) + ops.uv_map = m + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy diff --git a/uv_magic_uv/legacy/op/copy_paste_uv_object.py b/uv_magic_uv/legacy/op/copy_paste_uv_object.py new file mode 100644 index 0000000000000000000000000000000000000000..e09b003b91a96f5ed5fdf5bdcf7499f7f79d8ee2 --- /dev/null +++ b/uv_magic_uv/legacy/op/copy_paste_uv_object.py @@ -0,0 +1,298 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bmesh +import bpy +from bpy.props import ( + StringProperty, + BoolProperty, +) + +from ...impl import copy_paste_uv_impl as impl +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry + +__all__ = [ + 'Properties', + 'MUV_OT_CopyPasteUVObject_CopyUV', + 'MUV_MT_CopyPasteUVObject_CopyUV', + 'MUV_OT_CopyPasteUVObject_PasteUV', + 'MUV_MT_CopyPasteUVObject_PasteUV', +] + + +def is_valid_context(context): + obj = context.object + + # only object mode is allowed to execute + if obj is None: + return False + if obj.type != 'MESH': + return False + if context.object.mode != 'OBJECT': + return False + + # only 'VIEW_3D' space is allowed to execute + for space in context.area.spaces: + if space.type == 'VIEW_3D': + break + else: + return False + + return True + + +@PropertyClassRegistry(legacy=True) +class Properties: + idname = "copy_paste_uv_object" + + @classmethod + def init_props(cls, scene): + class Props(): + src_info = None + + scene.muv_props.copy_paste_uv_object = Props() + + scene.muv_copy_paste_uv_object_copy_seams = BoolProperty( + name="Seams", + description="Copy Seams", + default=True + ) + + @classmethod + def del_props(cls, scene): + del scene.muv_props.copy_paste_uv_object + del scene.muv_copy_paste_uv_object_copy_seams + + +def memorize_view_3d_mode(fn): + def __memorize_view_3d_mode(self, context): + mode_orig = bpy.context.object.mode + result = fn(self, context) + bpy.ops.object.mode_set(mode=mode_orig) + return result + return __memorize_view_3d_mode + + +@BlClassRegistry(legacy=True) +class MUV_OT_CopyPasteUVObject_CopyUV(bpy.types.Operator): + """ + Operation class: Copy UV coordinate among objects + """ + + bl_idname = "object.muv_copy_paste_uv_object_operator_copy_uv" + bl_label = "Copy UV (Among Objects)" + bl_description = "Copy UV coordinate (Among Objects)" + bl_options = {'REGISTER', 'UNDO'} + + uv_map = StringProperty(default="__default", options={'HIDDEN'}) + + @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) + + @memorize_view_3d_mode + def execute(self, context): + props = context.scene.muv_props.copy_paste_uv_object + bpy.ops.object.mode_set(mode='EDIT') + obj = context.active_object + bm = common.create_bmesh(obj) + + # get UV layer + uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map) + if not uv_layers: + return {'CANCELLED'} + + # get selected face + src_info = impl.get_src_face_info(self, bm, uv_layers) + if src_info is None: + return {'CANCELLED'} + props.src_info = src_info + + self.report({'INFO'}, + "{}'s UV coordinates are copied".format(obj.name)) + + return {'FINISHED'} + + +@BlClassRegistry(legacy=True) +class MUV_MT_CopyPasteUVObject_CopyUV(bpy.types.Menu): + """ + Menu class: Copy UV coordinate among objects + """ + + bl_idname = "object.muv_copy_paste_uv_object_menu_copy_uv" + bl_label = "Copy UV (Among Objects) (Menu)" + bl_description = "Menu of Copy UV coordinate (Among Objects)" + + @classmethod + def poll(cls, context): + return is_valid_context(context) + + def draw(self, _): + layout = self.layout + # create sub menu + uv_maps = bpy.context.active_object.data.uv_textures.keys() + + ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname, + text="[Default]") + ops.uv_map = "__default" + + ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname, + text="[All]") + ops.uv_map = "__all" + + for m in uv_maps: + ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname, + text=m) + ops.uv_map = m + + +@BlClassRegistry(legacy=True) +class MUV_OT_CopyPasteUVObject_PasteUV(bpy.types.Operator): + """ + Operation class: Paste UV coordinate among objects + """ + + bl_idname = "object.muv_copy_paste_uv_object_operator_paste_uv" + bl_label = "Paste UV (Among Objects)" + bl_description = "Paste UV coordinate (Among Objects)" + bl_options = {'REGISTER', 'UNDO'} + + uv_map = StringProperty(default="__default", options={'HIDDEN'}) + copy_seams = BoolProperty( + name="Seams", + description="Copy Seams", + default=True + ) + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + sc = context.scene + props = sc.muv_props.copy_paste_uv_object + if not props.src_info: + return False + return is_valid_context(context) + + @memorize_view_3d_mode + def execute(self, context): + props = context.scene.muv_props.copy_paste_uv_object + if not props.src_info: + self.report({'WARNING'}, "Need copy UV at first") + return {'CANCELLED'} + + for o in bpy.data.objects: + if not hasattr(o.data, "uv_textures") or not o.select: + continue + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.context.scene.objects.active = o + bpy.ops.object.mode_set(mode='EDIT') + + obj = context.active_object + bm = common.create_bmesh(obj) + + # get UV layer + uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info, + self.uv_map) + if not uv_layers: + return {'CANCELLED'} + + # get selected face + dest_info = impl.get_dest_face_info(self, bm, uv_layers, + props.src_info, 'N_N') + if dest_info is None: + return {'CANCELLED'} + + # paste + ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers, + 'N_N', 0, 0, self.copy_seams) + if ret: + return {'CANCELLED'} + + bmesh.update_edit_mesh(obj.data) + if self.copy_seams is True: + obj.data.show_edge_seams = True + + self.report( + {'INFO'}, "{}'s UV coordinates are pasted".format(obj.name)) + + return {'FINISHED'} + + +@BlClassRegistry(legacy=True) +class MUV_MT_CopyPasteUVObject_PasteUV(bpy.types.Menu): + """ + Menu class: Paste UV coordinate among objects + """ + + bl_idname = "object.muv_copy_paste_uv_object_menu_paste_uv" + bl_label = "Paste UV (Among Objects) (Menu)" + bl_description = "Menu of Paste UV coordinate (Among Objects)" + + @classmethod + def poll(cls, context): + sc = context.scene + props = sc.muv_props.copy_paste_uv_object + if not props.src_info: + return False + return is_valid_context(context) + + def draw(self, context): + sc = context.scene + layout = self.layout + # create sub menu + uv_maps = [] + for obj in bpy.data.objects: + if hasattr(obj.data, "uv_textures") and obj.select: + uv_maps.extend(obj.data.uv_textures.keys()) + + ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname, + text="[Default]") + ops.uv_map = "__default" + ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams + + ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname, + text="[New]") + ops.uv_map = "__new" + ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams + + ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname, + text="[All]") + ops.uv_map = "__all" + ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams + + for m in uv_maps: + ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname, + text=m) + ops.uv_map = m + ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams diff --git a/uv_magic_uv/legacy/op/copy_paste_uv_uvedit.py b/uv_magic_uv/legacy/op/copy_paste_uv_uvedit.py new file mode 100644 index 0000000000000000000000000000000000000000..bb72d42a1b83708e2608e2db810a4c89bca103c5 --- /dev/null +++ b/uv_magic_uv/legacy/op/copy_paste_uv_uvedit.py @@ -0,0 +1,97 @@ +# <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__ = "imdjs, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy + +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry +from ...impl import copy_paste_uv_uvedit_impl as impl + + +__all__ = [ + 'Properties', + 'MUV_OT_CopyPasteUVUVEdit_CopyUV', + 'MUV_OT_CopyPasteUVUVEdit_PasteUV', +] + + +@PropertyClassRegistry(legacy=True) +class Properties: + idname = "copy_paste_uv_uvedit" + + @classmethod + def init_props(cls, scene): + class Props(): + src_uvs = None + + scene.muv_props.copy_paste_uv_uvedit = Props() + + @classmethod + def del_props(cls, scene): + del scene.muv_props.copy_paste_uv_uvedit + + +@BlClassRegistry(legacy=True) +class MUV_OT_CopyPasteUVUVEdit_CopyUV(bpy.types.Operator): + """ + Operation class: Copy UV coordinate on UV/Image Editor + """ + + bl_idname = "uv.muv_copy_paste_uv_uvedit_operator_copy_uv" + bl_label = "Copy UV (UV/Image Editor)" + bl_description = "Copy UV coordinate (only selected in UV/Image Editor)" + bl_options = {'REGISTER', 'UNDO'} + + def __init__(self): + self.__impl = impl.CopyUVImpl() + + @classmethod + def poll(cls, context): + return impl.CopyUVImpl.poll(context) + + def execute(self, context): + return self.__impl.execute(self, context) + + +@BlClassRegistry(legacy=True) +class MUV_OT_CopyPasteUVUVEdit_PasteUV(bpy.types.Operator): + """ + Operation class: Paste UV coordinate on UV/Image Editor + """ + + bl_idname = "uv.muv_copy_paste_uv_uvedit_operator_paste_uv" + bl_label = "Paste UV (UV/Image Editor)" + bl_description = "Paste UV coordinate (only selected in UV/Image Editor)" + bl_options = {'REGISTER', 'UNDO'} + + def __init__(self): + self.__impl = impl.PasteUVImpl() + + @classmethod + def poll(cls, context): + return impl.PasteUVImpl.poll(context) + + def execute(self, context): + return self.__impl.execute(self, context) diff --git a/uv_magic_uv/legacy/op/flip_rotate_uv.py b/uv_magic_uv/legacy/op/flip_rotate_uv.py new file mode 100644 index 0000000000000000000000000000000000000000..d94e4808c269ce75460fa4686a93ce93099ff143 --- /dev/null +++ b/uv_magic_uv/legacy/op/flip_rotate_uv.py @@ -0,0 +1,132 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy +import bmesh +from bpy.props import ( + BoolProperty, + IntProperty, +) + +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry +from ...impl import flip_rotate_impl as impl + +__all__ = [ + 'Properties', + 'MUV_OT_FlipRotate', +] + + +@PropertyClassRegistry(legacy=True) +class Properties: + idname = "flip_rotate_uv" + + @classmethod + def init_props(cls, scene): + scene.muv_flip_rotate_uv_enabled = BoolProperty( + name="Flip/Rotate UV Enabled", + description="Flip/Rotate UV is enabled", + default=False + ) + scene.muv_flip_rotate_uv_seams = BoolProperty( + name="Seams", + description="Seams", + default=True + ) + + @classmethod + def del_props(cls, scene): + del scene.muv_flip_rotate_uv_enabled + del scene.muv_flip_rotate_uv_seams + + +@BlClassRegistry(legacy=True) +class MUV_OT_FlipRotate(bpy.types.Operator): + """ + Operation class: Flip and Rotate UV coordinate + """ + + bl_idname = "uv.muv_flip_rotate_uv_operator" + bl_label = "Flip/Rotate UV" + bl_description = "Flip/Rotate UV coordinate" + bl_options = {'REGISTER', 'UNDO'} + + flip = BoolProperty( + name="Flip UV", + description="Flip UV...", + default=False + ) + rotate = IntProperty( + default=0, + name="Rotate UV", + min=0, + max=30 + ) + seams = BoolProperty( + name="Seams", + description="Seams", + default=True + ) + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + return impl.is_valid_context(context) + + def execute(self, context): + self.report({'INFO'}, "Flip/Rotate UV") + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + # get UV layer + uv_layer = impl.get_uv_layer(self, bm) + if not uv_layer: + return {'CANCELLED'} + + # get selected face + src_info = impl.get_src_face_info(self, bm, [uv_layer], True) + if not src_info: + return {'CANCELLED'} + + face_count = len(src_info[list(src_info.keys())[0]]) + self.report({'INFO'}, "{} face(s) are selected".format(face_count)) + + # paste + ret = impl.paste_uv(self, bm, src_info, src_info, [uv_layer], 'N_N', + self.flip, self.rotate, self.seams) + if ret: + return {'CANCELLED'} + + bmesh.update_edit_mesh(obj.data) + if self.seams is True: + obj.data.show_edge_seams = True + + return {'FINISHED'} diff --git a/uv_magic_uv/legacy/op/mirror_uv.py b/uv_magic_uv/legacy/op/mirror_uv.py new file mode 100644 index 0000000000000000000000000000000000000000..e869e5e8ab964f5ef9c52347643398f0315713a2 --- /dev/null +++ b/uv_magic_uv/legacy/op/mirror_uv.py @@ -0,0 +1,110 @@ +# <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__ = "Keith (Wahooney) Boshoff, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy +from bpy.props import ( + EnumProperty, + FloatProperty, + BoolProperty, +) + +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry +from ...impl import mirror_uv_impl as impl + + +__all__ = [ + 'Properties', + 'MUV_OT_MirrorUV', +] + + +@PropertyClassRegistry(legacy=True) +class Properties: + idname = "mirror_uv" + + @classmethod + def init_props(cls, scene): + scene.muv_mirror_uv_enabled = BoolProperty( + name="Mirror UV Enabled", + description="Mirror UV is enabled", + default=False + ) + scene.muv_mirror_uv_axis = EnumProperty( + items=[ + ('X', "X", "Mirror Along X axis"), + ('Y', "Y", "Mirror Along Y axis"), + ('Z', "Z", "Mirror Along Z axis") + ], + name="Axis", + description="Mirror Axis", + default='X' + ) + + @classmethod + def del_props(cls, scene): + del scene.muv_mirror_uv_enabled + del scene.muv_mirror_uv_axis + + +@BlClassRegistry(legacy=True) +class MUV_OT_MirrorUV(bpy.types.Operator): + """ + Operation class: Mirror UV + """ + + bl_idname = "uv.muv_mirror_uv_operator" + bl_label = "Mirror UV" + bl_options = {'REGISTER', 'UNDO'} + + axis = EnumProperty( + items=( + ('X', "X", "Mirror Along X axis"), + ('Y', "Y", "Mirror Along Y axis"), + ('Z', "Z", "Mirror Along Z axis") + ), + name="Axis", + description="Mirror Axis", + default='X' + ) + error = FloatProperty( + name="Error", + description="Error threshold", + default=0.001, + min=0.0, + max=100.0, + soft_min=0.0, + soft_max=1.0 + ) + + def __init__(self): + self.__impl = impl.MirrorUVImpl() + + @classmethod + def poll(cls, context): + return impl.MirrorUVImpl.poll(context) + + def execute(self, context): + return self.__impl.execute(self, context) diff --git a/uv_magic_uv/legacy/op/move_uv.py b/uv_magic_uv/legacy/op/move_uv.py new file mode 100644 index 0000000000000000000000000000000000000000..2988c2ce6cf699e551d05ef71f8cd35d0e67a544 --- /dev/null +++ b/uv_magic_uv/legacy/op/move_uv.py @@ -0,0 +1,82 @@ +# <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__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy +from bpy.props import BoolProperty + +from ...impl import move_uv_impl as impl +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry + + +__all__ = [ + 'Properties', + 'MUV_OT_MoveUV', +] + + +@PropertyClassRegistry(legacy=True) +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(legacy=True) +class MUV_OT_MoveUV(bpy.types.Operator): + """ + Operator class: Move UV + """ + + bl_idname = "uv.muv_move_uv_operator" + bl_label = "Move UV" + bl_options = {'REGISTER', 'UNDO'} + + def __init__(self): + self.__impl = impl.MoveUVImpl() + + @classmethod + def poll(cls, context): + return impl.MoveUVImpl.poll(context) + + @classmethod + def is_running(cls, _): + return impl.MoveUVImpl.is_running(_) + + def modal(self, context, event): + return self.__impl.modal(self, context, event) + + def execute(self, context): + return self.__impl.execute(self, context) diff --git a/uv_magic_uv/op/pack_uv.py b/uv_magic_uv/legacy/op/pack_uv.py similarity index 96% rename from uv_magic_uv/op/pack_uv.py rename to uv_magic_uv/legacy/op/pack_uv.py index 39340fda0a9aef34c2a40883a9f98529f751ceda..f8d5884324628e28b27b5152645c31c9b8df50a0 100644 --- a/uv_magic_uv/op/pack_uv.py +++ b/uv_magic_uv/legacy/op/pack_uv.py @@ -35,12 +35,14 @@ from bpy.props import ( ) from mathutils import Vector -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'Operator', + 'MUV_OT_PackUV', ] @@ -67,7 +69,10 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "pack_uv" + @classmethod def init_props(cls, scene): scene.muv_pack_uv_enabled = BoolProperty( @@ -99,7 +104,8 @@ class Properties: del scene.muv_pack_uv_allowable_size_deviation -class Operator(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_PackUV(bpy.types.Operator): """ Operation class: Pack UV with same UV islands are integrated Island matching algorithm diff --git a/uv_magic_uv/op/preserve_uv_aspect.py b/uv_magic_uv/legacy/op/preserve_uv_aspect.py similarity index 96% rename from uv_magic_uv/op/preserve_uv_aspect.py rename to uv_magic_uv/legacy/op/preserve_uv_aspect.py index cb11bd457e1a380bdb16348121c4ae1622aeec45..cf9349bcc4d412ec1e10e1d5d648fa93cf0ce916 100644 --- a/uv_magic_uv/op/preserve_uv_aspect.py +++ b/uv_magic_uv/legacy/op/preserve_uv_aspect.py @@ -28,12 +28,14 @@ import bmesh from bpy.props import StringProperty, EnumProperty, BoolProperty from mathutils import Vector -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'Operator', + 'MUV_OT_PreserveUVAspect', ] @@ -58,7 +60,10 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "preserve_uv_aspect" + @classmethod def init_props(cls, scene): def get_loaded_texture_name(_, __): @@ -101,7 +106,8 @@ class Properties: del scene.muv_preserve_uv_aspect_origin -class Operator(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_PreserveUVAspect(bpy.types.Operator): """ Operation class: Preserve UV Aspect """ diff --git a/uv_magic_uv/op/select_uv.py b/uv_magic_uv/legacy/op/select_uv.py similarity index 90% rename from uv_magic_uv/op/select_uv.py rename to uv_magic_uv/legacy/op/select_uv.py index 3a7bcbc3bb7e5377deaebfb0640e4f9115935596..bdc182d5cc46b81bf064c194d15fc1aa320837df 100644 --- a/uv_magic_uv/op/select_uv.py +++ b/uv_magic_uv/legacy/op/select_uv.py @@ -27,13 +27,15 @@ import bpy import bmesh from bpy.props import BoolProperty -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'OperatorSelectFlipped', - 'OperatorSelectOverlapped', + 'MUV_OT_SelectUV_SelectFlipped', + 'MUV_OT_SelectUV_SelectOverlapped', ] @@ -60,7 +62,10 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "select_uv" + @classmethod def init_props(cls, scene): scene.muv_select_uv_enabled = BoolProperty( @@ -74,7 +79,8 @@ class Properties: del scene.muv_select_uv_enabled -class OperatorSelectOverlapped(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_SelectUV_SelectOverlapped(bpy.types.Operator): """ Operation class: Select faces which have overlapped UVs """ @@ -118,7 +124,8 @@ class OperatorSelectOverlapped(bpy.types.Operator): return {'FINISHED'} -class OperatorSelectFlipped(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_SelectUV_SelectFlipped(bpy.types.Operator): """ Operation class: Select faces which have flipped UVs """ diff --git a/uv_magic_uv/op/smooth_uv.py b/uv_magic_uv/legacy/op/smooth_uv.py similarity index 96% rename from uv_magic_uv/op/smooth_uv.py rename to uv_magic_uv/legacy/op/smooth_uv.py index 31bef1556f6cfb99f5c44070e60e08f638cb2fa4..630625547eb31f2a75226a7fefac9b6fc077b3e9 100644 --- a/uv_magic_uv/op/smooth_uv.py +++ b/uv_magic_uv/legacy/op/smooth_uv.py @@ -27,12 +27,14 @@ import bpy import bmesh from bpy.props import BoolProperty, FloatProperty -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'Operator', + 'MUV_OT_SmoothUV', ] @@ -59,7 +61,10 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "smooth_uv" + @classmethod def init_props(cls, scene): scene.muv_smooth_uv_enabled = BoolProperty( @@ -93,7 +98,8 @@ class Properties: del scene.muv_smooth_uv_select -class Operator(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_SmoothUV(bpy.types.Operator): bl_idname = "uv.muv_smooth_uv_operator" bl_label = "Smooth" diff --git a/uv_magic_uv/op/texture_lock.py b/uv_magic_uv/legacy/op/texture_lock.py similarity index 93% rename from uv_magic_uv/op/texture_lock.py rename to uv_magic_uv/legacy/op/texture_lock.py index 4be97c62bbb10b4e2da352e544cd6e1b498e38be..65873106e20ae4ab2bedccc6a676afc0aaac8faa 100644 --- a/uv_magic_uv/op/texture_lock.py +++ b/uv_magic_uv/legacy/op/texture_lock.py @@ -31,14 +31,16 @@ import bmesh from mathutils import Vector from bpy.props import BoolProperty -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'OperatorLock', - 'OperatorUnlock', - 'OperatorIntr', + 'MUV_OT_TextureLock_Lock', + 'MUV_OT_TextureLock_Unlock', + 'MUV_OT_TextureLock_Intr', ] @@ -213,7 +215,10 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "texture_lock" + @classmethod def init_props(cls, scene): class Props(): @@ -222,7 +227,7 @@ class Properties: scene.muv_props.texture_lock = Props() def get_func(_): - return OperatorIntr.is_running(bpy.context) + return MUV_OT_TextureLock_Intr.is_running(bpy.context) def set_func(_, __): pass @@ -256,7 +261,8 @@ class Properties: del scene.muv_texture_lock_connect -class OperatorLock(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_TextureLock_Lock(bpy.types.Operator): """ Operation class: Lock Texture """ @@ -302,7 +308,8 @@ class OperatorLock(bpy.types.Operator): return {'FINISHED'} -class OperatorUnlock(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_TextureLock_Unlock(bpy.types.Operator): """ Operation class: Unlock Texture """ @@ -326,7 +333,11 @@ class OperatorUnlock(bpy.types.Operator): props = sc.muv_props.texture_lock if not props.verts_orig: return False - return OperatorLock.is_ready(context) and is_valid_context(context) + if not MUV_OT_TextureLock_Lock.is_ready(context): + return False + if not is_valid_context(context): + return False + return True def execute(self, context): sc = context.scene @@ -383,7 +394,8 @@ class OperatorUnlock(bpy.types.Operator): return {'FINISHED'} -class OperatorIntr(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_TextureLock_Intr(bpy.types.Operator): """ Operation class: Texture Lock (Interactive mode) """ @@ -500,10 +512,10 @@ class OperatorIntr(bpy.types.Operator): def modal(self, context, event): if not is_valid_context(context): - OperatorIntr.handle_remove(context) + MUV_OT_TextureLock_Intr.handle_remove(context) return {'FINISHED'} - if not OperatorIntr.is_running(context): + if not MUV_OT_TextureLock_Intr.is_running(context): return {'FINISHED'} if context.area: @@ -521,11 +533,11 @@ class OperatorIntr(bpy.types.Operator): if not is_valid_context(context): return {'CANCELLED'} - if not OperatorIntr.is_running(context): - OperatorIntr.handle_add(self, context) + if not MUV_OT_TextureLock_Intr.is_running(context): + MUV_OT_TextureLock_Intr.handle_add(self, context) return {'RUNNING_MODAL'} else: - OperatorIntr.handle_remove(context) + MUV_OT_TextureLock_Intr.handle_remove(context) if context.area: context.area.tag_redraw() diff --git a/uv_magic_uv/op/texture_projection.py b/uv_magic_uv/legacy/op/texture_projection.py similarity index 93% rename from uv_magic_uv/op/texture_projection.py rename to uv_magic_uv/legacy/op/texture_projection.py index bdf0ad67f4d34ec4d511d91e33d14809eca93cb0..58f69c9d8733143eb5ad17d975b61565ddff68e4 100644 --- a/uv_magic_uv/op/texture_projection.py +++ b/uv_magic_uv/legacy/op/texture_projection.py @@ -36,13 +36,15 @@ from bpy.props import ( FloatProperty, ) -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'Operator', - 'OperatorProject', + 'MUV_OT_TextureProjection', + 'MUV_OT_TextureProjection_Project', ] @@ -143,11 +145,14 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "texture_projection" + @classmethod def init_props(cls, scene): def get_func(_): - return Operator.is_running(bpy.context) + return MUV_OT_TextureProjection.is_running(bpy.context) def set_func(_, __): pass @@ -214,7 +219,8 @@ class Properties: del scene.muv_texture_projection_assign_uvmap -class Operator(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_TextureProjection(bpy.types.Operator): """ Operation class: Texture Projection Render texture @@ -240,7 +246,7 @@ class Operator(bpy.types.Operator): @classmethod def handle_add(cls, obj, context): cls.__handle = bpy.types.SpaceView3D.draw_handler_add( - Operator.draw_texture, + MUV_OT_TextureProjection.draw_texture, (obj, context), 'WINDOW', 'POST_PIXEL') @classmethod @@ -301,10 +307,10 @@ class Operator(bpy.types.Operator): bgl.glEnd() def invoke(self, context, _): - if not Operator.is_running(context): - Operator.handle_add(self, context) + if not MUV_OT_TextureProjection.is_running(context): + MUV_OT_TextureProjection.handle_add(self, context) else: - Operator.handle_remove() + MUV_OT_TextureProjection.handle_remove() if context.area: context.area.tag_redraw() @@ -312,7 +318,8 @@ class Operator(bpy.types.Operator): return {'FINISHED'} -class OperatorProject(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_TextureProjection_Project(bpy.types.Operator): """ Operation class: Project texture """ @@ -327,7 +334,7 @@ class OperatorProject(bpy.types.Operator): # we can not get area/space/region from console if common.is_console_mode(): return True - if not Operator.is_running(context): + if not MUV_OT_TextureProjection.is_running(context): return False return is_valid_context(context) diff --git a/uv_magic_uv/op/texture_wrap.py b/uv_magic_uv/legacy/op/texture_wrap.py similarity index 95% rename from uv_magic_uv/op/texture_wrap.py rename to uv_magic_uv/legacy/op/texture_wrap.py index a7c58847654780e2adbc6698c3cf4c04ae15187e..cb4cc78c9f04ed48736997e506988dd43f763cd3 100644 --- a/uv_magic_uv/op/texture_wrap.py +++ b/uv_magic_uv/legacy/op/texture_wrap.py @@ -29,13 +29,15 @@ from bpy.props import ( BoolProperty, ) -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'OperatorRefer', - 'OperatorSet', + 'MUV_OT_TextureWrap_Refer', + 'MUV_OT_TextureWrap_Set', ] @@ -60,7 +62,10 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "texture_wrap" + @classmethod def init_props(cls, scene): class Props(): @@ -93,7 +98,8 @@ class Properties: del scene.muv_texture_wrap_selseq -class OperatorRefer(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_TextureWrap_Refer(bpy.types.Operator): """ Operation class: Refer UV """ @@ -132,7 +138,8 @@ class OperatorRefer(bpy.types.Operator): return {'FINISHED'} -class OperatorSet(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_TextureWrap_Set(bpy.types.Operator): """ Operation class: Set UV """ diff --git a/uv_magic_uv/legacy/op/transfer_uv.py b/uv_magic_uv/legacy/op/transfer_uv.py new file mode 100644 index 0000000000000000000000000000000000000000..cd0e4dd9feffde6d3d95a6e8484bcdc38e24ce48 --- /dev/null +++ b/uv_magic_uv/legacy/op/transfer_uv.py @@ -0,0 +1,172 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>, Mifth, MaxRobinot" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy +import bmesh +from bpy.props import BoolProperty + +from ... import common +from ...impl import transfer_uv_impl as impl +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry + + +__all__ = [ + 'Properties', + 'MUV_OT_TransferUV_CopyUV', + 'MUV_OT_TransferUV_PasteUV', +] + + +@PropertyClassRegistry(legacy=True) +class Properties: + idname = "transfer_uv" + + @classmethod + def init_props(cls, scene): + class Props(): + topology_copied = None + + scene.muv_props.transfer_uv = Props() + + scene.muv_transfer_uv_enabled = BoolProperty( + name="Transfer UV Enabled", + description="Transfer UV is enabled", + default=False + ) + scene.muv_transfer_uv_invert_normals = BoolProperty( + name="Invert Normals", + description="Invert Normals", + default=False + ) + scene.muv_transfer_uv_copy_seams = BoolProperty( + name="Copy Seams", + description="Copy Seams", + default=True + ) + + @classmethod + def del_props(cls, scene): + del scene.muv_transfer_uv_enabled + del scene.muv_transfer_uv_invert_normals + del scene.muv_transfer_uv_copy_seams + + +@BlClassRegistry(legacy=True) +class MUV_OT_TransferUV_CopyUV(bpy.types.Operator): + """ + Operation class: Transfer UV copy + Topological based copy + """ + + bl_idname = "uv.muv_transfer_uv_operator_copy_uv" + bl_label = "Transfer UV Copy UV" + bl_description = "Transfer UV Copy UV (Topological based copy)" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + return impl.is_valid_context(context) + + def execute(self, context): + props = context.scene.muv_props.transfer_uv + active_obj = context.scene.objects.active + bm = bmesh.from_edit_mesh(active_obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + uv_layer = impl.get_uv_layer(self, bm) + if uv_layer is None: + return {'CANCELLED'} + + faces = impl.get_selected_src_faces(self, bm, uv_layer) + if faces is None: + return {'CANCELLED'} + props.topology_copied = faces + + bmesh.update_edit_mesh(active_obj.data) + + return {'FINISHED'} + + +@BlClassRegistry(legacy=True) +class MUV_OT_TransferUV_PasteUV(bpy.types.Operator): + """ + Operation class: Transfer UV paste + Topological based paste + """ + + bl_idname = "uv.muv_transfer_uv_operator_paste_uv" + bl_label = "Transfer UV Paste UV" + bl_description = "Transfer UV Paste UV (Topological based paste)" + bl_options = {'REGISTER', 'UNDO'} + + invert_normals = BoolProperty( + name="Invert Normals", + description="Invert Normals", + default=False + ) + copy_seams = BoolProperty( + name="Copy Seams", + description="Copy Seams", + default=True + ) + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + sc = context.scene + props = sc.muv_props.transfer_uv + if not props.topology_copied: + return False + return impl.is_valid_context(context) + + def execute(self, context): + props = context.scene.muv_props.transfer_uv + active_obj = context.scene.objects.active + bm = bmesh.from_edit_mesh(active_obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + # get UV layer + uv_layer = impl.get_uv_layer(self, bm) + if uv_layer is None: + return {'CANCELLED'} + + ret = impl.paste_uv(self, bm, uv_layer, props.topology_copied, + self.invert_normals, self.copy_seams) + if ret: + return {'CANCELLED'} + + bmesh.update_edit_mesh(active_obj.data) + if self.copy_seams: + active_obj.data.show_edge_seams = True + + return {'FINISHED'} diff --git a/uv_magic_uv/op/unwrap_constraint.py b/uv_magic_uv/legacy/op/unwrap_constraint.py similarity index 94% rename from uv_magic_uv/op/unwrap_constraint.py rename to uv_magic_uv/legacy/op/unwrap_constraint.py index b2368fc45e3b3c4a7d70587f09dd0487c43dc8e9..f06efce18b2228702d9c9550c72e580fb35d5f46 100644 --- a/uv_magic_uv/op/unwrap_constraint.py +++ b/uv_magic_uv/legacy/op/unwrap_constraint.py @@ -29,12 +29,14 @@ from bpy.props import ( FloatProperty, ) -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'Operator', + 'MUV_OT_UnwrapConstraint', ] @@ -59,7 +61,10 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "unwrap_constraint" + @classmethod def init_props(cls, scene): scene.muv_unwrap_constraint_enabled = BoolProperty( @@ -85,7 +90,8 @@ class Properties: del scene.muv_unwrap_constraint_v_const -class Operator(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_UnwrapConstraint(bpy.types.Operator): """ Operation class: Unwrap with constrain UV coordinate """ diff --git a/uv_magic_uv/op/uv_bounding_box.py b/uv_magic_uv/legacy/op/uv_bounding_box.py similarity index 97% rename from uv_magic_uv/op/uv_bounding_box.py rename to uv_magic_uv/legacy/op/uv_bounding_box.py index 4aa8874bc32f4f5aae173e960e50b03f12cd5be4..47c27e41c752f8fef122d8cd727f9dfa4b8ee496 100644 --- a/uv_magic_uv/op/uv_bounding_box.py +++ b/uv_magic_uv/legacy/op/uv_bounding_box.py @@ -32,12 +32,14 @@ import mathutils import bmesh from bpy.props import BoolProperty, EnumProperty -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'Operator', + 'MUV_OT_UVBoundingBox', ] @@ -67,7 +69,10 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "uv_bounding_box" + @classmethod def init_props(cls, scene): class Props(): @@ -78,7 +83,7 @@ class Properties: scene.muv_props.uv_bounding_box = Props() def get_func(_): - return Operator.is_running(bpy.context) + return MUV_OT_UVBoundingBox.is_running(bpy.context) def set_func(_, __): pass @@ -599,7 +604,8 @@ class StateManager(): return self.__state -class Operator(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_UVBoundingBox(bpy.types.Operator): """ Operation class: UV Bounding Box """ @@ -677,7 +683,7 @@ class Operator(bpy.types.Operator): """ props = context.scene.muv_props.uv_bounding_box - if not Operator.is_running(context): + if not MUV_OT_UVBoundingBox.is_running(context): return if not is_valid_context(context): @@ -781,11 +787,11 @@ class Operator(bpy.types.Operator): props = context.scene.muv_props.uv_bounding_box common.redraw_all_areas() - if not Operator.is_running(context): + if not MUV_OT_UVBoundingBox.is_running(context): return {'FINISHED'} if not is_valid_context(context): - Operator.handle_remove(context) + MUV_OT_UVBoundingBox.handle_remove(context) return {'FINISHED'} region_types = [ @@ -812,15 +818,15 @@ class Operator(bpy.types.Operator): def invoke(self, context, _): props = context.scene.muv_props.uv_bounding_box - if Operator.is_running(context): - Operator.handle_remove(context) + if MUV_OT_UVBoundingBox.is_running(context): + MUV_OT_UVBoundingBox.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) + MUV_OT_UVBoundingBox.handle_add(self, context) props.ctrl_points_ini = self.__get_ctrl_point(props.uv_info_ini) trans_mat = self.__cmd_exec.execute() diff --git a/uv_magic_uv/op/uv_inspection.py b/uv_magic_uv/legacy/op/uv_inspection.py similarity index 89% rename from uv_magic_uv/op/uv_inspection.py rename to uv_magic_uv/legacy/op/uv_inspection.py index 0c05e03d8eaeb54207fee8d303f34bce2ec365ea..57d424685ec3910b0fdb38d4750dd2d506946572 100644 --- a/uv_magic_uv/op/uv_inspection.py +++ b/uv_magic_uv/legacy/op/uv_inspection.py @@ -28,13 +28,15 @@ import bmesh import bgl from bpy.props import BoolProperty, EnumProperty -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'OperatorRender', - 'OperatorUpdate', + 'MUV_OT_UVInspection_Render', + 'MUV_OT_UVInspection_Update', ] @@ -61,7 +63,10 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "uv_inspection" + @classmethod def init_props(cls, scene): class Props(): @@ -71,7 +76,7 @@ class Properties: scene.muv_props.uv_inspection = Props() def get_func(_): - return OperatorRender.is_running(bpy.context) + return MUV_OT_UVInspection_Render.is_running(bpy.context) def set_func(_, __): pass @@ -122,7 +127,8 @@ class Properties: del scene.muv_uv_inspection_show_mode -class OperatorRender(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_UVInspection_Render(bpy.types.Operator): """ Operation class: Render UV Inspection No operation (only rendering) @@ -149,7 +155,8 @@ class OperatorRender(bpy.types.Operator): def handle_add(cls, obj, context): sie = bpy.types.SpaceImageEditor cls.__handle = sie.draw_handler_add( - OperatorRender.draw, (obj, context), 'WINDOW', 'POST_PIXEL') + MUV_OT_UVInspection_Render.draw, (obj, context), + 'WINDOW', 'POST_PIXEL') @classmethod def handle_remove(cls): @@ -164,7 +171,7 @@ class OperatorRender(bpy.types.Operator): props = sc.muv_props.uv_inspection prefs = context.user_preferences.addons["uv_magic_uv"].preferences - if not OperatorRender.is_running(context): + if not MUV_OT_UVInspection_Render.is_running(context): return # OpenGL configuration @@ -213,11 +220,11 @@ class OperatorRender(bpy.types.Operator): bgl.glEnd() def invoke(self, context, _): - if not OperatorRender.is_running(context): + if not MUV_OT_UVInspection_Render.is_running(context): update_uvinsp_info(context) - OperatorRender.handle_add(self, context) + MUV_OT_UVInspection_Render.handle_add(self, context) else: - OperatorRender.handle_remove() + MUV_OT_UVInspection_Render.handle_remove() if context.area: context.area.tag_redraw() @@ -244,7 +251,8 @@ def update_uvinsp_info(context): props.flipped_info = common.get_flipped_uv_info(sel_faces, uv_layer) -class OperatorUpdate(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_UVInspection_Update(bpy.types.Operator): """ Operation class: Update """ @@ -259,7 +267,7 @@ class OperatorUpdate(bpy.types.Operator): # we can not get area/space/region from console if common.is_console_mode(): return True - if not OperatorRender.is_running(context): + if not MUV_OT_UVInspection_Render.is_running(context): return False return is_valid_context(context) diff --git a/uv_magic_uv/op/uv_sculpt.py b/uv_magic_uv/legacy/op/uv_sculpt.py similarity index 96% rename from uv_magic_uv/op/uv_sculpt.py rename to uv_magic_uv/legacy/op/uv_sculpt.py index 63c1adfe42d1eb7332a6e506654de160d9ad5ee7..3754a759c06dcfed49c1f5ad6644b139301d834b 100644 --- a/uv_magic_uv/op/uv_sculpt.py +++ b/uv_magic_uv/legacy/op/uv_sculpt.py @@ -39,12 +39,14 @@ from bpy.props import ( FloatProperty, ) -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'Operator', + 'MUV_OT_UVSculpt', ] @@ -69,11 +71,14 @@ def is_valid_context(context): return True +@PropertyClassRegistry(legacy=True) class Properties: + idname = "uv_sculpt" + @classmethod def init_props(cls, scene): def get_func(_): - return Operator.is_running(bpy.context) + return MUV_OT_UVSculpt.is_running(bpy.context) def set_func(_, __): pass @@ -151,7 +156,8 @@ class Properties: del scene.muv_uv_sculpt_relax_method -class Operator(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_UVSculpt(bpy.types.Operator): """ Operation class: UV Sculpt in View3D """ @@ -427,8 +433,8 @@ class Operator(bpy.types.Operator): if context.area: context.area.tag_redraw() - if not Operator.is_running(context): - Operator.handle_remove(context) + if not MUV_OT_UVSculpt.is_running(context): + MUV_OT_UVSculpt.handle_remove(context) return {'FINISHED'} @@ -469,9 +475,9 @@ class Operator(bpy.types.Operator): if context.area: context.area.tag_redraw() - if Operator.is_running(context): - Operator.handle_remove(context) + if MUV_OT_UVSculpt.is_running(context): + MUV_OT_UVSculpt.handle_remove(context) else: - Operator.handle_add(self, context) + MUV_OT_UVSculpt.handle_add(self, context) return {'RUNNING_MODAL'} diff --git a/uv_magic_uv/legacy/op/uvw.py b/uv_magic_uv/legacy/op/uvw.py new file mode 100644 index 0000000000000000000000000000000000000000..56777b18aa18f1a451a0bcbafb42a31ead56012d --- /dev/null +++ b/uv_magic_uv/legacy/op/uvw.py @@ -0,0 +1,181 @@ +# <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__ = "Alexander Milovsky, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy +import bmesh +from bpy.props import ( + FloatProperty, + FloatVectorProperty, + BoolProperty +) + +from ... import common +from ...impl import uvw_impl as impl +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry + + +__all__ = [ + 'Properties', + 'MUV_OT_UVW_BoxMap', + 'MUV_OT_UVW_BestPlanerMap', +] + + +@PropertyClassRegistry(legacy=True) +class Properties: + idname = "uvw" + + @classmethod + def init_props(cls, scene): + scene.muv_uvw_enabled = BoolProperty( + name="UVW Enabled", + description="UVW is enabled", + default=False + ) + scene.muv_uvw_assign_uvmap = BoolProperty( + name="Assign UVMap", + description="Assign UVMap when no UVmaps are available", + default=True + ) + + @classmethod + def del_props(cls, scene): + del scene.muv_uvw_enabled + del scene.muv_uvw_assign_uvmap + + +@BlClassRegistry(legacy=True) +class MUV_OT_UVW_BoxMap(bpy.types.Operator): + bl_idname = "uv.muv_uvw_operator_box_map" + bl_label = "Box Map" + bl_options = {'REGISTER', 'UNDO'} + + size = FloatProperty( + name="Size", + default=1.0, + precision=4 + ) + rotation = FloatVectorProperty( + name="XYZ Rotation", + size=3, + default=(0.0, 0.0, 0.0) + ) + offset = FloatVectorProperty( + name="XYZ Offset", + size=3, + default=(0.0, 0.0, 0.0) + ) + tex_aspect = FloatProperty( + name="Texture Aspect", + default=1.0, + precision=4 + ) + assign_uvmap = BoolProperty( + name="Assign UVMap", + description="Assign UVMap when no UVmaps are available", + default=True + ) + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + return impl.is_valid_context(context) + + def execute(self, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + # get UV layer + uv_layer = impl.get_uv_layer(self, bm, self.assign_uvmap) + if not uv_layer: + return {'CANCELLED'} + + impl.apply_box_map(bm, uv_layer, self.size, self.offset, + self.rotation, self.tex_aspect) + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} + + +@BlClassRegistry(legacy=True) +class MUV_OT_UVW_BestPlanerMap(bpy.types.Operator): + bl_idname = "uv.muv_uvw_operator_best_planer_map" + bl_label = "Best Planer Map" + bl_options = {'REGISTER', 'UNDO'} + + size = FloatProperty( + name="Size", + default=1.0, + precision=4 + ) + rotation = FloatProperty( + name="XY Rotation", + default=0.0 + ) + offset = FloatVectorProperty( + name="XY Offset", + size=2, + default=(0.0, 0.0) + ) + tex_aspect = FloatProperty( + name="Texture Aspect", + default=1.0, + precision=4 + ) + assign_uvmap = BoolProperty( + name="Assign UVMap", + description="Assign UVMap when no UVmaps are available", + default=True + ) + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + return impl.is_valid_context(context) + + def execute(self, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + # get UV layer + uv_layer = impl.get_uv_layer(self, bm, self.assign_uvmap) + if not uv_layer: + return {'CANCELLED'} + + impl.apply_planer_map(bm, uv_layer, self.size, self.offset, + self.rotation, self.tex_aspect) + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} diff --git a/uv_magic_uv/op/world_scale_uv.py b/uv_magic_uv/legacy/op/world_scale_uv.py similarity index 96% rename from uv_magic_uv/op/world_scale_uv.py rename to uv_magic_uv/legacy/op/world_scale_uv.py index e1a44954b982a5d036d857b19d0528f0037ea632..e56b6bfae3c4ca2c4f6cc8b31cce48103ceb556e 100644 --- a/uv_magic_uv/op/world_scale_uv.py +++ b/uv_magic_uv/legacy/op/world_scale_uv.py @@ -35,15 +35,17 @@ from bpy.props import ( BoolProperty, ) -from .. import common +from ... import common +from ...utils.bl_class_registry import BlClassRegistry +from ...utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'OperatorMeasure', - 'OperatorApplyManual', - 'OperatorApplyScalingDensity', - 'OperatorApplyProportionalToMesh', + 'MUV_OT_WorldScaleUV_Measure', + 'MUV_OT_WorldScaleUV_ApplyManual', + 'MUV_OT_WorldScaleUV_ApplyScalingDensity', + 'MUV_OT_WorldScaleUV_ApplyProportionalToMesh', ] @@ -83,7 +85,10 @@ def measure_wsuv_info(obj, tex_size=None): return uv_area, mesh_area, density +@PropertyClassRegistry(legacy=True) class Properties: + idname = "world_scale_uv" + @classmethod def init_props(cls, scene): scene.muv_world_scale_uv_enabled = BoolProperty( @@ -172,7 +177,8 @@ class Properties: del scene.muv_world_scale_uv_origin -class OperatorMeasure(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_WorldScaleUV_Measure(bpy.types.Operator): """ Operation class: Measure face size """ @@ -310,7 +316,8 @@ def apply(obj, origin, factor): bmesh.update_edit_mesh(obj.data) -class OperatorApplyManual(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_WorldScaleUV_ApplyManual(bpy.types.Operator): """ Operation class: Apply scaled UV (Manual) """ @@ -407,7 +414,8 @@ class OperatorApplyManual(bpy.types.Operator): return self.__apply_manual(context) -class OperatorApplyScalingDensity(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_WorldScaleUV_ApplyScalingDensity(bpy.types.Operator): """ Operation class: Apply scaled UV (Scaling Density) """ @@ -529,7 +537,8 @@ class OperatorApplyScalingDensity(bpy.types.Operator): return self.__apply_scaling_density(context) -class OperatorApplyProportionalToMesh(bpy.types.Operator): +@BlClassRegistry(legacy=True) +class MUV_OT_WorldScaleUV_ApplyProportionalToMesh(bpy.types.Operator): """ Operation class: Apply scaled UV (Proportional to mesh) """ diff --git a/uv_magic_uv/legacy/preferences.py b/uv_magic_uv/legacy/preferences.py new file mode 100644 index 0000000000000000000000000000000000000000..931cc1d49152e0eb2af3f06ba0591f1eb0f18f4a --- /dev/null +++ b/uv_magic_uv/legacy/preferences.py @@ -0,0 +1,468 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy +from bpy.props import ( + FloatProperty, + FloatVectorProperty, + BoolProperty, + EnumProperty, + IntProperty, +) +from bpy.types import AddonPreferences + +from . import op +from . import ui +from .. import addon_updater_ops +from ..utils.bl_class_registry import BlClassRegistry + +__all__ = [ + 'add_builtin_menu', + 'remove_builtin_menu', + 'Preferences' +] + + +def view3d_uvmap_menu_fn(self, context): + layout = self.layout + sc = context.scene + + layout.separator() + layout.label(text="Copy/Paste UV", icon='IMAGE_COL') + # Copy/Paste UV + layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_CopyPasteUV.bl_idname, + text="Copy/Paste UV") + # Transfer UV + layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_TransferUV.bl_idname, + text="Transfer UV") + + layout.separator() + layout.label("UV Manipulation", icon='IMAGE_COL') + # Flip/Rotate UV + ops = layout.operator(op.flip_rotate_uv.MUV_OT_FlipRotate.bl_idname, + text="Flip/Rotate UV") + ops.seams = sc.muv_flip_rotate_uv_seams + # Mirror UV + ops = layout.operator(op.mirror_uv.MUV_OT_MirrorUV.bl_idname, + text="Mirror UV") + ops.axis = sc.muv_mirror_uv_axis + # Move UV + layout.operator(op.move_uv.MUV_OT_MoveUV.bl_idname, text="Move UV") + # World Scale UV + layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_WorldScaleUV.bl_idname, + text="World Scale UV") + # Preserve UV + layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_PreserveUVAspect.bl_idname, + text="Preserve UV") + # Texture Lock + layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_TextureLock.bl_idname, + text="Texture Lock") + # Texture Wrap + layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_TextureWrap.bl_idname, + text="Texture Wrap") + # UV Sculpt + layout.prop(sc, "muv_uv_sculpt_enable", text="UV Sculpt") + + layout.separator() + layout.label("UV Mapping", icon='IMAGE_COL') + # Unwrap Constraint + ops = layout.operator( + op.unwrap_constraint.MUV_OT_UnwrapConstraint.bl_idname, + text="Unwrap Constraint") + ops.u_const = sc.muv_unwrap_constraint_u_const + ops.v_const = sc.muv_unwrap_constraint_v_const + # Texture Projection + layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_TextureProjection.bl_idname, + text="Texture Projection") + # UVW + layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_UVW.bl_idname, text="UVW") + + +def view3d_object_menu_fn(self, _): + layout = self.layout + + layout.separator() + # Copy/Paste UV (Among Objecct) + layout.menu(ui.VIEW3D_MT_object.MUV_MT_CopyPasteUV_Object.bl_idname, + text="Copy/Paste UV") + layout.label("Copy/Paste UV", icon='IMAGE_COL') + + +def image_uvs_menu_fn(self, context): + layout = self.layout + sc = context.scene + + layout.separator() + # Align UV Cursor + layout.menu(ui.IMAGE_MT_uvs.MUV_MT_AlignUVCursor.bl_idname, + text="Align UV Cursor") + # UV Bounding Box + layout.prop(sc, "muv_uv_bounding_box_show", text="UV Bounding Box") + # UV Inspection + layout.menu(ui.IMAGE_MT_uvs.MUV_MT_UVInspection.bl_idname, + text="UV Inspection") + layout.label("Editor Enhancement", icon='IMAGE_COL') + + layout.separator() + # Align UV + layout.menu(ui.IMAGE_MT_uvs.MUV_MT_AlignUV.bl_idname, text="Align UV") + # Smooth UV + ops = layout.operator(op.smooth_uv.MUV_OT_SmoothUV.bl_idname, + text="Smooth") + ops.transmission = sc.muv_smooth_uv_transmission + ops.select = sc.muv_smooth_uv_select + ops.mesh_infl = sc.muv_smooth_uv_mesh_infl + # Select UV + layout.menu(ui.IMAGE_MT_uvs.MUV_MT_SelectUV.bl_idname, text="Select UV") + # Pack UV + ops = layout.operator(op.pack_uv.MUV_OT_PackUV.bl_idname, text="Pack UV") + ops.allowable_center_deviation = sc.muv_pack_uv_allowable_center_deviation + ops.allowable_size_deviation = sc.muv_pack_uv_allowable_size_deviation + layout.label("UV Manipulation", icon='IMAGE_COL') + + layout.separator() + # Copy/Paste UV (on UV/Image Editor) + layout.menu(ui.IMAGE_MT_uvs.MUV_MT_CopyPasteUV_UVEdit.bl_idname, + text="Copy/Paste UV") + layout.label("Copy/Paste UV", icon='IMAGE_COL') + + +def add_builtin_menu(): + bpy.types.VIEW3D_MT_uv_map.append(view3d_uvmap_menu_fn) + bpy.types.VIEW3D_MT_object.append(view3d_object_menu_fn) + bpy.types.IMAGE_MT_uvs.append(image_uvs_menu_fn) + + +def remove_builtin_menu(): + bpy.types.IMAGE_MT_uvs.remove(image_uvs_menu_fn) + bpy.types.VIEW3D_MT_uv_map.remove(view3d_uvmap_menu_fn) + bpy.types.VIEW3D_MT_object.remove(view3d_object_menu_fn) + + +@BlClassRegistry(legacy=True) +class Preferences(AddonPreferences): + """Preferences class: Preferences for this add-on""" + + bl_idname = "uv_magic_uv" + + def update_enable_builtin_menu(self, _): + if self['enable_builtin_menu']: + add_builtin_menu() + else: + remove_builtin_menu() + + # enable to add features to built-in menu + enable_builtin_menu = BoolProperty( + name="Built-in Menu", + description="Enable built-in menu", + default=True, + update=update_enable_builtin_menu + ) + + # for UV Sculpt + uv_sculpt_brush_color = FloatVectorProperty( + name="Color", + description="Color", + default=(1.0, 0.4, 0.4, 1.0), + min=0.0, + max=1.0, + size=4, + subtype='COLOR' + ) + + # for Overlapped UV + uv_inspection_overlapped_color = FloatVectorProperty( + name="Color", + description="Color", + default=(0.0, 0.0, 1.0, 0.3), + min=0.0, + max=1.0, + size=4, + subtype='COLOR' + ) + + # for Flipped UV + uv_inspection_flipped_color = FloatVectorProperty( + name="Color", + description="Color", + default=(1.0, 0.0, 0.0, 0.3), + min=0.0, + max=1.0, + size=4, + subtype='COLOR' + ) + + # for Texture Projection + texture_projection_canvas_padding = FloatVectorProperty( + name="Canvas Padding", + description="Canvas Padding", + size=2, + max=50.0, + min=0.0, + default=(20.0, 20.0)) + + # for UV Bounding Box + uv_bounding_box_cp_size = FloatProperty( + name="Size", + description="Control Point Size", + default=6.0, + min=3.0, + max=100.0) + uv_bounding_box_cp_react_size = FloatProperty( + name="React Size", + description="Size event fired", + default=10.0, + min=3.0, + max=100.0) + + # for UI + category = EnumProperty( + name="Category", + description="Preferences Category", + items=[ + ('INFO', "Information", "Information about this add-on"), + ('CONFIG', "Configuration", "Configuration about this add-on"), + ('UPDATE', "Update", "Update this add-on"), + ], + default='INFO' + ) + info_desc_expanded = BoolProperty( + name="Description", + description="Description", + default=False + ) + info_loc_expanded = BoolProperty( + name="Location", + description="Location", + default=False + ) + conf_uv_sculpt_expanded = BoolProperty( + name="UV Sculpt", + description="UV Sculpt", + default=False + ) + conf_uv_inspection_expanded = BoolProperty( + name="UV Inspection", + description="UV Inspection", + default=False + ) + conf_texture_projection_expanded = BoolProperty( + name="Texture Projection", + description="Texture Projection", + default=False + ) + conf_uv_bounding_box_expanded = BoolProperty( + name="UV Bounding Box", + description="UV Bounding Box", + default=False + ) + + # for add-on updater + auto_check_update = BoolProperty( + name="Auto-check for Update", + description="If enabled, auto-check for updates using an interval", + default=False + ) + updater_intrval_months = IntProperty( + name='Months', + description="Number of months between checking for updates", + default=0, + min=0 + ) + updater_intrval_days = IntProperty( + name='Days', + description="Number of days between checking for updates", + default=7, + min=0 + ) + updater_intrval_hours = IntProperty( + name='Hours', + description="Number of hours between checking for updates", + default=0, + min=0, + max=23 + ) + updater_intrval_minutes = IntProperty( + name='Minutes', + description="Number of minutes between checking for updates", + default=0, + min=0, + max=59 + ) + + def draw(self, context): + layout = self.layout + + layout.row().prop(self, "category", expand=True) + + if self.category == 'INFO': + layout.prop( + self, "info_desc_expanded", text="Description", + icon='DISCLOSURE_TRI_DOWN' if self.info_desc_expanded + else 'DISCLOSURE_TRI_RIGHT') + if self.info_desc_expanded: + column = layout.column(align=True) + column.label("Magic UV is composed of many UV editing" + + " features.") + column.label("See tutorial page if you are new to this" + + " add-on.") + column.label("https://github.com/nutti/Magic-UV/wiki/Tutorial") + + layout.prop( + self, "info_loc_expanded", text="Location", + icon='DISCLOSURE_TRI_DOWN' if self.info_loc_expanded + else 'DISCLOSURE_TRI_RIGHT') + if self.info_loc_expanded: + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("3D View > Tool shelf > Copy/Paste UV (Object mode)") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Copy/Paste UV (Among objects)") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("3D View > Tool shelf > Copy/Paste UV (Edit mode)") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Copy/Paste UV (Among faces in 3D View)") + col.label("Transfer UV") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Flip/Rotate UV") + col.label("Mirror UV") + col.label("Move UV") + col.label("World Scale UV") + col.label("Preserve UV Aspect") + col.label("Texture Lock") + col.label("Texture Wrap") + col.label("UV Sculpt") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Unwrap Constraint") + col.label("Texture Projection") + col.label("UVW") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("UV/Image Editor > Tool shelf > Copy/Paste UV") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Copy/Paste UV (Among faces in UV/Image Editor)") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("UV/Image Editor > Tool shelf > UV Manipulation") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Align UV") + col.label("Smooth UV") + col.label("Select UV") + col.label("Pack UV (Extension)") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("UV/Image Editor > Tool shelf > Editor Enhancement") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Align UV Cursor") + col.label("UV Cursor Location") + col.label("UV Bounding Box") + col.label("UV Inspection") + + elif self.category == 'CONFIG': + layout.prop(self, "enable_builtin_menu", text="Built-in Menu") + + layout.separator() + + layout.prop( + self, "conf_uv_sculpt_expanded", text="UV Sculpt", + icon='DISCLOSURE_TRI_DOWN' if self.conf_uv_sculpt_expanded + else 'DISCLOSURE_TRI_RIGHT') + if self.conf_uv_sculpt_expanded: + sp = layout.split(percentage=0.05) + col = sp.column() # spacer + sp = sp.split(percentage=0.3) + col = sp.column() + col.label("Brush Color:") + col.prop(self, "uv_sculpt_brush_color", text="") + layout.separator() + + layout.prop( + self, "conf_uv_inspection_expanded", text="UV Inspection", + icon='DISCLOSURE_TRI_DOWN' if self.conf_uv_inspection_expanded + else 'DISCLOSURE_TRI_RIGHT') + if self.conf_uv_inspection_expanded: + sp = layout.split(percentage=0.05) + col = sp.column() # spacer + sp = sp.split(percentage=0.3) + col = sp.column() + col.label("Overlapped UV Color:") + col.prop(self, "uv_inspection_overlapped_color", text="") + sp = sp.split(percentage=0.45) + col = sp.column() + col.label("Flipped UV Color:") + col.prop(self, "uv_inspection_flipped_color", text="") + layout.separator() + + layout.prop( + self, "conf_texture_projection_expanded", + text="Texture Projection", + icon='DISCLOSURE_TRI_DOWN' + if self.conf_texture_projection_expanded + else 'DISCLOSURE_TRI_RIGHT') + if self.conf_texture_projection_expanded: + sp = layout.split(percentage=0.05) + col = sp.column() # spacer + sp = sp.split(percentage=0.3) + col = sp.column() + col.prop(self, "texture_projection_canvas_padding") + layout.separator() + + layout.prop( + self, "conf_uv_bounding_box_expanded", text="UV Bounding Box", + icon='DISCLOSURE_TRI_DOWN' + if self.conf_uv_bounding_box_expanded + else 'DISCLOSURE_TRI_RIGHT') + if self.conf_uv_bounding_box_expanded: + sp = layout.split(percentage=0.05) + col = sp.column() # spacer + sp = sp.split(percentage=0.3) + col = sp.column() + col.label("Control Point:") + col.prop(self, "uv_bounding_box_cp_size") + col.prop(self, "uv_bounding_box_cp_react_size") + layout.separator() + + elif self.category == 'UPDATE': + addon_updater_ops.update_settings_ui(self, context) diff --git a/uv_magic_uv/legacy/properites.py b/uv_magic_uv/legacy/properites.py new file mode 100644 index 0000000000000000000000000000000000000000..b64a48f39010fdfa5357146a0bcbbaac08dea18e --- /dev/null +++ b/uv_magic_uv/legacy/properites.py @@ -0,0 +1,61 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + + +from ..utils.property_class_registry import PropertyClassRegistry + +__all__ = [ + 'MUV_Properties', + 'init_props', + 'clear_props', +] + + +# Properties used in this add-on. +# pylint: disable=W0612 +class MUV_Properties(): + def __init__(self): + self.prefs = MUV_Prefs() + + +class MUV_Prefs(): + expanded = { + "info_desc": False, + "info_loc": False, + "conf_uvsculpt": False, + "conf_uvinsp": False, + "conf_texproj": False, + "conf_uvbb": False + } + + +def init_props(scene): + scene.muv_props = MUV_Properties() + PropertyClassRegistry.init_props(scene) + + +def clear_props(scene): + PropertyClassRegistry.del_props(scene) + del scene.muv_props diff --git a/uv_magic_uv/legacy/ui/IMAGE_MT_uvs.py b/uv_magic_uv/legacy/ui/IMAGE_MT_uvs.py new file mode 100644 index 0000000000000000000000000000000000000000..bf071bf5f07ea56d291e8fa1f6bc3640ea8d11dc --- /dev/null +++ b/uv_magic_uv/legacy/ui/IMAGE_MT_uvs.py @@ -0,0 +1,197 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy + +from ..op import ( + align_uv_cursor, + copy_paste_uv_uvedit, + align_uv, + select_uv, + uv_inspection +) +from ...utils.bl_class_registry import BlClassRegistry + +__all__ = [ + 'MUV_MT_CopyPasteUV_UVEdit', + 'MUV_MT_AlignUV', + 'MUV_MT_SelectUV', + 'MUV_MT_AlignUVCursor', + 'MUV_MT_UVInspection', +] + + +@BlClassRegistry(legacy=True) +class MUV_MT_CopyPasteUV_UVEdit(bpy.types.Menu): + """ + Menu class: Master menu of Copy/Paste UV coordinate on UV/ImageEditor + """ + + bl_idname = "uv.muv_copy_paste_uv_uvedit_menu" + bl_label = "Copy/Paste UV" + bl_description = "Copy and Paste UV coordinate among object" + + def draw(self, _): + layout = self.layout + + layout.operator( + copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_CopyUV.bl_idname, + text="Copy") + layout.operator( + copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_PasteUV.bl_idname, + text="Paste") + + +@BlClassRegistry(legacy=True) +class MUV_MT_AlignUV(bpy.types.Menu): + """ + Menu class: Master menu of Align UV + """ + + bl_idname = "uv.muv_align_uv_menu" + bl_label = "Align UV" + bl_description = "Align UV" + + def draw(self, context): + layout = self.layout + sc = context.scene + + ops = layout.operator(align_uv.MUV_OT_AlignUV_Circle.bl_idname, + text="Circle") + ops.transmission = sc.muv_align_uv_transmission + ops.select = sc.muv_align_uv_select + + ops = layout.operator(align_uv.MUV_OT_AlignUV_Straighten.bl_idname, + text="Straighten") + ops.transmission = sc.muv_align_uv_transmission + ops.select = sc.muv_align_uv_select + ops.vertical = sc.muv_align_uv_vertical + ops.horizontal = sc.muv_align_uv_horizontal + + ops = layout.operator(align_uv.MUV_OT_AlignUV_Axis.bl_idname, + text="XY-axis") + ops.transmission = sc.muv_align_uv_transmission + ops.select = sc.muv_align_uv_select + ops.vertical = sc.muv_align_uv_vertical + ops.horizontal = sc.muv_align_uv_horizontal + ops.location = sc.muv_align_uv_location + + +@BlClassRegistry(legacy=True) +class MUV_MT_SelectUV(bpy.types.Menu): + """ + Menu class: Master menu of Select UV + """ + + bl_idname = "uv.muv_select_uv_menu" + bl_label = "Select UV" + bl_description = "Select UV" + + def draw(self, _): + layout = self.layout + + layout.operator(select_uv.MUV_OT_SelectUV_SelectOverlapped.bl_idname, + text="Overlapped") + layout.operator(select_uv.MUV_OT_SelectUV_SelectFlipped.bl_idname, + text="Flipped") + + +@BlClassRegistry(legacy=True) +class MUV_MT_AlignUVCursor(bpy.types.Menu): + """ + Menu class: Master menu of Align UV Cursor + """ + + bl_idname = "uv.muv_align_uv_cursor_menu" + bl_label = "Align UV Cursor" + bl_description = "Align UV cursor" + + def draw(self, context): + layout = self.layout + sc = context.scene + + ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, + text="Left Top") + ops.position = 'LEFT_TOP' + ops.base = sc.muv_align_uv_cursor_align_method + + ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, + text="Middle Top") + ops.position = 'MIDDLE_TOP' + ops.base = sc.muv_align_uv_cursor_align_method + + ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, + text="Right Top") + ops.position = 'RIGHT_TOP' + ops.base = sc.muv_align_uv_cursor_align_method + + ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, + text="Left Middle") + ops.position = 'LEFT_MIDDLE' + ops.base = sc.muv_align_uv_cursor_align_method + + ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, + text="Center") + ops.position = 'CENTER' + ops.base = sc.muv_align_uv_cursor_align_method + + ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, + text="Right Middle") + ops.position = 'RIGHT_MIDDLE' + ops.base = sc.muv_align_uv_cursor_align_method + + ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, + text="Left Bottom") + ops.position = 'LEFT_BOTTOM' + ops.base = sc.muv_align_uv_cursor_align_method + + ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, + text="Middle Bottom") + ops.position = 'MIDDLE_BOTTOM' + ops.base = sc.muv_align_uv_cursor_align_method + + ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, + text="Right Bottom") + ops.position = 'RIGHT_BOTTOM' + ops.base = sc.muv_align_uv_cursor_align_method + + +@BlClassRegistry(legacy=True) +class MUV_MT_UVInspection(bpy.types.Menu): + """ + Menu class: Master menu of UV Inspection + """ + + bl_idname = "uv.muv_uv_inspection_menu" + bl_label = "UV Inspection" + bl_description = "UV Inspection" + + def draw(self, context): + layout = self.layout + sc = context.scene + + layout.prop(sc, "muv_uv_inspection_show", text="UV Inspection") + layout.operator(uv_inspection.MUV_OT_UVInspection_Update.bl_idname, + text="Update") diff --git a/uv_magic_uv/legacy/ui/VIEW3D_MT_object.py b/uv_magic_uv/legacy/ui/VIEW3D_MT_object.py new file mode 100644 index 0000000000000000000000000000000000000000..e1c751ae798dfc1793bae3dfc08b362dd62e2a17 --- /dev/null +++ b/uv_magic_uv/legacy/ui/VIEW3D_MT_object.py @@ -0,0 +1,54 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy + +from ..op import copy_paste_uv_object +from ...utils.bl_class_registry import BlClassRegistry + +__all__ = [ + 'MUV_MT_CopyPasteUV_Object', +] + + +@BlClassRegistry(legacy=True) +class MUV_MT_CopyPasteUV_Object(bpy.types.Menu): + """ + Menu class: Master menu of Copy/Paste UV coordinate among object + """ + + bl_idname = "uv.muv_copy_paste_uv_object_menu" + bl_label = "Copy/Paste UV" + bl_description = "Copy and Paste UV coordinate among object" + + def draw(self, _): + layout = self.layout + + layout.menu( + copy_paste_uv_object.MUV_MT_CopyPasteUVObject_CopyUV.bl_idname, + text="Copy") + layout.menu( + copy_paste_uv_object.MUV_MT_CopyPasteUVObject_PasteUV.bl_idname, + text="Paste") diff --git a/uv_magic_uv/legacy/ui/VIEW3D_MT_uv_map.py b/uv_magic_uv/legacy/ui/VIEW3D_MT_uv_map.py new file mode 100644 index 0000000000000000000000000000000000000000..d229322a034aa382920c3b0ab782bb1d1f891d20 --- /dev/null +++ b/uv_magic_uv/legacy/ui/VIEW3D_MT_uv_map.py @@ -0,0 +1,257 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy +import bpy.utils + +from ..op import ( + texture_lock, + copy_paste_uv, + preserve_uv_aspect, + texture_projection, + texture_wrap, + transfer_uv, + uvw, + world_scale_uv +) +from ..op.world_scale_uv import MUV_OT_WorldScaleUV_ApplyProportionalToMesh +from ...utils.bl_class_registry import BlClassRegistry + +__all__ = [ + 'MUV_MT_CopyPasteUV', + 'MUV_MT_TransferUV', + 'MUV_MT_TextureLock', + 'MUV_MT_WorldScaleUV', + 'MUV_MT_TextureWrap', + 'MUV_MT_UVW', + 'MUV_MT_TextureProjection', + 'MUV_MT_PreserveUVAspect', +] + + +@BlClassRegistry(legacy=True) +class MUV_MT_CopyPasteUV(bpy.types.Menu): + """ + Menu class: Master menu of Copy/Paste UV coordinate + """ + + bl_idname = "uv.muv_copy_paste_uv_menu" + bl_label = "Copy/Paste UV" + bl_description = "Copy and Paste UV coordinate" + + def draw(self, _): + layout = self.layout + + layout.label(text="Default") + layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_CopyUV.bl_idname, + text="Copy") + layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_PasteUV.bl_idname, + text="Paste") + + layout.separator() + + layout.label(text="Selection Sequence") + layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqCopyUV.bl_idname, + text="Copy") + layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqPasteUV.bl_idname, + text="Paste") + + +@BlClassRegistry(legacy=True) +class MUV_MT_TransferUV(bpy.types.Menu): + """ + Menu class: Master menu of Transfer UV coordinate + """ + + bl_idname = "uv.muv_transfer_uv_menu" + bl_label = "Transfer UV" + bl_description = "Transfer UV coordinate" + + def draw(self, context): + layout = self.layout + sc = context.scene + + layout.operator(transfer_uv.MUV_OT_TransferUV_CopyUV.bl_idname, + text="Copy") + ops = layout.operator(transfer_uv.MUV_OT_TransferUV_PasteUV.bl_idname, + text="Paste") + ops.invert_normals = sc.muv_transfer_uv_invert_normals + ops.copy_seams = sc.muv_transfer_uv_copy_seams + + +@BlClassRegistry(legacy=True) +class MUV_MT_TextureLock(bpy.types.Menu): + """ + Menu class: Master menu of Texture Lock + """ + + bl_idname = "uv.muv_texture_lock_menu" + bl_label = "Texture Lock" + bl_description = "Lock texture when vertices of mesh (Preserve UV)" + + def draw(self, context): + layout = self.layout + sc = context.scene + + layout.label("Normal Mode") + layout.operator( + texture_lock.MUV_OT_TextureLock_Lock.bl_idname, + text="Lock" + if not texture_lock.MUV_OT_TextureLock_Lock.is_ready(context) + else "ReLock") + ops = layout.operator(texture_lock.MUV_OT_TextureLock_Unlock.bl_idname, + text="Unlock") + ops.connect = sc.muv_texture_lock_connect + + layout.separator() + + layout.label("Interactive Mode") + layout.prop(sc, "muv_texture_lock_lock", text="Lock") + + +@BlClassRegistry(legacy=True) +class MUV_MT_WorldScaleUV(bpy.types.Menu): + """ + Menu class: Master menu of world scale UV + """ + + bl_idname = "uv.muv_world_scale_uv_menu" + bl_label = "World Scale UV" + bl_description = "" + + def draw(self, context): + layout = self.layout + sc = context.scene + + layout.operator(world_scale_uv.MUV_OT_WorldScaleUV_Measure.bl_idname, + text="Measure") + + layout.operator( + world_scale_uv.MUV_OT_WorldScaleUV_ApplyManual.bl_idname, + text="Apply (Manual)") + + ops = layout.operator( + world_scale_uv.MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname, + text="Apply (Same Desity)") + ops.src_density = sc.muv_world_scale_uv_src_density + ops.same_density = True + + ops = layout.operator( + world_scale_uv.MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname, + text="Apply (Scaling Desity)") + ops.src_density = sc.muv_world_scale_uv_src_density + ops.same_density = False + ops.tgt_scaling_factor = sc.muv_world_scale_uv_tgt_scaling_factor + + ops = layout.operator( + MUV_OT_WorldScaleUV_ApplyProportionalToMesh.bl_idname, + text="Apply (Proportional to Mesh)") + ops.src_density = sc.muv_world_scale_uv_src_density + ops.src_uv_area = sc.muv_world_scale_uv_src_uv_area + ops.src_mesh_area = sc.muv_world_scale_uv_src_mesh_area + ops.origin = sc.muv_world_scale_uv_origin + + +@BlClassRegistry(legacy=True) +class MUV_MT_TextureWrap(bpy.types.Menu): + """ + Menu class: Master menu of Texture Wrap + """ + + bl_idname = "uv.muv_texture_wrap_menu" + bl_label = "Texture Wrap" + bl_description = "" + + def draw(self, _): + layout = self.layout + + layout.operator(texture_wrap.MUV_OT_TextureWrap_Refer.bl_idname, + text="Refer") + layout.operator(texture_wrap.MUV_OT_TextureWrap_Set.bl_idname, + text="Set") + + +@BlClassRegistry(legacy=True) +class MUV_MT_UVW(bpy.types.Menu): + """ + Menu class: Master menu of UVW + """ + + bl_idname = "uv.muv_uvw_menu" + bl_label = "UVW" + bl_description = "" + + def draw(self, context): + layout = self.layout + sc = context.scene + + ops = layout.operator(uvw.MUV_OT_UVW_BoxMap.bl_idname, text="Box") + ops.assign_uvmap = sc.muv_uvw_assign_uvmap + + ops = layout.operator(uvw.MUV_OT_UVW_BestPlanerMap.bl_idname, + text="Best Planner") + ops.assign_uvmap = sc.muv_uvw_assign_uvmap + + +@BlClassRegistry(legacy=True) +class MUV_MT_TextureProjection(bpy.types.Menu): + """ + Menu class: Master menu of Texture Projection + """ + + bl_idname = "uv.muv_texture_projection_menu" + bl_label = "Texture Projection" + bl_description = "" + + def draw(self, context): + layout = self.layout + sc = context.scene + + layout.prop(sc, "muv_texture_projection_enable", + text="Texture Projection") + layout.operator( + texture_projection.MUV_OT_TextureProjection_Project.bl_idname, + text="Project") + + +@BlClassRegistry(legacy=True) +class MUV_MT_PreserveUVAspect(bpy.types.Menu): + """ + Menu class: Master menu of Preserve UV Aspect + """ + + bl_idname = "uv.muv_preserve_uv_aspect_menu" + bl_label = "Preserve UV Aspect" + bl_description = "" + + def draw(self, context): + layout = self.layout + sc = context.scene + + for key in bpy.data.images.keys(): + ops = layout.operator( + preserve_uv_aspect.MUV_OT_PreserveUVAspect.bl_idname, text=key) + ops.dest_img_name = key + ops.origin = sc.muv_preserve_uv_aspect_origin diff --git a/uv_magic_uv/legacy/ui/__init__.py b/uv_magic_uv/legacy/ui/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bf790a8e4203c5d0e6593d695283e020c3368d49 --- /dev/null +++ b/uv_magic_uv/legacy/ui/__init__.py @@ -0,0 +1,50 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +if "bpy" in locals(): + import importlib + importlib.reload(view3d_copy_paste_uv_objectmode) + importlib.reload(view3d_copy_paste_uv_editmode) + importlib.reload(view3d_uv_manipulation) + importlib.reload(view3d_uv_mapping) + importlib.reload(uvedit_copy_paste_uv) + importlib.reload(uvedit_uv_manipulation) + importlib.reload(uvedit_editor_enhancement) + importlib.reload(VIEW3D_MT_uv_map) + importlib.reload(VIEW3D_MT_object) + importlib.reload(IMAGE_MT_uvs) +else: + from . import view3d_copy_paste_uv_objectmode + from . import view3d_copy_paste_uv_editmode + from . import view3d_uv_manipulation + from . import view3d_uv_mapping + from . import uvedit_copy_paste_uv + from . import uvedit_uv_manipulation + from . import uvedit_editor_enhancement + from . import VIEW3D_MT_uv_map + from . import VIEW3D_MT_object + from . import IMAGE_MT_uvs + +import bpy \ No newline at end of file diff --git a/uv_magic_uv/legacy/ui/uvedit_copy_paste_uv.py b/uv_magic_uv/legacy/ui/uvedit_copy_paste_uv.py new file mode 100644 index 0000000000000000000000000000000000000000..9848f03b78951ca083828a90d32912459a8ecaeb --- /dev/null +++ b/uv_magic_uv/legacy/ui/uvedit_copy_paste_uv.py @@ -0,0 +1,62 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy + +from ..op import copy_paste_uv_uvedit +from ...utils.bl_class_registry import BlClassRegistry + +__all__ = [ + 'MUV_PT_UVEdit_CopyPasteUV', +] + + +@BlClassRegistry(legacy=True) +class MUV_PT_UVEdit_CopyPasteUV(bpy.types.Panel): + """ + Panel class: Copy/Paste UV on Property Panel on UV/ImageEditor + """ + + bl_space_type = 'IMAGE_EDITOR' + bl_region_type = 'TOOLS' + bl_label = "Copy/Paste UV" + bl_category = "Magic UV" + bl_context = 'mesh_edit' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, _): + layout = self.layout + + row = layout.row(align=True) + row.operator( + copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_CopyUV.bl_idname, + text="Copy") + row.operator( + copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_PasteUV.bl_idname, + text="Paste") diff --git a/uv_magic_uv/ui/uvedit_editor_enhancement.py b/uv_magic_uv/legacy/ui/uvedit_editor_enhancement.py similarity index 70% rename from uv_magic_uv/ui/uvedit_editor_enhancement.py rename to uv_magic_uv/legacy/ui/uvedit_editor_enhancement.py index 9cde0cad6b753404907ef146472323e22ad45d88..3f750febfbefbc51fd5f062b8916ad4572ea0b72 100644 --- a/uv_magic_uv/ui/uvedit_editor_enhancement.py +++ b/uv_magic_uv/legacy/ui/uvedit_editor_enhancement.py @@ -25,17 +25,20 @@ __date__ = "17 Nov 2018" import bpy -from ..op import align_uv_cursor -from ..op import uv_bounding_box -from ..op import uv_inspection - +from ..op import ( + align_uv_cursor, + uv_bounding_box, + uv_inspection, +) +from ...utils.bl_class_registry import BlClassRegistry __all__ = [ - 'PanelEditorEnhancement', + 'MUV_PT_UVEdit_EditorEnhancement', ] -class PanelEditorEnhancement(bpy.types.Panel): +@BlClassRegistry(legacy=True) +class MUV_PT_UVEdit_EditorEnhancement(bpy.types.Panel): """ Panel class: UV/Image Editor Enhancement """ @@ -63,43 +66,43 @@ class PanelEditorEnhancement(bpy.types.Panel): col = box.column(align=True) row = col.row(align=True) - ops = row.operator(align_uv_cursor.Operator.bl_idname, + ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, text="Left Top") ops.position = 'LEFT_TOP' ops.base = sc.muv_align_uv_cursor_align_method - ops = row.operator(align_uv_cursor.Operator.bl_idname, + ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, text="Middle Top") ops.position = 'MIDDLE_TOP' ops.base = sc.muv_align_uv_cursor_align_method - ops = row.operator(align_uv_cursor.Operator.bl_idname, + ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, text="Right Top") ops.position = 'RIGHT_TOP' ops.base = sc.muv_align_uv_cursor_align_method row = col.row(align=True) - ops = row.operator(align_uv_cursor.Operator.bl_idname, + ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, text="Left Middle") ops.position = 'LEFT_MIDDLE' ops.base = sc.muv_align_uv_cursor_align_method - ops = row.operator(align_uv_cursor.Operator.bl_idname, + ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, text="Center") ops.position = 'CENTER' ops.base = sc.muv_align_uv_cursor_align_method - ops = row.operator(align_uv_cursor.Operator.bl_idname, + ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, text="Right Middle") ops.position = 'RIGHT_MIDDLE' ops.base = sc.muv_align_uv_cursor_align_method row = col.row(align=True) - ops = row.operator(align_uv_cursor.Operator.bl_idname, + ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, text="Left Bottom") ops.position = 'LEFT_BOTTOM' ops.base = sc.muv_align_uv_cursor_align_method - ops = row.operator(align_uv_cursor.Operator.bl_idname, + ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, text="Middle Bottom") ops.position = 'MIDDLE_BOTTOM' ops.base = sc.muv_align_uv_cursor_align_method - ops = row.operator(align_uv_cursor.Operator.bl_idname, + ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname, text="Right Bottom") ops.position = 'RIGHT_BOTTOM' ops.base = sc.muv_align_uv_cursor_align_method @@ -113,13 +116,14 @@ class PanelEditorEnhancement(bpy.types.Panel): box = layout.box() box.prop(sc, "muv_uv_bounding_box_enabled", text="UV Bounding Box") if sc.muv_uv_bounding_box_enabled: - box.prop(sc, "muv_uv_bounding_box_show", - text="Hide" - if uv_bounding_box.Operator.is_running(context) - else "Show", - icon='RESTRICT_VIEW_OFF' - if uv_bounding_box.Operator.is_running(context) - else 'RESTRICT_VIEW_ON') + box.prop( + sc, "muv_uv_bounding_box_show", + text="Hide" + if uv_bounding_box.MUV_OT_UVBoundingBox.is_running(context) + else "Show", + icon='RESTRICT_VIEW_OFF' + if uv_bounding_box.MUV_OT_UVBoundingBox.is_running(context) + else 'RESTRICT_VIEW_ON') box.prop(sc, "muv_uv_bounding_box_uniform_scaling", text="Uniform Scaling") box.prop(sc, "muv_uv_bounding_box_boundary", text="Boundary") @@ -128,14 +132,15 @@ class PanelEditorEnhancement(bpy.types.Panel): box.prop(sc, "muv_uv_inspection_enabled", text="UV Inspection") if sc.muv_uv_inspection_enabled: row = box.row() - row.prop(sc, "muv_uv_inspection_show", - text="Hide" - if uv_inspection.OperatorRender.is_running(context) - else "Show", - icon='RESTRICT_VIEW_OFF' - if uv_inspection.OperatorRender.is_running(context) - else 'RESTRICT_VIEW_ON') - row.operator(uv_inspection.OperatorUpdate.bl_idname, + row.prop( + sc, "muv_uv_inspection_show", + text="Hide" + if uv_inspection.MUV_OT_UVInspection_Render.is_running(context) + else "Show", + icon='RESTRICT_VIEW_OFF' + if uv_inspection.MUV_OT_UVInspection_Render.is_running(context) + else 'RESTRICT_VIEW_ON') + row.operator(uv_inspection.MUV_OT_UVInspection_Update.bl_idname, text="Update") row = box.row() row.prop(sc, "muv_uv_inspection_show_overlapped") diff --git a/uv_magic_uv/ui/uvedit_uv_manipulation.py b/uv_magic_uv/legacy/ui/uvedit_uv_manipulation.py similarity index 84% rename from uv_magic_uv/ui/uvedit_uv_manipulation.py rename to uv_magic_uv/legacy/ui/uvedit_uv_manipulation.py index 0f6a105e96586cd2e9f43e1c8888591b61d3a2f2..96cf17d324c17eeba2b45cf478559702a237ff90 100644 --- a/uv_magic_uv/ui/uvedit_uv_manipulation.py +++ b/uv_magic_uv/legacy/ui/uvedit_uv_manipulation.py @@ -25,18 +25,21 @@ __date__ = "17 Nov 2018" import bpy -from ..op import align_uv -from ..op import smooth_uv -from ..op import pack_uv -from ..op import select_uv - +from ..op import ( + align_uv, + smooth_uv, + pack_uv, + select_uv, +) +from ...utils.bl_class_registry import BlClassRegistry __all__ = [ - 'MenuUVManipulation', + 'MUV_PT_UVEdit_UVManipulation', ] -class MenuUVManipulation(bpy.types.Panel): +@BlClassRegistry(legacy=True) +class MUV_PT_UVEdit_UVManipulation(bpy.types.Panel): """ Panel class: UV Manipulation on Property Panel on UV/ImageEditor """ @@ -61,11 +64,11 @@ class MenuUVManipulation(bpy.types.Panel): if sc.muv_align_uv_enabled: col = box.column() row = col.row(align=True) - ops = row.operator(align_uv.OperatorCircle.bl_idname, + ops = row.operator(align_uv.MUV_OT_AlignUV_Circle.bl_idname, text="Circle") ops.transmission = sc.muv_align_uv_transmission ops.select = sc.muv_align_uv_select - ops = row.operator(align_uv.OperatorStraighten.bl_idname, + ops = row.operator(align_uv.MUV_OT_AlignUV_Straighten.bl_idname, text="Straighten") ops.transmission = sc.muv_align_uv_transmission ops.select = sc.muv_align_uv_select @@ -73,7 +76,8 @@ class MenuUVManipulation(bpy.types.Panel): ops.horizontal = sc.muv_align_uv_horizontal ops.mesh_infl = sc.muv_align_uv_mesh_infl row = col.row() - ops = row.operator(align_uv.OperatorAxis.bl_idname, text="XY-axis") + ops = row.operator(align_uv.MUV_OT_AlignUV_Axis.bl_idname, + text="XY-axis") ops.transmission = sc.muv_align_uv_transmission ops.select = sc.muv_align_uv_select ops.vertical = sc.muv_align_uv_vertical @@ -94,7 +98,7 @@ class MenuUVManipulation(bpy.types.Panel): box = layout.box() box.prop(sc, "muv_smooth_uv_enabled", text="Smooth UV") if sc.muv_smooth_uv_enabled: - ops = box.operator(smooth_uv.Operator.bl_idname, + ops = box.operator(smooth_uv.MUV_OT_SmoothUV.bl_idname, text="Smooth") ops.transmission = sc.muv_smooth_uv_transmission ops.select = sc.muv_smooth_uv_select @@ -109,13 +113,13 @@ class MenuUVManipulation(bpy.types.Panel): box.prop(sc, "muv_select_uv_enabled", text="Select UV") if sc.muv_select_uv_enabled: row = box.row(align=True) - row.operator(select_uv.OperatorSelectOverlapped.bl_idname) - row.operator(select_uv.OperatorSelectFlipped.bl_idname) + row.operator(select_uv.MUV_OT_SelectUV_SelectOverlapped.bl_idname) + row.operator(select_uv.MUV_OT_SelectUV_SelectFlipped.bl_idname) box = layout.box() box.prop(sc, "muv_pack_uv_enabled", text="Pack UV (Extension)") if sc.muv_pack_uv_enabled: - ops = box.operator(pack_uv.Operator.bl_idname, text="Pack UV") + ops = box.operator(pack_uv.MUV_OT_PackUV.bl_idname, text="Pack UV") ops.allowable_center_deviation = \ sc.muv_pack_uv_allowable_center_deviation ops.allowable_size_deviation = \ diff --git a/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_editmode.py b/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_editmode.py new file mode 100644 index 0000000000000000000000000000000000000000..ee2713b22d19cfd193dd1ddd430e679c41aa0c1a --- /dev/null +++ b/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_editmode.py @@ -0,0 +1,93 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy + +from ..op import ( + transfer_uv, + copy_paste_uv, +) +from ...utils.bl_class_registry import BlClassRegistry + +__all__ = [ + 'MUV_PT_View3D_Edit_CopyPasteUV', +] + + +@BlClassRegistry(legacy=True) +class MUV_PT_View3D_Edit_CopyPasteUV(bpy.types.Panel): + """ + Panel class: Copy/Paste UV on Property Panel on View3D + """ + + bl_space_type = 'VIEW_3D' + bl_region_type = 'TOOLS' + bl_label = "Copy/Paste UV" + bl_category = "Magic UV" + bl_context = 'mesh_edit' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, context): + sc = context.scene + layout = self.layout + + box = layout.box() + box.prop(sc, "muv_copy_paste_uv_enabled", text="Copy/Paste UV") + if sc.muv_copy_paste_uv_enabled: + row = box.row(align=True) + if sc.muv_copy_paste_uv_mode == 'DEFAULT': + row.menu(copy_paste_uv.MUV_MT_CopyPasteUV_CopyUV.bl_idname, + text="Copy") + row.menu(copy_paste_uv.MUV_MT_CopyPasteUV_PasteUV.bl_idname, + text="Paste") + elif sc.muv_copy_paste_uv_mode == 'SEL_SEQ': + row.menu( + copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqCopyUV.bl_idname, + text="Copy") + row.menu( + copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqPasteUV.bl_idname, + text="Paste") + box.prop(sc, "muv_copy_paste_uv_mode", expand=True) + box.prop(sc, "muv_copy_paste_uv_copy_seams", text="Seams") + box.prop(sc, "muv_copy_paste_uv_strategy", text="Strategy") + + box = layout.box() + box.prop(sc, "muv_transfer_uv_enabled", text="Transfer UV") + if sc.muv_transfer_uv_enabled: + row = box.row(align=True) + row.operator(transfer_uv.MUV_OT_TransferUV_CopyUV.bl_idname, + text="Copy") + ops = row.operator(transfer_uv.MUV_OT_TransferUV_PasteUV.bl_idname, + text="Paste") + ops.invert_normals = sc.muv_transfer_uv_invert_normals + ops.copy_seams = sc.muv_transfer_uv_copy_seams + row = box.row() + row.prop(sc, "muv_transfer_uv_invert_normals", + text="Invert Normals") + row.prop(sc, "muv_transfer_uv_copy_seams", text="Seams") diff --git a/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_objectmode.py b/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_objectmode.py new file mode 100644 index 0000000000000000000000000000000000000000..58031b0f3158a2f78689741713bd55ac3d899bb7 --- /dev/null +++ b/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_objectmode.py @@ -0,0 +1,65 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy + +from ..op import copy_paste_uv_object +from ...utils.bl_class_registry import BlClassRegistry + +__all__ = [ + 'MUV_PT_View3D_Object_CopyPasteUV', +] + + +@BlClassRegistry(legacy=True) +class MUV_PT_View3D_Object_CopyPasteUV(bpy.types.Panel): + """ + Panel class: Copy/Paste UV on Property Panel on View3D + """ + + bl_space_type = 'VIEW_3D' + bl_region_type = 'TOOLS' + bl_label = "Copy/Paste UV" + bl_category = "Magic UV" + bl_context = 'objectmode' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, context): + sc = context.scene + layout = self.layout + + row = layout.row(align=True) + row.menu( + copy_paste_uv_object.MUV_MT_CopyPasteUVObject_CopyUV.bl_idname, + text="Copy") + row.menu( + copy_paste_uv_object.MUV_MT_CopyPasteUVObject_PasteUV.bl_idname, + text="Paste") + layout.prop(sc, "muv_copy_paste_uv_object_copy_seams", + text="Seams") diff --git a/uv_magic_uv/legacy/ui/view3d_uv_manipulation.py b/uv_magic_uv/legacy/ui/view3d_uv_manipulation.py new file mode 100644 index 0000000000000000000000000000000000000000..7d828a38fb1bf5aef2356f0a955badb4b915c17e --- /dev/null +++ b/uv_magic_uv/legacy/ui/view3d_uv_manipulation.py @@ -0,0 +1,289 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy + +from ..op import ( + move_uv, + flip_rotate_uv, + mirror_uv, + preserve_uv_aspect, + texture_lock, + texture_wrap, + uv_sculpt, + world_scale_uv, +) +from ..op.world_scale_uv import ( + MUV_OT_WorldScaleUV_ApplyProportionalToMesh, + MUV_OT_WorldScaleUV_ApplyScalingDensity +) +from ...utils.bl_class_registry import BlClassRegistry + +__all__ = [ + 'MUV_PT_View3D_UVManipulation', +] + + +@BlClassRegistry(legacy=True) +class MUV_PT_View3D_UVManipulation(bpy.types.Panel): + """ + Panel class: UV Manipulation on Property Panel on View3D + """ + + bl_space_type = 'VIEW_3D' + bl_region_type = 'TOOLS' + bl_label = "UV Manipulation" + bl_category = "Magic UV" + bl_context = 'mesh_edit' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, context): + sc = context.scene + layout = self.layout + + box = layout.box() + box.prop(sc, "muv_flip_rotate_uv_enabled", text="Flip/Rotate UV") + if sc.muv_flip_rotate_uv_enabled: + row = box.row() + ops = row.operator(flip_rotate_uv.MUV_OT_FlipRotate.bl_idname, + text="Flip/Rotate") + ops.seams = sc.muv_flip_rotate_uv_seams + row.prop(sc, "muv_flip_rotate_uv_seams", text="Seams") + + box = layout.box() + box.prop(sc, "muv_mirror_uv_enabled", text="Mirror UV") + if sc.muv_mirror_uv_enabled: + row = box.row() + ops = row.operator(mirror_uv.MUV_OT_MirrorUV.bl_idname, + text="Mirror") + ops.axis = sc.muv_mirror_uv_axis + row.prop(sc, "muv_mirror_uv_axis", text="") + + box = layout.box() + box.prop(sc, "muv_move_uv_enabled", text="Move UV") + if sc.muv_move_uv_enabled: + col = box.column() + if not move_uv.MUV_OT_MoveUV.is_running(context): + col.operator(move_uv.MUV_OT_MoveUV.bl_idname, icon='PLAY', + text="Start") + else: + col.operator(move_uv.MUV_OT_MoveUV.bl_idname, icon='PAUSE', + text="Stop") + + box = layout.box() + box.prop(sc, "muv_world_scale_uv_enabled", text="World Scale UV") + if sc.muv_world_scale_uv_enabled: + box.prop(sc, "muv_world_scale_uv_mode", text="") + + if sc.muv_world_scale_uv_mode == 'MANUAL': + sp = box.split(percentage=0.5) + col = sp.column() + col.prop(sc, "muv_world_scale_uv_tgt_texture_size", + text="Texture Size") + sp = sp.split(percentage=1.0) + col = sp.column() + col.label("Density:") + col.prop(sc, "muv_world_scale_uv_tgt_density", text="") + box.prop(sc, "muv_world_scale_uv_origin", text="Origin") + ops = box.operator( + world_scale_uv.MUV_OT_WorldScaleUV_ApplyManual.bl_idname, + text="Apply") + ops.tgt_density = sc.muv_world_scale_uv_tgt_density + ops.tgt_texture_size = sc.muv_world_scale_uv_tgt_texture_size + ops.origin = sc.muv_world_scale_uv_origin + ops.show_dialog = False + + elif sc.muv_world_scale_uv_mode == 'SAME_DENSITY': + sp = box.split(percentage=0.4) + col = sp.column(align=True) + col.label("Source:") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.operator( + world_scale_uv.MUV_OT_WorldScaleUV_Measure.bl_idname, + text="Measure") + + sp = box.split(percentage=0.7) + col = sp.column(align=True) + col.prop(sc, "muv_world_scale_uv_src_density", text="Density") + col.enabled = False + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("px2/cm2") + + box.separator() + box.prop(sc, "muv_world_scale_uv_origin", text="Origin") + ops = box.operator( + MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname, + text="Apply") + ops.src_density = sc.muv_world_scale_uv_src_density + ops.origin = sc.muv_world_scale_uv_origin + ops.same_density = True + ops.show_dialog = False + + elif sc.muv_world_scale_uv_mode == 'SCALING_DENSITY': + sp = box.split(percentage=0.4) + col = sp.column(align=True) + col.label("Source:") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.operator( + world_scale_uv.MUV_OT_WorldScaleUV_Measure.bl_idname, + text="Measure") + + sp = box.split(percentage=0.7) + col = sp.column(align=True) + col.prop(sc, "muv_world_scale_uv_src_density", text="Density") + col.enabled = False + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("px2/cm2") + + box.separator() + box.prop(sc, "muv_world_scale_uv_tgt_scaling_factor", + text="Scaling Factor") + box.prop(sc, "muv_world_scale_uv_origin", text="Origin") + ops = box.operator( + MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname, + text="Apply") + ops.src_density = sc.muv_world_scale_uv_src_density + ops.origin = sc.muv_world_scale_uv_origin + ops.same_density = False + ops.show_dialog = False + ops.tgt_scaling_factor = \ + sc.muv_world_scale_uv_tgt_scaling_factor + + elif sc.muv_world_scale_uv_mode == 'PROPORTIONAL_TO_MESH': + sp = box.split(percentage=0.4) + col = sp.column(align=True) + col.label("Source:") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.operator( + world_scale_uv.MUV_OT_WorldScaleUV_Measure.bl_idname, + text="Measure") + + sp = box.split(percentage=0.7) + col = sp.column(align=True) + col.prop(sc, "muv_world_scale_uv_src_mesh_area", + text="Mesh Area") + col.prop(sc, "muv_world_scale_uv_src_uv_area", text="UV Area") + col.prop(sc, "muv_world_scale_uv_src_density", text="Density") + col.enabled = False + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("cm2") + col.label("px2") + col.label("px2/cm2") + col.enabled = False + + box.separator() + box.prop(sc, "muv_world_scale_uv_origin", text="Origin") + ops = box.operator( + MUV_OT_WorldScaleUV_ApplyProportionalToMesh.bl_idname, + text="Apply") + ops.src_density = sc.muv_world_scale_uv_src_density + ops.src_uv_area = sc.muv_world_scale_uv_src_uv_area + ops.src_mesh_area = sc.muv_world_scale_uv_src_mesh_area + ops.origin = sc.muv_world_scale_uv_origin + ops.show_dialog = False + + box = layout.box() + box.prop(sc, "muv_preserve_uv_aspect_enabled", + text="Preserve UV Aspect") + if sc.muv_preserve_uv_aspect_enabled: + row = box.row() + ops = row.operator( + preserve_uv_aspect.MUV_OT_PreserveUVAspect.bl_idname, + text="Change Image") + ops.dest_img_name = sc.muv_preserve_uv_aspect_tex_image + ops.origin = sc.muv_preserve_uv_aspect_origin + row.prop(sc, "muv_preserve_uv_aspect_tex_image", text="") + box.prop(sc, "muv_preserve_uv_aspect_origin", text="Origin") + + box = layout.box() + box.prop(sc, "muv_texture_lock_enabled", text="Texture Lock") + if sc.muv_texture_lock_enabled: + row = box.row(align=True) + col = row.column(align=True) + col.label("Normal Mode:") + col = row.column(align=True) + col.operator( + texture_lock.MUV_OT_TextureLock_Lock.bl_idname, + text="Lock" + if not texture_lock.MUV_OT_TextureLock_Lock.is_ready(context) + else "ReLock") + ops = col.operator( + texture_lock.MUV_OT_TextureLock_Unlock.bl_idname, + text="Unlock") + ops.connect = sc.muv_texture_lock_connect + col.prop(sc, "muv_texture_lock_connect", text="Connect") + + row = box.row(align=True) + row.label("Interactive Mode:") + box.prop( + sc, "muv_texture_lock_lock", + text="Unlock" + if texture_lock.MUV_OT_TextureLock_Intr.is_running(context) + else "Lock", + icon='RESTRICT_VIEW_OFF' + if texture_lock.MUV_OT_TextureLock_Intr.is_running(context) + else 'RESTRICT_VIEW_ON') + + box = layout.box() + box.prop(sc, "muv_texture_wrap_enabled", text="Texture Wrap") + if sc.muv_texture_wrap_enabled: + row = box.row(align=True) + row.operator(texture_wrap.MUV_OT_TextureWrap_Refer.bl_idname, + text="Refer") + row.operator(texture_wrap.MUV_OT_TextureWrap_Set.bl_idname, + text="Set") + box.prop(sc, "muv_texture_wrap_set_and_refer") + box.prop(sc, "muv_texture_wrap_selseq") + + box = layout.box() + box.prop(sc, "muv_uv_sculpt_enabled", text="UV Sculpt") + if sc.muv_uv_sculpt_enabled: + box.prop( + sc, "muv_uv_sculpt_enable", + text="Disable"if uv_sculpt.MUV_OT_UVSculpt.is_running(context) + else "Enable", + icon='RESTRICT_VIEW_OFF' + if uv_sculpt.MUV_OT_UVSculpt.is_running(context) + else 'RESTRICT_VIEW_ON') + col = box.column() + col.label("Brush:") + col.prop(sc, "muv_uv_sculpt_radius") + col.prop(sc, "muv_uv_sculpt_strength") + box.prop(sc, "muv_uv_sculpt_tools") + if sc.muv_uv_sculpt_tools == 'PINCH': + box.prop(sc, "muv_uv_sculpt_pinch_invert") + elif sc.muv_uv_sculpt_tools == 'RELAX': + box.prop(sc, "muv_uv_sculpt_relax_method") + box.prop(sc, "muv_uv_sculpt_show_brush") diff --git a/uv_magic_uv/legacy/ui/view3d_uv_mapping.py b/uv_magic_uv/legacy/ui/view3d_uv_mapping.py new file mode 100644 index 0000000000000000000000000000000000000000..3de86d0de00a0f698dd86a002e39cca95ba8c2a0 --- /dev/null +++ b/uv_magic_uv/legacy/ui/view3d_uv_mapping.py @@ -0,0 +1,116 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy + +from ..op import ( + uvw, + texture_projection, + unwrap_constraint, +) +from ..op.texture_projection import ( + MUV_OT_TextureProjection +) +from ...utils.bl_class_registry import BlClassRegistry + +__all__ = [ + 'MUV_PT_View3D_UVMapping', +] + + +@BlClassRegistry(legacy=True) +class MUV_PT_View3D_UVMapping(bpy.types.Panel): + """ + Panel class: UV Mapping on Property Panel on View3D + """ + + bl_space_type = 'VIEW_3D' + bl_region_type = 'TOOLS' + bl_label = "UV Mapping" + bl_category = "Magic UV" + bl_context = 'mesh_edit' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, context): + sc = context.scene + layout = self.layout + + box = layout.box() + box.prop(sc, "muv_unwrap_constraint_enabled", text="Unwrap Constraint") + if sc.muv_unwrap_constraint_enabled: + ops = box.operator( + unwrap_constraint.MUV_OT_UnwrapConstraint.bl_idname, + text="Unwrap") + ops.u_const = sc.muv_unwrap_constraint_u_const + ops.v_const = sc.muv_unwrap_constraint_v_const + row = box.row(align=True) + row.prop(sc, "muv_unwrap_constraint_u_const", text="U-Constraint") + row.prop(sc, "muv_unwrap_constraint_v_const", text="V-Constraint") + + box = layout.box() + box.prop(sc, "muv_texture_projection_enabled", + text="Texture Projection") + if sc.muv_texture_projection_enabled: + row = box.row() + row.prop( + sc, "muv_texture_projection_enable", + text="Disable" + if MUV_OT_TextureProjection.is_running(context) + else "Enable", + icon='RESTRICT_VIEW_OFF' + if MUV_OT_TextureProjection.is_running(context) + else 'RESTRICT_VIEW_ON') + row.prop(sc, "muv_texture_projection_tex_image", text="") + box.prop(sc, "muv_texture_projection_tex_transparency", + text="Transparency") + col = box.column(align=True) + row = col.row() + row.prop(sc, "muv_texture_projection_adjust_window", + text="Adjust Window") + if not sc.muv_texture_projection_adjust_window: + row.prop(sc, "muv_texture_projection_tex_magnitude", + text="Magnitude") + col.prop(sc, "muv_texture_projection_apply_tex_aspect", + text="Texture Aspect Ratio") + col.prop(sc, "muv_texture_projection_assign_uvmap", + text="Assign UVMap") + box.operator( + texture_projection.MUV_OT_TextureProjection_Project.bl_idname, + text="Project") + + box = layout.box() + box.prop(sc, "muv_uvw_enabled", text="UVW") + if sc.muv_uvw_enabled: + row = box.row(align=True) + ops = row.operator(uvw.MUV_OT_UVW_BoxMap.bl_idname, text="Box") + ops.assign_uvmap = sc.muv_uvw_assign_uvmap + ops = row.operator(uvw.MUV_OT_UVW_BestPlanerMap.bl_idname, + text="Best Planner") + ops.assign_uvmap = sc.muv_uvw_assign_uvmap + box.prop(sc, "muv_uvw_assign_uvmap", text="Assign UVMap") diff --git a/uv_magic_uv/op/__init__.py b/uv_magic_uv/op/__init__.py index 9535b76d094b148741f5563ad34f356f810cad5a..2142c15771a51e7f94d76d039c4aef41d62a4832 100644 --- a/uv_magic_uv/op/__init__.py +++ b/uv_magic_uv/op/__init__.py @@ -25,50 +25,22 @@ __date__ = "17 Nov 2018" if "bpy" in locals(): import importlib - importlib.reload(align_uv) - importlib.reload(align_uv_cursor) importlib.reload(copy_paste_uv) importlib.reload(copy_paste_uv_object) importlib.reload(copy_paste_uv_uvedit) importlib.reload(flip_rotate_uv) importlib.reload(mirror_uv) importlib.reload(move_uv) - importlib.reload(pack_uv) - importlib.reload(preserve_uv_aspect) - importlib.reload(select_uv) - importlib.reload(smooth_uv) - importlib.reload(texture_lock) - importlib.reload(texture_projection) - importlib.reload(texture_wrap) importlib.reload(transfer_uv) - importlib.reload(unwrap_constraint) - importlib.reload(uv_bounding_box) - importlib.reload(uv_inspection) - importlib.reload(uv_sculpt) importlib.reload(uvw) - importlib.reload(world_scale_uv) else: - from . import align_uv - from . import align_uv_cursor from . import copy_paste_uv from . import copy_paste_uv_object from . import copy_paste_uv_uvedit from . import flip_rotate_uv from . import mirror_uv from . import move_uv - from . import pack_uv - from . import preserve_uv_aspect - from . import select_uv - from . import smooth_uv - from . import texture_lock - from . import texture_projection - from . import texture_wrap from . import transfer_uv - from . import unwrap_constraint - from . import uv_bounding_box - from . import uv_inspection - from . import uv_sculpt from . import uvw - from . import world_scale_uv import bpy diff --git a/uv_magic_uv/op/copy_paste_uv.py b/uv_magic_uv/op/copy_paste_uv.py index cc1baa3009ae697eaef0473aa0750a60f96f9d1f..23bc8343853fa275a4d414e19a7ab68ea628de82 100644 --- a/uv_magic_uv/op/copy_paste_uv.py +++ b/uv_magic_uv/op/copy_paste_uv.py @@ -18,14 +18,14 @@ # # ##### END GPL LICENSE BLOCK ##### -__author__ = "imdjs, Nutti <nutti.metro@gmail.com>" +__author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester" __status__ = "production" __version__ = "5.2" __date__ = "17 Nov 2018" -import bpy import bmesh +import bpy.utils from bpy.props import ( StringProperty, BoolProperty, @@ -33,151 +33,28 @@ from bpy.props import ( EnumProperty, ) +from ..impl import copy_paste_uv_impl as impl from .. import common - +from ..utils.bl_class_registry import BlClassRegistry +from ..utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'OpeartorCopyUV', - 'MenuCopyUV', - 'OperatorPasteUV', - 'MenuPasteUV', - 'OperatorSelSeqCopyUV', - 'MenuSelSeqCopyUV', - 'OperatorSelSeqPasteUV', - 'MenuSelSeqPasteUV', + 'MUV_OT_CopyPasteUV_CopyUV', + 'MUV_MT_CopyPasteUV_CopyUV', + 'MUV_OT_CopyPasteUV_PasteUV', + 'MUV_MT_CopyPasteUV_PasteUV', + 'MUV_OT_CopyPasteUV_SelSeqCopyUV', + 'MUV_MT_CopyPasteUV_SelSeqCopyUV', + 'MUV_OT_CopyPasteUV_SelSeqPasteUV', + 'MUV_MT_CopyPasteUV_SelSeqPasteUV', ] -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 - - # only 'VIEW_3D' space is allowed to execute - for space in context.area.spaces: - if space.type == 'VIEW_3D': - break - else: - return False - - return True - - -def get_copy_uv_layers(ops_obj, bm): - uv_layers = [] - if ops_obj.uv_map == "__default": - if not bm.loops.layers.uv: - ops_obj.report( - {'WARNING'}, "Object must have more than one UV map") - return None - uv_layers.append(bm.loops.layers.uv.verify()) - ops_obj.report({'INFO'}, "Copy UV coordinate") - elif ops_obj.uv_map == "__all": - for uv in bm.loops.layers.uv.keys(): - uv_layers.append(bm.loops.layers.uv[uv]) - ops_obj.report({'INFO'}, "Copy UV coordinate (UV map: ALL)") - else: - uv_layers.append(bm.loops.layers.uv[ops_obj.uv_map]) - ops_obj.report( - {'INFO'}, "Copy UV coordinate (UV map:{})".format(ops_obj.uv_map)) - - return uv_layers - - -def get_paste_uv_layers(ops_obj, obj, bm, src_info): - uv_layers = [] - if ops_obj.uv_map == "__default": - if not bm.loops.layers.uv: - ops_obj.report( - {'WARNING'}, "Object must have more than one UV map") - return None - uv_layers.append(bm.loops.layers.uv.verify()) - ops_obj.report({'INFO'}, "Paste UV coordinate") - elif ops_obj.uv_map == "__new": - new_uv_map = common.create_new_uv_map(obj) - if not new_uv_map: - ops_obj.report({'WARNING'}, - "Reached to the maximum number of UV map") - return None - uv_layers.append(bm.loops.layers.uv[new_uv_map.name]) - ops_obj.report( - {'INFO'}, "Paste UV coordinate (UV map:{})".format(new_uv_map)) - elif ops_obj.uv_map == "__all": - for src_layer in src_info.keys(): - if src_layer not in bm.loops.layers.uv.keys(): - new_uv_map = common.create_new_uv_map(obj, src_layer) - if not new_uv_map: - ops_obj.report({'WARNING'}, - "Reached to the maximum number of UV map") - return None - uv_layers.append(bm.loops.layers.uv[src_layer]) - ops_obj.report({'INFO'}, "Paste UV coordinate (UV map: ALL)") - else: - uv_layers.append(bm.loops.layers.uv[ops_obj.uv_map]) - ops_obj.report( - {'INFO'}, "Paste UV coordinate (UV map:{})".format(ops_obj.uv_map)) - - return uv_layers - - -def paste_uv(ops_obj, bm, src_info, dest_info, uv_layers, strategy, flip, - rotate, copy_seams): - for slayer_name, dlayer in zip(src_info.keys(), uv_layers): - src_faces = src_info[slayer_name] - dest_faces = dest_info[dlayer.name] - - for idx, dinfo in enumerate(dest_faces): - sinfo = None - if strategy == 'N_N': - sinfo = src_faces[idx] - elif strategy == 'N_M': - sinfo = src_faces[idx % len(src_faces)] - - suv = sinfo["uvs"] - spuv = sinfo["pin_uvs"] - ss = sinfo["seams"] - if len(sinfo["uvs"]) != len(dinfo["uvs"]): - ops_obj.report({'WARNING'}, "Some faces are different size") - return -1 - - suvs_fr = [uv for uv in suv] - spuvs_fr = [pin_uv for pin_uv in spuv] - ss_fr = [s for s in ss] - - # flip UVs - if flip is True: - suvs_fr.reverse() - spuvs_fr.reverse() - ss_fr.reverse() - - # rotate UVs - for _ in range(rotate): - uv = suvs_fr.pop() - pin_uv = spuvs_fr.pop() - s = ss_fr.pop() - suvs_fr.insert(0, uv) - spuvs_fr.insert(0, pin_uv) - ss_fr.insert(0, s) - - # paste UVs - for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr, - spuvs_fr, ss_fr): - l[dlayer].uv = suv - l[dlayer].pin_uv = spuv - if copy_seams is True: - l.edge.seam = ss - - return 0 - - +@PropertyClassRegistry() class Properties: + idname = "copy_paste_uv" + @classmethod def init_props(cls, scene): class Props(): @@ -225,7 +102,8 @@ class Properties: del scene.muv_copy_paste_uv_strategy -class OpeartorCopyUV(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_CopyPasteUV_CopyUV(bpy.types.Operator): """ Operation class: Copy UV coordinate """ @@ -235,14 +113,14 @@ class OpeartorCopyUV(bpy.types.Operator): bl_description = "Copy UV coordinate" bl_options = {'REGISTER', 'UNDO'} - uv_map = StringProperty(default="__default", options={'HIDDEN'}) + uv_map: StringProperty(default="__default", options={'HIDDEN'}) @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) + return impl.is_valid_context(context) def execute(self, context): props = context.scene.muv_props.copy_paste_uv @@ -250,34 +128,24 @@ class OpeartorCopyUV(bpy.types.Operator): bm = common.create_bmesh(obj) # get UV layer - uv_layers = get_copy_uv_layers(self, bm) + uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map) if not uv_layers: return {'CANCELLED'} # get selected face - props.src_info = {} - for layer in uv_layers: - face_info = [] - for face in bm.faces: - if face.select: - info = { - "uvs": [l[layer].uv.copy() for l in face.loops], - "pin_uvs": [l[layer].pin_uv for l in face.loops], - "seams": [l.edge.seam for l in face.loops], - } - face_info.append(info) - if not face_info: - self.report({'WARNING'}, "No faces are selected") - return {'CANCELLED'} - props.src_info[layer.name] = face_info - - face_count = len([f for f in bm.faces if f.select]) + src_info = impl.get_src_face_info(self, bm, uv_layers) + if src_info is None: + return {'CANCELLED'} + props.src_info = src_info + + face_count = len(props.src_info[list(props.src_info.keys())[0]]) self.report({'INFO'}, "{} face(s) are copied".format(face_count)) return {'FINISHED'} -class MenuCopyUV(bpy.types.Menu): +@BlClassRegistry() +class MUV_MT_CopyPasteUV_CopyUV(bpy.types.Menu): """ Menu class: Copy UV coordinate """ @@ -288,7 +156,7 @@ class MenuCopyUV(bpy.types.Menu): @classmethod def poll(cls, context): - return is_valid_context(context) + return impl.is_valid_context(context) def draw(self, context): layout = self.layout @@ -297,18 +165,21 @@ class MenuCopyUV(bpy.types.Menu): bm = common.create_bmesh(obj) uv_maps = bm.loops.layers.uv.keys() - ops = layout.operator(OpeartorCopyUV.bl_idname, text="[Default]") + ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname, + text="[Default]") ops.uv_map = "__default" - ops = layout.operator(OpeartorCopyUV.bl_idname, text="[All]") + ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname, + text="[All]") ops.uv_map = "__all" for m in uv_maps: - ops = layout.operator(OpeartorCopyUV.bl_idname, text=m) + ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname, text=m) ops.uv_map = m -class OperatorPasteUV(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_CopyPasteUV_PasteUV(bpy.types.Operator): """ Operation class: Paste UV coordinate """ @@ -318,8 +189,8 @@ class OperatorPasteUV(bpy.types.Operator): bl_description = "Paste UV coordinate" bl_options = {'REGISTER', 'UNDO'} - uv_map = StringProperty(default="__default", options={'HIDDEN'}) - strategy = EnumProperty( + uv_map: StringProperty(default="__default", options={'HIDDEN'}) + strategy: EnumProperty( name="Strategy", description="Paste Strategy", items=[ @@ -328,18 +199,18 @@ class OperatorPasteUV(bpy.types.Operator): ], default="N_M" ) - flip_copied_uv = BoolProperty( + flip_copied_uv: BoolProperty( name="Flip Copied UV", description="Flip Copied UV...", default=False ) - rotate_copied_uv = IntProperty( + rotate_copied_uv: IntProperty( default=0, name="Rotate Copied UV", min=0, max=30 ) - copy_seams = BoolProperty( + copy_seams: BoolProperty( name="Seams", description="Copy Seams", default=True @@ -354,7 +225,7 @@ class OperatorPasteUV(bpy.types.Operator): props = sc.muv_props.copy_paste_uv if not props.src_info: return False - return is_valid_context(context) + return impl.is_valid_context(context) def execute(self, context): props = context.scene.muv_props.copy_paste_uv @@ -365,53 +236,34 @@ class OperatorPasteUV(bpy.types.Operator): bm = common.create_bmesh(obj) # get UV layer - uv_layers = get_paste_uv_layers(self, obj, bm, props.src_info) + uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info, + self.uv_map) if not uv_layers: return {'CANCELLED'} # get selected face - dest_face_count = 0 - dest_info = {} - for layer in uv_layers: - face_info = [] - for face in bm.faces: - if face.select: - info = { - "uvs": [l[layer].uv.copy() for l in face.loops], - } - face_info.append(info) - if not face_info: - self.report({'WARNING'}, "No faces are selected") - return {'CANCELLED'} - key = list(props.src_info.keys())[0] - src_face_count = len(props.src_info[key]) - dest_face_count = len(face_info) - if self.strategy == 'N_N' and src_face_count != dest_face_count: - self.report( - {'WARNING'}, - "Number of selected faces is different from copied" + - "(src:{}, dest:{})" - .format(src_face_count, dest_face_count)) - return {'CANCELLED'} - dest_info[layer.name] = face_info + dest_info = impl.get_dest_face_info(self, bm, uv_layers, + props.src_info, self.strategy) + if dest_info is None: + return {'CANCELLED'} # paste - ret = paste_uv(self, bm, props.src_info, dest_info, uv_layers, - self.strategy, self.flip_copied_uv, - self.rotate_copied_uv, self.copy_seams) + ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers, + self.strategy, self.flip_copied_uv, + self.rotate_copied_uv, self.copy_seams) if ret: return {'CANCELLED'} - self.report({'INFO'}, "{} face(s) are pasted".format(dest_face_count)) + face_count = len(props.src_info[list(dest_info.keys())[0]]) + self.report({'INFO'}, "{} face(s) are pasted".format(face_count)) bmesh.update_edit_mesh(obj.data) - if self.copy_seams is True: - obj.data.show_edge_seams = True return {'FINISHED'} -class MenuPasteUV(bpy.types.Menu): +@BlClassRegistry() +class MUV_MT_CopyPasteUV_PasteUV(bpy.types.Menu): """ Menu class: Paste UV coordinate """ @@ -426,7 +278,7 @@ class MenuPasteUV(bpy.types.Menu): props = sc.muv_props.copy_paste_uv if not props.src_info: return False - return is_valid_context(context) + return impl.is_valid_context(context) def draw(self, context): sc = context.scene @@ -436,29 +288,33 @@ class MenuPasteUV(bpy.types.Menu): bm = common.create_bmesh(obj) uv_maps = bm.loops.layers.uv.keys() - ops = layout.operator(OperatorPasteUV.bl_idname, text="[Default]") + ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname, + text="[Default]") ops.uv_map = "__default" ops.copy_seams = sc.muv_copy_paste_uv_copy_seams ops.strategy = sc.muv_copy_paste_uv_strategy - ops = layout.operator(OperatorPasteUV.bl_idname, text="[New]") + ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname, + text="[New]") ops.uv_map = "__new" ops.copy_seams = sc.muv_copy_paste_uv_copy_seams ops.strategy = sc.muv_copy_paste_uv_strategy - ops = layout.operator(OperatorPasteUV.bl_idname, text="[All]") + ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname, + text="[All]") ops.uv_map = "__all" ops.copy_seams = sc.muv_copy_paste_uv_copy_seams ops.strategy = sc.muv_copy_paste_uv_strategy for m in uv_maps: - ops = layout.operator(OperatorPasteUV.bl_idname, text=m) + ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname, text=m) ops.uv_map = m ops.copy_seams = sc.muv_copy_paste_uv_copy_seams ops.strategy = sc.muv_copy_paste_uv_strategy -class OperatorSelSeqCopyUV(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_CopyPasteUV_SelSeqCopyUV(bpy.types.Operator): """ Operation class: Copy UV coordinate by selection sequence """ @@ -468,14 +324,14 @@ class OperatorSelSeqCopyUV(bpy.types.Operator): bl_description = "Copy UV data by selection sequence" bl_options = {'REGISTER', 'UNDO'} - uv_map = StringProperty(default="__default", options={'HIDDEN'}) + uv_map: StringProperty(default="__default", options={'HIDDEN'}) @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) + return impl.is_valid_context(context) def execute(self, context): props = context.scene.muv_props.copy_paste_uv_selseq @@ -483,34 +339,24 @@ class OperatorSelSeqCopyUV(bpy.types.Operator): bm = common.create_bmesh(obj) # get UV layer - uv_layers = get_copy_uv_layers(self, bm) + uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map) if not uv_layers: return {'CANCELLED'} # get selected face - props.src_info = {} - for layer in uv_layers: - face_info = [] - for hist in bm.select_history: - if isinstance(hist, bmesh.types.BMFace) and hist.select: - info = { - "uvs": [l[layer].uv.copy() for l in hist.loops], - "pin_uvs": [l[layer].pin_uv for l in hist.loops], - "seams": [l.edge.seam for l in hist.loops], - } - face_info.append(info) - if not face_info: - self.report({'WARNING'}, "No faces are selected") - return {'CANCELLED'} - props.src_info[layer.name] = face_info - - face_count = len([f for f in bm.faces if f.select]) + src_info = impl.get_select_history_src_face_info(self, bm, uv_layers) + if src_info is None: + return {'CANCELLED'} + props.src_info = src_info + + face_count = len(props.src_info[list(props.src_info.keys())[0]]) self.report({'INFO'}, "{} face(s) are selected".format(face_count)) return {'FINISHED'} -class MenuSelSeqCopyUV(bpy.types.Menu): +@BlClassRegistry() +class MUV_MT_CopyPasteUV_SelSeqCopyUV(bpy.types.Menu): """ Menu class: Copy UV coordinate by selection sequence """ @@ -521,7 +367,7 @@ class MenuSelSeqCopyUV(bpy.types.Menu): @classmethod def poll(cls, context): - return is_valid_context(context) + return impl.is_valid_context(context) def draw(self, context): layout = self.layout @@ -529,18 +375,22 @@ class MenuSelSeqCopyUV(bpy.types.Menu): bm = common.create_bmesh(obj) uv_maps = bm.loops.layers.uv.keys() - ops = layout.operator(OperatorSelSeqCopyUV.bl_idname, text="[Default]") + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname, + text="[Default]") ops.uv_map = "__default" - ops = layout.operator(OperatorSelSeqCopyUV.bl_idname, text="[All]") + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname, + text="[All]") ops.uv_map = "__all" for m in uv_maps: - ops = layout.operator(OperatorSelSeqCopyUV.bl_idname, text=m) + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname, + text=m) ops.uv_map = m -class OperatorSelSeqPasteUV(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_CopyPasteUV_SelSeqPasteUV(bpy.types.Operator): """ Operation class: Paste UV coordinate by selection sequence """ @@ -550,8 +400,8 @@ class OperatorSelSeqPasteUV(bpy.types.Operator): bl_description = "Paste UV coordinate by selection sequence" bl_options = {'REGISTER', 'UNDO'} - uv_map = StringProperty(default="__default", options={'HIDDEN'}) - strategy = EnumProperty( + uv_map: StringProperty(default="__default", options={'HIDDEN'}) + strategy: EnumProperty( name="Strategy", description="Paste Strategy", items=[ @@ -560,18 +410,18 @@ class OperatorSelSeqPasteUV(bpy.types.Operator): ], default="N_M" ) - flip_copied_uv = BoolProperty( + flip_copied_uv: BoolProperty( name="Flip Copied UV", description="Flip Copied UV...", default=False ) - rotate_copied_uv = IntProperty( + rotate_copied_uv: IntProperty( default=0, name="Rotate Copied UV", min=0, max=30 ) - copy_seams = BoolProperty( + copy_seams: BoolProperty( name="Seams", description="Copy Seams", default=True @@ -586,7 +436,7 @@ class OperatorSelSeqPasteUV(bpy.types.Operator): props = sc.muv_props.copy_paste_uv_selseq if not props.src_info: return False - return is_valid_context(context) + return impl.is_valid_context(context) def execute(self, context): props = context.scene.muv_props.copy_paste_uv_selseq @@ -597,53 +447,35 @@ class OperatorSelSeqPasteUV(bpy.types.Operator): bm = common.create_bmesh(obj) # get UV layer - uv_layers = get_paste_uv_layers(self, obj, bm, props.src_info) + uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info, + self.uv_map) if not uv_layers: return {'CANCELLED'} # get selected face - dest_face_count = 0 - dest_info = {} - for layer in uv_layers: - face_info = [] - for hist in bm.select_history: - if isinstance(hist, bmesh.types.BMFace) and hist.select: - info = { - "uvs": [l[layer].uv.copy() for l in hist.loops], - } - face_info.append(info) - if not face_info: - self.report({'WARNING'}, "No faces are selected") - return {'CANCELLED'} - key = list(props.src_info.keys())[0] - src_face_count = len(props.src_info[key]) - dest_face_count = len(face_info) - if self.strategy == 'N_N' and src_face_count != dest_face_count: - self.report( - {'WARNING'}, - "Number of selected faces is different from copied" + - "(src:{}, dest:{})" - .format(src_face_count, dest_face_count)) - return {'CANCELLED'} - dest_info[layer.name] = face_info + dest_info = impl.get_select_history_dest_face_info(self, bm, uv_layers, + props.src_info, + self.strategy) + if dest_info is None: + return {'CANCELLED'} # paste - ret = paste_uv(self, bm, props.src_info, dest_info, uv_layers, - self.strategy, self.flip_copied_uv, - self.rotate_copied_uv, self.copy_seams) + ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers, + self.strategy, self.flip_copied_uv, + self.rotate_copied_uv, self.copy_seams) if ret: return {'CANCELLED'} - self.report({'INFO'}, "{} face(s) are pasted".format(dest_face_count)) + face_count = len(props.src_info[list(dest_info.keys())[0]]) + self.report({'INFO'}, "{} face(s) are pasted".format(face_count)) bmesh.update_edit_mesh(obj.data) - if self.copy_seams is True: - obj.data.show_edge_seams = True return {'FINISHED'} -class MenuSelSeqPasteUV(bpy.types.Menu): +@BlClassRegistry() +class MUV_MT_CopyPasteUV_SelSeqPasteUV(bpy.types.Menu): """ Menu class: Paste UV coordinate by selection sequence """ @@ -658,7 +490,7 @@ class MenuSelSeqPasteUV(bpy.types.Menu): props = sc.muv_props.copy_paste_uv_selseq if not props.src_uvs or not props.src_pin_uvs: return False - return is_valid_context(context) + return impl.is_valid_context(context) def draw(self, context): sc = context.scene @@ -668,24 +500,27 @@ class MenuSelSeqPasteUV(bpy.types.Menu): bm = common.create_bmesh(obj) uv_maps = bm.loops.layers.uv.keys() - ops = layout.operator(OperatorSelSeqPasteUV.bl_idname, + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname, text="[Default]") ops.uv_map = "__default" ops.copy_seams = sc.muv_copy_paste_uv_copy_seams ops.strategy = sc.muv_copy_paste_uv_strategy - ops = layout.operator(OperatorSelSeqPasteUV.bl_idname, text="[New]") + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname, + text="[New]") ops.uv_map = "__new" ops.copy_seams = sc.muv_copy_paste_uv_copy_seams ops.strategy = sc.muv_copy_paste_uv_strategy - ops = layout.operator(OperatorSelSeqPasteUV.bl_idname, text="[All]") + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname, + text="[All]") ops.uv_map = "__all" ops.copy_seams = sc.muv_copy_paste_uv_copy_seams ops.strategy = sc.muv_copy_paste_uv_strategy for m in uv_maps: - ops = layout.operator(OperatorSelSeqPasteUV.bl_idname, text=m) + ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname, + text=m) ops.uv_map = m ops.copy_seams = sc.muv_copy_paste_uv_copy_seams ops.strategy = sc.muv_copy_paste_uv_strategy diff --git a/uv_magic_uv/op/copy_paste_uv_object.py b/uv_magic_uv/op/copy_paste_uv_object.py index eb6d87c91b9adfb1719f578c4a65dd080be6c4e8..d9f42447a538735b5d8c0515cb729e8f249ea14a 100644 --- a/uv_magic_uv/op/copy_paste_uv_object.py +++ b/uv_magic_uv/op/copy_paste_uv_object.py @@ -23,27 +23,24 @@ __status__ = "production" __version__ = "5.2" __date__ = "17 Nov 2018" -import bpy import bmesh +import bpy from bpy.props import ( StringProperty, BoolProperty, ) +from ..impl import copy_paste_uv_impl as impl from .. import common -from .copy_paste_uv import ( - get_copy_uv_layers, - get_paste_uv_layers, - paste_uv -) - +from ..utils.bl_class_registry import BlClassRegistry +from ..utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'OperatorCopyUV', - 'MenuCopyUV', - 'OperatorPasteUV', - 'MenuPasteUV', + 'MUV_OT_CopyPasteUVObject_CopyUV', + 'MUV_MT_CopyPasteUVObject_CopyUV', + 'MUV_OT_CopyPasteUVObject_PasteUV', + 'MUV_MT_CopyPasteUVObject_PasteUV', ] @@ -68,16 +65,10 @@ def is_valid_context(context): return True -def memorize_view_3d_mode(fn): - def __memorize_view_3d_mode(self, context): - mode_orig = bpy.context.object.mode - result = fn(self, context) - bpy.ops.object.mode_set(mode=mode_orig) - return result - return __memorize_view_3d_mode - - +@PropertyClassRegistry() class Properties: + idname = "copy_paste_uv_object" + @classmethod def init_props(cls, scene): class Props(): @@ -97,7 +88,17 @@ class Properties: del scene.muv_copy_paste_uv_object_copy_seams -class OperatorCopyUV(bpy.types.Operator): +def memorize_view_3d_mode(fn): + def __memorize_view_3d_mode(self, context): + mode_orig = bpy.context.object.mode + result = fn(self, context) + bpy.ops.object.mode_set(mode=mode_orig) + return result + return __memorize_view_3d_mode + + +@BlClassRegistry() +class MUV_OT_CopyPasteUVObject_CopyUV(bpy.types.Operator): """ Operation class: Copy UV coordinate among objects """ @@ -107,7 +108,7 @@ class OperatorCopyUV(bpy.types.Operator): bl_description = "Copy UV coordinate (Among Objects)" bl_options = {'REGISTER', 'UNDO'} - uv_map = StringProperty(default="__default", options={'HIDDEN'}) + uv_map: StringProperty(default="__default", options={'HIDDEN'}) @classmethod def poll(cls, context): @@ -124,23 +125,15 @@ class OperatorCopyUV(bpy.types.Operator): bm = common.create_bmesh(obj) # get UV layer - uv_layers = get_copy_uv_layers(self, bm) + uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map) if not uv_layers: return {'CANCELLED'} # get selected face - props.src_info = {} - for layer in uv_layers: - face_info = [] - for face in bm.faces: - if face.select: - info = { - "uvs": [l[layer].uv.copy() for l in face.loops], - "pin_uvs": [l[layer].pin_uv for l in face.loops], - "seams": [l.edge.seam for l in face.loops], - } - face_info.append(info) - props.src_info[layer.name] = face_info + src_info = impl.get_src_face_info(self, bm, uv_layers) + if src_info is None: + return {'CANCELLED'} + props.src_info = src_info self.report({'INFO'}, "{}'s UV coordinates are copied".format(obj.name)) @@ -148,7 +141,8 @@ class OperatorCopyUV(bpy.types.Operator): return {'FINISHED'} -class MenuCopyUV(bpy.types.Menu): +@BlClassRegistry() +class MUV_MT_CopyPasteUVObject_CopyUV(bpy.types.Menu): """ Menu class: Copy UV coordinate among objects """ @@ -164,20 +158,24 @@ class MenuCopyUV(bpy.types.Menu): def draw(self, _): layout = self.layout # create sub menu - uv_maps = bpy.context.active_object.data.uv_textures.keys() + uv_maps = bpy.context.active_object.data.uv_layers.keys() - ops = layout.operator(OperatorCopyUV.bl_idname, text="[Default]") + ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname, + text="[Default]") ops.uv_map = "__default" - ops = layout.operator(OperatorCopyUV.bl_idname, text="[All]") + ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname, + text="[All]") ops.uv_map = "__all" for m in uv_maps: - ops = layout.operator(OperatorCopyUV.bl_idname, text=m) + ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname, + text=m) ops.uv_map = m -class OperatorPasteUV(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_CopyPasteUVObject_PasteUV(bpy.types.Operator): """ Operation class: Paste UV coordinate among objects """ @@ -187,8 +185,8 @@ class OperatorPasteUV(bpy.types.Operator): bl_description = "Paste UV coordinate (Among Objects)" bl_options = {'REGISTER', 'UNDO'} - uv_map = StringProperty(default="__default", options={'HIDDEN'}) - copy_seams = BoolProperty( + uv_map: StringProperty(default="__default", options={'HIDDEN'}) + copy_seams: BoolProperty( name="Seams", description="Copy Seams", default=True @@ -213,52 +211,35 @@ class OperatorPasteUV(bpy.types.Operator): return {'CANCELLED'} for o in bpy.data.objects: - if not hasattr(o.data, "uv_textures") or not o.select: + if not hasattr(o.data, "uv_layers") or not o.select_get(): continue bpy.ops.object.mode_set(mode='OBJECT') - bpy.context.scene.objects.active = o + bpy.context.view_layer.objects.active = o bpy.ops.object.mode_set(mode='EDIT') obj = context.active_object bm = common.create_bmesh(obj) # get UV layer - uv_layers = get_paste_uv_layers(self, obj, bm, props.src_info) + uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info, + self.uv_map) if not uv_layers: return {'CANCELLED'} # get selected face - dest_info = {} - for layer in uv_layers: - face_info = [] - for face in bm.faces: - if face.select: - info = { - "uvs": [l[layer].uv.copy() for l in face.loops], - } - face_info.append(info) - key = list(props.src_info.keys())[0] - src_face_count = len(props.src_info[key]) - dest_face_count = len(face_info) - if src_face_count != dest_face_count: - self.report( - {'WARNING'}, - "Number of selected faces is different from copied" + - "(src:{}, dest:{})" - .format(src_face_count, dest_face_count)) - return {'CANCELLED'} - dest_info[layer.name] = face_info + dest_info = impl.get_dest_face_info(self, bm, uv_layers, + props.src_info, 'N_N') + if dest_info is None: + return {'CANCELLED'} # paste - ret = paste_uv(self, bm, props.src_info, dest_info, uv_layers, - 'N_N', 0, 0, self.copy_seams) + ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers, + 'N_N', 0, 0, self.copy_seams) if ret: return {'CANCELLED'} bmesh.update_edit_mesh(obj.data) - if self.copy_seams is True: - obj.data.show_edge_seams = True self.report( {'INFO'}, "{}'s UV coordinates are pasted".format(obj.name)) @@ -266,7 +247,8 @@ class OperatorPasteUV(bpy.types.Operator): return {'FINISHED'} -class MenuPasteUV(bpy.types.Menu): +@BlClassRegistry() +class MUV_MT_CopyPasteUVObject_PasteUV(bpy.types.Menu): """ Menu class: Paste UV coordinate among objects """ @@ -289,22 +271,26 @@ class MenuPasteUV(bpy.types.Menu): # create sub menu uv_maps = [] for obj in bpy.data.objects: - if hasattr(obj.data, "uv_textures") and obj.select: - uv_maps.extend(obj.data.uv_textures.keys()) + if hasattr(obj.data, "uv_layers") and obj.select_get(): + uv_maps.extend(obj.data.uv_layers.keys()) - ops = layout.operator(OperatorPasteUV.bl_idname, text="[Default]") + ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname, + text="[Default]") ops.uv_map = "__default" ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams - ops = layout.operator(OperatorPasteUV.bl_idname, text="[New]") + ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname, + text="[New]") ops.uv_map = "__new" ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams - ops = layout.operator(OperatorPasteUV.bl_idname, text="[All]") + ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname, + text="[All]") ops.uv_map = "__all" ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams for m in uv_maps: - ops = layout.operator(OperatorPasteUV.bl_idname, text=m) + ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname, + text=m) ops.uv_map = m ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams diff --git a/uv_magic_uv/op/copy_paste_uv_uvedit.py b/uv_magic_uv/op/copy_paste_uv_uvedit.py index e591b5f1ee579d1bc66ebdebca66228a26624e98..719687a6016e510212c10ac05f2c9fa8994bda7d 100644 --- a/uv_magic_uv/op/copy_paste_uv_uvedit.py +++ b/uv_magic_uv/op/copy_paste_uv_uvedit.py @@ -18,52 +18,29 @@ # # ##### END GPL LICENSE BLOCK ##### -__author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester" +__author__ = "imdjs, Nutti <nutti.metro@gmail.com>" __status__ = "production" __version__ = "5.2" __date__ = "17 Nov 2018" -import math -from math import atan2, sin, cos - import bpy -import bmesh -from mathutils import Vector -from .. import common +from ..utils.bl_class_registry import BlClassRegistry +from ..utils.property_class_registry import PropertyClassRegistry +from ..impl import copy_paste_uv_uvedit_impl as impl __all__ = [ 'Properties', - 'OperatorCopyUV', - 'OperatorPasteUV', + 'MUV_OT_CopyPasteUVUVEdit_CopyUV', + 'MUV_OT_CopyPasteUVUVEdit_PasteUV', ] -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 - - +@PropertyClassRegistry() class Properties: + idname = "copy_paste_uv_uvedit" + @classmethod def init_props(cls, scene): class Props(): @@ -76,7 +53,8 @@ class Properties: del scene.muv_props.copy_paste_uv_uvedit -class OperatorCopyUV(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_CopyPasteUVUVEdit_CopyUV(bpy.types.Operator): """ Operation class: Copy UV coordinate on UV/Image Editor """ @@ -86,38 +64,19 @@ class OperatorCopyUV(bpy.types.Operator): bl_description = "Copy UV coordinate (only selected in UV/Image Editor)" bl_options = {'REGISTER', 'UNDO'} + def __init__(self): + self.__impl = impl.CopyUVImpl() + @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) + return impl.CopyUVImpl.poll(context) def execute(self, context): - props = context.scene.muv_props.copy_paste_uv_uvedit - obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - uv_layer = bm.loops.layers.uv.verify() - if common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() - - props.src_uvs = [] - for face in bm.faces: - if not face.select: - continue - skip = False - for l in face.loops: - if not l[uv_layer].select: - skip = True - break - if skip: - continue - props.src_uvs.append([l[uv_layer].uv.copy() for l in face.loops]) - - return {'FINISHED'} - - -class OperatorPasteUV(bpy.types.Operator): + return self.__impl.execute(self, context) + + +@BlClassRegistry() +class MUV_OT_CopyPasteUVUVEdit_PasteUV(bpy.types.Operator): """ Operation class: Paste UV coordinate on UV/Image Editor """ @@ -127,72 +86,12 @@ class OperatorPasteUV(bpy.types.Operator): bl_description = "Paste UV coordinate (only selected in UV/Image Editor)" bl_options = {'REGISTER', 'UNDO'} + def __init__(self): + self.__impl = impl.PasteUVImpl() + @classmethod def poll(cls, context): - # we can not get area/space/region from console - if common.is_console_mode(): - return True - sc = context.scene - props = sc.muv_props.copy_paste_uv_uvedit - if not props.src_uvs: - return False - return is_valid_context(context) + return impl.PasteUVImpl.poll(context) def execute(self, context): - props = context.scene.muv_props.copy_paste_uv_uvedit - obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - uv_layer = bm.loops.layers.uv.verify() - if common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() - - dest_uvs = [] - dest_face_indices = [] - for face in bm.faces: - if not face.select: - continue - skip = False - for l in face.loops: - if not l[uv_layer].select: - skip = True - break - if skip: - continue - dest_face_indices.append(face.index) - uvs = [l[uv_layer].uv.copy() for l in face.loops] - dest_uvs.append(uvs) - - for suvs, duvs in zip(props.src_uvs, dest_uvs): - src_diff = suvs[1] - suvs[0] - dest_diff = duvs[1] - duvs[0] - - src_base = suvs[0] - dest_base = duvs[0] - - src_rad = atan2(src_diff.y, src_diff.x) - dest_rad = atan2(dest_diff.y, dest_diff.x) - if src_rad < dest_rad: - radian = dest_rad - src_rad - elif src_rad > dest_rad: - radian = math.pi * 2 - (src_rad - dest_rad) - else: # src_rad == dest_rad - radian = 0.0 - - ratio = dest_diff.length / src_diff.length - break - - for suvs, fidx in zip(props.src_uvs, dest_face_indices): - for l, suv in zip(bm.faces[fidx].loops, suvs): - base = suv - src_base - radian_ref = atan2(base.y, base.x) - radian_fin = (radian + radian_ref) - length = base.length - turn = Vector((length * cos(radian_fin), - length * sin(radian_fin))) - target_uv = Vector((turn.x * ratio, turn.y * ratio)) + \ - dest_base - l[uv_layer].uv = target_uv - - bmesh.update_edit_mesh(obj.data) - - return {'FINISHED'} + return self.__impl.execute(self, context) diff --git a/uv_magic_uv/op/flip_rotate_uv.py b/uv_magic_uv/op/flip_rotate_uv.py index 751bb8fb7b378ef885ad4ef11caf2b70b66c47e7..d16370528b1927d9df1fadf5bd713253036dd1a4 100644 --- a/uv_magic_uv/op/flip_rotate_uv.py +++ b/uv_magic_uv/op/flip_rotate_uv.py @@ -31,36 +31,20 @@ from bpy.props import ( ) from .. import common - +from ..utils.bl_class_registry import BlClassRegistry +from ..utils.property_class_registry import PropertyClassRegistry +from ..impl import flip_rotate_impl as impl __all__ = [ 'Properties', - 'Operator', + 'MUV_OT_FlipRotate', ] -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 - - # only 'VIEW_3D' space is allowed to execute - for space in context.area.spaces: - if space.type == 'VIEW_3D': - break - else: - return False - - return True - - +@PropertyClassRegistry() class Properties: + idname = "flip_rotate_uv" + @classmethod def init_props(cls, scene): scene.muv_flip_rotate_uv_enabled = BoolProperty( @@ -80,7 +64,8 @@ class Properties: del scene.muv_flip_rotate_uv_seams -class Operator(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_FlipRotate(bpy.types.Operator): """ Operation class: Flip and Rotate UV coordinate """ @@ -90,18 +75,18 @@ class Operator(bpy.types.Operator): bl_description = "Flip/Rotate UV coordinate" bl_options = {'REGISTER', 'UNDO'} - flip = BoolProperty( + flip: BoolProperty( name="Flip UV", description="Flip UV...", default=False ) - rotate = IntProperty( + rotate: IntProperty( default=0, name="Rotate UV", min=0, max=30 ) - seams = BoolProperty( + seams: BoolProperty( name="Seams", description="Seams", default=True @@ -112,7 +97,7 @@ class Operator(bpy.types.Operator): # we can not get area/space/region from console if common.is_console_mode(): return True - return is_valid_context(context) + return impl.is_valid_context(context) def execute(self, context): self.report({'INFO'}, "Flip/Rotate UV") @@ -122,61 +107,24 @@ class Operator(bpy.types.Operator): bm.faces.ensure_lookup_table() # get UV layer - if not bm.loops.layers.uv: - self.report({'WARNING'}, "Object must have more than one UV map") + uv_layer = impl.get_uv_layer(self, bm) + if not uv_layer: return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() # get selected face - dest_uvs = [] - dest_pin_uvs = [] - dest_seams = [] - dest_face_indices = [] - for face in bm.faces: - if face.select: - dest_face_indices.append(face.index) - uvs = [l[uv_layer].uv.copy() for l in face.loops] - pin_uvs = [l[uv_layer].pin_uv for l in face.loops] - seams = [l.edge.seam for l in face.loops] - dest_uvs.append(uvs) - dest_pin_uvs.append(pin_uvs) - dest_seams.append(seams) - if not dest_uvs or not dest_pin_uvs: - self.report({'WARNING'}, "No faces are selected") + src_info = impl.get_src_face_info(self, bm, [uv_layer], True) + if not src_info: return {'CANCELLED'} - self.report({'INFO'}, "%d face(s) are selected" % len(dest_uvs)) + + face_count = len(src_info[list(src_info.keys())[0]]) + self.report({'INFO'}, "{} face(s) are selected".format(face_count)) # paste - for idx, duvs, dpuvs, dss in zip(dest_face_indices, dest_uvs, - dest_pin_uvs, dest_seams): - duvs_fr = [uv for uv in duvs] - dpuvs_fr = [pin_uv for pin_uv in dpuvs] - dss_fr = [s for s in dss] - # flip UVs - if self.flip is True: - duvs_fr.reverse() - dpuvs_fr.reverse() - dss_fr.reverse() - # rotate UVs - for _ in range(self.rotate): - uv = duvs_fr.pop() - pin_uv = dpuvs_fr.pop() - s = dss_fr.pop() - duvs_fr.insert(0, uv) - dpuvs_fr.insert(0, pin_uv) - dss_fr.insert(0, s) - # paste UVs - for l, duv, dpuv, ds in zip( - bm.faces[idx].loops, duvs_fr, dpuvs_fr, dss_fr): - l[uv_layer].uv = duv - l[uv_layer].pin_uv = dpuv - if self.seams is True: - l.edge.seam = ds - - self.report({'INFO'}, "%d face(s) are flipped/rotated" % len(dest_uvs)) + ret = impl.paste_uv(self, bm, src_info, src_info, [uv_layer], 'N_N', + self.flip, self.rotate, self.seams) + if ret: + return {'CANCELLED'} bmesh.update_edit_mesh(obj.data) - if self.seams is True: - obj.data.show_edge_seams = True return {'FINISHED'} diff --git a/uv_magic_uv/op/mirror_uv.py b/uv_magic_uv/op/mirror_uv.py index 11ad2bcaaf93a2667d17c8c952865debd3698f36..6793ca232e2bdcafbb4a95c02dbc1edc0cd3bbbb 100644 --- a/uv_magic_uv/op/mirror_uv.py +++ b/uv_magic_uv/op/mirror_uv.py @@ -29,40 +29,22 @@ from bpy.props import ( FloatProperty, BoolProperty, ) -import bmesh -from mathutils import Vector -from .. import common +from ..utils.bl_class_registry import BlClassRegistry +from ..utils.property_class_registry import PropertyClassRegistry +from ..impl import mirror_uv_impl as impl __all__ = [ 'Properties', - 'Operator', + 'MUV_OT_MirrorUV', ] -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 - - # only 'VIEW_3D' space is allowed to execute - for space in context.area.spaces: - if space.type == 'VIEW_3D': - break - else: - return False - - return True - - +@PropertyClassRegistry() class Properties: + idname = "mirror_uv" + @classmethod def init_props(cls, scene): scene.muv_mirror_uv_enabled = BoolProperty( @@ -87,7 +69,8 @@ class Properties: del scene.muv_mirror_uv_axis -class Operator(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_MirrorUV(bpy.types.Operator): """ Operation class: Mirror UV """ @@ -96,7 +79,7 @@ class Operator(bpy.types.Operator): bl_label = "Mirror UV" bl_options = {'REGISTER', 'UNDO'} - axis = EnumProperty( + axis: EnumProperty( items=( ('X', "X", "Mirror Along X axis"), ('Y', "Y", "Mirror Along Y axis"), @@ -106,7 +89,7 @@ class Operator(bpy.types.Operator): description="Mirror Axis", default='X' ) - error = FloatProperty( + error: FloatProperty( name="Error", description="Error threshold", default=0.001, @@ -116,95 +99,12 @@ class Operator(bpy.types.Operator): soft_max=1.0 ) - def __is_vector_similar(self, v1, v2, error): - """ - Check if two vectors are similar, within an error threshold - """ - within_err_x = abs(v2.x - v1.x) < error - within_err_y = abs(v2.y - v1.y) < error - within_err_z = abs(v2.z - v1.z) < error - - return within_err_x and within_err_y and within_err_z - - def __mirror_uvs(self, uv_layer, src, dst, axis, error): - """ - Copy UV coordinates from one UV face to another - """ - for sl in src.loops: - suv = sl[uv_layer].uv.copy() - svco = sl.vert.co.copy() - for dl in dst.loops: - dvco = dl.vert.co.copy() - if axis == 'X': - dvco.x = -dvco.x - elif axis == 'Y': - dvco.y = -dvco.y - elif axis == 'Z': - dvco.z = -dvco.z - - if self.__is_vector_similar(svco, dvco, error): - dl[uv_layer].uv = suv.copy() - - def __get_face_center(self, face): - """ - Get center coordinate of the face - """ - center = Vector((0.0, 0.0, 0.0)) - for v in face.verts: - center = center + v.co - - return center / len(face.verts) + def __init__(self): + self.__impl = impl.MirrorUVImpl() @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) + return impl.MirrorUVImpl.poll(context) def execute(self, context): - obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - - error = self.error - axis = self.axis - - if common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() - 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() - - faces = [f for f in bm.faces if f.select] - for f_dst in faces: - count = len(f_dst.verts) - for f_src in bm.faces: - # check if this is a candidate to do mirror UV - if f_src.index == f_dst.index: - continue - if count != len(f_src.verts): - continue - - # test if the vertices x values are the same sign - dst = self.__get_face_center(f_dst) - src = self.__get_face_center(f_src) - if (dst.x > 0 and src.x > 0) or (dst.x < 0 and src.x < 0): - continue - - # invert source axis - if axis == 'X': - src.x = -src.x - elif axis == 'Y': - src.y = -src.z - elif axis == 'Z': - src.z = -src.z - - # do mirror UV - if self.__is_vector_similar(dst, src, error): - self.__mirror_uvs( - uv_layer, f_src, f_dst, self.axis, self.error) - - bmesh.update_edit_mesh(obj.data) - - return {'FINISHED'} + return self.__impl.execute(self, context) diff --git a/uv_magic_uv/op/move_uv.py b/uv_magic_uv/op/move_uv.py index a229ae34ddc2bb8727a07631c9eb1aed2b31390d..653918d350e20c1f82e3f7b110efe73e7900dd10 100644 --- a/uv_magic_uv/op/move_uv.py +++ b/uv_magic_uv/op/move_uv.py @@ -24,40 +24,23 @@ __version__ = "5.2" __date__ = "17 Nov 2018" import bpy -import bmesh -from mathutils import Vector from bpy.props import BoolProperty -from .. import common +from ..impl import move_uv_impl as impl +from ..utils.bl_class_registry import BlClassRegistry +from ..utils.property_class_registry import PropertyClassRegistry + __all__ = [ 'Properties', - 'Operator', + 'MUV_OT_MoveUV', ] -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 - - # only 'VIEW_3D' space is allowed to execute - for space in context.area.spaces: - if space.type == 'VIEW_3D': - break - else: - return False - - return True - - +@PropertyClassRegistry() class Properties: + idname = "move_uv" + @classmethod def init_props(cls, scene): scene.muv_move_uv_enabled = BoolProperty( @@ -71,7 +54,8 @@ class Properties: del scene.muv_move_uv_enabled -class Operator(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_MoveUV(bpy.types.Operator): """ Operator class: Move UV """ @@ -80,108 +64,19 @@ class Operator(bpy.types.Operator): 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 + self.__impl = impl.MoveUVImpl() @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) + return impl.MoveUVImpl.poll(context) @classmethod def is_running(cls, _): - return cls.__running - - def __find_uv(self, context): - bm = bmesh.from_edit_mesh(context.object.data) - topology_dict = [] - uvs = [] - active_uv = bm.loops.layers.uv.active - 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 + return impl.MoveUVImpl.is_running(_) def modal(self, context, 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 = context.object - bm = bmesh.from_edit_mesh(obj.data) - active_uv = bm.loops.layers.uv.active - for fidx, vidx in self.__topology_dict: - l = bm.faces[fidx].loops[vidx] - l[active_uv].uv = l[active_uv].uv + dv - bmesh.update_edit_mesh(obj.data) - - # check mouse preference - if context.user_preferences.inputs.select_mouse == 'RIGHT': - confirm_btn = 'LEFTMOUSE' - cancel_btn = 'RIGHTMOUSE' - else: - confirm_btn = 'RIGHTMOUSE' - cancel_btn = 'LEFTMOUSE' - - # 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 - Operator.__running = False - return {'FINISHED'} - # confirmed - if event.type == confirm_btn and event.value == 'PRESS': - Operator.__running = False - return {'FINISHED'} - - return {'RUNNING_MODAL'} + return self.__impl.modal(self, context, event) def execute(self, context): - Operator.__running = True - self.__operating = False - self.__first_time = True - - context.window_manager.modal_handler_add(self) - self.__topology_dict, self.__ini_uvs = self.__find_uv(context) - - if context.area: - context.area.tag_redraw() - - return {'RUNNING_MODAL'} + return self.__impl.execute(self, context) diff --git a/uv_magic_uv/op/transfer_uv.py b/uv_magic_uv/op/transfer_uv.py index ef6fc3bebc43a034a16ce0367109b75c49648962..db05b34379ef8c6da680abc5292b4515802c23e5 100644 --- a/uv_magic_uv/op/transfer_uv.py +++ b/uv_magic_uv/op/transfer_uv.py @@ -23,43 +23,27 @@ __status__ = "production" __version__ = "5.2" __date__ = "17 Nov 2018" -from collections import OrderedDict - import bpy import bmesh from bpy.props import BoolProperty from .. import common +from ..impl import transfer_uv_impl as impl +from ..utils.bl_class_registry import BlClassRegistry +from ..utils.property_class_registry import PropertyClassRegistry __all__ = [ - 'OperatorCopyUV', - 'OperatorPasteUV', + 'Properties', + 'MUV_OT_TransferUV_CopyUV', + 'MUV_OT_TransferUV_PasteUV', ] -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 - - # only 'VIEW_3D' space is allowed to execute - for space in context.area.spaces: - if space.type == 'VIEW_3D': - break - else: - return False - - return True - - +@PropertyClassRegistry() class Properties: + idname = "transfer_uv" + @classmethod def init_props(cls, scene): class Props(): @@ -90,7 +74,8 @@ class Properties: del scene.muv_transfer_uv_copy_seams -class OperatorCopyUV(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_TransferUV_CopyUV(bpy.types.Operator): """ Operation class: Transfer UV copy Topological based copy @@ -106,56 +91,30 @@ class OperatorCopyUV(bpy.types.Operator): # we can not get area/space/region from console if common.is_console_mode(): return True - return is_valid_context(context) + return impl.is_valid_context(context) def execute(self, context): props = context.scene.muv_props.transfer_uv - active_obj = context.scene.objects.active + active_obj = context.active_object bm = bmesh.from_edit_mesh(active_obj.data) - if common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() + bm.faces.ensure_lookup_table() - # get UV layer - if not bm.loops.layers.uv: - self.report({'WARNING'}, "Object must have more than one UV map") + uv_layer = impl.get_uv_layer(self, bm) + if uv_layer is None: return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - props.topology_copied = [] - - # get selected faces - active_face = bm.faces.active - sel_faces = [face for face in bm.faces if face.select] - if len(sel_faces) != 2: - self.report({'WARNING'}, "Two faces must be selected") - return {'CANCELLED'} - if not active_face or active_face not in sel_faces: - self.report({'WARNING'}, "Two faces must be active") - return {'CANCELLED'} - - # parse all faces according to selection - active_face_nor = active_face.normal.copy() - all_sorted_faces = main_parse( - self, uv_layer, sel_faces, active_face, - active_face_nor) - - if all_sorted_faces: - for face_data in all_sorted_faces.values(): - edges = face_data[1] - uv_loops = face_data[2] - uvs = [l.uv.copy() for l in uv_loops] - pin_uvs = [l.pin_uv for l in uv_loops] - seams = [e.seam for e in edges] - props.topology_copied.append([uvs, pin_uvs, seams]) - else: + faces = impl.get_selected_src_faces(self, bm, uv_layer) + if faces is None: return {'CANCELLED'} + props.topology_copied = faces bmesh.update_edit_mesh(active_obj.data) return {'FINISHED'} -class OperatorPasteUV(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_TransferUV_PasteUV(bpy.types.Operator): """ Operation class: Transfer UV paste Topological based paste @@ -166,12 +125,12 @@ class OperatorPasteUV(bpy.types.Operator): bl_description = "Transfer UV Paste UV (Topological based paste)" bl_options = {'REGISTER', 'UNDO'} - invert_normals = BoolProperty( + invert_normals: BoolProperty( name="Invert Normals", description="Invert Normals", default=False ) - copy_seams = BoolProperty( + copy_seams: BoolProperty( name="Copy Seams", description="Copy Seams", default=True @@ -186,253 +145,24 @@ class OperatorPasteUV(bpy.types.Operator): props = sc.muv_props.transfer_uv if not props.topology_copied: return False - return is_valid_context(context) + return impl.is_valid_context(context) def execute(self, context): props = context.scene.muv_props.transfer_uv - active_obj = context.scene.objects.active + active_obj = context.active_object bm = bmesh.from_edit_mesh(active_obj.data) - if common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() + bm.faces.ensure_lookup_table() # get UV layer - if not bm.loops.layers.uv: - self.report({'WARNING'}, "Object must have more than one UV map") + uv_layer = impl.get_uv_layer(self, bm) + if uv_layer is None: return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - # get selection history - all_sel_faces = [ - e for e in bm.select_history - if isinstance(e, bmesh.types.BMFace) and e.select] - if len(all_sel_faces) % 2 != 0: - self.report({'WARNING'}, "Two faces must be selected") + ret = impl.paste_uv(self, bm, uv_layer, props.topology_copied, + self.invert_normals, self.copy_seams) + if ret: return {'CANCELLED'} - # parse selection history - for i, _ in enumerate(all_sel_faces): - if (i == 0) or (i % 2 == 0): - continue - sel_faces = [all_sel_faces[i - 1], all_sel_faces[i]] - active_face = all_sel_faces[i] - - # parse all faces according to selection history - active_face_nor = active_face.normal.copy() - if self.invert_normals: - active_face_nor.negate() - all_sorted_faces = main_parse( - self, uv_layer, sel_faces, active_face, - active_face_nor) - - if all_sorted_faces: - # check amount of copied/pasted faces - if len(all_sorted_faces) != len(props.topology_copied): - self.report( - {'WARNING'}, - "Mesh has different amount of faces" - ) - return {'CANCELLED'} - - for j, face_data in enumerate(all_sorted_faces.values()): - copied_data = props.topology_copied[j] - - # check amount of copied/pasted verts - if len(copied_data[0]) != len(face_data[2]): - bpy.ops.mesh.select_all(action='DESELECT') - # select problematic face - list(all_sorted_faces.keys())[j].select = True - self.report( - {'WARNING'}, - "Face have different amount of vertices" - ) - return {'FINISHED'} - - for k, (edge, uvloop) in enumerate(zip(face_data[1], - face_data[2])): - uvloop.uv = copied_data[0][k] - uvloop.pin_uv = copied_data[1][k] - if self.copy_seams: - edge.seam = copied_data[2][k] - else: - return {'CANCELLED'} - bmesh.update_edit_mesh(active_obj.data) - if self.copy_seams: - active_obj.data.show_edge_seams = True return {'FINISHED'} - - -def main_parse( - self, uv_layer, sel_faces, - active_face, active_face_nor): - all_sorted_faces = OrderedDict() # This is the main stuff - - used_verts = set() - used_edges = set() - - faces_to_parse = [] - - # get shared edge of two faces - cross_edges = [] - for edge in active_face.edges: - if edge in sel_faces[0].edges and edge in sel_faces[1].edges: - cross_edges.append(edge) - - # parse two selected faces - if cross_edges and len(cross_edges) == 1: - shared_edge = cross_edges[0] - vert1 = None - vert2 = None - - dot_n = active_face_nor.normalized() - edge_vec_1 = (shared_edge.verts[1].co - shared_edge.verts[0].co) - edge_vec_len = edge_vec_1.length - edge_vec_1 = edge_vec_1.normalized() - - af_center = active_face.calc_center_median() - af_vec = shared_edge.verts[0].co + (edge_vec_1 * (edge_vec_len * 0.5)) - af_vec = (af_vec - af_center).normalized() - - if af_vec.cross(edge_vec_1).dot(dot_n) > 0: - vert1 = shared_edge.verts[0] - vert2 = shared_edge.verts[1] - else: - vert1 = shared_edge.verts[1] - vert2 = shared_edge.verts[0] - - # get active face stuff and uvs - face_stuff = get_other_verts_edges( - active_face, vert1, vert2, shared_edge, uv_layer) - all_sorted_faces[active_face] = face_stuff - used_verts.update(active_face.verts) - used_edges.update(active_face.edges) - - # get first selected face stuff and uvs as they share shared_edge - second_face = sel_faces[0] - if second_face is active_face: - second_face = sel_faces[1] - face_stuff = get_other_verts_edges( - second_face, vert1, vert2, shared_edge, uv_layer) - all_sorted_faces[second_face] = face_stuff - used_verts.update(second_face.verts) - used_edges.update(second_face.edges) - - # first Grow - faces_to_parse.append(active_face) - faces_to_parse.append(second_face) - - else: - self.report({'WARNING'}, "Two faces should share one edge") - return None - - # parse all faces - while True: - new_parsed_faces = [] - if not faces_to_parse: - break - for face in faces_to_parse: - face_stuff = all_sorted_faces.get(face) - new_faces = parse_faces( - face, face_stuff, used_verts, used_edges, all_sorted_faces, - uv_layer) - if new_faces == 'CANCELLED': - self.report({'WARNING'}, "More than 2 faces share edge") - return None - - new_parsed_faces += new_faces - faces_to_parse = new_parsed_faces - - return all_sorted_faces - - -def parse_faces( - check_face, face_stuff, used_verts, used_edges, all_sorted_faces, - uv_layer): - """recurse faces around the new_grow only""" - - new_shared_faces = [] - for sorted_edge in face_stuff[1]: - shared_faces = sorted_edge.link_faces - if shared_faces: - if len(shared_faces) > 2: - bpy.ops.mesh.select_all(action='DESELECT') - for face_sel in shared_faces: - face_sel.select = True - shared_faces = [] - return 'CANCELLED' - - clear_shared_faces = get_new_shared_faces( - check_face, sorted_edge, shared_faces, all_sorted_faces.keys()) - if clear_shared_faces: - shared_face = clear_shared_faces[0] - # get vertices of the edge - vert1 = sorted_edge.verts[0] - vert2 = sorted_edge.verts[1] - - common.debug_print(face_stuff[0], vert1, vert2) - if face_stuff[0].index(vert1) > face_stuff[0].index(vert2): - vert1 = sorted_edge.verts[1] - vert2 = sorted_edge.verts[0] - - common.debug_print(shared_face.verts, vert1, vert2) - new_face_stuff = get_other_verts_edges( - shared_face, vert1, vert2, sorted_edge, uv_layer) - all_sorted_faces[shared_face] = new_face_stuff - used_verts.update(shared_face.verts) - used_edges.update(shared_face.edges) - - if common.is_debug_mode(): - shared_face.select = True # test which faces are parsed - - new_shared_faces.append(shared_face) - - return new_shared_faces - - -def get_new_shared_faces(orig_face, shared_edge, check_faces, used_faces): - shared_faces = [] - - for face in check_faces: - is_shared_edge = shared_edge in face.edges - not_used = face not in used_faces - not_orig = face is not orig_face - not_hide = face.hide is False - if is_shared_edge and not_used and not_orig and not_hide: - shared_faces.append(face) - - return shared_faces - - -def get_other_verts_edges(face, vert1, vert2, first_edge, uv_layer): - face_edges = [first_edge] - face_verts = [vert1, vert2] - face_loops = [] - - other_edges = [edge for edge in face.edges if edge not in face_edges] - - for _ in range(len(other_edges)): - found_edge = None - # get sorted verts and edges - for edge in other_edges: - if face_verts[-1] in edge.verts: - other_vert = edge.other_vert(face_verts[-1]) - - if other_vert not in face_verts: - face_verts.append(other_vert) - - found_edge = edge - if found_edge not in face_edges: - face_edges.append(edge) - break - - other_edges.remove(found_edge) - - # get sorted uvs - for vert in face_verts: - for loop in face.loops: - if loop.vert is vert: - face_loops.append(loop[uv_layer]) - break - - return [face_verts, face_edges, face_loops] diff --git a/uv_magic_uv/op/uvw.py b/uv_magic_uv/op/uvw.py index 44858187ae7aa8d894c6f61e00f8a09598ae7f22..c97e0e042e2f1b3f3c47264306a381ed393d96ff 100644 --- a/uv_magic_uv/op/uvw.py +++ b/uv_magic_uv/op/uvw.py @@ -23,8 +23,6 @@ __status__ = "production" __version__ = "5.2" __date__ = "17 Nov 2018" -from math import sin, cos, pi - import bpy import bmesh from bpy.props import ( @@ -32,40 +30,24 @@ from bpy.props import ( FloatVectorProperty, BoolProperty ) -from mathutils import Vector from .. import common +from ..impl import uvw_impl as impl +from ..utils.bl_class_registry import BlClassRegistry +from ..utils.property_class_registry import PropertyClassRegistry __all__ = [ 'Properties', - 'OperatorBoxMap', - 'OperatorBestPlanerMap', + 'MUV_OT_UVW_BoxMap', + 'MUV_OT_UVW_BestPlanerMap', ] -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 - - # only 'VIEW_3D' space is allowed to execute - for space in context.area.spaces: - if space.type == 'VIEW_3D': - break - else: - return False - - return True - - +@PropertyClassRegistry() class Properties: + idname = "uvw" + @classmethod def init_props(cls, scene): scene.muv_uvw_enabled = BoolProperty( @@ -85,32 +67,33 @@ class Properties: del scene.muv_uvw_assign_uvmap -class OperatorBoxMap(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_UVW_BoxMap(bpy.types.Operator): bl_idname = "uv.muv_uvw_operator_box_map" bl_label = "Box Map" bl_options = {'REGISTER', 'UNDO'} - size = FloatProperty( + size: FloatProperty( name="Size", default=1.0, precision=4 ) - rotation = FloatVectorProperty( + rotation: FloatVectorProperty( name="XYZ Rotation", size=3, default=(0.0, 0.0, 0.0) ) - offset = FloatVectorProperty( + offset: FloatVectorProperty( name="XYZ Offset", size=3, default=(0.0, 0.0, 0.0) ) - tex_aspect = FloatProperty( + tex_aspect: FloatProperty( name="Texture Aspect", default=1.0, precision=4 ) - assign_uvmap = BoolProperty( + assign_uvmap: BoolProperty( name="Assign UVMap", description="Assign UVMap when no UVmaps are available", default=True @@ -121,7 +104,7 @@ class OperatorBoxMap(bpy.types.Operator): # we can not get area/space/region from console if common.is_console_mode(): return True - return is_valid_context(context) + return impl.is_valid_context(context) def execute(self, context): obj = context.active_object @@ -130,102 +113,43 @@ class OperatorBoxMap(bpy.types.Operator): bm.faces.ensure_lookup_table() # get UV layer - if not bm.loops.layers.uv: - if self.assign_uvmap: - bm.loops.layers.uv.new() - else: - self.report( - {'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - - scale = 1.0 / self.size - - sx = 1.0 * scale - sy = 1.0 * scale - sz = 1.0 * scale - ofx = self.offset[0] - ofy = self.offset[1] - ofz = self.offset[2] - rx = self.rotation[0] * pi / 180.0 - ry = self.rotation[1] * pi / 180.0 - rz = self.rotation[2] * pi / 180.0 - aspect = self.tex_aspect - - sel_faces = [f for f in bm.faces if f.select] - - # update UV coordinate - for f in sel_faces: - n = f.normal - for l in f.loops: - co = l.vert.co - x = co.x * sx - y = co.y * sy - z = co.z * sz - - # X-plane - if abs(n[0]) >= abs(n[1]) and abs(n[0]) >= abs(n[2]): - if n[0] >= 0.0: - u = (y - ofy) * cos(rx) + (z - ofz) * sin(rx) - v = -(y * aspect - ofy) * sin(rx) +\ - (z * aspect - ofz) * cos(rx) - else: - u = -(y - ofy) * cos(rx) + (z - ofz) * sin(rx) - v = (y * aspect - ofy) * sin(rx) +\ - (z * aspect - ofz) * cos(rx) - # Y-plane - elif abs(n[1]) >= abs(n[0]) and abs(n[1]) >= abs(n[2]): - if n[1] >= 0.0: - u = -(x - ofx) * cos(ry) + (z - ofz) * sin(ry) - v = (x * aspect - ofx) * sin(ry) +\ - (z * aspect - ofz) * cos(ry) - else: - u = (x - ofx) * cos(ry) + (z - ofz) * sin(ry) - v = -(x * aspect - ofx) * sin(ry) +\ - (z * aspect - ofz) * cos(ry) - # Z-plane - else: - if n[2] >= 0.0: - u = (x - ofx) * cos(rz) + (y - ofy) * sin(rz) - v = -(x * aspect - ofx) * sin(rz) +\ - (y * aspect - ofy) * cos(rz) - else: - u = -(x - ofx) * cos(rz) - (y + ofy) * sin(rz) - v = -(x * aspect + ofx) * sin(rz) +\ - (y * aspect - ofy) * cos(rz) - - l[uv_layer].uv = Vector((u, v)) + uv_layer = impl.get_uv_layer(self, bm, self.assign_uvmap) + if not uv_layer: + return {'CANCELLED'} + impl.apply_box_map(bm, uv_layer, self.size, self.offset, + self.rotation, self.tex_aspect) bmesh.update_edit_mesh(obj.data) return {'FINISHED'} -class OperatorBestPlanerMap(bpy.types.Operator): +@BlClassRegistry() +class MUV_OT_UVW_BestPlanerMap(bpy.types.Operator): bl_idname = "uv.muv_uvw_operator_best_planer_map" bl_label = "Best Planer Map" bl_options = {'REGISTER', 'UNDO'} - size = FloatProperty( + size: FloatProperty( name="Size", default=1.0, precision=4 ) - rotation = FloatProperty( + rotation: FloatProperty( name="XY Rotation", default=0.0 ) - offset = FloatVectorProperty( + offset: FloatVectorProperty( name="XY Offset", size=2, default=(0.0, 0.0) ) - tex_aspect = FloatProperty( + tex_aspect: FloatProperty( name="Texture Aspect", default=1.0, precision=4 ) - assign_uvmap = BoolProperty( + assign_uvmap: BoolProperty( name="Assign UVMap", description="Assign UVMap when no UVmaps are available", default=True @@ -236,7 +160,7 @@ class OperatorBestPlanerMap(bpy.types.Operator): # we can not get area/space/region from console if common.is_console_mode(): return True - return is_valid_context(context) + return impl.is_valid_context(context) def execute(self, context): obj = context.active_object @@ -245,44 +169,12 @@ class OperatorBestPlanerMap(bpy.types.Operator): bm.faces.ensure_lookup_table() # get UV layer - if not bm.loops.layers.uv: - if self.assign_uvmap: - bm.loops.layers.uv.new() - else: - self.report( - {'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} - - uv_layer = bm.loops.layers.uv.verify() - - scale = 1.0 / self.size - - sx = 1.0 * scale - sy = 1.0 * scale - ofx = self.offset[0] - ofy = self.offset[1] - rz = self.rotation * pi / 180.0 - aspect = self.tex_aspect - - sel_faces = [f for f in bm.faces if f.select] - - # calculate average of normal - n_ave = Vector((0.0, 0.0, 0.0)) - for f in sel_faces: - n_ave = n_ave + f.normal - q = n_ave.rotation_difference(Vector((0.0, 0.0, 1.0))) - - # update UV coordinate - for f in sel_faces: - for l in f.loops: - co = q * l.vert.co - x = co.x * sx - y = co.y * sy - - u = x * cos(rz) - y * sin(rz) + ofx - v = -x * aspect * sin(rz) - y * aspect * cos(rz) + ofy + uv_layer = impl.get_uv_layer(self, bm, self.assign_uvmap) + if not uv_layer: + return {'CANCELLED'} - l[uv_layer].uv = Vector((u, v)) + impl.apply_planer_map(bm, uv_layer, self.size, self.offset, + self.rotation, self.tex_aspect) bmesh.update_edit_mesh(obj.data) diff --git a/uv_magic_uv/preferences.py b/uv_magic_uv/preferences.py index 376258d0c2e03ce294cb18ae2a51468775f8a4d4..3ba943761dfc76fa5340bbf90025f4abadbd8398 100644 --- a/uv_magic_uv/preferences.py +++ b/uv_magic_uv/preferences.py @@ -33,11 +33,10 @@ from bpy.props import ( ) from bpy.types import AddonPreferences -from . import ui from . import op +from . import ui from . import addon_updater_ops - __all__ = [ 'add_builtin_menu', 'remove_builtin_menu', @@ -50,100 +49,50 @@ def view3d_uvmap_menu_fn(self, context): sc = context.scene layout.separator() - layout.label("Copy/Paste UV", icon='IMAGE_COL') + layout.label(text="Copy/Paste UV", icon='IMAGE') # Copy/Paste UV - layout.menu(ui.VIEW3D_MT_uv_map.MenuCopyPasteUV.bl_idname, + layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_CopyPasteUV.bl_idname, text="Copy/Paste UV") # Transfer UV - layout.menu(ui.VIEW3D_MT_uv_map.MenuTransferUV.bl_idname, + layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_TransferUV.bl_idname, text="Transfer UV") layout.separator() - layout.label("UV Manipulation", icon='IMAGE_COL') + layout.label(text="UV Manipulation", icon='IMAGE') # Flip/Rotate UV - ops = layout.operator(op.flip_rotate_uv.Operator.bl_idname, + ops = layout.operator(op.flip_rotate_uv.MUV_OT_FlipRotate.bl_idname, text="Flip/Rotate UV") ops.seams = sc.muv_flip_rotate_uv_seams # Mirror UV - ops = layout.operator(op.mirror_uv.Operator.bl_idname, text="Mirror UV") + ops = layout.operator(op.mirror_uv.MUV_OT_MirrorUV.bl_idname, + text="Mirror UV") ops.axis = sc.muv_mirror_uv_axis # Move UV - layout.operator(op.move_uv.Operator.bl_idname, text="Move UV") - # World Scale UV - layout.menu(ui.VIEW3D_MT_uv_map.MenuWorldScaleUV.bl_idname, - text="World Scale UV") - # Preserve UV - layout.menu(ui.VIEW3D_MT_uv_map.MenuPreserveUVAspect.bl_idname, - text="Preserve UV") - # Texture Lock - layout.menu(ui.VIEW3D_MT_uv_map.MenuTextureLock.bl_idname, - text="Texture Lock") - # Texture Wrap - layout.menu(ui.VIEW3D_MT_uv_map.MenuTextureWrap.bl_idname, - text="Texture Wrap") - # UV Sculpt - layout.prop(sc, "muv_uv_sculpt_enable", text="UV Sculpt") + layout.operator(op.move_uv.MUV_OT_MoveUV.bl_idname, text="Move UV") layout.separator() - layout.label("UV Mapping", icon='IMAGE_COL') - # Unwrap Constraint - ops = layout.operator(op.unwrap_constraint.Operator.bl_idname, - text="Unwrap Constraint") - ops.u_const = sc.muv_unwrap_constraint_u_const - ops.v_const = sc.muv_unwrap_constraint_v_const - # Texture Projection - layout.menu(ui.VIEW3D_MT_uv_map.MenuTextureProjection.bl_idname, - text="Texture Projection") # UVW - layout.menu(ui.VIEW3D_MT_uv_map.MenuUVW.bl_idname, text="UVW") + layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_UVW.bl_idname, text="UVW") def view3d_object_menu_fn(self, _): layout = self.layout layout.separator() + layout.label(text="Copy/Paste UV", icon='IMAGE') # Copy/Paste UV (Among Objecct) - layout.menu(ui.VIEW3D_MT_object.MenuCopyPasteUVObject.bl_idname, + layout.menu(ui.VIEW3D_MT_object.MUV_MT_CopyPasteUV_Object.bl_idname, text="Copy/Paste UV") - layout.label("Copy/Paste UV", icon='IMAGE_COL') -def image_uvs_menu_fn(self, context): +def image_uvs_menu_fn(self, _): layout = self.layout - sc = context.scene - - layout.separator() - # Align UV Cursor - layout.menu(ui.IMAGE_MT_uvs.MenuAlignUVCursor.bl_idname, - text="Align UV Cursor") - # UV Bounding Box - layout.prop(sc, "muv_uv_bounding_box_show", text="UV Bounding Box") - # UV Inspection - layout.menu(ui.IMAGE_MT_uvs.MenuUVInspection.bl_idname, - text="UV Inspection") - layout.label("Editor Enhancement", icon='IMAGE_COL') - - layout.separator() - # Align UV - layout.menu(ui.IMAGE_MT_uvs.MenuAlignUV.bl_idname, text="Align UV") - # Smooth UV - ops = layout.operator(op.smooth_uv.Operator.bl_idname, text="Smooth") - ops.transmission = sc.muv_smooth_uv_transmission - ops.select = sc.muv_smooth_uv_select - ops.mesh_infl = sc.muv_smooth_uv_mesh_infl - # Select UV - layout.menu(ui.IMAGE_MT_uvs.MenuSelectUV.bl_idname, text="Select UV") - # Pack UV - ops = layout.operator(op.pack_uv.Operator.bl_idname, text="Pack UV") - ops.allowable_center_deviation = sc.muv_pack_uv_allowable_center_deviation - ops.allowable_size_deviation = sc.muv_pack_uv_allowable_size_deviation - layout.label("UV Manipulation", icon='IMAGE_COL') layout.separator() # Copy/Paste UV (on UV/Image Editor) - layout.menu(ui.IMAGE_MT_uvs.MenuCopyPasteUVUVEdit.bl_idname, + layout.label(text="Copy/Paste UV", icon='IMAGE') + layout.menu(ui.IMAGE_MT_uvs.MUV_MT_CopyPasteUV_UVEdit.bl_idname, text="Copy/Paste UV") - layout.label("Copy/Paste UV", icon='IMAGE_COL') def add_builtin_menu(): @@ -154,14 +103,14 @@ def add_builtin_menu(): def remove_builtin_menu(): bpy.types.IMAGE_MT_uvs.remove(image_uvs_menu_fn) - bpy.types.VIEW3D_MT_object.remove(view3d_object_menu_fn) + bpy.types.VIEW3D_MT_object.append(view3d_object_menu_fn) bpy.types.VIEW3D_MT_uv_map.remove(view3d_uvmap_menu_fn) class Preferences(AddonPreferences): """Preferences class: Preferences for this add-on""" - bl_idname = __package__ + bl_idname = "uv_magic_uv" def update_enable_builtin_menu(self, _): if self['enable_builtin_menu']: diff --git a/uv_magic_uv/properites.py b/uv_magic_uv/properites.py index e4634e5139932ef792c86b08adf9a72d6e4b253a..60ce26eb10922b797645122d854ad52666941767 100644 --- a/uv_magic_uv/properites.py +++ b/uv_magic_uv/properites.py @@ -24,30 +24,7 @@ __version__ = "5.2" __date__ = "17 Nov 2018" -from .op import ( - align_uv, - align_uv_cursor, - copy_paste_uv, - copy_paste_uv_object, - copy_paste_uv_uvedit, - flip_rotate_uv, - mirror_uv, - move_uv, - pack_uv, - preserve_uv_aspect, - select_uv, - smooth_uv, - texture_lock, - texture_projection, - texture_wrap, - transfer_uv, - unwrap_constraint, - uv_bounding_box, - uv_inspection, - uv_sculpt, - uvw, - world_scale_uv, -) +from .utils.property_class_registry import PropertyClassRegistry __all__ = [ @@ -77,53 +54,9 @@ class MUV_Prefs(): def init_props(scene): scene.muv_props = MUV_Properties() - - align_uv.Properties.init_props(scene) - align_uv_cursor.Properties.init_props(scene) - copy_paste_uv.Properties.init_props(scene) - copy_paste_uv_object.Properties.init_props(scene) - copy_paste_uv_uvedit.Properties.init_props(scene) - flip_rotate_uv.Properties.init_props(scene) - mirror_uv.Properties.init_props(scene) - move_uv.Properties.init_props(scene) - pack_uv.Properties.init_props(scene) - preserve_uv_aspect.Properties.init_props(scene) - select_uv.Properties.init_props(scene) - smooth_uv.Properties.init_props(scene) - texture_lock.Properties.init_props(scene) - texture_projection.Properties.init_props(scene) - texture_wrap.Properties.init_props(scene) - transfer_uv.Properties.init_props(scene) - unwrap_constraint.Properties.init_props(scene) - uv_bounding_box.Properties.init_props(scene) - uv_inspection.Properties.init_props(scene) - uv_sculpt.Properties.init_props(scene) - uvw.Properties.init_props(scene) - world_scale_uv.Properties.init_props(scene) + PropertyClassRegistry.init_props(scene) def clear_props(scene): - align_uv.Properties.del_props(scene) - align_uv_cursor.Properties.del_props(scene) - copy_paste_uv.Properties.del_props(scene) - copy_paste_uv_object.Properties.del_props(scene) - copy_paste_uv_uvedit.Properties.del_props(scene) - flip_rotate_uv.Properties.del_props(scene) - mirror_uv.Properties.del_props(scene) - move_uv.Properties.del_props(scene) - pack_uv.Properties.del_props(scene) - preserve_uv_aspect.Properties.del_props(scene) - select_uv.Properties.del_props(scene) - smooth_uv.Properties.del_props(scene) - texture_lock.Properties.del_props(scene) - texture_projection.Properties.del_props(scene) - texture_wrap.Properties.del_props(scene) - transfer_uv.Properties.del_props(scene) - unwrap_constraint.Properties.del_props(scene) - uv_bounding_box.Properties.del_props(scene) - uv_inspection.Properties.del_props(scene) - uv_sculpt.Properties.del_props(scene) - uvw.Properties.del_props(scene) - world_scale_uv.Properties.del_props(scene) - + PropertyClassRegistry.del_props(scene) del scene.muv_props diff --git a/uv_magic_uv/ui/IMAGE_MT_uvs.py b/uv_magic_uv/ui/IMAGE_MT_uvs.py index 9beb7e2ff46bce1237965fa575c8626d962fbea4..e7dda379cba8e375de9e8f7b826fd45f6ab14ac2 100644 --- a/uv_magic_uv/ui/IMAGE_MT_uvs.py +++ b/uv_magic_uv/ui/IMAGE_MT_uvs.py @@ -24,23 +24,19 @@ __version__ = "5.2" __date__ = "17 Nov 2018" import bpy -from ..op import copy_paste_uv_uvedit -from ..op import align_uv -from ..op import uv_inspection -from ..op import align_uv_cursor -from ..op import select_uv +from ..op import ( + copy_paste_uv_uvedit, +) +from ..utils.bl_class_registry import BlClassRegistry __all__ = [ - 'MenuCopyPasteUVUVEdit', - 'MenuAlignUV', - 'MenuSelectUV', - 'MenuAlignUVCursor', - 'MenuUVInspection', + 'MUV_MT_CopyPasteUV_UVEdit', ] -class MenuCopyPasteUVUVEdit(bpy.types.Menu): +@BlClassRegistry() +class MUV_MT_CopyPasteUV_UVEdit(bpy.types.Menu): """ Menu class: Master menu of Copy/Paste UV coordinate on UV/ImageEditor """ @@ -52,135 +48,9 @@ class MenuCopyPasteUVUVEdit(bpy.types.Menu): def draw(self, _): layout = self.layout - layout.operator(copy_paste_uv_uvedit.OperatorCopyUV.bl_idname, - text="Copy") - layout.operator(copy_paste_uv_uvedit.OperatorPasteUV.bl_idname, - text="Paste") - - -class MenuAlignUV(bpy.types.Menu): - """ - Menu class: Master menu of Align UV - """ - - bl_idname = "uv.muv_align_uv_menu" - bl_label = "Align UV" - bl_description = "Align UV" - - def draw(self, context): - layout = self.layout - sc = context.scene - - ops = layout.operator(align_uv.OperatorCircle.bl_idname, text="Circle") - ops.transmission = sc.muv_align_uv_transmission - ops.select = sc.muv_align_uv_select - - ops = layout.operator(align_uv.OperatorStraighten.bl_idname, - text="Straighten") - ops.transmission = sc.muv_align_uv_transmission - ops.select = sc.muv_align_uv_select - ops.vertical = sc.muv_align_uv_vertical - ops.horizontal = sc.muv_align_uv_horizontal - - ops = layout.operator(align_uv.OperatorAxis.bl_idname, - text="XY-axis") - ops.transmission = sc.muv_align_uv_transmission - ops.select = sc.muv_align_uv_select - ops.vertical = sc.muv_align_uv_vertical - ops.horizontal = sc.muv_align_uv_horizontal - ops.location = sc.muv_align_uv_location - - -class MenuSelectUV(bpy.types.Menu): - """ - Menu class: Master menu of Select UV - """ - - bl_idname = "uv.muv_select_uv_menu" - bl_label = "Select UV" - bl_description = "Select UV" - - def draw(self, _): - layout = self.layout - - layout.operator(select_uv.OperatorSelectOverlapped.bl_idname, - text="Overlapped") - layout.operator(select_uv.OperatorSelectFlipped.bl_idname, - text="Flipped") - - -class MenuAlignUVCursor(bpy.types.Menu): - """ - Menu class: Master menu of Align UV Cursor - """ - - bl_idname = "uv.muv_align_uv_cursor_menu" - bl_label = "Align UV Cursor" - bl_description = "Align UV cursor" - - def draw(self, context): - layout = self.layout - sc = context.scene - - ops = layout.operator(align_uv_cursor.Operator.bl_idname, - text="Left Top") - ops.position = 'LEFT_TOP' - ops.base = sc.muv_align_uv_cursor_align_method - - ops = layout.operator(align_uv_cursor.Operator.bl_idname, - text="Middle Top") - ops.position = 'MIDDLE_TOP' - ops.base = sc.muv_align_uv_cursor_align_method - - ops = layout.operator(align_uv_cursor.Operator.bl_idname, - text="Right Top") - ops.position = 'RIGHT_TOP' - ops.base = sc.muv_align_uv_cursor_align_method - - ops = layout.operator(align_uv_cursor.Operator.bl_idname, - text="Left Middle") - ops.position = 'LEFT_MIDDLE' - ops.base = sc.muv_align_uv_cursor_align_method - - ops = layout.operator(align_uv_cursor.Operator.bl_idname, - text="Center") - ops.position = 'CENTER' - ops.base = sc.muv_align_uv_cursor_align_method - - ops = layout.operator(align_uv_cursor.Operator.bl_idname, - text="Right Middle") - ops.position = 'RIGHT_MIDDLE' - ops.base = sc.muv_align_uv_cursor_align_method - - ops = layout.operator(align_uv_cursor.Operator.bl_idname, - text="Left Bottom") - ops.position = 'LEFT_BOTTOM' - ops.base = sc.muv_align_uv_cursor_align_method - - ops = layout.operator(align_uv_cursor.Operator.bl_idname, - text="Middle Bottom") - ops.position = 'MIDDLE_BOTTOM' - ops.base = sc.muv_align_uv_cursor_align_method - - ops = layout.operator(align_uv_cursor.Operator.bl_idname, - text="Right Bottom") - ops.position = 'RIGHT_BOTTOM' - ops.base = sc.muv_align_uv_cursor_align_method - - -class MenuUVInspection(bpy.types.Menu): - """ - Menu class: Master menu of UV Inspection - """ - - bl_idname = "uv.muv_uv_inspection_menu" - bl_label = "UV Inspection" - bl_description = "UV Inspection" - - def draw(self, context): - layout = self.layout - sc = context.scene - - layout.prop(sc, "muv_uv_inspection_show", text="UV Inspection") - layout.operator(uv_inspection.OperatorUpdate.bl_idname, - text="Update") + layout.operator( + copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_CopyUV.bl_idname, + text="Copy") + layout.operator( + copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_PasteUV.bl_idname, + text="Paste") diff --git a/uv_magic_uv/ui/VIEW3D_MT_object.py b/uv_magic_uv/ui/VIEW3D_MT_object.py index c73157cc2cea69fb45cfe6a6420991750bdc131c..318cd82c6ea6506a1176da5413359906ff4e116a 100644 --- a/uv_magic_uv/ui/VIEW3D_MT_object.py +++ b/uv_magic_uv/ui/VIEW3D_MT_object.py @@ -24,15 +24,17 @@ __version__ = "5.2" __date__ = "17 Nov 2018" import bpy -from ..op import copy_paste_uv_object +from ..op import copy_paste_uv_object +from ..utils.bl_class_registry import BlClassRegistry __all__ = [ - 'MenuCopyPasteUVObject', + 'MUV_MT_CopyPasteUV_Object', ] -class MenuCopyPasteUVObject(bpy.types.Menu): +@BlClassRegistry() +class MUV_MT_CopyPasteUV_Object(bpy.types.Menu): """ Menu class: Master menu of Copy/Paste UV coordinate among object """ @@ -44,7 +46,9 @@ class MenuCopyPasteUVObject(bpy.types.Menu): def draw(self, _): layout = self.layout - layout.menu(copy_paste_uv_object.MenuCopyUV.bl_idname, - text="Copy") - layout.menu(copy_paste_uv_object.MenuPasteUV.bl_idname, - text="Paste") + layout.menu( + copy_paste_uv_object.MUV_MT_CopyPasteUVObject_CopyUV.bl_idname, + text="Copy") + layout.menu( + copy_paste_uv_object.MUV_MT_CopyPasteUVObject_PasteUV.bl_idname, + text="Paste") diff --git a/uv_magic_uv/ui/VIEW3D_MT_uv_map.py b/uv_magic_uv/ui/VIEW3D_MT_uv_map.py index bb59c12c7dff289c674218eca6be2237cf16d3b4..c5698504450765dd7aa3dcd02729c9862ff11159 100644 --- a/uv_magic_uv/ui/VIEW3D_MT_uv_map.py +++ b/uv_magic_uv/ui/VIEW3D_MT_uv_map.py @@ -23,30 +23,24 @@ __status__ = "production" __version__ = "5.2" __date__ = "17 Nov 2018" -import bpy -from ..op import copy_paste_uv -from ..op import transfer_uv -from ..op import texture_lock -from ..op import world_scale_uv -from ..op import uvw -from ..op import texture_projection -from ..op import texture_wrap -from ..op import preserve_uv_aspect +import bpy.utils +from ..op import ( + copy_paste_uv, + transfer_uv, + uvw, +) +from ..utils.bl_class_registry import BlClassRegistry __all__ = [ - 'MenuCopyPasteUV', - 'MenuTransferUV', - 'MenuTextureLock', - 'MenuWorldScaleUV', - 'MenuTextureWrap', - 'MenuUVW', - 'MenuTextureProjection', - 'MenuPreserveUVAspect', + 'MUV_MT_CopyPasteUV', + 'MUV_MT_TransferUV', + 'MUV_MT_UVW', ] -class MenuCopyPasteUV(bpy.types.Menu): +@BlClassRegistry() +class MUV_MT_CopyPasteUV(bpy.types.Menu): """ Menu class: Master menu of Copy/Paste UV coordinate """ @@ -58,20 +52,23 @@ class MenuCopyPasteUV(bpy.types.Menu): def draw(self, _): layout = self.layout - layout.label("Default") - layout.menu(copy_paste_uv.MenuCopyUV.bl_idname, text="Copy") - layout.menu(copy_paste_uv.MenuPasteUV.bl_idname, text="Paste") + layout.label(text="Default") + layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_CopyUV.bl_idname, + text="Copy") + layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_PasteUV.bl_idname, + text="Paste") layout.separator() - layout.label("Selection Sequence") - layout.menu(copy_paste_uv.MenuSelSeqCopyUV.bl_idname, + layout.label(text="Selection Sequence") + layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqCopyUV.bl_idname, text="Copy") - layout.menu(copy_paste_uv.MenuSelSeqPasteUV.bl_idname, + layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqPasteUV.bl_idname, text="Paste") -class MenuTransferUV(bpy.types.Menu): +@BlClassRegistry() +class MUV_MT_TransferUV(bpy.types.Menu): """ Menu class: Master menu of Transfer UV coordinate """ @@ -84,99 +81,16 @@ class MenuTransferUV(bpy.types.Menu): layout = self.layout sc = context.scene - layout.operator(transfer_uv.OperatorCopyUV.bl_idname, text="Copy") - ops = layout.operator(transfer_uv.OperatorPasteUV.bl_idname, + layout.operator(transfer_uv.MUV_OT_TransferUV_CopyUV.bl_idname, + text="Copy") + ops = layout.operator(transfer_uv.MUV_OT_TransferUV_PasteUV.bl_idname, text="Paste") ops.invert_normals = sc.muv_transfer_uv_invert_normals ops.copy_seams = sc.muv_transfer_uv_copy_seams -class MenuTextureLock(bpy.types.Menu): - """ - Menu class: Master menu of Texture Lock - """ - - bl_idname = "uv.muv_texture_lock_menu" - bl_label = "Texture Lock" - bl_description = "Lock texture when vertices of mesh (Preserve UV)" - - def draw(self, context): - layout = self.layout - sc = context.scene - - layout.label("Normal Mode") - layout.operator(texture_lock.OperatorLock.bl_idname, - text="Lock" - if not texture_lock.OperatorLock.is_ready(context) - else "ReLock") - ops = layout.operator(texture_lock.OperatorUnlock.bl_idname, - text="Unlock") - ops.connect = sc.muv_texture_lock_connect - - layout.separator() - - layout.label("Interactive Mode") - layout.prop(sc, "muv_texture_lock_lock", text="Lock") - - -class MenuWorldScaleUV(bpy.types.Menu): - """ - Menu class: Master menu of world scale UV - """ - - bl_idname = "uv.muv_world_scale_uv_menu" - bl_label = "World Scale UV" - bl_description = "" - - def draw(self, context): - layout = self.layout - sc = context.scene - - layout.operator(world_scale_uv.OperatorMeasure.bl_idname, - text="Measure") - - layout.operator(world_scale_uv.OperatorApplyManual.bl_idname, - text="Apply (Manual)") - - ops = layout.operator( - world_scale_uv.OperatorApplyScalingDensity.bl_idname, - text="Apply (Same Desity)") - ops.src_density = sc.muv_world_scale_uv_src_density - ops.same_density = True - - ops = layout.operator( - world_scale_uv.OperatorApplyScalingDensity.bl_idname, - text="Apply (Scaling Desity)") - ops.src_density = sc.muv_world_scale_uv_src_density - ops.same_density = False - ops.tgt_scaling_factor = sc.muv_world_scale_uv_tgt_scaling_factor - - ops = layout.operator( - world_scale_uv.OperatorApplyProportionalToMesh.bl_idname, - text="Apply (Proportional to Mesh)") - ops.src_density = sc.muv_world_scale_uv_src_density - ops.src_uv_area = sc.muv_world_scale_uv_src_uv_area - ops.src_mesh_area = sc.muv_world_scale_uv_src_mesh_area - ops.origin = sc.muv_world_scale_uv_origin - - -class MenuTextureWrap(bpy.types.Menu): - """ - Menu class: Master menu of Texture Wrap - """ - - bl_idname = "uv.muv_texture_wrap_menu" - bl_label = "Texture Wrap" - bl_description = "" - - def draw(self, _): - layout = self.layout - - layout.operator(texture_wrap.OperatorRefer.bl_idname, text="Refer") - layout.operator(texture_wrap.OperatorSet.bl_idname, text="Set") - - -class MenuUVW(bpy.types.Menu): +@BlClassRegistry() +class MUV_MT_UVW(bpy.types.Menu): """ Menu class: Master menu of UVW """ @@ -189,48 +103,9 @@ class MenuUVW(bpy.types.Menu): layout = self.layout sc = context.scene - ops = layout.operator(uvw.OperatorBoxMap.bl_idname, text="Box") + ops = layout.operator(uvw.MUV_OT_UVW_BoxMap.bl_idname, text="Box") ops.assign_uvmap = sc.muv_uvw_assign_uvmap - ops = layout.operator(uvw.OperatorBestPlanerMap.bl_idname, + ops = layout.operator(uvw.MUV_OT_UVW_BestPlanerMap.bl_idname, text="Best Planner") ops.assign_uvmap = sc.muv_uvw_assign_uvmap - - -class MenuTextureProjection(bpy.types.Menu): - """ - Menu class: Master menu of Texture Projection - """ - - bl_idname = "uv.muv_texture_projection_menu" - bl_label = "Texture Projection" - bl_description = "" - - def draw(self, context): - layout = self.layout - sc = context.scene - - layout.prop(sc, "muv_texture_projection_enable", - text="Texture Projection") - layout.operator(texture_projection.OperatorProject.bl_idname, - text="Project") - - -class MenuPreserveUVAspect(bpy.types.Menu): - """ - Menu class: Master menu of Preserve UV Aspect - """ - - bl_idname = "uv.muv_preserve_uv_aspect_menu" - bl_label = "Preserve UV Aspect" - bl_description = "" - - def draw(self, context): - layout = self.layout - sc = context.scene - - for key in bpy.data.images.keys(): - ops = layout.operator( - preserve_uv_aspect.Operator.bl_idname, text=key) - ops.dest_img_name = key - ops.origin = sc.muv_preserve_uv_aspect_origin diff --git a/uv_magic_uv/ui/__init__.py b/uv_magic_uv/ui/__init__.py index b377ed2343eb6db5c9678e47456dc722fcf30373..5f7e0c5ef62402521134aa07473cd60e903ef919 100644 --- a/uv_magic_uv/ui/__init__.py +++ b/uv_magic_uv/ui/__init__.py @@ -25,26 +25,22 @@ __date__ = "17 Nov 2018" if "bpy" in locals(): import importlib - importlib.reload(view3d_copy_paste_uv_objectmode) importlib.reload(view3d_copy_paste_uv_editmode) + importlib.reload(view3d_copy_paste_uv_objectmode) importlib.reload(view3d_uv_manipulation) importlib.reload(view3d_uv_mapping) importlib.reload(uvedit_copy_paste_uv) - importlib.reload(uvedit_uv_manipulation) - importlib.reload(uvedit_editor_enhancement) - importlib.reload(VIEW3D_MT_uv_map) importlib.reload(VIEW3D_MT_object) + importlib.reload(VIEW3D_MT_uv_map) importlib.reload(IMAGE_MT_uvs) else: - from . import view3d_copy_paste_uv_objectmode from . import view3d_copy_paste_uv_editmode + from . import view3d_copy_paste_uv_objectmode from . import view3d_uv_manipulation from . import view3d_uv_mapping from . import uvedit_copy_paste_uv - from . import uvedit_uv_manipulation - from . import uvedit_editor_enhancement - from . import VIEW3D_MT_uv_map from . import VIEW3D_MT_object + from . import VIEW3D_MT_uv_map from . import IMAGE_MT_uvs import bpy diff --git a/uv_magic_uv/ui/uvedit_copy_paste_uv.py b/uv_magic_uv/ui/uvedit_copy_paste_uv.py index 271277a086bd695e4438d2f55dafdc1eb31bf6e8..e21a5abd336d6af9c7eb259582a29de1ad89a452 100644 --- a/uv_magic_uv/ui/uvedit_copy_paste_uv.py +++ b/uv_magic_uv/ui/uvedit_copy_paste_uv.py @@ -26,20 +26,21 @@ __date__ = "17 Nov 2018" import bpy from ..op import copy_paste_uv_uvedit - +from ..utils.bl_class_registry import BlClassRegistry __all__ = [ - 'PanelCopyPasteUV', + 'MUV_PT_UVEdit_CopyPasteUV', ] -class PanelCopyPasteUV(bpy.types.Panel): +@BlClassRegistry() +class MUV_PT_UVEdit_CopyPasteUV(bpy.types.Panel): """ Panel class: Copy/Paste UV on Property Panel on UV/ImageEditor """ bl_space_type = 'IMAGE_EDITOR' - bl_region_type = 'TOOLS' + bl_region_type = 'UI' bl_label = "Copy/Paste UV" bl_category = "Magic UV" bl_context = 'mesh_edit' @@ -47,13 +48,15 @@ class PanelCopyPasteUV(bpy.types.Panel): def draw_header(self, _): layout = self.layout - layout.label(text="", icon='IMAGE_COL') + layout.label(text="", icon='IMAGE') def draw(self, _): layout = self.layout row = layout.row(align=True) - row.operator(copy_paste_uv_uvedit.OperatorCopyUV.bl_idname, - text="Copy") - row.operator(copy_paste_uv_uvedit.OperatorPasteUV.bl_idname, - text="Paste") + row.operator( + copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_CopyUV.bl_idname, + text="Copy") + row.operator( + copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_PasteUV.bl_idname, + text="Paste") diff --git a/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py b/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py index c5906ca02b85187f18c83a715e084bd3af4ceecc..14fba24a2c7e80e254726308417ae4e6e30d93f6 100644 --- a/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py +++ b/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py @@ -25,22 +25,25 @@ __date__ = "17 Nov 2018" import bpy -from ..op import copy_paste_uv -from ..op import transfer_uv - +from ..op import ( + copy_paste_uv, + transfer_uv, +) +from ..utils.bl_class_registry import BlClassRegistry __all__ = [ - 'PanelCopyPasteUVEditMode', + 'MUV_PT_CopyPasteUVEditMode', ] -class PanelCopyPasteUVEditMode(bpy.types.Panel): +@BlClassRegistry() +class MUV_PT_CopyPasteUVEditMode(bpy.types.Panel): """ Panel class: Copy/Paste UV on Property Panel on View3D """ bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' + bl_region_type = 'UI' bl_label = "Copy/Paste UV" bl_category = "Magic UV" bl_context = 'mesh_edit' @@ -48,7 +51,7 @@ class PanelCopyPasteUVEditMode(bpy.types.Panel): def draw_header(self, _): layout = self.layout - layout.label(text="", icon='IMAGE_COL') + layout.label(text="", icon='IMAGE') def draw(self, context): sc = context.scene @@ -59,15 +62,17 @@ class PanelCopyPasteUVEditMode(bpy.types.Panel): if sc.muv_copy_paste_uv_enabled: row = box.row(align=True) if sc.muv_copy_paste_uv_mode == 'DEFAULT': - row.menu(copy_paste_uv.MenuCopyUV.bl_idname, + row.menu(copy_paste_uv.MUV_MT_CopyPasteUV_CopyUV.bl_idname, text="Copy") - row.menu(copy_paste_uv.MenuPasteUV.bl_idname, + row.menu(copy_paste_uv.MUV_MT_CopyPasteUV_PasteUV.bl_idname, text="Paste") elif sc.muv_copy_paste_uv_mode == 'SEL_SEQ': - row.menu(copy_paste_uv.MenuSelSeqCopyUV.bl_idname, - text="Copy") - row.menu(copy_paste_uv.MenuSelSeqPasteUV.bl_idname, - text="Paste") + row.menu( + copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqCopyUV.bl_idname, + text="Copy") + row.menu( + copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqPasteUV.bl_idname, + text="Paste") box.prop(sc, "muv_copy_paste_uv_mode", expand=True) box.prop(sc, "muv_copy_paste_uv_copy_seams", text="Seams") box.prop(sc, "muv_copy_paste_uv_strategy", text="Strategy") @@ -76,8 +81,9 @@ class PanelCopyPasteUVEditMode(bpy.types.Panel): box.prop(sc, "muv_transfer_uv_enabled", text="Transfer UV") if sc.muv_transfer_uv_enabled: row = box.row(align=True) - row.operator(transfer_uv.OperatorCopyUV.bl_idname, text="Copy") - ops = row.operator(transfer_uv.OperatorPasteUV.bl_idname, + row.operator(transfer_uv.MUV_OT_TransferUV_CopyUV.bl_idname, + text="Copy") + ops = row.operator(transfer_uv.MUV_OT_TransferUV_PasteUV.bl_idname, text="Paste") ops.invert_normals = sc.muv_transfer_uv_invert_normals ops.copy_seams = sc.muv_transfer_uv_copy_seams diff --git a/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py b/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py index a9203d87ee989eafc7768377ee22d4536890d6d8..6dd0d3b43584e8bd479da565dbe9b6de34381e95 100644 --- a/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py +++ b/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py @@ -26,20 +26,21 @@ __date__ = "17 Nov 2018" import bpy from ..op import copy_paste_uv_object - +from ..utils.bl_class_registry import BlClassRegistry __all__ = [ - 'PanelCopyPasteUVObjectMode', + 'MUV_PT_View3D_Object_CopyPasteUV', ] -class PanelCopyPasteUVObjectMode(bpy.types.Panel): +@BlClassRegistry() +class MUV_PT_View3D_Object_CopyPasteUV(bpy.types.Panel): """ Panel class: Copy/Paste UV on Property Panel on View3D """ bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' + bl_region_type = 'UI' bl_label = "Copy/Paste UV" bl_category = "Magic UV" bl_context = 'objectmode' @@ -47,16 +48,18 @@ class PanelCopyPasteUVObjectMode(bpy.types.Panel): def draw_header(self, _): layout = self.layout - layout.label(text="", icon='IMAGE_COL') + layout.label(text="", icon='IMAGE') def draw(self, context): sc = context.scene layout = self.layout row = layout.row(align=True) - row.menu(copy_paste_uv_object.MenuCopyUV.bl_idname, - text="Copy") - row.menu(copy_paste_uv_object.MenuPasteUV.bl_idname, - text="Paste") + row.menu( + copy_paste_uv_object.MUV_MT_CopyPasteUVObject_CopyUV.bl_idname, + text="Copy") + row.menu( + copy_paste_uv_object.MUV_MT_CopyPasteUVObject_PasteUV.bl_idname, + text="Paste") layout.prop(sc, "muv_copy_paste_uv_object_copy_seams", text="Seams") diff --git a/uv_magic_uv/ui/view3d_uv_manipulation.py b/uv_magic_uv/ui/view3d_uv_manipulation.py index be0bcf575bdf334ede14eaf368ede9563054dda0..365a0dc8a795a15755ec868655bfbd45e287eef9 100644 --- a/uv_magic_uv/ui/view3d_uv_manipulation.py +++ b/uv_magic_uv/ui/view3d_uv_manipulation.py @@ -25,28 +25,26 @@ __date__ = "17 Nov 2018" import bpy -from ..op import flip_rotate_uv -from ..op import mirror_uv -from ..op import move_uv -from ..op import preserve_uv_aspect -from ..op import texture_lock -from ..op import texture_wrap -from ..op import uv_sculpt -from ..op import world_scale_uv - +from ..op import ( + flip_rotate_uv, + mirror_uv, + move_uv, +) +from ..utils.bl_class_registry import BlClassRegistry __all__ = [ - 'PanelUVManipulation', + 'MUV_PT_View3D_UVManipulation', ] -class PanelUVManipulation(bpy.types.Panel): +@BlClassRegistry() +class MUV_PT_View3D_UVManipulation(bpy.types.Panel): """ Panel class: UV Manipulation on Property Panel on View3D """ bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' + bl_region_type = 'UI' bl_label = "UV Manipulation" bl_category = "Magic UV" bl_context = 'mesh_edit' @@ -54,7 +52,7 @@ class PanelUVManipulation(bpy.types.Panel): def draw_header(self, _): layout = self.layout - layout.label(text="", icon='IMAGE_COL') + layout.label(text="", icon='IMAGE') def draw(self, context): sc = context.scene @@ -64,7 +62,7 @@ class PanelUVManipulation(bpy.types.Panel): box.prop(sc, "muv_flip_rotate_uv_enabled", text="Flip/Rotate UV") if sc.muv_flip_rotate_uv_enabled: row = box.row() - ops = row.operator(flip_rotate_uv.Operator.bl_idname, + ops = row.operator(flip_rotate_uv.MUV_OT_FlipRotate.bl_idname, text="Flip/Rotate") ops.seams = sc.muv_flip_rotate_uv_seams row.prop(sc, "muv_flip_rotate_uv_seams", text="Seams") @@ -73,7 +71,8 @@ class PanelUVManipulation(bpy.types.Panel): box.prop(sc, "muv_mirror_uv_enabled", text="Mirror UV") if sc.muv_mirror_uv_enabled: row = box.row() - ops = row.operator(mirror_uv.Operator.bl_idname, text="Mirror") + ops = row.operator(mirror_uv.MUV_OT_MirrorUV.bl_idname, + text="Mirror") ops.axis = sc.muv_mirror_uv_axis row.prop(sc, "muv_mirror_uv_axis", text="") @@ -81,191 +80,9 @@ class PanelUVManipulation(bpy.types.Panel): box.prop(sc, "muv_move_uv_enabled", text="Move UV") if sc.muv_move_uv_enabled: col = box.column() - if not move_uv.Operator.is_running(context): - col.operator(move_uv.Operator.bl_idname, icon='PLAY', + if not move_uv.MUV_OT_MoveUV.is_running(context): + col.operator(move_uv.MUV_OT_MoveUV.bl_idname, icon='PLAY', text="Start") else: - col.operator(move_uv.Operator.bl_idname, icon='PAUSE', + col.operator(move_uv.MUV_OT_MoveUV.bl_idname, icon='PAUSE', text="Stop") - - box = layout.box() - box.prop(sc, "muv_world_scale_uv_enabled", text="World Scale UV") - if sc.muv_world_scale_uv_enabled: - box.prop(sc, "muv_world_scale_uv_mode", text="") - - if sc.muv_world_scale_uv_mode == 'MANUAL': - sp = box.split(percentage=0.5) - col = sp.column() - col.prop(sc, "muv_world_scale_uv_tgt_texture_size", - text="Texture Size") - sp = sp.split(percentage=1.0) - col = sp.column() - col.label("Density:") - col.prop(sc, "muv_world_scale_uv_tgt_density", text="") - box.prop(sc, "muv_world_scale_uv_origin", text="Origin") - ops = box.operator( - world_scale_uv.OperatorApplyManual.bl_idname, text="Apply") - ops.tgt_density = sc.muv_world_scale_uv_tgt_density - ops.tgt_texture_size = sc.muv_world_scale_uv_tgt_texture_size - ops.origin = sc.muv_world_scale_uv_origin - ops.show_dialog = False - - elif sc.muv_world_scale_uv_mode == 'SAME_DENSITY': - sp = box.split(percentage=0.4) - col = sp.column(align=True) - col.label("Source:") - sp = sp.split(percentage=1.0) - col = sp.column(align=True) - col.operator(world_scale_uv.OperatorMeasure.bl_idname, - text="Measure") - - sp = box.split(percentage=0.7) - col = sp.column(align=True) - col.prop(sc, "muv_world_scale_uv_src_density", text="Density") - col.enabled = False - sp = sp.split(percentage=1.0) - col = sp.column(align=True) - col.label("px2/cm2") - - box.separator() - box.prop(sc, "muv_world_scale_uv_origin", text="Origin") - ops = box.operator( - world_scale_uv.OperatorApplyScalingDensity.bl_idname, - text="Apply") - ops.src_density = sc.muv_world_scale_uv_src_density - ops.origin = sc.muv_world_scale_uv_origin - ops.same_density = True - ops.show_dialog = False - - elif sc.muv_world_scale_uv_mode == 'SCALING_DENSITY': - sp = box.split(percentage=0.4) - col = sp.column(align=True) - col.label("Source:") - sp = sp.split(percentage=1.0) - col = sp.column(align=True) - col.operator(world_scale_uv.OperatorMeasure.bl_idname, - text="Measure") - - sp = box.split(percentage=0.7) - col = sp.column(align=True) - col.prop(sc, "muv_world_scale_uv_src_density", text="Density") - col.enabled = False - sp = sp.split(percentage=1.0) - col = sp.column(align=True) - col.label("px2/cm2") - - box.separator() - box.prop(sc, "muv_world_scale_uv_tgt_scaling_factor", - text="Scaling Factor") - box.prop(sc, "muv_world_scale_uv_origin", text="Origin") - ops = box.operator( - world_scale_uv.OperatorApplyScalingDensity.bl_idname, - text="Apply") - ops.src_density = sc.muv_world_scale_uv_src_density - ops.origin = sc.muv_world_scale_uv_origin - ops.same_density = False - ops.show_dialog = False - ops.tgt_scaling_factor = \ - sc.muv_world_scale_uv_tgt_scaling_factor - - elif sc.muv_world_scale_uv_mode == 'PROPORTIONAL_TO_MESH': - sp = box.split(percentage=0.4) - col = sp.column(align=True) - col.label("Source:") - sp = sp.split(percentage=1.0) - col = sp.column(align=True) - col.operator(world_scale_uv.OperatorMeasure.bl_idname, - text="Measure") - - sp = box.split(percentage=0.7) - col = sp.column(align=True) - col.prop(sc, "muv_world_scale_uv_src_mesh_area", - text="Mesh Area") - col.prop(sc, "muv_world_scale_uv_src_uv_area", text="UV Area") - col.prop(sc, "muv_world_scale_uv_src_density", text="Density") - col.enabled = False - sp = sp.split(percentage=1.0) - col = sp.column(align=True) - col.label("cm2") - col.label("px2") - col.label("px2/cm2") - col.enabled = False - - box.separator() - box.prop(sc, "muv_world_scale_uv_origin", text="Origin") - ops = box.operator( - world_scale_uv.OperatorApplyProportionalToMesh.bl_idname, - text="Apply") - ops.src_density = sc.muv_world_scale_uv_src_density - ops.src_uv_area = sc.muv_world_scale_uv_src_uv_area - ops.src_mesh_area = sc.muv_world_scale_uv_src_mesh_area - ops.origin = sc.muv_world_scale_uv_origin - ops.show_dialog = False - - box = layout.box() - box.prop(sc, "muv_preserve_uv_aspect_enabled", - text="Preserve UV Aspect") - if sc.muv_preserve_uv_aspect_enabled: - row = box.row() - ops = row.operator( - preserve_uv_aspect.Operator.bl_idname, - text="Change Image") - ops.dest_img_name = sc.muv_preserve_uv_aspect_tex_image - ops.origin = sc.muv_preserve_uv_aspect_origin - row.prop(sc, "muv_preserve_uv_aspect_tex_image", text="") - box.prop(sc, "muv_preserve_uv_aspect_origin", text="Origin") - - box = layout.box() - box.prop(sc, "muv_texture_lock_enabled", text="Texture Lock") - if sc.muv_texture_lock_enabled: - row = box.row(align=True) - col = row.column(align=True) - col.label("Normal Mode:") - col = row.column(align=True) - col.operator(texture_lock.OperatorLock.bl_idname, - text="Lock" - if not texture_lock.OperatorLock.is_ready(context) - else "ReLock") - ops = col.operator(texture_lock.OperatorUnlock.bl_idname, - text="Unlock") - ops.connect = sc.muv_texture_lock_connect - col.prop(sc, "muv_texture_lock_connect", text="Connect") - - row = box.row(align=True) - row.label("Interactive Mode:") - box.prop(sc, "muv_texture_lock_lock", - text="Unlock" - if texture_lock.OperatorIntr.is_running(context) - else "Lock", - icon='RESTRICT_VIEW_OFF' - if texture_lock.OperatorIntr.is_running(context) - else 'RESTRICT_VIEW_ON') - - box = layout.box() - box.prop(sc, "muv_texture_wrap_enabled", text="Texture Wrap") - if sc.muv_texture_wrap_enabled: - row = box.row(align=True) - row.operator(texture_wrap.OperatorRefer.bl_idname, text="Refer") - row.operator(texture_wrap.OperatorSet.bl_idname, text="Set") - box.prop(sc, "muv_texture_wrap_set_and_refer") - box.prop(sc, "muv_texture_wrap_selseq") - - box = layout.box() - box.prop(sc, "muv_uv_sculpt_enabled", text="UV Sculpt") - if sc.muv_uv_sculpt_enabled: - box.prop(sc, "muv_uv_sculpt_enable", - text="Disable"if uv_sculpt.Operator.is_running(context) - else "Enable", - icon='RESTRICT_VIEW_OFF' - if uv_sculpt.Operator.is_running(context) - else 'RESTRICT_VIEW_ON') - col = box.column() - col.label("Brush:") - col.prop(sc, "muv_uv_sculpt_radius") - col.prop(sc, "muv_uv_sculpt_strength") - box.prop(sc, "muv_uv_sculpt_tools") - if sc.muv_uv_sculpt_tools == 'PINCH': - box.prop(sc, "muv_uv_sculpt_pinch_invert") - elif sc.muv_uv_sculpt_tools == 'RELAX': - box.prop(sc, "muv_uv_sculpt_relax_method") - box.prop(sc, "muv_uv_sculpt_show_brush") diff --git a/uv_magic_uv/ui/view3d_uv_mapping.py b/uv_magic_uv/ui/view3d_uv_mapping.py index 2aa62c26d37ddcffd01c347de7f751a20e92031d..c596008ebb8534c5429000e2eed9389953ad5925 100644 --- a/uv_magic_uv/ui/view3d_uv_mapping.py +++ b/uv_magic_uv/ui/view3d_uv_mapping.py @@ -25,23 +25,24 @@ __date__ = "17 Nov 2018" import bpy -from ..op import texture_projection -from ..op import unwrap_constraint -from ..op import uvw - +from ..op import ( + uvw, +) +from ..utils.bl_class_registry import BlClassRegistry __all__ = [ - 'UVMapping', + 'MUV_PT_View3D_UVMapping', ] -class UVMapping(bpy.types.Panel): +@BlClassRegistry() +class MUV_PT_View3D_UVMapping(bpy.types.Panel): """ Panel class: UV Mapping on Property Panel on View3D """ bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' + bl_region_type = 'UI' bl_label = "UV Mapping" bl_category = "Magic UV" bl_context = 'mesh_edit' @@ -49,60 +50,19 @@ class UVMapping(bpy.types.Panel): def draw_header(self, _): layout = self.layout - layout.label(text="", icon='IMAGE_COL') + layout.label(text="", icon='IMAGE') def draw(self, context): sc = context.scene layout = self.layout - box = layout.box() - box.prop(sc, "muv_unwrap_constraint_enabled", text="Unwrap Constraint") - if sc.muv_unwrap_constraint_enabled: - ops = box.operator( - unwrap_constraint.Operator.bl_idname, - text="Unwrap") - ops.u_const = sc.muv_unwrap_constraint_u_const - ops.v_const = sc.muv_unwrap_constraint_v_const - row = box.row(align=True) - row.prop(sc, "muv_unwrap_constraint_u_const", text="U-Constraint") - row.prop(sc, "muv_unwrap_constraint_v_const", text="V-Constraint") - - box = layout.box() - box.prop(sc, "muv_texture_projection_enabled", - text="Texture Projection") - if sc.muv_texture_projection_enabled: - row = box.row() - row.prop(sc, "muv_texture_projection_enable", - text="Disable" - if texture_projection.Operator.is_running(context) - else "Enable", - icon='RESTRICT_VIEW_OFF' - if texture_projection.Operator.is_running(context) - else 'RESTRICT_VIEW_ON') - row.prop(sc, "muv_texture_projection_tex_image", text="") - box.prop(sc, "muv_texture_projection_tex_transparency", - text="Transparency") - col = box.column(align=True) - row = col.row() - row.prop(sc, "muv_texture_projection_adjust_window", - text="Adjust Window") - if not sc.muv_texture_projection_adjust_window: - row.prop(sc, "muv_texture_projection_tex_magnitude", - text="Magnitude") - col.prop(sc, "muv_texture_projection_apply_tex_aspect", - text="Texture Aspect Ratio") - col.prop(sc, "muv_texture_projection_assign_uvmap", - text="Assign UVMap") - box.operator(texture_projection.OperatorProject.bl_idname, - text="Project") - box = layout.box() box.prop(sc, "muv_uvw_enabled", text="UVW") if sc.muv_uvw_enabled: row = box.row(align=True) - ops = row.operator(uvw.OperatorBoxMap.bl_idname, text="Box") + ops = row.operator(uvw.MUV_OT_UVW_BoxMap.bl_idname, text="Box") ops.assign_uvmap = sc.muv_uvw_assign_uvmap - ops = row.operator(uvw.OperatorBestPlanerMap.bl_idname, + ops = row.operator(uvw.MUV_OT_UVW_BestPlanerMap.bl_idname, text="Best Planner") ops.assign_uvmap = sc.muv_uvw_assign_uvmap box.prop(sc, "muv_uvw_assign_uvmap", text="Assign UVMap") diff --git a/uv_magic_uv/utils/__init__.py b/uv_magic_uv/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4ce9d9079326be15d662765d96a9c88d1350beca --- /dev/null +++ b/uv_magic_uv/utils/__init__.py @@ -0,0 +1,34 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +if "bpy" in locals(): + import importlib + importlib.reload(bl_class_registry) + importlib.reload(property_class_registry) +else: + from . import bl_class_registry + from . import property_class_registry + +import bpy diff --git a/uv_magic_uv/utils/bl_class_registry.py b/uv_magic_uv/utils/bl_class_registry.py new file mode 100644 index 0000000000000000000000000000000000000000..d173061566fd0369e68c57fec302d4f542325fe7 --- /dev/null +++ b/uv_magic_uv/utils/bl_class_registry.py @@ -0,0 +1,84 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +import bpy + +from .. import common + +__all__ = [ + 'BlClassRegistry', +] + + +class BlClassRegistry: + class_list = [] + + def __init__(self, *_, **kwargs): + self.legacy = kwargs.get('legacy', False) + + def __call__(self, cls): + if hasattr(cls, "bl_idname"): + BlClassRegistry.add_class(cls.bl_idname, cls, self.legacy) + else: + bl_idname = "{}{}{}{}".format(cls.bl_space_type, + cls.bl_region_type, + cls.bl_context, cls.bl_label) + BlClassRegistry.add_class(bl_idname, cls, self.legacy) + return cls + + @classmethod + def add_class(cls, bl_idname, op_class, legacy): + for class_ in cls.class_list: + if (class_["bl_idname"] == bl_idname) and \ + (class_["legacy"] == legacy): + raise RuntimeError("{} is already registered" + .format(bl_idname)) + + new_op = { + "bl_idname": bl_idname, + "class": op_class, + "legacy": legacy, + } + cls.class_list.append(new_op) + common.debug_print("{} is registered.".format(bl_idname)) + + @classmethod + def register(cls): + for class_ in cls.class_list: + bpy.utils.register_class(class_["class"]) + common.debug_print("{} is registered to Blender." + .format(class_["bl_idname"])) + + @classmethod + def unregister(cls): + for class_ in cls.class_list: + bpy.utils.unregister_class(class_["class"]) + common.debug_print("{} is unregistered from Blender." + .format(class_["bl_idname"])) + + @classmethod + def cleanup(cls): + cls.class_list = [] + common.debug_print("Cleanup registry.") diff --git a/uv_magic_uv/utils/property_class_registry.py b/uv_magic_uv/utils/property_class_registry.py new file mode 100644 index 0000000000000000000000000000000000000000..20df03422af86198fba12c33d3d493b0a9fabd66 --- /dev/null +++ b/uv_magic_uv/utils/property_class_registry.py @@ -0,0 +1,72 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.2" +__date__ = "17 Nov 2018" + +from .. import common + +__all__ = [ + 'PropertyClassRegistry', +] + + +class PropertyClassRegistry: + class_list = [] + + def __init__(self, *_, **kwargs): + self.legacy = kwargs.get('legacy', False) + + def __call__(self, cls): + PropertyClassRegistry.add_class(cls.idname, cls, self.legacy) + return cls + + @classmethod + def add_class(cls, idname, prop_class, legacy): + for class_ in cls.class_list: + if (class_["idname"] == idname) and (class_["legacy"] == legacy): + raise RuntimeError("{} is already registered".format(idname)) + + new_op = { + "idname": idname, + "class": prop_class, + "legacy": legacy, + } + cls.class_list.append(new_op) + common.debug_print("{} is registered.".format(idname)) + + @classmethod + def init_props(cls, scene): + for class_ in cls.class_list: + class_["class"].init_props(scene) + common.debug_print("{} is initialized.".format(class_["idname"])) + + @classmethod + def del_props(cls, scene): + for class_ in cls.class_list: + class_["class"].del_props(scene) + common.debug_print("{} is cleared.".format(class_["idname"])) + + @classmethod + def cleanup(cls): + cls.class_list = [] + common.debug_print("Cleanup registry.")