From b7af011ad5be56bd2f4b46abbe0b06c4849164c1 Mon Sep 17 00:00:00 2001 From: Nutti Date: Sun, 13 Nov 2016 09:07:24 +0900 Subject: [PATCH 1/2] Version 4.1 released --- README.md | 11 +- install.sh | 4 +- uv_magic_uv/__init__.py | 26 +- uv_magic_uv/muv_common.py | 8 +- uv_magic_uv/muv_cpuv_ops.py | 212 +++++++++++++- uv_magic_uv/muv_cpuv_selseq_ops.py | 16 +- uv_magic_uv/muv_fliprot_ops.py | 8 +- uv_magic_uv/muv_menu.py | 49 +++- uv_magic_uv/muv_mirroruv_ops.py | 157 +++++++++++ uv_magic_uv/muv_mvuv_ops.py | 97 ++++--- uv_magic_uv/muv_packuv_ops.py | 52 ++-- uv_magic_uv/muv_preferences.py | 6 +- uv_magic_uv/muv_props.py | 35 ++- uv_magic_uv/muv_texlock_ops.py | 435 +++++++++++++++++++++++++++++ uv_magic_uv/muv_texproj_ops.py | 33 ++- uv_magic_uv/muv_transuv_ops.py | 10 +- uv_magic_uv/muv_unwrapconst_ops.py | 122 ++++++++ uv_magic_uv/muv_uvbb_ops.py | 163 +++++++++-- uv_magic_uv/muv_wsuv_ops.py | 153 ++++++++++ 19 files changed, 1454 insertions(+), 143 deletions(-) create mode 100644 uv_magic_uv/muv_mirroruv_ops.py create mode 100644 uv_magic_uv/muv_texlock_ops.py create mode 100644 uv_magic_uv/muv_unwrapconst_ops.py create mode 100644 uv_magic_uv/muv_wsuv_ops.py diff --git a/README.md b/README.md index 2d87a1d9..0bee0251 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Blender Addon: Magic UV (Copy/Paste UV) +# Blender Add-on: Magic UV (Copy/Paste UV) This is a blender add-on "Magic UV". (also known as Copy/Paste UV for older version) You can manipulate UV with this plugin. @@ -14,6 +14,7 @@ If you want to try newest but unstable version, you can download it from [unstab |Version|Download URL| |---|---| |*unstable*|[Download](https://github.com/nutti/Magic-UV/archive/develop.zip)| +|4.1|[Download](https://github.com/nutti/Magic-UV/releases/tag/v4.1)| |4.0|[Download](https://github.com/nutti/Magic-UV/releases/tag/v4.0)| |3.2|[Download](https://github.com/nutti/Magic-UV/releases/tag/v3.2)| |3.1|[Download](https://github.com/nutti/Magic-UV/releases/tag/v3.1)| @@ -34,12 +35,17 @@ This add-on's features are as follows. * Copy/Paste UV Coordinates * Copy/Paste UV Coordinates (by selection sequence) +* Copy/Paste UV Coordinates (Among same objects) * Flip/Rotate UVs * Transfer UV * Manipulate UV with Bouding Box in UV Editor * Move UV from 3D View * Texture Projection * Pack UV (with same UV island packing) +* Texture Lock +* Mirror UV +* World Scale UV +* Unwrap Constraint ## Tutorials @@ -57,6 +63,7 @@ See the link below for further details. ## Change Log |Version|Release Date|Change Log| |---|---|---| +|4.1|2016.11.13|[1] **Add feature**
- Copy/Paste UV Coordinates (Among same objects)
- Texture Lock
- Mirror UV
- World Scale UV
- Unwrap Constraint
[2] Improve feature - Pack UV (with same UV island packing)
(1) Add option: Allowable center/size deviation option
[3] Fixed bug| |4.0|2016.5.14|[1] Rename this add-on
[2] **Add feature**
- Manipulate UV with Bounding Box in UV Editor
- Move UV from 3D View
- Texture Projection
- Pack UV (with same UV island packing)
[3] Improve feature - Copy/Paste UV
(1) N to M copy/paste UV
(2) Copy/Paste UV by selection sequence between specified UV maps
[4] Optimization/Refactoring
[5] Fixed bugs| |3.2|2015.6.20|Improve feature
- Transfer UV
(1) Copy/Paste to multiple meshes
(2) Partially copy/paste UV| |3.1|2015.6.17|Improve feature
- Transfer UV
(1) Improve algorithm| @@ -71,7 +78,7 @@ See the link below for further details. ## Bug report / Feature request This addon's project is in progress. -If you want to report problem or request feature, please make issues. +If you want to report problem or request feature, please make issues. https://github.com/nutti/Magic-UV/issues diff --git a/install.sh b/install.sh index d01027f5..77094890 100644 --- a/install.sh +++ b/install.sh @@ -1,4 +1,4 @@ #!/bin/sh -rm -rf ${HOME}/Library/Application\ Support/Blender/2.77/scripts/addons/uv_magic_uv -cp -r uv_magic_uv ${HOME}/Library/Application\ Support/Blender/2.77/scripts/addons/ +rm -rf ${HOME}/Library/Application\ Support/Blender/2.75/scripts/addons/uv_magic_uv +cp -r uv_magic_uv ${HOME}/Library/Application\ Support/Blender/2.75/scripts/addons/ diff --git a/uv_magic_uv/__init__.py b/uv_magic_uv/__init__.py index e180b129..e13e11eb 100644 --- a/uv_magic_uv/__init__.py +++ b/uv_magic_uv/__init__.py @@ -20,14 +20,14 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2016" +__version__ = "4.1" +__date__ = "13 Nov 2016" bl_info = { "name": "Magic UV", "author": "Nutti", - "version": (4, 0), + "version": (4, 1), "blender": (2, 77, 0), "location": "View3D > U, View3D > Property Panel, ImageEditor > Property Panel, ImageEditor > UVs", "description": "UV Manipulator Tools", @@ -52,6 +52,10 @@ imp.reload(muv_mvuv_ops) imp.reload(muv_texproj_ops) imp.reload(muv_packuv_ops) + imp.reload(muv_texlock_ops) + imp.reload(muv_mirroruv_ops) + imp.reload(muv_wsuv_ops) + imp.reload(muv_unwrapconst_ops) else: from . import muv_preferences from . import muv_menu @@ -65,6 +69,10 @@ from . import muv_mvuv_ops from . import muv_texproj_ops from . import muv_packuv_ops + from . import muv_texlock_ops + from . import muv_mirroruv_ops + from . import muv_wsuv_ops + from . import muv_unwrapconst_ops import bpy @@ -75,6 +83,10 @@ def view3d_uvmap_menu_fn(self, context): self.layout.operator(muv_fliprot_ops.MUV_FlipRot.bl_idname, icon="PLUGIN") self.layout.menu(muv_menu.MUV_TransUVMenu.bl_idname, icon="PLUGIN") self.layout.operator(muv_mvuv_ops.MUV_MVUV.bl_idname, icon="PLUGIN") + self.layout.menu(muv_menu.MUV_TexLockMenu.bl_idname, icon="PLUGIN") + self.layout.operator(muv_mirroruv_ops.MUV_MirrorUV.bl_idname, icon="PLUGIN") + self.layout.menu(muv_menu.MUV_WSUVMenu.bl_idname, icon="PLUGIN") + self.layout.operator(muv_unwrapconst_ops.MUV_UnwrapConstraint.bl_idname, icon='PLUGIN') def image_uvs_menu_fn(self, context): @@ -82,10 +94,16 @@ def image_uvs_menu_fn(self, context): self.layout.operator(muv_packuv_ops.MUV_PackUV.bl_idname, icon="PLUGIN") +def view3d_object_menu_fn(self, context): + self.layout.separator() + self.layout.menu(muv_menu.MUV_CPUVObjMenu.bl_idname, icon="PLUGIN") + + def register(): bpy.utils.register_module(__name__) bpy.types.VIEW3D_MT_uv_map.append(view3d_uvmap_menu_fn) bpy.types.IMAGE_MT_uvs.append(image_uvs_menu_fn) + bpy.types.VIEW3D_MT_object.append(view3d_object_menu_fn) muv_props.init_props(bpy.types.Scene) @@ -93,9 +111,9 @@ def unregister(): bpy.utils.unregister_module(__name__) bpy.types.VIEW3D_MT_uv_map.remove(view3d_uvmap_menu_fn) bpy.types.IMAGE_MT_uvs.remove(image_uvs_menu_fn) + bpy.types.VIEW3D_MT_object.remove(view3d_object_menu_fn) muv_props.clear_props(bpy.types.Scene) if __name__ == "__main__": register() - diff --git a/uv_magic_uv/muv_common.py b/uv_magic_uv/muv_common.py index 3867bdf5..83df3671 100644 --- a/uv_magic_uv/muv_common.py +++ b/uv_magic_uv/muv_common.py @@ -20,14 +20,17 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2016" +__version__ = "4.1" +__date__ = "13 Nov 2016" import bpy from . import muv_props +PHI = 3.1415926535 + + def debug_print(*s): """ Print message to console in debugging mode @@ -78,4 +81,3 @@ def get_space(area_type, region_type, space_type): break return (area, region, space) - diff --git a/uv_magic_uv/muv_cpuv_ops.py b/uv_magic_uv/muv_cpuv_ops.py index 63538546..7804a665 100644 --- a/uv_magic_uv/muv_cpuv_ops.py +++ b/uv_magic_uv/muv_cpuv_ops.py @@ -20,8 +20,8 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2016" +__version__ = "4.1" +__date__ = "13 Nov 2016" import bpy @@ -30,6 +30,15 @@ from . import muv_common +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 + + class MUV_CPUVCopyUV(bpy.types.Operator): """ Operation class: Copy UV coordinate @@ -123,7 +132,7 @@ class MUV_CPUVPasteUV(bpy.types.Operator): 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_N") + default="N_M") flip_copied_uv = BoolProperty( name="Flip Copied UV", @@ -160,7 +169,7 @@ def execute(self, context): uv_layer = bm.loops.layers.uv.verify() else: uv_layer = bm.loops.layers.uv[self.uv_map] - + # get selected face dest_uvs = [] dest_pin_uvs = [] @@ -182,7 +191,7 @@ def execute(self, context): "(src:%d, dest:%d)" % (len(props.src_uvs), len(dest_uvs))) return {'CANCELLED'} - + # paste for i, idx in enumerate(dest_face_indices): suv = None @@ -245,3 +254,196 @@ def draw(self, context): MUV_CPUVPasteUV.bl_idname, text=m, icon="PLUGIN").uv_map = m + +class MUV_CPUVObjCopyUV(bpy.types.Operator): + """ + Operation class: Copy UV coordinate per object + """ + + bl_idname = "object.muv_cpuv_obj_copy_uv" + bl_label = "Copy UV" + bl_description = "Copy UV coordinate" + bl_options = {'REGISTER', 'UNDO'} + + uv_map = bpy.props.StringProperty(options={'HIDDEN'}) + + @memorize_view_3d_mode + def execute(self, context): + props = context.scene.muv_props.cpuv_obj + if self.uv_map == "": + self.report({'INFO'}, "Copy UV coordinate per object") + else: + self.report( + {'INFO'}, + "Copy UV coordinate per object (UV map:" + self.uv_map + ")") + + bpy.ops.object.mode_set(mode='EDIT') + + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if muv_common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + # get UV layer + if self.uv_map == "": + 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() + else: + uv_layer = bm.loops.layers.uv[self.uv_map] + + # get selected face + props.src_uvs = [] + props.src_pin_uvs = [] + for face in bm.faces: + uvs = [l[uv_layer].uv.copy() for l in face.loops] + pin_uvs = [l[uv_layer].pin_uv for l in face.loops] + props.src_uvs.append(uvs) + props.src_pin_uvs.append(pin_uvs) + + self.report({'INFO'}, "%s's UV coordinates are copied" % (obj.name)) + + return {'FINISHED'} + + +class MUV_CPUVObjCopyUVMenu(bpy.types.Menu): + """ + Menu class: Copy UV coordinate per object + """ + + bl_idname = "object.muv_cpuv_obj_copy_uv_menu" + bl_label = "Copy UV" + bl_description = "Copy UV coordinate per object" + + def draw(self, context): + layout = self.layout + # create sub menu + obj = context.active_object + uv_maps = bpy.context.active_object.data.uv_textures.keys() + layout.operator( + MUV_CPUVObjCopyUV.bl_idname, + text="[Default]", icon="PLUGIN").uv_map = "" + for m in uv_maps: + layout.operator( + MUV_CPUVObjCopyUV.bl_idname, + text=m, icon="PLUGIN").uv_map = m + + +class MUV_CPUVObjPasteUV(bpy.types.Operator): + """ + Operation class: Paste UV coordinate per object + """ + + bl_idname = "object.muv_cpuv_obj_paste_uv" + bl_label = "Paste UV" + bl_description = "Paste UV coordinate" + bl_options = {'REGISTER', 'UNDO'} + + uv_map = bpy.props.StringProperty(options={'HIDDEN'}) + + @memorize_view_3d_mode + def execute(self, context): + props = context.scene.muv_props.cpuv_obj + if len(props.src_uvs) == 0 or len(props.src_pin_uvs) == 0: + self.report({'WARNING'}, "Need copy UV at first") + return {'CANCELLED'} + + obj_names = [] + + 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 = bmesh.from_edit_mesh(obj.data) + if muv_common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + if self.uv_map == "" or not self.uv_map in bm.loops.layers.uv.keys(): + self.report({'INFO'}, "Paste UV coordinate per object") + else: + self.report( + {'INFO'}, + "Paste UV coordinate per object (UV map: %s)" % + (self.uv_map)) + + # get UV layer + if self.uv_map == "" or not self.uv_map in bm.loops.layers.uv.keys(): + 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() + else: + uv_layer = bm.loops.layers.uv[self.uv_map] + + # get selected face + dest_uvs = [] + dest_pin_uvs = [] + dest_face_indices = [] + for face in bm.faces: + 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] + dest_uvs.append(uvs) + dest_pin_uvs.append(pin_uvs) + if len(props.src_uvs) != len(dest_uvs): + self.report( + {'WARNING'}, + "Number of faces is different from copied" + + "(src:%d, dest:%d)" % + (len(props.src_uvs), len(dest_uvs))) + return {'CANCELLED'} + + # paste + for i, idx in enumerate(dest_face_indices): + suv = props.src_uvs[i] + spuv = props.src_pin_uvs[i] + duv = dest_uvs[i] + if len(suv) != len(duv): + self.report({'WARNING'}, "Some faces are different size") + return {'CANCELLED'} + suvs_fr = [uv for uv in suv] + spuvs_fr = [pin_uv for pin_uv in spuv] + # paste UVs + for l, suv, spuv in zip(bm.faces[idx].loops, suvs_fr, spuvs_fr): + l[uv_layer].uv = suv + l[uv_layer].pin_uv = spuv + + bmesh.update_edit_mesh(obj.data) + + self.report({'INFO'}, "%s's UV coordinates are pasted" % (obj.name)) + + return {'FINISHED'} + + +class MUV_CPUVObjPasteUVMenu(bpy.types.Menu): + """ + Menu class: Paste UV coordinate per object + """ + + bl_idname = "object.muv_cpuv_obj_paste_uv_menu" + bl_label = "Paste UV" + bl_description = "Paste UV coordinate per object" + + def draw(self, context): + 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()) + uv_maps = list(set(uv_maps)) + layout.operator( + MUV_CPUVObjPasteUV.bl_idname, + text="[Default]", icon="PLUGIN").uv_map = "" + for m in uv_maps: + layout.operator( + MUV_CPUVObjPasteUV.bl_idname, + text=m, icon="PLUGIN").uv_map = m diff --git a/uv_magic_uv/muv_cpuv_selseq_ops.py b/uv_magic_uv/muv_cpuv_selseq_ops.py index 139dd883..2453578f 100644 --- a/uv_magic_uv/muv_cpuv_selseq_ops.py +++ b/uv_magic_uv/muv_cpuv_selseq_ops.py @@ -18,11 +18,10 @@ # # ##### END GPL LICENSE BLOCK ##### - __author__ = "Nutti " __status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2015" +__version__ = "4.1" +__date__ = "13 Nov 2016" import bpy @@ -40,7 +39,7 @@ class MUV_CPUVSelSeqCopyUV(bpy.types.Operator): bl_label = "Copy UV (Selection Sequence) (Operation)" bl_description = "Copy UV data by selection sequence (Operation)" bl_options = {'REGISTER', 'UNDO'} - + uv_map = bpy.props.StringProperty(options={'HIDDEN'}) def execute(self, context): @@ -56,7 +55,7 @@ def execute(self, context): bm = bmesh.from_edit_mesh(obj.data) if muv_common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() - + # get UV layer if self.uv_map == "": if not bm.loops.layers.uv: @@ -125,7 +124,7 @@ class MUV_CPUVSelSeqPasteUV(bpy.types.Operator): 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_N") + default="N_M") flip_copied_uv = BoolProperty( name="Flip Copied UV", @@ -164,7 +163,7 @@ def execute(self, context): uv_layer = bm.loops.layers.uv.verify() else: uv_layer = bm.loops.layers.uv[self.uv_map] - + # get selected face dest_uvs = [] dest_pin_uvs = [] @@ -186,7 +185,7 @@ def execute(self, context): "(src:%d, dest:%d)" % (len(props.src_uvs), len(dest_uvs))) return {'CANCELLED'} - + # paste for i, idx in enumerate(dest_face_indices): suv = None @@ -247,4 +246,3 @@ def draw(self, context): layout.operator( MUV_CPUVSelSeqPasteUV.bl_idname, text=m, icon="PLUGIN").uv_map = m - diff --git a/uv_magic_uv/muv_fliprot_ops.py b/uv_magic_uv/muv_fliprot_ops.py index b83957a0..9c9cc544 100644 --- a/uv_magic_uv/muv_fliprot_ops.py +++ b/uv_magic_uv/muv_fliprot_ops.py @@ -18,11 +18,10 @@ # # ##### END GPL LICENSE BLOCK ##### - __author__ = "Nutti " __status__ = "production" __version__ = "4.0" -__date__ = "14 May 2016" +__date__ = "13 Nov 2016" import bpy @@ -58,13 +57,13 @@ def execute(self, context): bm = bmesh.from_edit_mesh(obj.data) if muv_common.check_version(2, 73, 0) >= 0: 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") return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify() - + # get selected face dest_uvs = [] dest_pin_uvs = [] @@ -104,4 +103,3 @@ def execute(self, context): bmesh.update_edit_mesh(obj.data) return {'FINISHED'} - diff --git a/uv_magic_uv/muv_menu.py b/uv_magic_uv/muv_menu.py index 68a76225..22d62d29 100644 --- a/uv_magic_uv/muv_menu.py +++ b/uv_magic_uv/muv_menu.py @@ -20,14 +20,16 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2016" +__version__ = "4.1" +__date__ = "13 Nov 2016" import bpy from . import muv_cpuv_ops from . import muv_cpuv_selseq_ops from . import muv_transuv_ops +from . import muv_texlock_ops +from . import muv_wsuv_ops class MUV_CPUVMenu(bpy.types.Menu): @@ -46,6 +48,20 @@ def draw(self, context): self.layout.menu(muv_cpuv_selseq_ops.MUV_CPUVSelSeqPasteUVMenu.bl_idname, icon="PLUGIN") +class MUV_CPUVObjMenu(bpy.types.Menu): + """ + Menu class: Master menu of Copy/Paste UV coordinate per object + """ + + bl_idname = "object.muv_cpuv_obj_menu" + bl_label = "Copy/Paste UV" + bl_description = "Copy and Paste UV coordinate per object" + + def draw(self, context): + self.layout.menu(muv_cpuv_ops.MUV_CPUVObjCopyUVMenu.bl_idname, icon="PLUGIN") + self.layout.menu(muv_cpuv_ops.MUV_CPUVObjPasteUVMenu.bl_idname, icon="PLUGIN") + + class MUV_TransUVMenu(bpy.types.Menu): """ Menu class: Master menu of Transfer UV coordinate @@ -59,3 +75,32 @@ def draw(self, context): self.layout.operator(muv_transuv_ops.MUV_TransUVCopy.bl_idname, icon="PLUGIN") self.layout.operator(muv_transuv_ops.MUV_TransUVPaste.bl_idname, icon="PLUGIN") + +class MUV_TexLockMenu(bpy.types.Menu): + """ + Menu class: Master menu of Texture Lock + """ + + bl_idname = "uv.muv_texlock_menu" + bl_label = "Texture Lock" + bl_description = "Lock texture when vertices of mesh (Preserve UV)" + + def draw(self, context): + self.layout.operator(muv_texlock_ops.MUV_TexLockStart.bl_idname, icon="PLUGIN") + self.layout.operator(muv_texlock_ops.MUV_TexLockStop.bl_idname, icon="PLUGIN") + self.layout.operator(muv_texlock_ops.MUV_TexLockIntrStart.bl_idname, icon="PLUGIN") + self.layout.operator(muv_texlock_ops.MUV_TexLockIntrStop.bl_idname, icon="PLUGIN") + + +class MUV_WSUVMenu(bpy.types.Menu): + """ + Menu class: Master menu of world scale UV + """ + + bl_idname = "uv.muv_wsuv_menu" + bl_label = "World Scale UV" + bl_description = "" + + def draw(self, context): + self.layout.operator(muv_wsuv_ops.MUV_WSUVMeasure.bl_idname, icon="PLUGIN") + self.layout.operator(muv_wsuv_ops.MUV_WSUVApply.bl_idname, icon="PLUGIN") diff --git a/uv_magic_uv/muv_mirroruv_ops.py b/uv_magic_uv/muv_mirroruv_ops.py new file mode 100644 index 00000000..cf63b164 --- /dev/null +++ b/uv_magic_uv/muv_mirroruv_ops.py @@ -0,0 +1,157 @@ +# + +# ##### 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 " +__status__ = "production" +__version__ = "4.1" +__date__ = "13 Nov 2016" + + +import bpy +from bpy.props import EnumProperty, FloatProperty +import bmesh +from mathutils import Vector +from . import muv_common + + +class MUV_MirrorUV(bpy.types.Operator): + """ + Operation class: Mirror UV + """ + + bl_idname = "uv.muv_mirror_uv" + 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 __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) + + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj and obj.type == 'MESH') + + + def execute(self, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + + error = self.error + axis = self.axis + + if muv_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'} diff --git a/uv_magic_uv/muv_mvuv_ops.py b/uv_magic_uv/muv_mvuv_ops.py index 8d51db39..cda11649 100644 --- a/uv_magic_uv/muv_mvuv_ops.py +++ b/uv_magic_uv/muv_mvuv_ops.py @@ -1,13 +1,31 @@ +# + +# ##### 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 " __status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2016" +__version__ = "4.1" +__date__ = "13 Nov 2016" import bpy import bmesh -from bpy.app.handlers import persistent -from bpy_extras import view3d_utils from mathutils import Vector @@ -19,13 +37,14 @@ class MUV_MVUV(bpy.types.Operator): bl_idname = "view3d.muv_mvuv" bl_label = "Move the UV from View3D" bl_options = {'REGISTER', 'UNDO'} - + __topology_dict = [] - __first_mouse = Vector((0.0, 0.0)) + __prev_mouse = Vector((0.0, 0.0)) __offset_uv = Vector((0.0, 0.0)) - __old_offset_uv = Vector((0.0, 0.0)) + __prev_offset_uv = Vector((0.0, 0.0)) __first_time = True __ini_uvs = [] + __running = False def __find_uv(self, context): bm = bmesh.from_edit_mesh(context.object.data) @@ -52,48 +71,56 @@ def __find_uv(self, context): v2) vres = sv2 - sv1 va = vres.angle(Vector((0.0, 1.0))) - + uv1 = v.link_loops[0][active_uv].uv uv2 = v.link_loops[0].link_loop_next[active_uv].uv uvres = uv2 - uv1 uva = uvres.angle(Vector((0.0,1.0))) diff = uva - va first = False - - return topology_dict, uvs - + + return topology_dict, uvs + @classmethod def poll(cls, context): return (context.edit_object) def modal(self, context, event): if self.__first_time is True: - self.__first_mouse = Vector(( - event.mouse_region_x, event.mouse_region_y)) - if event.type == 'LEFTMOUSE' and event.value == 'RELEASE': - self.__first_time = False + self.__prev_mouse = Vector(( + event.mouse_region_x, event.mouse_region_y)) + self.__first_time = False return {'RUNNING_MODAL'} - ob = context.object - bm = bmesh.from_edit_mesh(ob.data) + # move UV div = 10000 self.__offset_uv += Vector(( - (event.mouse_region_x - self.__first_mouse.x) / div, - (event.mouse_region_y - self.__first_mouse.y) / div)) + (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 self.__running is True: + if event.type == 'LEFTMOUSE' and event.value == 'RELEASE': + self.__running = False + return {'RUNNING_MODAL'} + + # update UV + obj = context.object + bm = bmesh.from_edit_mesh(obj.data) active_uv = bm.loops.layers.uv.active - - o = self.__offset_uv - oo = self.__old_offset_uv for fidx, vidx in self.__topology_dict: - d = bm.faces[fidx].loops[vidx][active_uv] - vec = Vector((o.x - o.y, o.x + o.y)) - d.uv = d.uv - Vector((oo.x , oo.y)) + vec - - self.__old_offset_uv = vec - self.__first_mouse = Vector(( - event.mouse_region_x, event.mouse_region_y)) - ob.data.update() + 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' @@ -101,10 +128,12 @@ def modal(self, context, event): 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 - return {'FINISHED'} + return {'FINISHED'} + # confirmed if event.type == confirm_btn and event.value == 'PRESS': return {'FINISHED'} @@ -112,7 +141,7 @@ def modal(self, context, event): def execute(self, context): self.__first_time = True + self.__running = True context.window_manager.modal_handler_add(self) - self.__topology_dict, self.__ini_uvs = self.__find_uv(context) - return {'RUNNING_MODAL'} - + self.__topology_dict, self.__ini_uvs = self.__find_uv(context) + return {'RUNNING_MODAL'} diff --git a/uv_magic_uv/muv_packuv_ops.py b/uv_magic_uv/muv_packuv_ops.py index 18927e3e..a91a1a6a 100644 --- a/uv_magic_uv/muv_packuv_ops.py +++ b/uv_magic_uv/muv_packuv_ops.py @@ -18,10 +18,16 @@ # # ##### END GPL LICENSE BLOCK ##### +__author__ = "Nutti " +__status__ = "production" +__version__ = "4.1" +__date__ = "13 Nov 2016" + + import bpy import bmesh import mathutils -from bpy.props import FloatProperty, BoolProperty +from bpy.props import FloatProperty, FloatVectorProperty, BoolProperty from mathutils import Vector from math import fabs from collections import defaultdict @@ -29,12 +35,6 @@ import time -__author__ = "Nutti " -__status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2016" - - class MUV_PackUV(bpy.types.Operator): """ Operation class: Pack UV with same UV islands are integrated @@ -63,7 +63,23 @@ class MUV_PackUV(bpy.types.Operator): min=0, max=1, default=0.001) - + + allowable_center_deviation = FloatVectorProperty( + name="Allowable Center Deviation", + description="Allowable center deviation to judge same UV island", + min=0.000001, + max=0.1, + default=(0.001, 0.001), + size=2) + + allowable_size_deviation = FloatVectorProperty( + name="Allowable Size Deviation", + description="Allowable sizse deviation to judge same UV island", + min=0.000001, + max=0.1, + default=(0.001, 0.001), + size=2) + def __init__(self): self.__face_to_verts = defaultdict(set) self.__vert_to_faces = defaultdict(set) @@ -79,7 +95,7 @@ def execute(self, context): uv_layer = bm.loops.layers.uv.verify() selected_faces = [f for f in bm.faces if f.select] - + # create mesh database for f in selected_faces: for l in f.loops: @@ -155,18 +171,22 @@ def __group_island(self, island_info): break # all faces are parsed isl_1['group'] = num_group isl_1['sorted'] = isl_1['faces'] - + # search same island for isl_2 in island_info: if isl_2['group'] == -1: + dcx = isl_2['center'].x - isl_1['center'].x + dcy = isl_2['center'].y - isl_1['center'].y + dsx = isl_2['size'].x - isl_1['size'].x + dsy = isl_2['size'].y - isl_1['size'].y center_x_matched = ( - fabs(isl_2['center'].x - isl_1['center'].x) < 0.001) + fabs(dcx) < self.allowable_center_deviation[0]) center_y_matched = ( - fabs(isl_2['center'].y - isl_1['center'].y) < 0.001) + fabs(dcy) < self.allowable_center_deviation[1]) size_x_matched = ( - fabs(isl_2['size'].x - isl_1['size'].x) < 0.001) + fabs(dsx) < self.allowable_size_deviation[0]) size_y_matched = ( - fabs(isl_2['size'].y - isl_1['size'].y) < 0.001) + fabs(dsy) < self.allowable_size_deviation[1]) center_matched = center_x_matched and center_y_matched size_matched = size_x_matched and size_y_matched num_uv_matched = (isl_2['num_uv'] == isl_1['num_uv']) @@ -228,7 +248,7 @@ def __get_island_info(self, uv_layer, islands): info['faces'] = isl island_info.append(info) - + return island_info def __parse_island(self, bm, face_idx, faces_left, island): @@ -259,5 +279,3 @@ def __get_island(self, bm): uv_island_lists.append(current_island) return uv_island_lists - - diff --git a/uv_magic_uv/muv_preferences.py b/uv_magic_uv/muv_preferences.py index 6f3c35d9..0a955538 100644 --- a/uv_magic_uv/muv_preferences.py +++ b/uv_magic_uv/muv_preferences.py @@ -18,11 +18,10 @@ # # ##### END GPL LICENSE BLOCK ##### - __author__ = "Nutti " __status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2016" +__version__ = "4.1" +__date__ = "13 Nov 2016" import bpy @@ -48,4 +47,3 @@ def draw(self, context): layout = self.layout layout.prop(self, "enable_uvbb") layout.prop(self, "enable_texproj") - diff --git a/uv_magic_uv/muv_props.py b/uv_magic_uv/muv_props.py index 3f7aab4a..a4582be2 100644 --- a/uv_magic_uv/muv_props.py +++ b/uv_magic_uv/muv_props.py @@ -20,11 +20,12 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2016" +__version__ = "4.1" +__date__ = "13 Nov 2016" + import bpy -from bpy.props import FloatProperty, EnumProperty +from bpy.props import FloatProperty, EnumProperty, BoolProperty DEBUG = False @@ -38,17 +39,25 @@ def get_loaded_texture_name(scene, context): # Properties used in this add-on. class MUV_Properties(): cpuv = None + cpuv_obj = None cpuv_selseq = None transuv = None uvbb = None texproj = None + texlock = None + texwrap = None + wsuv = None def __init__(self): self.cpuv = MUV_CPUVProps() + self.cpuv_obj = MUV_CPUVProps() self.cpuv_selseq = MUV_CPUVSelSeqProps() self.transuv = MUV_TransUVProps() self.uvbb = MUV_UVBBProps() self.texproj = MUV_TexProjProps() + self.texlock = MUV_TexLockProps() + self.texwrap = MUV_TexWrapProps() + self.wsuv = MUV_WSUVProps() class MUV_CPUVProps(): @@ -76,6 +85,21 @@ class MUV_TexProjProps(): running = False +class MUV_TexLockProps(): + verts_orig = None + intr_verts_orig = None + intr_running = False + + +class MUV_TexWrapProps(): + src_face_index = -1 + + +class MUV_WSUVProps(): + ref_sv = None + ref_suv = None + + def init_props(scene): scene.muv_props = MUV_Properties() scene.muv_uvbb_cp_size = FloatProperty( @@ -90,6 +114,10 @@ def init_props(scene): default=10.0, min=3.0, max=100.0) + scene.muv_uvbb_uniform_scaling = BoolProperty( + name="Uniform Scaling", + description="Enable Uniform Scaling", + default=False) scene.muv_texproj_tex_magnitude = FloatProperty( name="Magnitude", description="Texture Magnitude.", @@ -115,4 +143,3 @@ def clear_props(scene): del scene.muv_texproj_tex_magnitude del scene.muv_texproj_tex_image del scene.muv_texproj_tex_transparency - diff --git a/uv_magic_uv/muv_texlock_ops.py b/uv_magic_uv/muv_texlock_ops.py new file mode 100644 index 00000000..6a2a2e8d --- /dev/null +++ b/uv_magic_uv/muv_texlock_ops.py @@ -0,0 +1,435 @@ +# + +# ##### 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 " +__status__ = "production" +__version__ = "4.1" +__date__ = "13 Nov 2016" + + +import bpy +import bmesh +from mathutils import Vector +from math import tan, atan2, cos, sqrt, sin, fabs +from bpy.props import BoolProperty +from . import muv_common + + +def get_vco(verts_orig, loop): + """ + Get vertex original coordinate from loop + """ + for vo in verts_orig: + if vo["vidx"] == loop.vert.index and vo["moved"] == False: + return vo["vco"] + return loop.vert.co + + +def get_link_loops(vert): + """ + Get loop linked to vertex + """ + link_loops = [] + for f in vert.link_faces: + adj_loops = [] + for loop in f.loops: + # self loop + if loop.vert == vert: + l = loop + # linked loop + else: + for e in loop.vert.link_edges: + if e.other_vert(loop.vert) == vert: + adj_loops.append(loop) + if len(adj_loops) < 2: + return None + + link_loops.append({"l": l, "l0": adj_loops[0], "l1": adj_loops[1]}) + return link_loops + + +def get_ini_geom(link_loop, uv_layer, verts_orig, v_orig): + """ + Get initial geometory + (Get interior angle of face in vertex/UV space) + """ + u = link_loop["l"][uv_layer].uv + v0 = get_vco(verts_orig, link_loop["l0"]) + u0 = link_loop["l0"][uv_layer].uv + lo0 = link_loop["l0"] + v1 = get_vco(verts_orig, link_loop["l1"]) + u1 = link_loop["l1"][uv_layer].uv + lo1 = link_loop["l1"] + + # get interior angle of face in vertex space + v0v1 = v1 - v0 + v0v = v_orig["vco"] - v0 + v1v = v_orig["vco"] - v1 + theta0 = v0v1.angle(v0v) + theta1 = v0v1.angle(-v1v) + if (theta0 + theta1) > muv_common.PHI: + theta0 = v0v1.angle(-v0v) + theta1 = v0v1.angle(v1v) + + # get interior angle of face in UV space + u0u1 = u1 - u0 + u0u = u - u0 + u1u = u - u1 + phi0 = u0u1.angle(u0u) + phi1 = u0u1.angle(-u1u) + if (phi0 + phi1) > muv_common.PHI: + phi0 = u0u1.angle(-u0u) + phi1 = u0u1.angle(u1u) + + # get direction of linked UV coordinate + # this will be used to judge whether angle is more or less than 180 degree + dir0 = u0u1.cross(u0u) > 0 + dir1 = u0u1.cross(u1u) > 0 + + return { + "theta0": theta0, + "theta1": theta1, + "phi0": phi0, + "phi1": phi1, + "dir0": dir0, + "dir1": dir1} + + +def get_target_uv(link_loop, uv_layer, verts_orig, v, ini_geom): + """ + Get target UV coordinate + """ + v0 = get_vco(verts_orig, link_loop["l0"]) + u0 = link_loop["l0"][uv_layer].uv + lo0 = link_loop["l0"] + v1 = get_vco(verts_orig, link_loop["l1"]) + u1 = link_loop["l1"][uv_layer].uv + lo1 = link_loop["l1"] + + # get interior angle of face in vertex space + v0v1 = v1 - v0 + v0v = v.co - v0 + v1v = v.co - v1 + theta0 = v0v1.angle(v0v) + theta1 = v0v1.angle(-v1v) + if (theta0 + theta1) > muv_common.PHI: + theta0 = v0v1.angle(-v0v) + theta1 = v0v1.angle(v1v) + + # calculate target interior angle in UV space + phi0 = theta0 * ini_geom["phi0"] / ini_geom["theta0"] + phi1 = theta1 * ini_geom["phi1"] / ini_geom["theta1"] + + uv0 = lo0[uv_layer].uv + uv1 = lo1[uv_layer].uv + + # calculate target vertex coordinate from target interior angle + tuv0, tuv1 = calc_tri_vert(uv0, uv1, phi0, phi1) + + # target UV coordinate depends on direction, so judge using direction of + # linked UV coordinate + u0u1 = uv1 - uv0 + u0u = tuv0 - uv0 + u1u = tuv0 - uv1 + dir0 = u0u1.cross(u0u) > 0 + dir1 = u0u1.cross(u1u) > 0 + if (ini_geom["dir0"] != dir0) or (ini_geom["dir1"] != dir1): + return tuv1 + + return tuv0 + + +def calc_tri_vert(v0, v1, angle0, angle1): + """ + Calculate rest coordinate from other coordinates and angle of end + """ + angle = muv_common.PHI - angle0 - angle1 + + alpha = atan2(v1.y - v0.y, v1.x - v0.x) + d = (v1.x - v0.x) / cos(alpha) + a = d * sin(angle0) / sin(angle) + b = d * sin(angle1) / sin(angle) + s = (a + b + d) / 2.0 + if fabs(d) < 0.0000001: + xd = 0 + yd = 0 + else: + xd = (b * b - a * a + d * d) / (2 * d) + yd = 2 * sqrt(s * (s - a) * (s - b) * (s - d)) / d + x1 = xd * cos(alpha) - yd * sin(alpha) + v0.x + y1 = xd * sin(alpha) + yd * cos(alpha) + v0.y + x2 = xd * cos(alpha) + yd * sin(alpha) + v0.x + y2 = xd * sin(alpha) - yd * cos(alpha) + v0.y + + return Vector((x1, y1)), Vector((x2, y2)) + + +class MUV_TexLockStart(bpy.types.Operator): + """ + Operation class: Start Texture Lock + """ + + bl_idname = "uv.muv_texlock_start" + bl_label = "Start" + bl_description = "Start Texture Lock" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + props = context.scene.muv_props.texlock + obj = bpy.context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if muv_common.check_version(2, 73, 0) >= 0: + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + 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() + + props.verts_orig = [ + {"vidx": v.index, "vco": v.co.copy(), "moved": False} + for v in bm.verts if v.select] + + return {'FINISHED'} + + +class MUV_TexLockStop(bpy.types.Operator): + """ + Operation class: Stop Texture Lock + """ + + bl_idname = "uv.muv_texlock_stop" + bl_label = "Stop" + bl_description = "Start Texture Lock" + bl_options = {'REGISTER', 'UNDO'} + + connect = BoolProperty( + name="Connect UV", + default=True) + + def execute(self, context): + props = context.scene.muv_props.texlock + obj = bpy.context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if muv_common.check_version(2, 73, 0) >= 0: + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + 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() + + verts = [v.index for v in bm.verts if v.select] + verts_orig = props.verts_orig + + # move UV followed by vertex coordinate + for vidx, v_orig in zip(verts, verts_orig): + if vidx != v_orig["vidx"]: + self.report({'ERROR'}, "Internal Error") + return {"CANCELLED"} + + v = bm.verts[vidx] + link_loops = get_link_loops(v) + + result = [] + + for ll in link_loops: + ini_geom = get_ini_geom(ll, uv_layer, verts_orig, v_orig) + target_uv = get_target_uv(ll, uv_layer, verts_orig, v, ini_geom) + result.append({"l": ll["l"], "uv": target_uv}) + + # connect other face's UV + if self.connect: + ave = Vector((0.0, 0.0)) + for r in result: + ave = ave + r["uv"] + ave = ave / len(result) + for r in result: + r["l"][uv_layer].uv = ave + else: + for r in result: + r["l"][uv_layer].uv = r["uv"] + v_orig["moved"] = True + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} + + +class MUV_TexLockUpdater(bpy.types.Operator): + """ + Operation class: Texture locking updater + """ + + bl_idname = "uv.muv_texlock_updater" + bl_label = "Texture Lock Updater" + bl_description = "Texture Lock Updater" + + __timer = None + + def __update_uv(self, context): + """ + Update UV when vertex coordinates are changed + """ + props = context.scene.muv_props.texlock + obj = bpy.context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if muv_common.check_version(2, 73, 0) >= 0: + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + 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() + + verts = [v.index for v in bm.verts if v.select] + verts_orig = props.intr_verts_orig + + for vidx, v_orig in zip(verts, verts_orig): + if vidx != v_orig["vidx"]: + self.report({'ERROR'}, "Internal Error") + return {"CANCELLED"} + + v = bm.verts[vidx] + link_loops = get_link_loops(v) + + result = [] + + for ll in link_loops: + ini_geom = get_ini_geom(ll, uv_layer, verts_orig, v_orig) + target_uv = get_target_uv(ll, uv_layer, verts_orig, v, ini_geom) + result.append({"l": ll["l"], "uv": target_uv}) + + # UV connect option is always true, because it raises unexpected behavior + ave = Vector((0.0, 0.0)) + for r in result: + ave = ave + r["uv"] + ave = ave / len(result) + for r in result: + r["l"][uv_layer].uv = ave + v_orig["moved"] = True + bmesh.update_edit_mesh(obj.data) + + muv_common.redraw_all_areas() + props.intr_verts_orig = [ + {"vidx": v.index, "vco": v.co.copy(), "moved": False} + for v in bm.verts if v.select] + + + def modal(self, context, event): + props = context.scene.muv_props.texlock + if context.area: + context.area.tag_redraw() + if props.intr_running is False: + return {'FINISHED'} + if event.type == 'TIMER': + self.__update_uv(context) + + return {'PASS_THROUGH'} + + def handle_add(self, context): + if self.__timer is None: + self.__timer = context.window_manager.event_timer_add( + 0.10, context.window) + context.window_manager.modal_handler_add(self) + + def handle_remove(self, context): + if self.__timer is not None: + context.window_manager.event_timer_remove(self.__timer) + self.__timer = None + + def execute(self, context): + props = context.scene.muv_props.texlock + if props.intr_running == False: + self.handle_add(context) + props.intr_running = True + return {'RUNNING_MODAL'} + else: + self.handle_remove(context) + props.intr_running = False + if context.area: + context.area.tag_redraw() + + return {'FINISHED'} + + +class MUV_TexLockIntrStart(bpy.types.Operator): + """ + Operation class: Start texture locking (Interactive mode) + """ + + bl_idname = "uv.muv_texlock_intr_start" + bl_label = "Texture Lock Start (Interactive mode)" + bl_description = "Texture Lock Start (Realtime UV update)" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + props = context.scene.muv_props.texlock + if props.intr_running is True: + return {'CANCELLED'} + + obj = bpy.context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if muv_common.check_version(2, 73, 0) >= 0: + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + 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() + + props.intr_verts_orig = [ + {"vidx": v.index, "vco": v.co.copy(), "moved": False} + for v in bm.verts if v.select] + + bpy.ops.uv.muv_texlock_updater() + + return {'FINISHED'} + +# Texture lock (Stop, Interactive mode) +class MUV_TexLockIntrStop(bpy.types.Operator): + """ + Operation class: Stop texture locking (interactive mode) + """ + + bl_idname = "uv.muv_texlock_intr_stop" + bl_label = "Texture Lock Stop (Interactive mode)" + bl_description = "Texture Lock Stop (Realtime UV update)" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + props = context.scene.muv_props.texlock + if props.intr_running is False: + return {'CANCELLED'} + + bpy.ops.uv.muv_texlock_updater() + + return {'FINISHED'} diff --git a/uv_magic_uv/muv_texproj_ops.py b/uv_magic_uv/muv_texproj_ops.py index 209117aa..689155f2 100644 --- a/uv_magic_uv/muv_texproj_ops.py +++ b/uv_magic_uv/muv_texproj_ops.py @@ -20,8 +20,8 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2016" +__version__ = "4.1" +__date__ = "13 Nov 2016" import bpy @@ -47,12 +47,12 @@ def get_canvas(context, magnitude): PAD_Y = 20 width = context.region.width height = context.region.height - + center_x = width * 0.5 center_y = height * 0.5 len_x = (width - PAD_X * 2.0) * magnitude len_y = (height - PAD_Y * 2.0) * magnitude - + x0 = int(center_x - len_x * 0.5) y0 = int(center_y - len_y * 0.5) x1 = int(center_x + len_x * 0.5) @@ -72,7 +72,7 @@ def rect_to_rect2(rect): def region_to_canvas(region, rg_vec, canvas): """ Convert screen region to canvas - """ + """ cv_rect = rect_to_rect2(canvas) cv_vec = mathutils.Vector() @@ -87,30 +87,30 @@ class MUV_TexProjRenderer(bpy.types.Operator): Operation class: Render selected texture No operation (only rendering texture) """ - + bl_idname = "uv.muv_texproj_renderer" bl_description = "Render selected texture" bl_label = "Texture renderer" __handle = None - + @staticmethod def handle_add(self, context): MUV_TexProjRenderer.__handle = bpy.types.SpaceView3D.draw_handler_add( MUV_TexProjRenderer.draw_texture, (self, context), 'WINDOW', 'POST_PIXEL') - + @staticmethod def handle_remove(self, context): if MUV_TexProjRenderer.__handle is not None: bpy.types.SpaceView3D.draw_handler_remove( MUV_TexProjRenderer.__handle, 'WINDOW') MUV_TexProjRenderer.__handle = None - + @staticmethod def draw_texture(self, context): sc = context.scene - + # no textures are selected if sc.muv_texproj_tex_image == "None": return @@ -140,7 +140,7 @@ def draw_texture(self, context): bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR) bgl.glTexEnvi( bgl.GL_TEXTURE_ENV, bgl.GL_TEXTURE_ENV_MODE, bgl.GL_MODULATE) - + # render texture bgl.glBegin(bgl.GL_QUADS) bgl.glColor4f(1.0, 1.0, 1.0, sc.muv_texproj_tex_transparency) @@ -154,7 +154,7 @@ class MUV_TexProjStart(bpy.types.Operator): """ Operation class: Start Texture Projection """ - + bl_idname = "uv.muv_texproj_start" bl_label = "Start Texture Projection" bl_description = "Start Texture Projection" @@ -175,7 +175,7 @@ class MUV_TexProjStop(bpy.types.Operator): """ Operation class: Stop Texture Projection """ - + bl_idname = "uv.muv_texproj_stop" bl_label = "Stop Texture Projection" bl_description = "Stop Texture Projection" @@ -196,7 +196,7 @@ class MUV_TexProjProject(bpy.types.Operator): """ Operation class: Project texture """ - + bl_idname = "uv.muv_texproj_project" bl_label = "Project Texture" bl_description = "Project Texture" @@ -210,7 +210,7 @@ def execute(self, context): return {'CANCELLED'} area, region, space = muv_common.get_space( 'VIEW_3D', 'WINDOW', 'VIEW_3D') - + # get faces to be texture projected obj = context.active_object world_mat = obj.matrix_world @@ -263,7 +263,7 @@ class OBJECT_PT_TP(bpy.types.Panel): bl_description = "Texture Projection Menu" bl_space_type = "VIEW_3D" bl_region_type = "UI" - + def draw(self, context): prefs = context.user_preferences.addons["uv_magic_uv"].preferences if prefs.enable_texproj is False: @@ -281,4 +281,3 @@ def draw(self, context): layout.prop(sc, "muv_texproj_tex_magnitude", text="Magnitude") layout.prop(sc, "muv_texproj_tex_transparency", text="Transparency") layout.operator(MUV_TexProjProject.bl_idname, text="Project") - diff --git a/uv_magic_uv/muv_transuv_ops.py b/uv_magic_uv/muv_transuv_ops.py index 83fb2471..f8453466 100644 --- a/uv_magic_uv/muv_transuv_ops.py +++ b/uv_magic_uv/muv_transuv_ops.py @@ -20,8 +20,9 @@ __author__ = "Nutti , Mifth, MaxRobinot" __status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2016" +__version__ = "4.1" +__date__ = "13 Nov 2016" + import bpy import bmesh @@ -76,7 +77,7 @@ def execute(self, context): if all_sorted_faces: for face_data in all_sorted_faces.values(): uv_loops = face_data[2] - uvs = [l.uv for l in uv_loops] + uvs = [l.uv.copy() for l in uv_loops] pin_uvs = [l.pin_uv for l in uv_loops] props.topology_copied.append([uvs, pin_uvs]) @@ -127,7 +128,7 @@ def execute(self, context): if i > 0 and i % 2 != 0: 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: @@ -340,4 +341,3 @@ def get_other_verts_edges(face, vert1, vert2, first_edge, uv_layer): break return [face_verts, face_edges, face_loops] - diff --git a/uv_magic_uv/muv_unwrapconst_ops.py b/uv_magic_uv/muv_unwrapconst_ops.py new file mode 100644 index 00000000..aff34a9f --- /dev/null +++ b/uv_magic_uv/muv_unwrapconst_ops.py @@ -0,0 +1,122 @@ +# ##### 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 " +__status__ = "production" +__version__ = "4.1" +__date__ = "13 Nov 2016" + + +import bpy +import bmesh +from bpy.props import BoolProperty, EnumProperty, FloatProperty +from . import muv_common + + +class MUV_UnwrapConstraint(bpy.types.Operator): + """ + Operation class: Unwrap with constrain UV coordinate + """ + + bl_idname = "uv.muv_unwrap_constraint" + bl_label = "Unwrap Constraint" + bl_description = "Unwrap while keeping uv coordinate" + bl_options = {'REGISTER', 'UNDO'} + + # property for original unwrap + method = EnumProperty( + name="Method", + description="Unwrapping method", + items=[ + ('ANGLE_BASED', 'Angle Based', 'Angle Based'), + ('CONFORMAL', 'Conformal', 'Conformal')], + default='ANGLE_BASED') + + fill_holes = BoolProperty( + name="Fill Holes", + description="Virtual fill holes in meshes before unwrapping", + default=True) + + correct_aspect = BoolProperty( + name="Correct Aspect", + description="Map UVs taking image aspect ratio into account", + default=True) + + use_subsurf_data = BoolProperty( + name="Use Subsurf Modifier", + description="Map UVs taking vertex position after subsurf into account", + default=False) + + margin = FloatProperty( + name="Margin", + description="Space between islands", + max=1.0, + min=0.0, + default=0.001) + + # property for this operation + u_const = BoolProperty( + name="U-Constraint", + description="Keep UV U-axis coordinate", + default=False) + + v_const = BoolProperty( + name="V-Constraint", + description="Keep UV V-axis coordinate", + default=False) + + + def execute(self, context): + obj = bpy.context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if muv_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() + + # get original UV coordinate + faces = [f for f in bm.faces if f.select] + uv_list = [] + for f in faces: + uvs = [l[uv_layer].uv.copy() for l in f.loops] + uv_list.append(uvs) + + # unwrap + bpy.ops.uv.unwrap( + method=self.method, + fill_holes=self.fill_holes, + correct_aspect=self.correct_aspect, + use_subsurf_data=self.use_subsurf_data, + margin=self.margin) + + # when U/V-Constraint is checked, revert original coordinate + for f, uvs in zip(faces, uv_list): + for l, uv in zip(f.loops, uvs): + if self.u_const: + l[uv_layer].uv.x = uv.x + if self.v_const: + l[uv_layer].uv.y = uv.y + + # update mesh + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} diff --git a/uv_magic_uv/muv_uvbb_ops.py b/uv_magic_uv/muv_uvbb_ops.py index 4b735f77..26186bb5 100644 --- a/uv_magic_uv/muv_uvbb_ops.py +++ b/uv_magic_uv/muv_uvbb_ops.py @@ -20,8 +20,8 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.0" -__date__ = "14 May 2016" +__version__ = "4.1" +__date__ = "13 Nov 2016" import bpy @@ -177,6 +177,77 @@ def set(self, x, y): self.__y = y +class MUV_UVBBUniformScalingCmd(MUV_UVBBCmd): + """ + Custom class: Uniform Scaling operation + """ + + __x = 0 # current x + __y = 0 # current y + __ix = 0 # initial x + __iy = 0 # initial y + __ox = 0 # origin of scaling x + __oy = 0 # origin of scaling y + __iox = 0 # initial origin of scaling x + __ioy = 0 # initial origin of scaling y + __mat = None + + def __init__(self, ix, iy, ox, oy, mat): + self.op = 'SCALING' + self.__ix = ix + self.__iy = iy + self.__x = ix + self.__y = iy + self.__ox = ox + self.__oy = oy + self.__mat = mat + # initial origin of scaling = M(to original transform) * (ox, oy) + iov = mat * mathutils.Vector((ox, oy, 0.0)) + self.__iox = iov.x + self.__ioy = iov.y + + def to_matrix(self): + """ + mat = M(to original transform)^-1 * Mt(to origin) * Ms * + Mt(to origin)^-1 * M(to original transform) + """ + m = self.__mat + mi = self.__mat.inverted() + mtoi = mathutils.Matrix.Translation((-self.__iox, -self.__ioy, 0.0)) + mto = mathutils.Matrix.Translation((self.__iox, self.__ioy, 0.0)) + # every point must be transformed to origin + t = m * mathutils.Vector((self.__ix, self.__iy, 0.0)) + tix, tiy = t.x, t.y + t = m * mathutils.Vector((self.__ox, self.__oy, 0.0)) + tox, toy = t.x, t.y + t = m * mathutils.Vector((self.__x, self.__y, 0.0)) + tx, ty = t.x, t.y + ms = mathutils.Matrix() + ms.identity() + tir = math.sqrt((tix - tox) * (tix - tox) + (tiy - toy) * (tiy - toy)) + tr = math.sqrt((tx - tox) * (tx - tox) + (ty - toy) * (ty - toy)) + + sr = tr / tir + + if ((tx - tox) * (tix - tox)) > 0: + self.__dir_x = 1 + else: + self.__dir_x = -1 + if ((ty - toy) * (tiy - toy)) > 0: + self.__dir_y = 1 + else: + self.__dir_y = -1 + + ms[0][0] = sr * self.__dir_x + ms[1][1] = sr * self.__dir_y + + return mi * mto * ms * mtoi * m + + def set(self, x, y): + self.__x = x + self.__y = y + + class MUV_UVBBCmdExecuter(): """ Custom class: manage command history and execute command @@ -188,7 +259,7 @@ class MUV_UVBBCmdExecuter(): def __init__(self): self.__cmd_list = [] self.__cmd_list_redo = [] - + def execute(self, begin=0, end=-1): """ create matrix from history @@ -199,7 +270,7 @@ def execute(self, begin=0, end=-1): if begin <= i and (end == -1 or i <= end): mat = cmd.to_matrix() * mat return mat - + def undo_size(self): """ get history size @@ -237,10 +308,18 @@ def redo(self): return self.__cmd_list.append(self.__cmd_list_redo.pop()) + def pop(self): + if len(self.__cmd_list) <= 0: + return None + return self.__cmd_list.pop() + + def push(self, cmd): + self.__cmd_list.push(cmd) + class MUV_UVBBRenderer(bpy.types.Operator): """ - Operation class: Render UV bounding box + Operation class: Render UV bounding box """ bl_idname = "uv.muv_uvbb_renderer" @@ -321,6 +400,10 @@ class MUV_UVBBState(IntEnum): SCALING_7 = 8 SCALING_8 = 9 ROTATING = 10 + UNIFORM_SCALING_1 = 11 + UNIFORM_SCALING_2 = 12 + UNIFORM_SCALING_3 = 13 + UNIFORM_SCALING_4 = 14 class MUV_UVBBStateBase(): @@ -352,6 +435,7 @@ def update(self, context, event, ctrl_points, mouse_view): Update state """ cp_react_size = context.scene.muv_uvbb_cp_react_size + is_uscaling = context.scene.muv_uvbb_uniform_scaling if event.type == 'LEFTMOUSE': if event.value == 'PRESS': x, y = context.region.view2d.view_to_region( @@ -361,27 +445,12 @@ def update(self, context, event, ctrl_points, mouse_view): in_cp_x = px + cp_react_size > x and px - cp_react_size < x in_cp_y = py + cp_react_size > y and py - cp_react_size < y if in_cp_x and in_cp_y: - return MUV_UVBBState.TRANSLATING + i - #if i == 0: - # return MUV_UVBBState.TRANSLATING - #elif i == 1: - # return MUV_UVBBState.SCALING_1 - #elif i == 2: - # return MUV_UVBBState.SCALING_2 - #elif i == 3: - # return MUV_UVBBState.SCALING_3 - #elif i == 4: - # return MUV_UVBBState.SCALING_4 - #elif i == 5: - # return MUV_UVBBState.SCALING_5 - #elif i == 6: - # return MUV_UVBBState.SCALING_6 - #elif i == 7: - # return MUV_UVBBState.SCALING_7 - #elif i == 8: - # return MUV_UVBBState.SCALING_8 - #elif i == 9: - # return MUV_UVBBState.ROTATING + if is_uscaling: + arr = [1, 3, 6, 8] + if i in arr: + return MUV_UVBBState.UNIFORM_SCALING_1 + arr.index(i) + else: + return MUV_UVBBState.TRANSLATING + i return MUV_UVBBState.NONE @@ -390,7 +459,7 @@ class MUV_UVBBStateTranslating(MUV_UVBBStateBase): """ Custom class: Translating state """ - + __cmd_exec = None def __init__(self, cmd_exec, mouse_view, ctrl_points): @@ -419,8 +488,8 @@ class MUV_UVBBStateScaling(MUV_UVBBStateBase): def __init__(self, cmd_exec, mouse_view, state, ctrl_points): self.__state = state self.__cmd_exec = cmd_exec - dir_x_list = [1, 1, 1, 0, 0, 0, 1, 1, 1] - dir_y_list = [1, 0, 1, 1, 1, 1, 0, 1, 1] + dir_x_list = [1, 1, 1, 0, 0, 1, 1, 1] + dir_y_list = [1, 0, 1, 1, 1, 1, 0, 1] idx = state - 2 ix, iy = ctrl_points[idx + 1].x, ctrl_points[idx + 1].y ox, oy = ctrl_points[8 - idx].x, ctrl_points[8 - idx].y @@ -438,6 +507,37 @@ def update(self, context, event, ctrl_points, mouse_view): return self.__state +class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase): + """ + Custom class: Uniform Scaling state + """ + + __state = None + __cmd_exec = None + + def __init__(self, cmd_exec, mouse_view, state, ctrl_points): + self.__state = state + self.__cmd_exec = cmd_exec + icp_idx = [1, 3, 6, 8] + ocp_idx = [8, 6, 3, 1] + idx = state - MUV_UVBBState.UNIFORM_SCALING_1 + ix, iy = ctrl_points[icp_idx[idx]].x, ctrl_points[icp_idx[idx]].y + ox, oy = ctrl_points[ocp_idx[idx]].x, ctrl_points[ocp_idx[idx]].y + mat = self.__cmd_exec.execute(end=self.__cmd_exec.undo_size()) + self.__cmd_exec.append(MUV_UVBBUniformScalingCmd( + ix, iy, ox, oy, mat.inverted())) + + def update(self, context, event, ctrl_points, mouse_view): + if event.type == 'LEFTMOUSE': + if event.value == 'RELEASE': + return MUV_UVBBState.NONE + if event.type == 'MOUSEMOVE': + x, y = mouse_view.x, mouse_view.y + self.__cmd_exec.top().set(x, y) + + return self.__state + + class MUV_UVBBStateRotating(MUV_UVBBStateBase): """ Custom class: Rotating state @@ -492,6 +592,9 @@ def __update_state(self, next_state, mouse_view, ctrl_points): self.__cmd_exec, mouse_view, ctrl_points) elif next_state == MUV_UVBBState.NONE: self.__state_obj = MUV_UVBBStateNone(self.__cmd_exec) + elif MUV_UVBBState.UNIFORM_SCALING_1 <= next_state <= MUV_UVBBState.UNIFORM_SCALING_4: + self.__state_obj = MUV_UVBBStateUniformScaling( + self.__cmd_exec, mouse_view, next_state, ctrl_points) self.__state = next_state @@ -673,4 +776,4 @@ def draw(self, context): layout.label(text="Control Point") layout.prop(sc, "muv_uvbb_cp_size", text="Size") layout.prop(sc, "muv_uvbb_cp_react_size", text="React Size") - + layout.prop(sc, "muv_uvbb_uniform_scaling", text="Uniform Scaling") diff --git a/uv_magic_uv/muv_wsuv_ops.py b/uv_magic_uv/muv_wsuv_ops.py new file mode 100644 index 00000000..31185399 --- /dev/null +++ b/uv_magic_uv/muv_wsuv_ops.py @@ -0,0 +1,153 @@ +# + +# ##### 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__ = "McBuff, Nutti " +__status__ = "production" +__version__ = "4.1" +__date__ = "13 Nov 2016" + + +import bpy +import bmesh +from mathutils import Vector +from . import muv_common + + +def calc_edge_scale(uv_layer, loop0, loop1): + v0 = loop0.vert.co + v1 = loop1.vert.co + uv0 = loop0[uv_layer].uv.copy() + uv1 = loop1[uv_layer].uv.copy() + + dv = v1 - v0 + duv = uv1 - uv0 + + scale = 0.0 + if dv.magnitude > 0.00000001: + scale = duv.magnitude / dv.magnitude + + return scale + + +def calc_face_scale(uv_layer, face): + es = 0.0 + for i, l in enumerate(face.loops[1:]): + es = es + calc_edge_scale(uv_layer, face.loops[i], l) + + return es + + +class MUV_WSUVMeasure(bpy.types.Operator): + """ + Operation class: Measure face size + """ + + bl_idname = "uv.muv_wsuv_measure" + bl_label = "Measure" + bl_description = "Measure face size for scale calculation" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + props = context.scene.muv_props.wsuv + obj = bpy.context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if muv_common.check_version(2, 73, 0) >= 0: + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + 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() + + sel_faces = [f for f in bm.faces if f.select] + + # measure average face size + scale = 0.0 + for f in sel_faces: + scale = scale + calc_face_scale(uv_layer, f) + + props.ref_scale = scale / len(sel_faces) + + return {'FINISHED'} + + +class MUV_WSUVApply(bpy.types.Operator): + """ + Operation class: Apply scaled UV + """ + + bl_idname = "uv.muv_wsuv_apply" + bl_label = "Apply" + bl_description = "Apply scaled UV based on scale calculation" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + props = context.scene.muv_props.wsuv + obj = bpy.context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if muv_common.check_version(2, 73, 0) >= 0: + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + 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() + + sel_faces = [f for f in bm.faces if f.select] + + # measure average face size + scale = 0.0 + for f in sel_faces: + scale = scale + calc_face_scale(uv_layer, f) + scale = scale / len(sel_faces) + + ratio = props.ref_scale / scale + + orig_area = bpy.context.area.type + bpy.context.area.type = 'IMAGE_EDITOR' + + # apply scaled UV + bpy.ops.transform.resize( + value=(ratio, ratio, ratio), + constraint_axis=(False, False, False), + constraint_orientation='GLOBAL', + mirror=False, + proportional='DISABLED', + proportional_edit_falloff='SMOOTH', + proportional_size=1, + snap=False, + snap_target='CLOSEST', + snap_point=(0, 0, 0), + snap_align=False, + snap_normal=(0, 0, 0), + texture_space=False, + release_confirm=False) + + bpy.context.area.type = orig_area + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} From 4146edd947fbb747def68d2a141652da9aadd287 Mon Sep 17 00:00:00 2001 From: Nutti Date: Sat, 4 Mar 2017 15:23:35 +0900 Subject: [PATCH 2/2] Version 4.2 released --- README.md | 15 +- update_timestamp.rb | 66 +++++++ uv_magic_uv/__init__.py | 25 ++- uv_magic_uv/muv_common.py | 7 +- uv_magic_uv/muv_cpuv_ops.py | 59 +++--- uv_magic_uv/muv_cpuv_selseq_ops.py | 37 ++-- uv_magic_uv/muv_fliprot_ops.py | 6 +- uv_magic_uv/muv_menu.py | 48 +++-- uv_magic_uv/muv_mirroruv_ops.py | 43 ++--- uv_magic_uv/muv_mvuv_ops.py | 20 +- uv_magic_uv/muv_packuv_ops.py | 10 +- uv_magic_uv/muv_preferences.py | 113 ++++++++++- uv_magic_uv/muv_preserve_uv_aspect.py | 119 ++++++++++++ uv_magic_uv/muv_props.py | 30 ++- uv_magic_uv/muv_texlock_ops.py | 40 ++-- uv_magic_uv/muv_texproj_ops.py | 115 ++++++++---- uv_magic_uv/muv_transuv_ops.py | 15 +- uv_magic_uv/muv_unwrapconst_ops.py | 16 +- uv_magic_uv/muv_uvbb_ops.py | 257 +++++++++++--------------- uv_magic_uv/muv_wsuv_ops.py | 7 +- 20 files changed, 665 insertions(+), 383 deletions(-) create mode 100644 update_timestamp.rb create mode 100644 uv_magic_uv/muv_preserve_uv_aspect.py diff --git a/README.md b/README.md index 0bee0251..be73d842 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Blender Add-on: Magic UV (Copy/Paste UV) This is a blender add-on "Magic UV". (also known as Copy/Paste UV for older version) -You can manipulate UV with this plugin. +"Magic UV" consists of many UV manipulation features. -This add-on is **Contrib** support level. +"Magic UV" is in **Contrib** support level. So, stable version is included on **Blender Test Build**. -Of cource, you can also download older version from below links. +Of course, you can also download older version from below links. If you want to try newest but unstable version, you can download it from [unstable version](https://github.com/nutti/Magic-UV/archive/develop.zip). @@ -14,6 +14,7 @@ If you want to try newest but unstable version, you can download it from [unstab |Version|Download URL| |---|---| |*unstable*|[Download](https://github.com/nutti/Magic-UV/archive/develop.zip)| +|4.2|[Download](https://github.com/nutti/Magic-UV/releases/tag/v4.2)| |4.1|[Download](https://github.com/nutti/Magic-UV/releases/tag/v4.1)| |4.0|[Download](https://github.com/nutti/Magic-UV/releases/tag/v4.0)| |3.2|[Download](https://github.com/nutti/Magic-UV/releases/tag/v3.2)| @@ -31,7 +32,7 @@ If you want to try newest but unstable version, you can download it from [unstab ## Features -This add-on's features are as follows. +"Magic UV" have features as follows. * Copy/Paste UV Coordinates * Copy/Paste UV Coordinates (by selection sequence) @@ -46,6 +47,7 @@ This add-on's features are as follows. * Mirror UV * World Scale UV * Unwrap Constraint +* Preserve UV Aspect ## Tutorials @@ -53,7 +55,7 @@ See [Wiki Pages](https://github.com/nutti/Magic-UV/wiki/Tutorial) . ## Related Links -This add-on is introduced in some places. +"Magic UV" is introduced in some places. See the link below for further details. * [Blender Wiki page](http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/UV/Copy_Paste_UVs) @@ -63,6 +65,7 @@ See the link below for further details. ## Change Log |Version|Release Date|Change Log| |---|---|---| +|4.2|2017.3.4|[1] **Add feature**
- Preserve UV Aspect
[2] Improve feature
- Texture Projection
(1) Add option: Texture rendering method
[3] Improve UI
[4] Optimization/Refactoring
[5] Fixed bug| |4.1|2016.11.13|[1] **Add feature**
- Copy/Paste UV Coordinates (Among same objects)
- Texture Lock
- Mirror UV
- World Scale UV
- Unwrap Constraint
[2] Improve feature - Pack UV (with same UV island packing)
(1) Add option: Allowable center/size deviation option
[3] Fixed bug| |4.0|2016.5.14|[1] Rename this add-on
[2] **Add feature**
- Manipulate UV with Bounding Box in UV Editor
- Move UV from 3D View
- Texture Projection
- Pack UV (with same UV island packing)
[3] Improve feature - Copy/Paste UV
(1) N to M copy/paste UV
(2) Copy/Paste UV by selection sequence between specified UV maps
[4] Optimization/Refactoring
[5] Fixed bugs| |3.2|2015.6.20|Improve feature
- Transfer UV
(1) Copy/Paste to multiple meshes
(2) Partially copy/paste UV| @@ -77,7 +80,7 @@ See the link below for further details. ## Bug report / Feature request -This addon's project is in progress. +This project is in progress. If you want to report problem or request feature, please make issues. https://github.com/nutti/Magic-UV/issues diff --git a/update_timestamp.rb b/update_timestamp.rb new file mode 100644 index 00000000..ac37f34f --- /dev/null +++ b/update_timestamp.rb @@ -0,0 +1,66 @@ +require 'fileutils' +require 'date' + +src_dir_path = 'uv_magic_uv' +tmp_dir_path = 'tmp' + +filelist = [] +entry = Dir.glob(src_dir_path + '/**/**') +entry.each {|e| + next e if File::ftype(e) == 'directory' + filelist.push(e) +} + +FileUtils.mkdir_p(tmp_dir_path) unless FileTest.exist?(tmp_dir_path) + +bl_ver = nil + +src_file = File.open(src_dir_path + '/__init__.py', 'r') +src_file.each_line do |line| + if /\s*"version"\s*:\s*\(\s*(\d+)\s*,\s*(\d+)\s*\),\s*/ =~ line + bl_ver = $1 + '.' + $2 + end +end + +if bl_ver == nil + print "Blender Version is not found" + exit 1 +end + +tmp_filelist = [] + +filelist.each {|src_path| + next src_path if File.extname(src_path) != '.py' + + path = src_path.dup + path.slice!(src_dir_path) + dest_path = tmp_dir_path + path + + # make converted file + File.open(src_path, 'r') {|src_file| + File.open(dest_path, 'w') {|dest_file| + src_file.each_line do |line| + if /^__date__/ =~ line + today = Date.today + mon = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + line = '__date__ = "' + today.day.to_s + ' ' + mon[today.month-1] + ' ' + today.year.to_s + '"' + end + if /^__version__/ =~ line + line = '__version__ = "' + bl_ver + '"' + end + dest_file.puts(line) + end + } + } + tmp_filelist.push([src_path, dest_path]) +} + +tmp_filelist.each {|file| + FileUtils.cp(file[1], file[0]) + puts file[0] +} + + +FileUtils.rm_rf(tmp_dir_path) if FileTest.exist?(tmp_dir_path) + +exit 0 diff --git a/uv_magic_uv/__init__.py b/uv_magic_uv/__init__.py index e13e11eb..67d1f68f 100644 --- a/uv_magic_uv/__init__.py +++ b/uv_magic_uv/__init__.py @@ -20,20 +20,20 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" bl_info = { "name": "Magic UV", - "author": "Nutti", - "version": (4, 1), + "author": "Nutti, Mifth, kgeogeo, mem, Keith (Wahooney) Boshoff, McBuff, MaxRobinot", + "version": (4, 2), "blender": (2, 77, 0), - "location": "View3D > U, View3D > Property Panel, ImageEditor > Property Panel, ImageEditor > UVs", - "description": "UV Manipulator Tools", + "location": "See Add-ons Preferences", + "description": "UV Manipulator Tools. See Add-ons Preferences for details", "warning": "", "support": "COMMUNITY", - "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/UV/Copy_Paste_UVs", + "wiki_url": "https://github.com/nutti/Magic-UV/wikil", "tracker_url": "https://github.com/nutti/Copy-And-Paste-UV", "category": "UV" } @@ -56,6 +56,7 @@ imp.reload(muv_mirroruv_ops) imp.reload(muv_wsuv_ops) imp.reload(muv_unwrapconst_ops) + imp.reload(muv_preserve_uv_aspect) else: from . import muv_preferences from . import muv_menu @@ -73,6 +74,7 @@ from . import muv_mirroruv_ops from . import muv_wsuv_ops from . import muv_unwrapconst_ops + from . import muv_preserve_uv_aspect import bpy @@ -84,9 +86,14 @@ def view3d_uvmap_menu_fn(self, context): self.layout.menu(muv_menu.MUV_TransUVMenu.bl_idname, icon="PLUGIN") self.layout.operator(muv_mvuv_ops.MUV_MVUV.bl_idname, icon="PLUGIN") self.layout.menu(muv_menu.MUV_TexLockMenu.bl_idname, icon="PLUGIN") - self.layout.operator(muv_mirroruv_ops.MUV_MirrorUV.bl_idname, icon="PLUGIN") + self.layout.operator( + muv_mirroruv_ops.MUV_MirrorUV.bl_idname, icon="PLUGIN") self.layout.menu(muv_menu.MUV_WSUVMenu.bl_idname, icon="PLUGIN") - self.layout.operator(muv_unwrapconst_ops.MUV_UnwrapConstraint.bl_idname, icon='PLUGIN') + self.layout.operator( + muv_unwrapconst_ops.MUV_UnwrapConstraint.bl_idname, icon='PLUGIN') + self.layout.menu( + muv_preserve_uv_aspect.MUV_PreserveUVAspectMenu.bl_idname, + icon='PLUGIN') def image_uvs_menu_fn(self, context): diff --git a/uv_magic_uv/muv_common.py b/uv_magic_uv/muv_common.py index 83df3671..8b495df9 100644 --- a/uv_magic_uv/muv_common.py +++ b/uv_magic_uv/muv_common.py @@ -20,17 +20,14 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy from . import muv_props -PHI = 3.1415926535 - - def debug_print(*s): """ Print message to console in debugging mode diff --git a/uv_magic_uv/muv_cpuv_ops.py b/uv_magic_uv/muv_cpuv_ops.py index 7804a665..e3d71b40 100644 --- a/uv_magic_uv/muv_cpuv_ops.py +++ b/uv_magic_uv/muv_cpuv_ops.py @@ -20,8 +20,8 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy @@ -49,7 +49,7 @@ class MUV_CPUVCopyUV(bpy.types.Operator): bl_description = "Copy UV coordinate (Operation)" bl_options = {'REGISTER', 'UNDO'} - uv_map = bpy.props.StringProperty(options={'HIDDEN'}) + uv_map = StringProperty(options={'HIDDEN'}) def execute(self, context): props = context.scene.muv_props.cpuv @@ -57,7 +57,7 @@ def execute(self, context): self.report({'INFO'}, "Copy UV coordinate") else: self.report( - {'INFO'}, "Copy UV coordinate (UV map:" + self.uv_map + ")") + {'INFO'}, "Copy UV coordinate (UV map:%s)" % (self.uv_map)) obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) if muv_common.check_version(2, 73, 0) >= 0: @@ -107,11 +107,13 @@ def draw(self, context): uv_maps = bm.loops.layers.uv.keys() layout.operator( MUV_CPUVCopyUV.bl_idname, - text="[Default]", icon="PLUGIN").uv_map = "" + text="[Default]", + icon="PLUGIN").uv_map = "" for m in uv_maps: layout.operator( MUV_CPUVCopyUV.bl_idname, - text=m, icon="PLUGIN").uv_map = m + text=m, + icon="PLUGIN").uv_map = m class MUV_CPUVPasteUV(bpy.types.Operator): @@ -124,21 +126,19 @@ class MUV_CPUVPasteUV(bpy.types.Operator): bl_description = "Paste UV coordinate (Operation)" bl_options = {'REGISTER', 'UNDO'} - uv_map = bpy.props.StringProperty(options={'HIDDEN'}) - + uv_map = StringProperty(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')], + ('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", @@ -154,7 +154,7 @@ def execute(self, context): self.report({'INFO'}, "Paste UV coordinate") else: self.report( - {'INFO'}, "Paste UV coordinate (UV map:" + self.uv_map + ")") + {'INFO'}, "Paste UV coordinate (UV map:%s)" % (self.uv_map)) obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) if muv_common.check_version(2, 73, 0) >= 0: @@ -163,8 +163,8 @@ def execute(self, context): # get UV layer if self.uv_map == "": if not bm.loops.layers.uv: - self.report({'WARNING'}, - "Object must have more than one UV map") + self.report( + {'WARNING'}, "Object must have more than one UV map") return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify() else: @@ -265,7 +265,7 @@ class MUV_CPUVObjCopyUV(bpy.types.Operator): bl_description = "Copy UV coordinate" bl_options = {'REGISTER', 'UNDO'} - uv_map = bpy.props.StringProperty(options={'HIDDEN'}) + uv_map = StringProperty(options={'HIDDEN'}) @memorize_view_3d_mode def execute(self, context): @@ -275,8 +275,7 @@ def execute(self, context): else: self.report( {'INFO'}, - "Copy UV coordinate per object (UV map:" + self.uv_map + ")") - + "Copy UV coordinate per object (UV map:%s)" % (self.uv_map)) bpy.ops.object.mode_set(mode='EDIT') obj = context.active_object @@ -341,7 +340,7 @@ class MUV_CPUVObjPasteUV(bpy.types.Operator): bl_description = "Paste UV coordinate" bl_options = {'REGISTER', 'UNDO'} - uv_map = bpy.props.StringProperty(options={'HIDDEN'}) + uv_map = StringProperty(options={'HIDDEN'}) @memorize_view_3d_mode def execute(self, context): @@ -351,7 +350,6 @@ def execute(self, context): return {'CANCELLED'} obj_names = [] - for o in bpy.data.objects: if not hasattr(o.data, "uv_textures") or not o.select: continue @@ -365,19 +363,21 @@ def execute(self, context): if muv_common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() - if self.uv_map == "" or not self.uv_map in bm.loops.layers.uv.keys(): + if (self.uv_map == "" or + not self.uv_map in bm.loops.layers.uv.keys()): self.report({'INFO'}, "Paste UV coordinate per object") else: self.report( {'INFO'}, - "Paste UV coordinate per object (UV map: %s)" % - (self.uv_map)) + "Paste UV coordinate per object (UV map: %s)" + % (self.uv_map)) # get UV layer - if self.uv_map == "" or not self.uv_map in bm.loops.layers.uv.keys(): + if (self.uv_map == "" or + not self.uv_map in bm.loops.layers.uv.keys()): if not bm.loops.layers.uv: - self.report({'WARNING'}, - "Object must have more than one UV map") + self.report( + {'WARNING'}, "Object must have more than one UV map") return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify() else: @@ -396,9 +396,9 @@ def execute(self, context): if len(props.src_uvs) != len(dest_uvs): self.report( {'WARNING'}, - "Number of faces is different from copied" + - "(src:%d, dest:%d)" % - (len(props.src_uvs), len(dest_uvs))) + "Number of faces is different from copied " + + "(src:%d, dest:%d)" + % (len(props.src_uvs), len(dest_uvs))) return {'CANCELLED'} # paste @@ -418,7 +418,8 @@ def execute(self, context): bmesh.update_edit_mesh(obj.data) - self.report({'INFO'}, "%s's UV coordinates are pasted" % (obj.name)) + self.report( + {'INFO'}, "%s's UV coordinates are pasted" % (obj.name)) return {'FINISHED'} diff --git a/uv_magic_uv/muv_cpuv_selseq_ops.py b/uv_magic_uv/muv_cpuv_selseq_ops.py index 2453578f..b97435c6 100644 --- a/uv_magic_uv/muv_cpuv_selseq_ops.py +++ b/uv_magic_uv/muv_cpuv_selseq_ops.py @@ -20,13 +20,13 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy import bmesh -from bpy.props import BoolProperty, IntProperty, EnumProperty +from bpy.props import StringProperty, BoolProperty, IntProperty, EnumProperty from . import muv_common @@ -40,7 +40,7 @@ class MUV_CPUVSelSeqCopyUV(bpy.types.Operator): bl_description = "Copy UV data by selection sequence (Operation)" bl_options = {'REGISTER', 'UNDO'} - uv_map = bpy.props.StringProperty(options={'HIDDEN'}) + uv_map = StringProperty(options={'HIDDEN'}) def execute(self, context): props = context.scene.muv_props.cpuv_selseq @@ -49,8 +49,8 @@ def execute(self, context): else: self.report( {'INFO'}, - "Copy UV coordinate (selection sequence) (UV map:" + - self.uv_map + ")") + "Copy UV coordinate (selection sequence) (UV map:%s)" + % (self.uv_map)) obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) if muv_common.check_version(2, 73, 0) >= 0: @@ -116,21 +116,19 @@ class MUV_CPUVSelSeqPasteUV(bpy.types.Operator): bl_description = "Paste UV coordinate by selection sequence (Operation)" bl_options = {'REGISTER', 'UNDO'} - uv_map = bpy.props.StringProperty(options={'HIDDEN'}) - + uv_map = StringProperty(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')], + ('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", @@ -147,8 +145,9 @@ def execute(self, context): else: self.report( {'INFO'}, - "Paste UV coordinate (selection sequence) (UV map:" + - self.uv_map + ")") + "Paste UV coordinate (selection sequence) (UV map:%s)" + % (self.uv_map)) + obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) if muv_common.check_version(2, 73, 0) >= 0: @@ -157,8 +156,8 @@ def execute(self, context): # get UV layer if self.uv_map == "": if not bm.loops.layers.uv: - self.report({'WARNING'}, - "Object must have more than one UV map") + self.report( + {'WARNING'}, "Object must have more than one UV map") return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify() else: @@ -181,9 +180,9 @@ def execute(self, context): if self.strategy == 'N_N' and len(props.src_uvs) != len(dest_uvs): self.report( {'WARNING'}, - "Number of selected faces is different from copied faces" + - "(src:%d, dest:%d)" % - (len(props.src_uvs), len(dest_uvs))) + "Number of selected faces is different from copied faces " + + "(src:%d, dest:%d)" + % (len(props.src_uvs), len(dest_uvs))) return {'CANCELLED'} # paste @@ -218,12 +217,14 @@ def execute(self, context): for l, suv, spuv in zip(bm.faces[idx].loops, suvs_fr, spuvs_fr): l[uv_layer].uv = suv l[uv_layer].pin_uv = spuv + self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs)) bmesh.update_edit_mesh(obj.data) return {'FINISHED'} + class MUV_CPUVSelSeqPasteUVMenu(bpy.types.Menu): """ Menu class: Paste UV coordinate by selection sequence diff --git a/uv_magic_uv/muv_fliprot_ops.py b/uv_magic_uv/muv_fliprot_ops.py index 9c9cc544..2718f5c1 100644 --- a/uv_magic_uv/muv_fliprot_ops.py +++ b/uv_magic_uv/muv_fliprot_ops.py @@ -20,8 +20,8 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.0" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy @@ -44,7 +44,6 @@ class MUV_FlipRot(bpy.types.Operator): name="Flip UV", description="Flip UV...", default=False) - rotate = IntProperty( default=0, name="Rotate UV", @@ -98,6 +97,7 @@ def execute(self, context): for l, duv, dpuv in zip(bm.faces[idx].loops, duvs_fr, dpuvs_fr): l[uv_layer].uv = duv l[uv_layer].pin_uv = dpuv + self.report({'INFO'}, "%d face(s) are flipped/rotated" % len(dest_uvs)) bmesh.update_edit_mesh(obj.data) diff --git a/uv_magic_uv/muv_menu.py b/uv_magic_uv/muv_menu.py index 22d62d29..51374fb5 100644 --- a/uv_magic_uv/muv_menu.py +++ b/uv_magic_uv/muv_menu.py @@ -20,8 +20,8 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy @@ -42,10 +42,16 @@ class MUV_CPUVMenu(bpy.types.Menu): bl_description = "Copy and Paste UV coordinate" def draw(self, context): - self.layout.menu(muv_cpuv_ops.MUV_CPUVCopyUVMenu.bl_idname, icon="PLUGIN") - self.layout.menu(muv_cpuv_ops.MUV_CPUVPasteUVMenu.bl_idname, icon="PLUGIN") - self.layout.menu(muv_cpuv_selseq_ops.MUV_CPUVSelSeqCopyUVMenu.bl_idname, icon="PLUGIN") - self.layout.menu(muv_cpuv_selseq_ops.MUV_CPUVSelSeqPasteUVMenu.bl_idname, icon="PLUGIN") + self.layout.menu( + muv_cpuv_ops.MUV_CPUVCopyUVMenu.bl_idname, icon="PLUGIN") + self.layout.menu( + muv_cpuv_ops.MUV_CPUVPasteUVMenu.bl_idname, icon="PLUGIN") + self.layout.menu( + muv_cpuv_selseq_ops.MUV_CPUVSelSeqCopyUVMenu.bl_idname, + icon="PLUGIN") + self.layout.menu( + muv_cpuv_selseq_ops.MUV_CPUVSelSeqPasteUVMenu.bl_idname, + icon="PLUGIN") class MUV_CPUVObjMenu(bpy.types.Menu): @@ -58,8 +64,10 @@ class MUV_CPUVObjMenu(bpy.types.Menu): bl_description = "Copy and Paste UV coordinate per object" def draw(self, context): - self.layout.menu(muv_cpuv_ops.MUV_CPUVObjCopyUVMenu.bl_idname, icon="PLUGIN") - self.layout.menu(muv_cpuv_ops.MUV_CPUVObjPasteUVMenu.bl_idname, icon="PLUGIN") + self.layout.menu( + muv_cpuv_ops.MUV_CPUVObjCopyUVMenu.bl_idname, icon="PLUGIN") + self.layout.menu( + muv_cpuv_ops.MUV_CPUVObjPasteUVMenu.bl_idname, icon="PLUGIN") class MUV_TransUVMenu(bpy.types.Menu): @@ -72,8 +80,10 @@ class MUV_TransUVMenu(bpy.types.Menu): bl_description = "Transfer UV coordinate" def draw(self, context): - self.layout.operator(muv_transuv_ops.MUV_TransUVCopy.bl_idname, icon="PLUGIN") - self.layout.operator(muv_transuv_ops.MUV_TransUVPaste.bl_idname, icon="PLUGIN") + self.layout.operator( + muv_transuv_ops.MUV_TransUVCopy.bl_idname, icon="PLUGIN") + self.layout.operator( + muv_transuv_ops.MUV_TransUVPaste.bl_idname, icon="PLUGIN") class MUV_TexLockMenu(bpy.types.Menu): @@ -86,10 +96,14 @@ class MUV_TexLockMenu(bpy.types.Menu): bl_description = "Lock texture when vertices of mesh (Preserve UV)" def draw(self, context): - self.layout.operator(muv_texlock_ops.MUV_TexLockStart.bl_idname, icon="PLUGIN") - self.layout.operator(muv_texlock_ops.MUV_TexLockStop.bl_idname, icon="PLUGIN") - self.layout.operator(muv_texlock_ops.MUV_TexLockIntrStart.bl_idname, icon="PLUGIN") - self.layout.operator(muv_texlock_ops.MUV_TexLockIntrStop.bl_idname, icon="PLUGIN") + self.layout.operator( + muv_texlock_ops.MUV_TexLockStart.bl_idname, icon="PLUGIN") + self.layout.operator( + muv_texlock_ops.MUV_TexLockStop.bl_idname, icon="PLUGIN") + self.layout.operator( + muv_texlock_ops.MUV_TexLockIntrStart.bl_idname, icon="PLUGIN") + self.layout.operator( + muv_texlock_ops.MUV_TexLockIntrStop.bl_idname, icon="PLUGIN") class MUV_WSUVMenu(bpy.types.Menu): @@ -102,5 +116,7 @@ class MUV_WSUVMenu(bpy.types.Menu): bl_description = "" def draw(self, context): - self.layout.operator(muv_wsuv_ops.MUV_WSUVMeasure.bl_idname, icon="PLUGIN") - self.layout.operator(muv_wsuv_ops.MUV_WSUVApply.bl_idname, icon="PLUGIN") + self.layout.operator( + muv_wsuv_ops.MUV_WSUVMeasure.bl_idname, icon="PLUGIN") + self.layout.operator( + muv_wsuv_ops.MUV_WSUVApply.bl_idname, icon="PLUGIN") diff --git a/uv_magic_uv/muv_mirroruv_ops.py b/uv_magic_uv/muv_mirroruv_ops.py index cf63b164..77cbd6b4 100644 --- a/uv_magic_uv/muv_mirroruv_ops.py +++ b/uv_magic_uv/muv_mirroruv_ops.py @@ -20,8 +20,8 @@ __author__ = "Keith (Wahooney) Boshoff, Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy @@ -41,23 +41,22 @@ class MUV_MirrorUV(bpy.types.Operator): 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') - + 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) - + name="Error", + description="Error threshold", + default=0.001, + min=0.0, + max=100.0, + soft_min=0.0, + soft_max=1.0) def __is_vector_similar(self, v1, v2, error): """ @@ -69,7 +68,6 @@ def __is_vector_similar(self, v1, v2, 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 @@ -89,7 +87,6 @@ def __mirror_uvs(self, uv_layer, src, dst, axis, error): 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 @@ -100,13 +97,11 @@ def __get_face_center(self, face): return center / len(face.verts) - @classmethod def poll(cls, context): obj = context.active_object return (obj and obj.type == 'MESH') - def execute(self, context): obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) @@ -117,8 +112,7 @@ def execute(self, context): if muv_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") + self.report({'WARNING'}, "Object must have more than one UV map") return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify() @@ -126,7 +120,6 @@ def execute(self, context): 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 diff --git a/uv_magic_uv/muv_mvuv_ops.py b/uv_magic_uv/muv_mvuv_ops.py index cda11649..c8967473 100644 --- a/uv_magic_uv/muv_mvuv_ops.py +++ b/uv_magic_uv/muv_mvuv_ops.py @@ -20,13 +20,14 @@ __author__ = "kgeogeo, mem, Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy import bmesh from mathutils import Vector +from bpy_extras import view3d_utils class MUV_MVUV(bpy.types.Operator): @@ -38,13 +39,14 @@ class MUV_MVUV(bpy.types.Operator): bl_label = "Move the UV from View3D" bl_options = {'REGISTER', 'UNDO'} - __topology_dict = [] - __prev_mouse = Vector((0.0, 0.0)) - __offset_uv = Vector((0.0, 0.0)) - __prev_offset_uv = Vector((0.0, 0.0)) - __first_time = True - __ini_uvs = [] - __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.__running = False def __find_uv(self, context): bm = bmesh.from_edit_mesh(context.object.data) diff --git a/uv_magic_uv/muv_packuv_ops.py b/uv_magic_uv/muv_packuv_ops.py index a91a1a6a..1f391a11 100644 --- a/uv_magic_uv/muv_packuv_ops.py +++ b/uv_magic_uv/muv_packuv_ops.py @@ -20,8 +20,8 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy @@ -49,21 +49,16 @@ class MUV_PackUV(bpy.types.Operator): bl_description = "Pack UV (Same UV Islands are integrated)" bl_options = {'REGISTER', 'UNDO'} - __face_to_verts = defaultdict(set) - __vert_to_faces = defaultdict(set) - rotate = BoolProperty( name="Rotate", description="Rotate option used by default pack UV function", default=False) - margin = FloatProperty( name="Margin", description="Margin used by default pack UV function", min=0, max=1, default=0.001) - allowable_center_deviation = FloatVectorProperty( name="Allowable Center Deviation", description="Allowable center deviation to judge same UV island", @@ -71,7 +66,6 @@ class MUV_PackUV(bpy.types.Operator): max=0.1, default=(0.001, 0.001), size=2) - allowable_size_deviation = FloatVectorProperty( name="Allowable Size Deviation", description="Allowable sizse deviation to judge same UV island", diff --git a/uv_magic_uv/muv_preferences.py b/uv_magic_uv/muv_preferences.py index 0a955538..2d459d4c 100644 --- a/uv_magic_uv/muv_preferences.py +++ b/uv_magic_uv/muv_preferences.py @@ -20,30 +20,123 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy from bpy.props import * from bpy.types import AddonPreferences + class MUV_Preferences(AddonPreferences): """Preferences class: Preferences for this add-on""" bl_idname = __package__ + # enable/disable switcher enable_texproj = BoolProperty( - name="Enable feature: Texture Projection", - default=False, - ) - + name="Texture Projection", + default=True) enable_uvbb = BoolProperty( - name="Enable feature: UV Bounding Box", - default=False, - ) + name="Bounding Box", + default=True) + + # for Texture Projection + texproj_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 + uvbb_cp_size = FloatProperty( + name="Size", + description="Control Point Size", + default=6.0, + min=3.0, + max=100.0) + uvbb_cp_react_size = FloatProperty( + name="React Size", + description="Size event fired", + default=10.0, + min=3.0, + max=100.0) def draw(self, context): layout = self.layout - layout.prop(self, "enable_uvbb") + + layout.label("Switch Enable/Disable and Configurate Features:") + layout.prop(self, "enable_texproj") + if self.enable_texproj: + sp = layout.split(percentage=0.05) + col = sp.column() # spacer + sp = sp.split(percentage=0.3) + col = sp.column() + col.label("Texture Display: ") + col.prop(self, "texproj_canvas_padding") + + layout.prop(self, "enable_uvbb") + if self.enable_uvbb: + 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, "uvbb_cp_size") + col.prop(self, "uvbb_cp_react_size") + + layout.label("Description:") + column = layout.column(align=True) + column.label("Magic UV is composed of many UV editing features.") + column.label("See tutorial page if you know about this add-on.") + column.label("https://github.com/nutti/Magic-UV/wiki/Tutorial") + + layout.label("Location:") + + row = layout.row(align=True) + sp = row.split(percentage=0.3) + sp.label("View3D > U") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Copy/Paste UV Coordinates") + col.label("Copy/Paste UV Coordinates (by selection sequence)") + col.label("Flip/Rotate UVs") + col.label("Transfer UV") + col.label("Move UV from 3D View") + col.label("Texture Lock") + col.label("Mirror UV") + col.label("World Scale UV") + col.label("Unwrap Constraint") + col.label("Preserve UV Aspect") + + row = layout.row(align=True) + sp = row.split(percentage=0.3) + sp.label("View3D > Object") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Copy/Paste UV Coordinates (Among same objects)") + + row = layout.row(align=True) + sp = row.split(percentage=0.3) + sp.label("ImageEditor > Property Panel") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Manipulate UV with Bounding Box in UV Editor") + + row = layout.row(align=True) + sp = row.split(percentage=0.3) + sp.label("View3D > Property Panel") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Texture Projection") + + row = layout.row(align=True) + sp = row.split(percentage=0.3) + sp.label("ImageEditor > UVs") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Pack UV (with same UV island packing)") diff --git a/uv_magic_uv/muv_preserve_uv_aspect.py b/uv_magic_uv/muv_preserve_uv_aspect.py new file mode 100644 index 00000000..d3458a09 --- /dev/null +++ b/uv_magic_uv/muv_preserve_uv_aspect.py @@ -0,0 +1,119 @@ +# + +# ##### 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 " +__status__ = "production" +__version__ = "4.2" +__date__ = "4 Mar 2017" + + +import bpy +import bmesh +from bpy.props import StringProperty +from mathutils import Vector +from . import muv_common + + +class MUV_PreserveUVAspect(bpy.types.Operator): + """ + Operation class: Preserve UV Aspect + """ + + bl_idname = "uv.muv_preserve_uv_aspect" + bl_label = "Preserve UV Aspect" + bl_options = {'REGISTER', 'UNDO'} + + dest_img_name = StringProperty(options={'HIDDEN'}) + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj and obj.type == 'MESH') + + def execute(self, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + + if muv_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() + tex_layer = bm.faces.layers.tex.verify() + + sel_faces = [f for f in bm.faces if f.select] + dest_img = bpy.data.images[self.dest_img_name] + + info = {} + + for f in sel_faces: + if not f[tex_layer].image in info.keys(): + info[f[tex_layer].image] = {} + info[f[tex_layer].image]['faces'] = [] + info[f[tex_layer].image]['faces'].append(f) + + for img in info.keys(): + src_img = img + ratio = Vector(( + dest_img.size[0] / src_img.size[0], + dest_img.size[1] / src_img.size[1])) + origin = Vector((100000.0, 100000.0)) + for f in info[img]['faces']: + for l in f.loops: + uv = l[uv_layer].uv + origin.x = min(uv.x, origin.x) + origin.y = min(uv.y, origin.y) + info[img]['ratio'] = ratio + info[img]['origin'] = origin + + for img in info.keys(): + for f in info[img]['faces']: + f[tex_layer].image = dest_img + for l in f.loops: + uv = l[uv_layer].uv + diff = uv - info[img]['origin'] + diff.x = diff.x / info[img]['ratio'].x + diff.y = diff.y / info[img]['ratio'].y + uv.x = origin.x + diff.x + uv.y = origin.y + diff.y + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} + + +class MUV_PreserveUVAspectMenu(bpy.types.Menu): + """ + Menu class: Preserve UV Aspect + """ + + bl_idname = "uv.muv_preserve_uv_aspect_menu" + bl_label = "Preserve UV Aspect" + bl_description = "Preserve UV Aspect" + + def draw(self, context): + layout = self.layout + # create sub menu + for key in bpy.data.images.keys(): + layout.operator( + MUV_PreserveUVAspect.bl_idname, + text=key, icon="PLUGIN").dest_img_name = key diff --git a/uv_magic_uv/muv_props.py b/uv_magic_uv/muv_props.py index a4582be2..e7b0b43c 100644 --- a/uv_magic_uv/muv_props.py +++ b/uv_magic_uv/muv_props.py @@ -20,13 +20,14 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy from bpy.props import FloatProperty, EnumProperty, BoolProperty + DEBUG = False @@ -102,18 +103,6 @@ class MUV_WSUVProps(): def init_props(scene): scene.muv_props = MUV_Properties() - scene.muv_uvbb_cp_size = FloatProperty( - name="Size", - description="Control Point Size", - default=6.0, - min=3.0, - max=100.0) - scene.muv_uvbb_cp_react_size = FloatProperty( - name="React Size", - description="Size event fired", - default=10.0, - min=3.0, - max=100.0) scene.muv_uvbb_uniform_scaling = BoolProperty( name="Uniform Scaling", description="Enable Uniform Scaling", @@ -134,12 +123,21 @@ def init_props(scene): default=0.2, min=0.0, max=1.0) + scene.muv_texproj_adjust_window = BoolProperty( + name="Adjust Window", + description="Size of renderered texture is fitted to window.", + default=True) + scene.muv_texproj_apply_tex_aspect = BoolProperty( + name="Texture Aspect Ratio", + description="Apply Texture Aspect ratio to displayed texture.", + default=True) def clear_props(scene): del scene.muv_props - del scene.muv_uvbb_cp_size - del scene.muv_uvbb_cp_react_size + del scene.muv_uvbb_uniform_scaling del scene.muv_texproj_tex_magnitude del scene.muv_texproj_tex_image del scene.muv_texproj_tex_transparency + del scene.muv_texproj_adjust_window + del scene.muv_texproj_apply_tex_aspect diff --git a/uv_magic_uv/muv_texlock_ops.py b/uv_magic_uv/muv_texlock_ops.py index 6a2a2e8d..7b586ec4 100644 --- a/uv_magic_uv/muv_texlock_ops.py +++ b/uv_magic_uv/muv_texlock_ops.py @@ -20,13 +20,14 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy import bmesh from mathutils import Vector +import math from math import tan, atan2, cos, sqrt, sin, fabs from bpy.props import BoolProperty from . import muv_common @@ -84,7 +85,7 @@ def get_ini_geom(link_loop, uv_layer, verts_orig, v_orig): v1v = v_orig["vco"] - v1 theta0 = v0v1.angle(v0v) theta1 = v0v1.angle(-v1v) - if (theta0 + theta1) > muv_common.PHI: + if (theta0 + theta1) > math.pi: theta0 = v0v1.angle(-v0v) theta1 = v0v1.angle(v1v) @@ -94,7 +95,7 @@ def get_ini_geom(link_loop, uv_layer, verts_orig, v_orig): u1u = u - u1 phi0 = u0u1.angle(u0u) phi1 = u0u1.angle(-u1u) - if (phi0 + phi1) > muv_common.PHI: + if (phi0 + phi1) > math.pi: phi0 = u0u1.angle(-u0u) phi1 = u0u1.angle(u1u) @@ -129,7 +130,7 @@ def get_target_uv(link_loop, uv_layer, verts_orig, v, ini_geom): v1v = v.co - v1 theta0 = v0v1.angle(v0v) theta1 = v0v1.angle(-v1v) - if (theta0 + theta1) > muv_common.PHI: + if (theta0 + theta1) > math.pi: theta0 = v0v1.angle(-v0v) theta1 = v0v1.angle(v1v) @@ -160,7 +161,7 @@ def calc_tri_vert(v0, v1, angle0, angle1): """ Calculate rest coordinate from other coordinates and angle of end """ - angle = muv_common.PHI - angle0 - angle1 + angle = math.pi - angle0 - angle1 alpha = atan2(v1.y - v0.y, v1.x - v0.x) d = (v1.x - v0.x) / cos(alpha) @@ -258,7 +259,8 @@ def execute(self, context): for ll in link_loops: ini_geom = get_ini_geom(ll, uv_layer, verts_orig, v_orig) - target_uv = get_target_uv(ll, uv_layer, verts_orig, v, ini_geom) + target_uv = get_target_uv( + ll, uv_layer, verts_orig, v, ini_geom) result.append({"l": ll["l"], "uv": target_uv}) # connect other face's UV @@ -287,7 +289,8 @@ class MUV_TexLockUpdater(bpy.types.Operator): bl_label = "Texture Lock Updater" bl_description = "Texture Lock Updater" - __timer = None + def __init__(self): + self.__timer = None def __update_uv(self, context): """ @@ -302,8 +305,7 @@ def __update_uv(self, context): bm.faces.ensure_lookup_table() if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") + self.report({'WARNING'}, "Object must have more than one UV map") return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify() @@ -319,13 +321,14 @@ def __update_uv(self, context): link_loops = get_link_loops(v) result = [] - for ll in link_loops: ini_geom = get_ini_geom(ll, uv_layer, verts_orig, v_orig) - target_uv = get_target_uv(ll, uv_layer, verts_orig, v, ini_geom) + target_uv = get_target_uv( + ll, uv_layer, verts_orig, v, ini_geom) result.append({"l": ll["l"], "uv": target_uv}) - # UV connect option is always true, because it raises unexpected behavior + # UV connect option is always true, because it raises + # unexpected behavior ave = Vector((0.0, 0.0)) for r in result: ave = ave + r["uv"] @@ -346,19 +349,20 @@ def modal(self, context, event): if context.area: context.area.tag_redraw() if props.intr_running is False: + self.__handle_remove(context) return {'FINISHED'} if event.type == 'TIMER': self.__update_uv(context) return {'PASS_THROUGH'} - def handle_add(self, context): + def __handle_add(self, context): if self.__timer is None: self.__timer = context.window_manager.event_timer_add( 0.10, context.window) context.window_manager.modal_handler_add(self) - def handle_remove(self, context): + def __handle_remove(self, context): if self.__timer is not None: context.window_manager.event_timer_remove(self.__timer) self.__timer = None @@ -366,11 +370,10 @@ def handle_remove(self, context): def execute(self, context): props = context.scene.muv_props.texlock if props.intr_running == False: - self.handle_add(context) + self.__handle_add(context) props.intr_running = True return {'RUNNING_MODAL'} else: - self.handle_remove(context) props.intr_running = False if context.area: context.area.tag_redraw() @@ -401,8 +404,7 @@ def execute(self, context): bm.faces.ensure_lookup_table() if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") + self.report({'WARNING'}, "Object must have more than one UV map") return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify() diff --git a/uv_magic_uv/muv_texproj_ops.py b/uv_magic_uv/muv_texproj_ops.py index 689155f2..1b00f786 100644 --- a/uv_magic_uv/muv_texproj_ops.py +++ b/uv_magic_uv/muv_texproj_ops.py @@ -20,8 +20,8 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy @@ -42,16 +42,38 @@ def get_canvas(context, magnitude): """ Get canvas to be renderred texture """ - - PAD_X = 20 - PAD_Y = 20 - width = context.region.width - height = context.region.height - - center_x = width * 0.5 - center_y = height * 0.5 - len_x = (width - PAD_X * 2.0) * magnitude - len_y = (height - PAD_Y * 2.0) * magnitude + sc = context.scene + prefs = context.user_preferences.addons["uv_magic_uv"].preferences + + region_w = context.region.width + region_h = context.region.height + canvas_w = region_w - prefs.texproj_canvas_padding[0] * 2.0 + canvas_h = region_h - prefs.texproj_canvas_padding[1] * 2.0 + + img = bpy.data.images[sc.muv_texproj_tex_image] + tex_w = img.size[0] + tex_h = img.size[1] + + center_x = region_w * 0.5 + center_y = region_h * 0.5 + + if sc.muv_texproj_adjust_window: + ratio_x = canvas_w / tex_w + ratio_y = canvas_h / tex_h + if sc.muv_texproj_apply_tex_aspect: + ratio = ratio_y if ratio_x > ratio_y else ratio_x + len_x = ratio * tex_w + len_y = ratio * tex_h + else: + len_x = canvas_w + len_y = canvas_h + else: + if sc.muv_texproj_apply_tex_aspect: + len_x = tex_w * magnitude + len_y = tex_h * magnitude + else: + len_x = region_w * magnitude + len_y = region_h * magnitude x0 = int(center_x - len_x * 0.5) y0 = int(center_y - len_y * 0.5) @@ -115,6 +137,9 @@ def draw_texture(self, context): if sc.muv_texproj_tex_image == "None": return + # get texture to be renderred + img = bpy.data.images[sc.muv_texproj_tex_image] + # setup rendering region rect = get_canvas(context, sc.muv_texproj_tex_magnitude) positions = [ @@ -122,17 +147,19 @@ def draw_texture(self, context): [rect.x0, rect.y1], [rect.x1, rect.y1], [rect.x1, rect.y0] - ] - tex_coords = [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]] - - # get texture to be renderred - img = bpy.data.images[sc.muv_texproj_tex_image] + ] + tex_coords = [ + [0.0, 0.0], + [0.0, 1.0], + [1.0, 1.0], + [1.0, 0.0] + ] # OpenGL configuration bgl.glEnable(bgl.GL_BLEND) bgl.glEnable(bgl.GL_TEXTURE_2D) if img.bindcode: - bind = img.bindcode + bind = img.bindcode[0] bgl.glBindTexture(bgl.GL_TEXTURE_2D, bind) bgl.glTexParameteri( bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR) @@ -228,17 +255,21 @@ def execute(self, context): sel_faces = [f for f in bm.faces if f.select] # transform 3d space to screen region - v_screen = [view3d_utils.location_3d_to_region_2d( - region, - space.region_3d, - world_mat * l.vert.co) - for f in sel_faces for l in f.loops] + v_screen = [ + view3d_utils.location_3d_to_region_2d( + region, + space.region_3d, + world_mat * l.vert.co) + for f in sel_faces for l in f.loops + ] # transform screen region to canvas - v_canvas = [region_to_canvas( - region, v, - get_canvas(bpy.context, sc.muv_texproj_tex_magnitude)) - for v in v_screen] + v_canvas = [ + region_to_canvas( + region, v, + get_canvas(bpy.context, sc.muv_texproj_tex_magnitude)) + for v in v_screen + ] # project texture to object i = 0; @@ -261,23 +292,33 @@ class OBJECT_PT_TP(bpy.types.Panel): bl_label = "Texture Projection" bl_description = "Texture Projection Menu" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_context = 'mesh_edit' - def draw(self, context): + @classmethod + def poll(cls, context): prefs = context.user_preferences.addons["uv_magic_uv"].preferences - if prefs.enable_texproj is False: - return + return prefs.enable_texproj + def draw_header(self, context): + layout = self.layout + layout.label(text="", icon='PLUGIN') + + def draw(self, context): sc = context.scene layout = self.layout props = sc.muv_props.texproj if props.running == False: - layout.operator(MUV_TexProjStart.bl_idname, text="Start", icon='PLAY') + layout.operator( + MUV_TexProjStart.bl_idname, text="Start", icon='PLAY') else: - layout.operator(MUV_TexProjStop.bl_idname, text="Stop", icon='PAUSE') - layout.label(text="Image: ") - layout.prop(sc, "muv_texproj_tex_image", text="") - layout.prop(sc, "muv_texproj_tex_magnitude", text="Magnitude") + layout.operator( + MUV_TexProjStop.bl_idname, text="Stop", icon='PAUSE') + layout.prop(sc, "muv_texproj_tex_image", text="Image") layout.prop(sc, "muv_texproj_tex_transparency", text="Transparency") + layout.prop(sc, "muv_texproj_adjust_window", text="Adjust Window") + if not sc.muv_texproj_adjust_window: + layout.prop(sc, "muv_texproj_tex_magnitude", text="Magnitude") + layout.prop(sc, "muv_texproj_apply_tex_aspect", text="Texture Aspect Ratio") layout.operator(MUV_TexProjProject.bl_idname, text="Project") diff --git a/uv_magic_uv/muv_transuv_ops.py b/uv_magic_uv/muv_transuv_ops.py index f8453466..a123df7a 100644 --- a/uv_magic_uv/muv_transuv_ops.py +++ b/uv_magic_uv/muv_transuv_ops.py @@ -20,8 +20,8 @@ __author__ = "Nutti , Mifth, MaxRobinot" __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy @@ -166,7 +166,8 @@ def execute(self, context): def main_parse( - self, active_obj, bm, uv_layer, sel_faces, active_face, active_face_nor): + self, active_obj, bm, uv_layer, sel_faces, + active_face, active_face_nor): all_sorted_faces = OrderedDict() # This is the main stuff used_verts = set() @@ -230,30 +231,26 @@ def main_parse( # 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, self) - 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, self): + check_face, face_stuff, used_verts, used_edges, all_sorted_faces, + uv_layer, self): """recurse faces around the new_grow only""" new_shared_faces = [] diff --git a/uv_magic_uv/muv_unwrapconst_ops.py b/uv_magic_uv/muv_unwrapconst_ops.py index aff34a9f..36eadb96 100644 --- a/uv_magic_uv/muv_unwrapconst_ops.py +++ b/uv_magic_uv/muv_unwrapconst_ops.py @@ -18,8 +18,8 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy @@ -44,24 +44,21 @@ class MUV_UnwrapConstraint(bpy.types.Operator): description="Unwrapping method", items=[ ('ANGLE_BASED', 'Angle Based', 'Angle Based'), - ('CONFORMAL', 'Conformal', 'Conformal')], + ('CONFORMAL', 'Conformal', 'Conformal') + ], default='ANGLE_BASED') - fill_holes = BoolProperty( name="Fill Holes", description="Virtual fill holes in meshes before unwrapping", default=True) - correct_aspect = BoolProperty( name="Correct Aspect", description="Map UVs taking image aspect ratio into account", default=True) - use_subsurf_data = BoolProperty( name="Use Subsurf Modifier", description="Map UVs taking vertex position after subsurf into account", default=False) - margin = FloatProperty( name="Margin", description="Space between islands", @@ -74,13 +71,11 @@ class MUV_UnwrapConstraint(bpy.types.Operator): name="U-Constraint", description="Keep UV U-axis coordinate", default=False) - v_const = BoolProperty( name="V-Constraint", description="Keep UV V-axis coordinate", default=False) - def execute(self, context): obj = bpy.context.active_object bm = bmesh.from_edit_mesh(obj.data) @@ -88,8 +83,7 @@ def execute(self, context): bm.faces.ensure_lookup_table() if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") + self.report({'WARNING'}, "Object must have more than one UV map") return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify() diff --git a/uv_magic_uv/muv_uvbb_ops.py b/uv_magic_uv/muv_uvbb_ops.py index 26186bb5..4180ec38 100644 --- a/uv_magic_uv/muv_uvbb_ops.py +++ b/uv_magic_uv/muv_uvbb_ops.py @@ -20,14 +20,15 @@ __author__ = "Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy import bgl import mathutils import bmesh +from bpy.props import BoolProperty import copy from enum import IntEnum import math @@ -39,10 +40,8 @@ class MUV_UVBBCmd(): Custom class: Base class of command """ - op = 'NONE' # operation - def __init__(self): - self.op = 'NONE' + self.op = 'NONE' # operation def to_matrix(self): # mat = I @@ -56,17 +55,12 @@ class MUV_UVBBTranslationCmd(MUV_UVBBCmd): Custom class: Translation operation """ - __x = 0 # current x - __y = 0 # current y - __ix = 0 # initial x - __iy = 0 # initial y - def __init__(self, ix, iy): self.op = 'TRANSLATION' - self.__x = ix - self.__y = iy - self.__ix = ix - self.__iy = iy + self.__x = ix # current x + self.__y = iy # current y + self.__ix = ix # initial x + self.__iy = iy # initial y def to_matrix(self): # mat = Mt @@ -84,21 +78,15 @@ class MUV_UVBBRotationCmd(MUV_UVBBCmd): Custom class: Rotation operation """ - __x = 0 # current x - __y = 0 # current y - __cx = 0 # center of rotation x - __cy = 0 # center of rotation y - __iangle = 0 # initial rotation angle - def __init__(self, ix, iy, cx, cy): self.op = 'ROTATION' - self.__x = ix - self.__y = iy - self.__cx = cx - self.__cy = cy + self.__x = ix # current x + self.__y = iy # current y + self.__cx = cx # center of rotation x + self.__cy = cy # center of rotation y dx = self.__x - self.__cx dy = self.__y - self.__cy - self.__iangle = math.atan2(dy, dx) + self.__iangle = math.atan2(dy, dx) # initial rotation angle def to_matrix(self): # mat = Mt * Mr * Mt^-1 @@ -120,33 +108,21 @@ class MUV_UVBBScalingCmd(MUV_UVBBCmd): Custom class: Scaling operation """ - __x = 0 # current x - __y = 0 # current y - __ix = 0 # initial x - __iy = 0 # initial y - __ox = 0 # origin of scaling x - __oy = 0 # origin of scaling y - __iox = 0 # initial origin of scaling x - __ioy = 0 # initial origin of scaling y - __dir_x = 0 # direction of scaling x - __dir_y = 0 # direction of scaling y - __mat = None - def __init__(self, ix, iy, ox, oy, dir_x, dir_y, mat): self.op = 'SCALING' - self.__ix = ix - self.__iy = iy - self.__x = ix - self.__y = iy - self.__ox = ox - self.__oy = oy - self.__dir_x = dir_x - self.__dir_y = dir_y + self.__ix = ix # initial x + self.__iy = iy # initial y + self.__x = ix # current x + self.__y = iy # current y + self.__ox = ox # origin of scaling x + self.__oy = oy # origin of scaling y + self.__dir_x = dir_x # direction of scaling x + self.__dir_y = dir_y # direction of scaling y self.__mat = mat # initial origin of scaling = M(to original transform) * (ox, oy) iov = mat * mathutils.Vector((ox, oy, 0.0)) - self.__iox = iov.x - self.__ioy = iov.y + self.__iox = iov.x # initial origin of scaling X + self.__ioy = iov.y # initial origin of scaling y def to_matrix(self): """ @@ -182,29 +158,19 @@ class MUV_UVBBUniformScalingCmd(MUV_UVBBCmd): Custom class: Uniform Scaling operation """ - __x = 0 # current x - __y = 0 # current y - __ix = 0 # initial x - __iy = 0 # initial y - __ox = 0 # origin of scaling x - __oy = 0 # origin of scaling y - __iox = 0 # initial origin of scaling x - __ioy = 0 # initial origin of scaling y - __mat = None - def __init__(self, ix, iy, ox, oy, mat): self.op = 'SCALING' - self.__ix = ix - self.__iy = iy - self.__x = ix - self.__y = iy - self.__ox = ox - self.__oy = oy + self.__ix = ix # initial x + self.__iy = iy # initial y + self.__x = ix # current x + self.__y = iy # current y + self.__ox = ox # origin of scaling x + self.__oy = oy # origin of scaling y self.__mat = mat # initial origin of scaling = M(to original transform) * (ox, oy) iov = mat * mathutils.Vector((ox, oy, 0.0)) - self.__iox = iov.x - self.__ioy = iov.y + self.__iox = iov.x # initial origin of scaling x + self.__ioy = iov.y # initial origin of scaling y def to_matrix(self): """ @@ -253,12 +219,9 @@ class MUV_UVBBCmdExecuter(): Custom class: manage command history and execute command """ - __cmd_list = [] # history - __cmd_list_redo = [] # redo list - def __init__(self): - self.__cmd_list = [] - self.__cmd_list_redo = [] + self.__cmd_list = [] # history + self.__cmd_list_redo = [] # redo list def execute(self, begin=0, end=-1): """ @@ -327,8 +290,6 @@ class MUV_UVBBRenderer(bpy.types.Operator): bl_description = "Bounding Box Renderer about UV in Image Editor" __handle = None - __timer = None - __ctrl_points = [] @staticmethod def handle_add(self, context): @@ -337,10 +298,6 @@ def handle_add(self, context): MUV_UVBBRenderer.__handle = sie.draw_handler_add( MUV_UVBBRenderer.draw_bb, (self, context), "WINDOW", "POST_PIXEL") - if MUV_UVBBRenderer.__timer is None: - MUV_UVBBRenderer.__timer = context.window_manager.event_timer_add( - 0.10, context.window) - context.window_manager.modal_handler_add(self) @staticmethod def handle_remove(self, context): @@ -349,16 +306,14 @@ def handle_remove(self, context): sie.draw_handler_remove( MUV_UVBBRenderer.__handle, "WINDOW") MUV_UVBBRenderer.__handle = None - if MUV_UVBBRenderer.__timer is not None: - context.window_manager.event_timer_remove(MUV_UVBBRenderer.__timer) - MUV_UVBBRenderer.__timer = None @staticmethod def __draw_ctrl_point(self, context, pos): """ Draw control point """ - cp_size = context.scene.muv_uvbb_cp_size + prefs = context.user_preferences.addons["uv_magic_uv"].preferences + cp_size = prefs.uvbb_cp_size offset = cp_size / 2 verts = [ [pos.x - offset, pos.y - offset], @@ -425,8 +380,6 @@ class MUV_UVBBStateNone(MUV_UVBBStateBase): Wait for event from mouse """ - __cmd_exec = None - def __init__(self, cmd_exec): self.__cmd_exec = cmd_exec @@ -434,7 +387,8 @@ def update(self, context, event, ctrl_points, mouse_view): """ Update state """ - cp_react_size = context.scene.muv_uvbb_cp_react_size + prefs = context.user_preferences.addons["uv_magic_uv"].preferences + cp_react_size = prefs.uvbb_cp_react_size is_uscaling = context.scene.muv_uvbb_uniform_scaling if event.type == 'LEFTMOUSE': if event.value == 'PRESS': @@ -442,13 +396,16 @@ def update(self, context, event, ctrl_points, mouse_view): mouse_view.x, mouse_view.y) for i, p in enumerate(ctrl_points): px, py = context.region.view2d.view_to_region(p.x, p.y) - in_cp_x = px + cp_react_size > x and px - cp_react_size < x - in_cp_y = py + cp_react_size > y and py - cp_react_size < y + in_cp_x = (px + cp_react_size > x and + px - cp_react_size < x) + in_cp_y = (py + cp_react_size > y and + py - cp_react_size < y) if in_cp_x and in_cp_y: if is_uscaling: arr = [1, 3, 6, 8] if i in arr: - return MUV_UVBBState.UNIFORM_SCALING_1 + arr.index(i) + return (MUV_UVBBState.UNIFORM_SCALING_1 + + arr.index(i)) else: return MUV_UVBBState.TRANSLATING + i @@ -460,8 +417,6 @@ class MUV_UVBBStateTranslating(MUV_UVBBStateBase): Custom class: Translating state """ - __cmd_exec = None - def __init__(self, cmd_exec, mouse_view, ctrl_points): self.__cmd_exec = cmd_exec ix, iy = ctrl_points[0].x, ctrl_points[0].y @@ -482,9 +437,6 @@ class MUV_UVBBStateScaling(MUV_UVBBStateBase): Custom class: Scaling state """ - __state = None - __cmd_exec = None - def __init__(self, cmd_exec, mouse_view, state, ctrl_points): self.__state = state self.__cmd_exec = cmd_exec @@ -495,7 +447,8 @@ def __init__(self, cmd_exec, mouse_view, state, ctrl_points): ox, oy = ctrl_points[8 - idx].x, ctrl_points[8 - idx].y dir_x, dir_y = dir_x_list[idx], dir_y_list[idx] mat = self.__cmd_exec.execute(end=self.__cmd_exec.undo_size()) - self.__cmd_exec.append(MUV_UVBBScalingCmd(ix, iy, ox, oy, dir_x, dir_y, mat.inverted())) + self.__cmd_exec.append( + MUV_UVBBScalingCmd(ix, iy, ox, oy, dir_x, dir_y, mat.inverted())) def update(self, context, event, ctrl_points, mouse_view): if event.type == 'LEFTMOUSE': @@ -512,9 +465,6 @@ class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase): Custom class: Uniform Scaling state """ - __state = None - __cmd_exec = None - def __init__(self, cmd_exec, mouse_view, state, ctrl_points): self.__state = state self.__cmd_exec = cmd_exec @@ -543,8 +493,6 @@ class MUV_UVBBStateRotating(MUV_UVBBStateBase): Custom class: Rotating state """ - __cmd_exec = None - def __init__(self, cmd_exec, mouse_view, ctrl_points): self.__cmd_exec = cmd_exec ix, iy = ctrl_points[9].x, ctrl_points[9].y @@ -566,12 +514,9 @@ class MUV_UVBBStateMgr(): Custom class: Manage state about this feature """ - __state = MUV_UVBBState.NONE # current state - __cmd_exec = None # command executer - def __init__(self, cmd_exec): - self.__cmd_exec = cmd_exec - self.__state = MUV_UVBBState.NONE + self.__cmd_exec = cmd_exec # command executer + self.__state = MUV_UVBBState.NONE # current state self.__state_obj = MUV_UVBBStateNone(self.__cmd_exec) def __update_state(self, next_state, mouse_view, ctrl_points): @@ -592,7 +537,8 @@ def __update_state(self, next_state, mouse_view, ctrl_points): self.__cmd_exec, mouse_view, ctrl_points) elif next_state == MUV_UVBBState.NONE: self.__state_obj = MUV_UVBBStateNone(self.__cmd_exec) - elif MUV_UVBBState.UNIFORM_SCALING_1 <= next_state <= MUV_UVBBState.UNIFORM_SCALING_4: + elif (MUV_UVBBState.UNIFORM_SCALING_1 <= next_state + <= MUV_UVBBState.UNIFORM_SCALING_4): self.__state_obj = MUV_UVBBStateUniformScaling( self.__cmd_exec, mouse_view, next_state, ctrl_points) @@ -619,12 +565,23 @@ class MUV_UVBBUpdater(bpy.types.Operator): bl_description = "Update UV Bounding Box" bl_options = {'REGISTER', 'UNDO'} - __state_mgr = None # State Manager - __cmd_exec = MUV_UVBBCmdExecuter() # Command executer - def __init__(self): - self.__cmd_exec = MUV_UVBBCmdExecuter() - self.__state_mgr = MUV_UVBBStateMgr(self.__cmd_exec) + self.__timer = None + self.__cmd_exec = MUV_UVBBCmdExecuter() # Command executer + self.__state_mgr = MUV_UVBBStateMgr(self.__cmd_exec) # State Manager + + def __handle_add(self, context): + if self.__timer is None: + self.__timer = context.window_manager.event_timer_add( + 0.1, context.window) + context.window_manager.modal_handler_add(self) + MUV_UVBBRenderer.handle_add(self, context) + + def __handle_remove(self, context): + MUV_UVBBRenderer.handle_remove(self, context) + if self.__timer is not None: + context.window_manager.event_timer_remove(self.__timer) + self.__timer = None def __get_uv_info(self, context): """ @@ -668,17 +625,17 @@ def __get_ctrl_point(self, context, uv_info_ini): top = uv.y points = [ - mathutils.Vector(((left + right) * 0.5, (top + bottom) * 0.5, 0.0)), - mathutils.Vector((left, top, 0.0)), - mathutils.Vector((left, (top + bottom) * 0.5, 0.0)), - mathutils.Vector((left, bottom, 0.0)), - mathutils.Vector(((left + right) * 0.5, top, 0.0)), - mathutils.Vector(((left + right) * 0.5, bottom, 0.0)), - mathutils.Vector((right, top, 0.0)), - mathutils.Vector((right, (top + bottom) * 0.5, 0.0)), - mathutils.Vector((right, bottom, 0.0)), - mathutils.Vector(((left + right) * 0.5, top + 0.03, 0.0)) - ] + mathutils.Vector(((left + right) * 0.5, (top + bottom) * 0.5, 0.0)), + mathutils.Vector((left, top, 0.0)), + mathutils.Vector((left, (top + bottom) * 0.5, 0.0)), + mathutils.Vector((left, bottom, 0.0)), + mathutils.Vector(((left + right) * 0.5, top, 0.0)), + mathutils.Vector(((left + right) * 0.5, bottom, 0.0)), + mathutils.Vector((right, top, 0.0)), + mathutils.Vector((right, (top + bottom) * 0.5, 0.0)), + mathutils.Vector((right, bottom, 0.0)), + mathutils.Vector(((left + right) * 0.5, top + 0.03, 0.0)) + ] return points @@ -686,7 +643,6 @@ def __update_uvs(self, context, uv_info_ini, trans_mat): """ Update UV coordinate """ - obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) if muv_common.check_version(2, 73, 0) >= 0: @@ -703,23 +659,23 @@ def __update_uvs(self, context, uv_info_ini, trans_mat): bm.faces[fidx].loops[lidx][uv_layer].uv = mathutils.Vector( (av.x, av.y)) - def __update_ctrl_point(self, context, ctrl_points_ini, trans_mat): """ Update control point """ - return [trans_mat * cp for cp in ctrl_points_ini] def modal(self, context, event): props = context.scene.muv_props.uvbb muv_common.redraw_all_areas() if props.running is False: + self.__handle_remove(context) return {'FINISHED'} if event.type == 'TIMER': trans_mat = self.__cmd_exec.execute() self.__update_uvs(context, props.uv_info_ini, trans_mat) - props.ctrl_points = self.__update_ctrl_point(context, props.ctrl_points_ini, trans_mat) + props.ctrl_points = self.__update_ctrl_point( + context, props.ctrl_points_ini, trans_mat) self.__state_mgr.update(context, props.ctrl_points, event) @@ -727,25 +683,25 @@ def modal(self, context, event): def execute(self, context): props = context.scene.muv_props.uvbb - if props.running == False: - props.uv_info_ini = self.__get_uv_info(context) - if props.uv_info_ini == None: - return {'CANCELLED'} - props.ctrl_points_ini = self.__get_ctrl_point(context, props.uv_info_ini) - trans_mat = self.__cmd_exec.execute() - # Update is needed in order to display control point - self.__update_uvs(context, props.uv_info_ini, trans_mat) - props.ctrl_points = self.__update_ctrl_point( - context, props.ctrl_points_ini, trans_mat) - MUV_UVBBRenderer.handle_add(self, context) - props.running = True - else: - MUV_UVBBRenderer.handle_remove(self, context) + + if props.running == True: props.running = False - if context.area: - context.area.tag_redraw() + return {'FINISHED'} - return {'FINISHED'} + props.uv_info_ini = self.__get_uv_info(context) + if props.uv_info_ini == None: + return {'CANCELLED'} + props.ctrl_points_ini = self.__get_ctrl_point( + context, props.uv_info_ini) + trans_mat = self.__cmd_exec.execute() + # Update is needed in order to display control point + self.__update_uvs(context, props.uv_info_ini, trans_mat) + props.ctrl_points = self.__update_ctrl_point( + context, props.ctrl_points_ini, trans_mat) + self.__handle_add(context) + props.running = True + + return {'RUNNING_MODAL'} class IMAGE_PT_MUV_UVBB(bpy.types.Panel): @@ -755,16 +711,22 @@ class IMAGE_PT_MUV_UVBB(bpy.types.Panel): bl_space_type = 'IMAGE_EDITOR' bl_region_type = 'UI' - bl_label = 'UV Bounding Box' + bl_label = "UV Bounding Box" + bl_context = 'mesh_edit' - def draw(self, context): + @classmethod + def poll(cls, context): prefs = context.user_preferences.addons["uv_magic_uv"].preferences - if prefs.enable_uvbb is False: - return + return prefs.enable_uvbb + + def draw_header(self, context): + layout = self.layout + layout.label(text="", icon='PLUGIN') + + def draw(self, context): sc = context.scene props = sc.muv_props.uvbb layout = self.layout - layout.label(text="", icon='PLUGIN') if props.running == False: layout.operator( MUV_UVBBUpdater.bl_idname, text="Display UV Bounding Box", @@ -773,7 +735,4 @@ def draw(self, context): layout.operator( MUV_UVBBUpdater.bl_idname, text="Hide UV Bounding Box", icon='PAUSE') - layout.label(text="Control Point") - layout.prop(sc, "muv_uvbb_cp_size", text="Size") - layout.prop(sc, "muv_uvbb_cp_react_size", text="React Size") - layout.prop(sc, "muv_uvbb_uniform_scaling", text="Uniform Scaling") + layout.prop(sc, "muv_uvbb_uniform_scaling", text="Uniform Scaling") diff --git a/uv_magic_uv/muv_wsuv_ops.py b/uv_magic_uv/muv_wsuv_ops.py index 31185399..f4bfa867 100644 --- a/uv_magic_uv/muv_wsuv_ops.py +++ b/uv_magic_uv/muv_wsuv_ops.py @@ -20,8 +20,8 @@ __author__ = "McBuff, Nutti " __status__ = "production" -__version__ = "4.1" -__date__ = "13 Nov 2016" +__version__ = "4.2" +__date__ = "4 Mar 2017" import bpy @@ -74,8 +74,7 @@ def execute(self, context): bm.faces.ensure_lookup_table() if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") + self.report({'WARNING'}, "Object must have more than one UV map") return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify()