Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNutti <nutti.metro@gmail.com>2018-02-16 15:04:36 +0300
committerNutti <nutti.metro@gmail.com>2018-02-16 15:04:36 +0300
commitfdc914d653d4cf9e8c6ad5ea3d71fdefb9529491 (patch)
treef06d9e4b851a39c62ee8d4308a3587211df23bfa /uv_magic_uv
parentbfab29085ca84ad74125aa18a26a0025a1e09ace (diff)
Magic UV: Release v5.0
* Add features - Align UV Cursor - UV Cursor Location - Align UV - Smooth UV - UV Inspection - Select UV - Texture Wrap - UV Sculpt * Improve features - Copy/Paste UV: Add menu to UV/Image Editor - World Scale UV: Add information about Texel Density - UV Bounding Box: Add option "Bound" - Texture Projection: Add option "Assign UVMap" - UVW: Add option "Assign UVMap" * Improve UI * Fixed bugs * Optimization/Refactoring
Diffstat (limited to 'uv_magic_uv')
-rw-r--r--uv_magic_uv/__init__.py103
-rw-r--r--uv_magic_uv/common.py592
-rw-r--r--uv_magic_uv/muv_common.py83
-rw-r--r--uv_magic_uv/muv_cpuv_selseq_ops.py279
-rw-r--r--uv_magic_uv/muv_menu.py138
-rw-r--r--uv_magic_uv/muv_preferences.py144
-rw-r--r--uv_magic_uv/muv_props.py148
-rw-r--r--uv_magic_uv/op/__init__.py72
-rw-r--r--uv_magic_uv/op/align_uv.py784
-rw-r--r--uv_magic_uv/op/align_uv_cursor.py154
-rw-r--r--uv_magic_uv/op/copy_paste_uv.py (renamed from uv_magic_uv/muv_cpuv_ops.py)435
-rw-r--r--uv_magic_uv/op/copy_paste_uv_object.py252
-rw-r--r--uv_magic_uv/op/copy_paste_uv_uvedit.py144
-rw-r--r--uv_magic_uv/op/flip_rotate_uv.py (renamed from uv_magic_uv/muv_fliprot_ops.py)9
-rw-r--r--uv_magic_uv/op/mirror_uv.py (renamed from uv_magic_uv/muv_mirroruv_ops.py)9
-rw-r--r--uv_magic_uv/op/move_uv.py (renamed from uv_magic_uv/muv_mvuv_ops.py)13
-rw-r--r--uv_magic_uv/op/pack_uv.py (renamed from uv_magic_uv/muv_packuv_ops.py)119
-rw-r--r--uv_magic_uv/op/preserve_uv_aspect.py (renamed from uv_magic_uv/muv_preserve_uv_aspect.py)28
-rw-r--r--uv_magic_uv/op/smooth_uv.py215
-rw-r--r--uv_magic_uv/op/texture_lock.py (renamed from uv_magic_uv/muv_texlock_ops.py)32
-rw-r--r--uv_magic_uv/op/texture_projection.py (renamed from uv_magic_uv/muv_texproj_ops.py)67
-rw-r--r--uv_magic_uv/op/texture_wrap.py212
-rw-r--r--uv_magic_uv/op/transfer_uv.py (renamed from uv_magic_uv/muv_transuv_ops.py)17
-rw-r--r--uv_magic_uv/op/unwrap_constraint.py (renamed from uv_magic_uv/muv_unwrapconst_ops.py)16
-rw-r--r--uv_magic_uv/op/uv_bounding_box.py (renamed from uv_magic_uv/muv_uvbb_ops.py)56
-rw-r--r--uv_magic_uv/op/uv_inspection.py623
-rw-r--r--uv_magic_uv/op/uv_sculpt.py355
-rw-r--r--uv_magic_uv/op/uvw.py (renamed from uv_magic_uv/muv_uvw_ops.py)43
-rw-r--r--uv_magic_uv/op/world_scale_uv.py (renamed from uv_magic_uv/muv_wsuv_ops.py)130
-rw-r--r--uv_magic_uv/preferences.py216
-rw-r--r--uv_magic_uv/properites.py755
-rw-r--r--uv_magic_uv/ui/__init__.py44
-rw-r--r--uv_magic_uv/ui/uvedit_copy_paste_uv.py54
-rw-r--r--uv_magic_uv/ui/uvedit_editor_enhance.py136
-rw-r--r--uv_magic_uv/ui/uvedit_uv_manipulation.py117
-rw-r--r--uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py81
-rw-r--r--uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py56
-rw-r--r--uv_magic_uv/ui/view3d_uv_manipulation.py180
-rw-r--r--uv_magic_uv/ui/view3d_uv_mapping.py99
39 files changed, 5629 insertions, 1381 deletions
diff --git a/uv_magic_uv/__init__.py b/uv_magic_uv/__init__.py
index 171a5ac4..97f8bb79 100644
--- a/uv_magic_uv/__init__.py
+++ b/uv_magic_uv/__init__.py
@@ -20,18 +20,18 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
bl_info = {
"name": "Magic UV",
- "author": "Nutti, Mifth, Jace Priester, kgeogeo, mem, "
+ "author": "Nutti, Mifth, Jace Priester, kgeogeo, mem, imdjs"
"Keith (Wahooney) Boshoff, McBuff, MaxRobinot, Alexander Milovsky",
- "version": (4, 5, 0),
+ "version": (5, 0, 0),
"blender": (2, 79, 0),
"location": "See Add-ons Preferences",
- "description": "UV Manipulator Tools. See Add-ons Preferences for details",
+ "description": "UV Toolset. See Add-ons Preferences for details",
"warning": "",
"support": "COMMUNITY",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
@@ -42,98 +42,29 @@ bl_info = {
if "bpy" in locals():
import importlib
- importlib.reload(muv_preferences)
- importlib.reload(muv_menu)
- importlib.reload(muv_common)
- importlib.reload(muv_props)
- importlib.reload(muv_cpuv_ops)
- importlib.reload(muv_cpuv_selseq_ops)
- importlib.reload(muv_fliprot_ops)
- importlib.reload(muv_transuv_ops)
- importlib.reload(muv_uvbb_ops)
- importlib.reload(muv_mvuv_ops)
- importlib.reload(muv_texproj_ops)
- importlib.reload(muv_packuv_ops)
- importlib.reload(muv_texlock_ops)
- importlib.reload(muv_mirroruv_ops)
- importlib.reload(muv_wsuv_ops)
- importlib.reload(muv_unwrapconst_ops)
- importlib.reload(muv_preserve_uv_aspect)
- importlib.reload(muv_uvw_ops)
+ importlib.reload(op)
+ importlib.reload(ui)
+ importlib.reload(common)
+ importlib.reload(preferences)
+ importlib.reload(properites)
else:
- from . import muv_preferences
- from . import muv_menu
- from . import muv_common
- from . import muv_props
- from . import muv_cpuv_ops
- from . import muv_cpuv_selseq_ops
- from . import muv_fliprot_ops
- from . import muv_transuv_ops
- from . import muv_uvbb_ops
- 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
- from . import muv_preserve_uv_aspect
- from . import muv_uvw_ops
+ from . import op
+ from . import ui
+ from . import common
+ from . import preferences
+ from . import properites
import bpy
-def view3d_uvmap_menu_fn(self, context):
- self.layout.separator()
- self.layout.menu(muv_menu.MUV_CPUVMenu.bl_idname, icon="IMAGE_COL")
- self.layout.operator(
- muv_fliprot_ops.MUV_FlipRot.bl_idname, icon="IMAGE_COL")
- self.layout.menu(muv_menu.MUV_TransUVMenu.bl_idname, icon="IMAGE_COL")
- self.layout.operator(muv_mvuv_ops.MUV_MVUV.bl_idname, icon="IMAGE_COL")
- self.layout.menu(muv_menu.MUV_TexLockMenu.bl_idname, icon="IMAGE_COL")
- self.layout.operator(
- muv_mirroruv_ops.MUV_MirrorUV.bl_idname, icon="IMAGE_COL")
- self.layout.menu(muv_menu.MUV_WSUVMenu.bl_idname, icon="IMAGE_COL")
- self.layout.operator(
- muv_unwrapconst_ops.MUV_UnwrapConstraint.bl_idname, icon='IMAGE_COL')
- self.layout.menu(
- muv_preserve_uv_aspect.MUV_PreserveUVAspectMenu.bl_idname,
- icon='IMAGE_COL')
- self.layout.menu(muv_menu.MUV_UVWMenu.bl_idname, icon="IMAGE_COL")
-
-
-def image_uvs_menu_fn(self, context):
- self.layout.separator()
- self.layout.operator(muv_packuv_ops.MUV_PackUV.bl_idname, icon="IMAGE_COL")
-
-
-def view3d_object_menu_fn(self, context):
- self.layout.separator()
- self.layout.menu(muv_menu.MUV_CPUVObjMenu.bl_idname, icon="IMAGE_COL")
-
-
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)
- try:
- bpy.types.VIEW3D_MT_Object.append(view3d_object_menu_fn)
- except:
- pass
- muv_props.init_props(bpy.types.Scene)
+ properites.init_props(bpy.types.Scene)
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)
- try:
- bpy.types.VIEW3D_MT_Object.remove(view3d_object_menu_fn)
- except:
- pass
- muv_props.clear_props(bpy.types.Scene)
+ properites.clear_props(bpy.types.Scene)
if __name__ == "__main__":
diff --git a/uv_magic_uv/common.py b/uv_magic_uv/common.py
new file mode 100644
index 00000000..dc8876a0
--- /dev/null
+++ b/uv_magic_uv/common.py
@@ -0,0 +1,592 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+from collections import defaultdict
+from pprint import pprint
+from math import fabs, sqrt
+
+import bpy
+from mathutils import Vector
+import bmesh
+
+
+DEBUG = False
+
+
+def debug_print(*s):
+ """
+ Print message to console in debugging mode
+ """
+
+ if DEBUG:
+ pprint(s)
+
+
+def check_version(major, minor, _):
+ """
+ Check blender version
+ """
+
+ if bpy.app.version[0] == major and bpy.app.version[1] == minor:
+ return 0
+ if bpy.app.version[0] > major:
+ return 1
+ if bpy.app.version[1] > minor:
+ return 1
+ return -1
+
+
+def redraw_all_areas():
+ """
+ Redraw all areas
+ """
+
+ for area in bpy.context.screen.areas:
+ area.tag_redraw()
+
+
+def get_space(area_type, region_type, space_type):
+ """
+ Get current area/region/space
+ """
+
+ area = None
+ region = None
+ space = None
+
+ for area in bpy.context.screen.areas:
+ if area.type == area_type:
+ break
+ else:
+ return (None, None, None)
+ for region in area.regions:
+ if region.type == region_type:
+ break
+ for space in area.spaces:
+ if space.type == space_type:
+ break
+
+ return (area, region, space)
+
+
+def __get_island_info(uv_layer, islands):
+ """
+ get information about each island
+ """
+
+ island_info = []
+ for isl in islands:
+ info = {}
+ max_uv = Vector((-10000000.0, -10000000.0))
+ min_uv = Vector((10000000.0, 10000000.0))
+ ave_uv = Vector((0.0, 0.0))
+ num_uv = 0
+ for face in isl:
+ n = 0
+ a = Vector((0.0, 0.0))
+ ma = Vector((-10000000.0, -10000000.0))
+ mi = Vector((10000000.0, 10000000.0))
+ for l in face['face'].loops:
+ uv = l[uv_layer].uv
+ ma.x = max(uv.x, ma.x)
+ ma.y = max(uv.y, ma.y)
+ mi.x = min(uv.x, mi.x)
+ mi.y = min(uv.y, mi.y)
+ a = a + uv
+ n = n + 1
+ ave_uv = ave_uv + a
+ num_uv = num_uv + n
+ a = a / n
+ max_uv.x = max(ma.x, max_uv.x)
+ max_uv.y = max(ma.y, max_uv.y)
+ min_uv.x = min(mi.x, min_uv.x)
+ min_uv.y = min(mi.y, min_uv.y)
+ face['max_uv'] = ma
+ face['min_uv'] = mi
+ face['ave_uv'] = a
+ ave_uv = ave_uv / num_uv
+
+ info['center'] = ave_uv
+ info['size'] = max_uv - min_uv
+ info['num_uv'] = num_uv
+ info['group'] = -1
+ info['faces'] = isl
+ info['max'] = max_uv
+ info['min'] = min_uv
+
+ island_info.append(info)
+
+ return island_info
+
+
+def __parse_island(bm, face_idx, faces_left, island,
+ face_to_verts, vert_to_faces):
+ """
+ Parse island
+ """
+
+ if face_idx in faces_left:
+ faces_left.remove(face_idx)
+ island.append({'face': bm.faces[face_idx]})
+ for v in face_to_verts[face_idx]:
+ connected_faces = vert_to_faces[v]
+ if connected_faces:
+ for cf in connected_faces:
+ __parse_island(bm, cf, faces_left, island, face_to_verts,
+ vert_to_faces)
+
+
+def __get_island(bm, face_to_verts, vert_to_faces):
+ """
+ Get island list
+ """
+
+ uv_island_lists = []
+ faces_left = set(face_to_verts.keys())
+ while faces_left:
+ current_island = []
+ face_idx = list(faces_left)[0]
+ __parse_island(bm, face_idx, faces_left, current_island,
+ face_to_verts, vert_to_faces)
+ uv_island_lists.append(current_island)
+
+ return uv_island_lists
+
+
+def __create_vert_face_db(faces, uv_layer):
+ # create mesh database for all faces
+ face_to_verts = defaultdict(set)
+ vert_to_faces = defaultdict(set)
+ for f in faces:
+ for l in f.loops:
+ id_ = l[uv_layer].uv.to_tuple(5), l.vert.index
+ face_to_verts[f.index].add(id_)
+ vert_to_faces[id_].add(f.index)
+
+ return (face_to_verts, vert_to_faces)
+
+
+def get_island_info(obj, only_selected=True):
+ bm = bmesh.from_edit_mesh(obj.data)
+ if check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ return get_island_info_from_bmesh(bm, only_selected)
+
+
+def get_island_info_from_bmesh(bm, only_selected=True):
+ if not bm.loops.layers.uv:
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+
+ # create database
+ if only_selected:
+ selected_faces = [f for f in bm.faces if f.select]
+ else:
+ selected_faces = [f for f in bm.faces]
+
+ return get_island_info_from_faces(bm, selected_faces, uv_layer)
+
+
+def get_island_info_from_faces(bm, faces, uv_layer):
+ ftv, vtf = __create_vert_face_db(faces, uv_layer)
+
+ # Get island information
+ uv_island_lists = __get_island(bm, ftv, vtf)
+ island_info = __get_island_info(uv_layer, uv_island_lists)
+
+ return island_info
+
+
+def get_uvimg_editor_board_size(area):
+ if area.spaces.active.image:
+ return area.spaces.active.image.size
+
+ return (255.0, 255.0)
+
+
+def calc_polygon_2d_area(points):
+ area = 0.0
+ for i, p1 in enumerate(points):
+ p2 = points[(i + 1) % len(points)]
+ v1 = p1 - points[0]
+ v2 = p2 - points[0]
+ a = v1.x * v2.y - v1.y * v2.x
+ area = area + a
+
+ return fabs(0.5 * area)
+
+
+def calc_polygon_3d_area(points):
+ area = 0.0
+ for i, p1 in enumerate(points):
+ p2 = points[(i + 1) % len(points)]
+ v1 = p1 - points[0]
+ v2 = p2 - points[0]
+ cx = v1.y * v2.z - v1.z * v2.y
+ cy = v1.z * v2.x - v1.x * v2.z
+ cz = v1.x * v2.y - v1.y * v2.x
+ a = sqrt(cx * cx + cy * cy + cz * cz)
+ area = area + a
+
+ return 0.5 * area
+
+
+def measure_mesh_area(obj):
+ bm = bmesh.from_edit_mesh(obj.data)
+ if check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ sel_faces = [f for f in bm.faces if f.select]
+
+ # measure
+ mesh_area = 0.0
+ for f in sel_faces:
+ verts = [l.vert.co for l in f.loops]
+ f_mesh_area = calc_polygon_3d_area(verts)
+ mesh_area = mesh_area + f_mesh_area
+
+ return mesh_area
+
+
+def measure_uv_area(obj):
+ bm = bmesh.from_edit_mesh(obj.data)
+ if 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:
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+
+ if not bm.faces.layers.tex:
+ return None
+ tex_layer = bm.faces.layers.tex.verify()
+
+ sel_faces = [f for f in bm.faces if f.select]
+
+ # measure
+ uv_area = 0.0
+ for f in sel_faces:
+ uvs = [l[uv_layer].uv for l in f.loops]
+ f_uv_area = calc_polygon_2d_area(uvs)
+ if tex_layer:
+ img = f[tex_layer].image
+ if not img:
+ return None
+ uv_area = uv_area + f_uv_area * img.size[0] * img.size[1]
+
+ return uv_area
+
+
+def diff_point_to_segment(a, b, p):
+ ab = b - a
+ normal_ab = ab.normalized()
+
+ ap = p - a
+ dist_ax = normal_ab.dot(ap)
+
+ # cross point
+ x = a + normal_ab * dist_ax
+
+ # difference between cross point and point
+ xp = p - x
+
+ return xp, x
+
+
+# get selected loop pair whose loops are connected each other
+def __get_loop_pairs(l, uv_layer):
+
+ def __get_loop_pairs_internal(l_, pairs_, uv_layer_, parsed_):
+ parsed_.append(l_)
+ for ll in l_.vert.link_loops:
+ # forward direction
+ lln = ll.link_loop_next
+ # if there is same pair, skip it
+ found = False
+ for p in pairs_:
+ if (ll in p) and (lln in p):
+ found = True
+ break
+ # two loops must be selected
+ if ll[uv_layer_].select and lln[uv_layer_].select:
+ if not found:
+ pairs_.append([ll, lln])
+ if lln not in parsed_:
+ __get_loop_pairs_internal(lln, pairs_, uv_layer_, parsed_)
+
+ # backward direction
+ llp = ll.link_loop_prev
+ # if there is same pair, skip it
+ found = False
+ for p in pairs_:
+ if (ll in p) and (llp in p):
+ found = True
+ break
+ # two loops must be selected
+ if ll[uv_layer_].select and llp[uv_layer_].select:
+ if not found:
+ pairs_.append([ll, llp])
+ if llp not in parsed_:
+ __get_loop_pairs_internal(llp, pairs_, uv_layer_, parsed_)
+
+ pairs = []
+ parsed = []
+ __get_loop_pairs_internal(l, pairs, uv_layer, parsed)
+
+ return pairs
+
+
+# sort pair by vertex
+# (v0, v1) - (v1, v2) - (v2, v3) ....
+def __sort_loop_pairs(uv_layer, pairs, closed):
+ rest = pairs
+ sorted_pairs = [rest[0]]
+ rest.remove(rest[0])
+
+ # prepend
+ while True:
+ p1 = sorted_pairs[0]
+ for p2 in rest:
+ if p1[0].vert == p2[0].vert:
+ sorted_pairs.insert(0, [p2[1], p2[0]])
+ rest.remove(p2)
+ break
+ elif p1[0].vert == p2[1].vert:
+ sorted_pairs.insert(0, [p2[0], p2[1]])
+ rest.remove(p2)
+ break
+ else:
+ break
+
+ # append
+ while True:
+ p1 = sorted_pairs[-1]
+ for p2 in rest:
+ if p1[1].vert == p2[0].vert:
+ sorted_pairs.append([p2[0], p2[1]])
+ rest.remove(p2)
+ break
+ elif p1[1].vert == p2[1].vert:
+ sorted_pairs.append([p2[1], p2[0]])
+ rest.remove(p2)
+ break
+ else:
+ break
+
+ begin_vert = sorted_pairs[0][0].vert
+ end_vert = sorted_pairs[-1][-1].vert
+ if begin_vert != end_vert:
+ return sorted_pairs, ""
+ if closed and (begin_vert == end_vert):
+ # if the sequence of UV is circular, it is ok
+ return sorted_pairs, ""
+
+ # if the begin vertex and the end vertex are same, search the UVs which
+ # are separated each other
+ tmp_pairs = sorted_pairs
+ for i, (p1, p2) in enumerate(zip(tmp_pairs[:-1], tmp_pairs[1:])):
+ diff = p2[0][uv_layer].uv - p1[-1][uv_layer].uv
+ if diff.length > 0.000000001:
+ # UVs are separated
+ sorted_pairs = tmp_pairs[i + 1:]
+ sorted_pairs.extend(tmp_pairs[:i + 1])
+ break
+ else:
+ p1 = tmp_pairs[0]
+ p2 = tmp_pairs[-1]
+ diff = p2[-1][uv_layer].uv - p1[0][uv_layer].uv
+ if diff.length < 0.000000001:
+ # all UVs are not separated
+ return None, "All UVs are not separted"
+
+ return sorted_pairs, ""
+
+
+# get index of the island group which includes loop
+def __get_island_group_include_loop(loop, island_info):
+ for i, isl in enumerate(island_info):
+ for f in isl['faces']:
+ for l in f['face'].loops:
+ if l == loop:
+ return i # found
+
+ return -1 # not found
+
+
+# get index of the island group which includes pair.
+# if island group is not same between loops, it will be invalid
+def __get_island_group_include_pair(pair, island_info):
+ l1_grp = __get_island_group_include_loop(pair[0], island_info)
+ if l1_grp == -1:
+ return -1 # not found
+
+ for p in pair[1:]:
+ l2_grp = __get_island_group_include_loop(p, island_info)
+ if (l2_grp == -1) or (l1_grp != l2_grp):
+ return -1 # not found or invalid
+
+ return l1_grp
+
+
+# x ---- x <- next_loop_pair
+# | |
+# o ---- o <- pair
+def __get_next_loop_pair(pair):
+ lp = pair[0].link_loop_prev
+ if lp.vert == pair[1].vert:
+ lp = pair[0].link_loop_next
+ if lp.vert == pair[1].vert:
+ # no loop is found
+ return None
+
+ ln = pair[1].link_loop_next
+ if ln.vert == pair[0].vert:
+ ln = pair[1].link_loop_prev
+ if ln.vert == pair[0].vert:
+ # no loop is found
+ return None
+
+ # tri-face
+ if lp == ln:
+ return [lp]
+
+ # quad-face
+ return [lp, ln]
+
+
+# | ---- |
+# % ---- % <- next_poly_loop_pair
+# x ---- x <- next_loop_pair
+# | |
+# o ---- o <- pair
+def __get_next_poly_loop_pair(pair):
+ v1 = pair[0].vert
+ v2 = pair[1].vert
+ for l1 in v1.link_loops:
+ if l1 == pair[0]:
+ continue
+ for l2 in v2.link_loops:
+ if l2 == pair[1]:
+ continue
+ if l1.link_loop_next == l2:
+ return [l1, l2]
+ elif l1.link_loop_prev == l2:
+ return [l1, l2]
+
+ # no next poly loop is found
+ return None
+
+
+# get loop sequence in the same island
+def __get_loop_sequence_internal(uv_layer, pairs, island_info, closed):
+ loop_sequences = []
+ for pair in pairs:
+ seqs = [pair]
+ p = pair
+ isl_grp = __get_island_group_include_pair(pair, island_info)
+ if isl_grp == -1:
+ return None, "Can not find the island or invalid island"
+
+ while True:
+ nlp = __get_next_loop_pair(p)
+ if not nlp:
+ break # no more loop pair
+ nlp_isl_grp = __get_island_group_include_pair(nlp, island_info)
+ if nlp_isl_grp != isl_grp:
+ break # another island
+ for nlpl in nlp:
+ if nlpl[uv_layer].select:
+ return None, "Do not select UV which does not belong to " \
+ "the end edge"
+
+ seqs.append(nlp)
+
+ # when face is triangle, it indicates CLOSED
+ if (len(nlp) == 1) and closed:
+ break
+
+ nplp = __get_next_poly_loop_pair(nlp)
+ if not nplp:
+ break # no more loop pair
+ nplp_isl_grp = __get_island_group_include_pair(nplp, island_info)
+ if nplp_isl_grp != isl_grp:
+ break # another island
+
+ # check if the UVs are already parsed.
+ # this check is needed for the mesh which has the circular
+ # sequence of the verticies
+ matched = False
+ for p1 in seqs:
+ p2 = nplp
+ if ((p1[0] == p2[0]) and (p1[1] == p2[1])) or \
+ ((p1[0] == p2[1]) and (p1[1] == p2[0])):
+ matched = True
+ if matched:
+ debug_print("This is a circular sequence")
+ break
+
+ for nlpl in nplp:
+ if nlpl[uv_layer].select:
+ return None, "Do not select UV which does not belong to " \
+ "the end edge"
+
+ seqs.append(nplp)
+
+ p = nplp
+
+ loop_sequences.append(seqs)
+ return loop_sequences, ""
+
+
+def get_loop_sequences(bm, uv_layer):
+ sel_faces = [f for f in bm.faces if f.select]
+
+ # get candidate loops
+ cand_loops = []
+ for f in sel_faces:
+ for l in f.loops:
+ if l[uv_layer].select:
+ cand_loops.append(l)
+
+ if len(cand_loops) < 2:
+ return None, "More than 2 UVs must be selected"
+
+ first_loop = cand_loops[0]
+ isl_info = get_island_info_from_bmesh(bm, False)
+ loop_pairs = __get_loop_pairs(first_loop, uv_layer)
+ loop_pairs, err = __sort_loop_pairs(uv_layer, loop_pairs, False)
+ if not loop_pairs:
+ return None, err
+ loop_seqs, err = __get_loop_sequence_internal(uv_layer, loop_pairs,
+ isl_info, False)
+ if not loop_seqs:
+ return None, err
+
+ return loop_seqs, ""
diff --git a/uv_magic_uv/muv_common.py b/uv_magic_uv/muv_common.py
deleted file mode 100644
index b52971ec..00000000
--- a/uv_magic_uv/muv_common.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# <pep8-80 compliant>
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-__author__ = "Nutti <nutti.metro@gmail.com>"
-__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
-
-import bpy
-from . import muv_props
-
-
-def debug_print(*s):
- """
- Print message to console in debugging mode
- """
-
- if muv_props.DEBUG:
- print(s)
-
-
-def check_version(major, minor, _):
- """
- Check blender version
- """
-
- if bpy.app.version[0] == major and bpy.app.version[1] == minor:
- return 0
- if bpy.app.version[0] > major:
- return 1
- if bpy.app.version[1] > minor:
- return 1
- return -1
-
-
-def redraw_all_areas():
- """
- Redraw all areas
- """
-
- for area in bpy.context.screen.areas:
- area.tag_redraw()
-
-
-def get_space(area_type, region_type, space_type):
- """
- Get current area/region/space
- """
-
- area = None
- region = None
- space = None
-
- for area in bpy.context.screen.areas:
- if area.type == area_type:
- break
- else:
- return (None, None, None)
- for region in area.regions:
- if region.type == region_type:
- break
- for space in area.spaces:
- if space.type == space_type:
- break
-
- return (area, region, space)
diff --git a/uv_magic_uv/muv_cpuv_selseq_ops.py b/uv_magic_uv/muv_cpuv_selseq_ops.py
deleted file mode 100644
index 3cf69ff7..00000000
--- a/uv_magic_uv/muv_cpuv_selseq_ops.py
+++ /dev/null
@@ -1,279 +0,0 @@
-# <pep8-80 compliant>
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-__author__ = "Nutti <nutti.metro@gmail.com>"
-__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
-
-import bpy
-import bmesh
-from bpy.props import (
- StringProperty,
- BoolProperty,
- IntProperty,
- EnumProperty,
-)
-from . import muv_common
-
-
-class MUV_CPUVSelSeqCopyUV(bpy.types.Operator):
- """
- Operation class: Copy UV coordinate by selection sequence
- """
-
- bl_idname = "uv.muv_cpuv_selseq_copy_uv"
- bl_label = "Copy UV (Selection Sequence) (Operation)"
- bl_description = "Copy UV data by selection sequence (Operation)"
- bl_options = {'REGISTER', 'UNDO'}
-
- uv_map = StringProperty(options={'HIDDEN'})
-
- def execute(self, context):
- props = context.scene.muv_props.cpuv_selseq
- if self.uv_map == "":
- self.report({'INFO'}, "Copy UV coordinate (selection sequence)")
- else:
- self.report(
- {'INFO'},
- "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:
- 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 = []
- props.src_seams = []
- for hist in bm.select_history:
- if isinstance(hist, bmesh.types.BMFace) and hist.select:
- uvs = [l[uv_layer].uv.copy() for l in hist.loops]
- pin_uvs = [l[uv_layer].pin_uv for l in hist.loops]
- seams = [l.edge.seam for l in hist.loops]
- props.src_uvs.append(uvs)
- props.src_pin_uvs.append(pin_uvs)
- props.src_seams.append(seams)
- if not props.src_uvs or not props.src_pin_uvs:
- self.report({'WARNING'}, "No faces are selected")
- return {'CANCELLED'}
- self.report({'INFO'}, "%d face(s) are selected" % len(props.src_uvs))
-
- return {'FINISHED'}
-
-
-class MUV_CPUVSelSeqCopyUVMenu(bpy.types.Menu):
- """
- Menu class: Copy UV coordinate by selection sequence
- """
-
- bl_idname = "uv.muv_cpuv_selseq_copy_uv_menu"
- bl_label = "Copy UV (Selection Sequence)"
- bl_description = "Copy UV coordinate by selection sequence"
-
- def draw(self, context):
- layout = self.layout
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- uv_maps = bm.loops.layers.uv.keys()
- layout.operator(
- MUV_CPUVSelSeqCopyUV.bl_idname,
- text="[Default]", icon="IMAGE_COL").uv_map = ""
- for m in uv_maps:
- layout.operator(
- MUV_CPUVSelSeqCopyUV.bl_idname,
- text=m, icon="IMAGE_COL").uv_map = m
-
-
-class MUV_CPUVSelSeqPasteUV(bpy.types.Operator):
- """
- Operation class: Paste UV coordinate by selection sequence
- """
-
- bl_idname = "uv.muv_cpuv_selseq_paste_uv"
- bl_label = "Paste UV (Selection Sequence) (Operation)"
- bl_description = "Paste UV coordinate by selection sequence (Operation)"
- bl_options = {'REGISTER', 'UNDO'}
-
- 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')
- ],
- default="N_M"
- )
- flip_copied_uv = BoolProperty(
- name="Flip Copied UV",
- description="Flip Copied UV...",
- default=False
- )
- rotate_copied_uv = IntProperty(
- default=0,
- name="Rotate Copied UV",
- min=0,
- max=30
- )
- copy_seams = BoolProperty(
- name="Copy Seams",
- description="Copy Seams",
- default=True
- )
-
- def execute(self, context):
- props = context.scene.muv_props.cpuv_selseq
- if not props.src_uvs or not props.src_pin_uvs:
- self.report({'WARNING'}, "Need copy UV at first")
- return {'CANCELLED'}
- if self.uv_map == "":
- self.report({'INFO'}, "Paste UV coordinate (selection sequence)")
- else:
- self.report(
- {'INFO'},
- "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:
- 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
- dest_uvs = []
- dest_pin_uvs = []
- dest_seams = []
- dest_face_indices = []
- for hist in bm.select_history:
- if isinstance(hist, bmesh.types.BMFace) and hist.select:
- dest_face_indices.append(hist.index)
- uvs = [l[uv_layer].uv.copy() for l in hist.loops]
- pin_uvs = [l[uv_layer].pin_uv for l in hist.loops]
- seams = [l.edge.seam for l in hist.loops]
- dest_uvs.append(uvs)
- dest_pin_uvs.append(pin_uvs)
- dest_seams.append(seams)
- if not dest_uvs or not dest_pin_uvs:
- self.report({'WARNING'}, "No faces are selected")
- return {'CANCELLED'}
- 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)))
- return {'CANCELLED'}
-
- # paste
- for i, idx in enumerate(dest_face_indices):
- suv = None
- spuv = None
- ss = None
- duv = None
- if self.strategy == 'N_N':
- suv = props.src_uvs[i]
- spuv = props.src_pin_uvs[i]
- ss = props.src_seams[i]
- duv = dest_uvs[i]
- elif self.strategy == 'N_M':
- suv = props.src_uvs[i % len(props.src_uvs)]
- spuv = props.src_pin_uvs[i % len(props.src_pin_uvs)]
- ss = props.src_seams[i % len(props.src_seams)]
- 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]
- ss_fr = [s for s in ss]
- # flip UVs
- if self.flip_copied_uv is True:
- suvs_fr.reverse()
- spuvs_fr.reverse()
- ss_fr.reverse()
- # rotate UVs
- for _ in range(self.rotate_copied_uv):
- uv = suvs_fr.pop()
- pin_uv = spuvs_fr.pop()
- s = ss_fr.pop()
- suvs_fr.insert(0, uv)
- spuvs_fr.insert(0, pin_uv)
- ss_fr.insert(0, s)
- # paste UVs
- for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr,
- spuvs_fr, ss_fr):
- l[uv_layer].uv = suv
- l[uv_layer].pin_uv = spuv
- if self.copy_seams is True:
- l.edge.seam = ss
-
- self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs))
-
- bmesh.update_edit_mesh(obj.data)
- if self.copy_seams is True:
- obj.data.show_edge_seams = True
-
- return {'FINISHED'}
-
-
-class MUV_CPUVSelSeqPasteUVMenu(bpy.types.Menu):
- """
- Menu class: Paste UV coordinate by selection sequence
- """
-
- bl_idname = "uv.muv_cpuv_selseq_paste_uv_menu"
- bl_label = "Paste UV (Selection Sequence)"
- bl_description = "Paste UV coordinate by selection sequence"
-
- def draw(self, context):
- layout = self.layout
- # create sub menu
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- uv_maps = bm.loops.layers.uv.keys()
- layout.operator(
- MUV_CPUVSelSeqPasteUV.bl_idname,
- text="[Default]", icon="IMAGE_COL").uv_map = ""
- for m in uv_maps:
- layout.operator(
- MUV_CPUVSelSeqPasteUV.bl_idname,
- text=m, icon="IMAGE_COL").uv_map = m
diff --git a/uv_magic_uv/muv_menu.py b/uv_magic_uv/muv_menu.py
deleted file mode 100644
index 47c79bbd..00000000
--- a/uv_magic_uv/muv_menu.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# <pep8-80 compliant>
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-__author__ = "Nutti <nutti.metro@gmail.com>"
-__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
-
-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
-from . import muv_uvw_ops
-
-
-class MUV_CPUVMenu(bpy.types.Menu):
- """
- Menu class: Master menu of Copy/Paste UV coordinate
- """
-
- bl_idname = "uv.muv_cpuv_menu"
- bl_label = "Copy/Paste UV"
- bl_description = "Copy and Paste UV coordinate"
-
- def draw(self, _):
- self.layout.menu(
- muv_cpuv_ops.MUV_CPUVCopyUVMenu.bl_idname, icon="IMAGE_COL")
- self.layout.menu(
- muv_cpuv_ops.MUV_CPUVPasteUVMenu.bl_idname, icon="IMAGE_COL")
- self.layout.menu(
- muv_cpuv_selseq_ops.MUV_CPUVSelSeqCopyUVMenu.bl_idname,
- icon="IMAGE_COL")
- self.layout.menu(
- muv_cpuv_selseq_ops.MUV_CPUVSelSeqPasteUVMenu.bl_idname,
- icon="IMAGE_COL")
-
-
-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, _):
- self.layout.menu(
- muv_cpuv_ops.MUV_CPUVObjCopyUVMenu.bl_idname, icon="IMAGE_COL")
- self.layout.menu(
- muv_cpuv_ops.MUV_CPUVObjPasteUVMenu.bl_idname, icon="IMAGE_COL")
-
-
-class MUV_TransUVMenu(bpy.types.Menu):
- """
- Menu class: Master menu of Transfer UV coordinate
- """
-
- bl_idname = "uv.muv_transuv_menu"
- bl_label = "Transfer UV"
- bl_description = "Transfer UV coordinate"
-
- def draw(self, _):
- self.layout.operator(
- muv_transuv_ops.MUV_TransUVCopy.bl_idname, icon="IMAGE_COL")
- self.layout.operator(
- muv_transuv_ops.MUV_TransUVPaste.bl_idname, icon="IMAGE_COL")
-
-
-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, _):
- self.layout.operator(
- muv_texlock_ops.MUV_TexLockStart.bl_idname, icon="IMAGE_COL")
- self.layout.operator(
- muv_texlock_ops.MUV_TexLockStop.bl_idname, icon="IMAGE_COL")
- self.layout.operator(
- muv_texlock_ops.MUV_TexLockIntrStart.bl_idname, icon="IMAGE_COL")
- self.layout.operator(
- muv_texlock_ops.MUV_TexLockIntrStop.bl_idname, icon="IMAGE_COL")
-
-
-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, _):
- self.layout.operator(
- muv_wsuv_ops.MUV_WSUVMeasure.bl_idname, icon="IMAGE_COL")
- self.layout.operator(
- muv_wsuv_ops.MUV_WSUVApply.bl_idname, icon="IMAGE_COL")
-
-
-class MUV_UVWMenu(bpy.types.Menu):
- """
- Menu class: Master menu of UVW
- """
-
- bl_idname = "uv.muv_uvw_menu"
- bl_label = "UVW"
- bl_description = ""
-
- def draw(self, _):
- self.layout.operator(
- muv_uvw_ops.MUV_UVWBoxMap.bl_idname, icon="IMAGE_COL")
- self.layout.operator(
- muv_uvw_ops.MUV_UVWBestPlanerMap.bl_idname, icon="IMAGE_COL")
diff --git a/uv_magic_uv/muv_preferences.py b/uv_magic_uv/muv_preferences.py
deleted file mode 100644
index e14ce99b..00000000
--- a/uv_magic_uv/muv_preferences.py
+++ /dev/null
@@ -1,144 +0,0 @@
-# <pep8-80 compliant>
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-__author__ = "Nutti <nutti.metro@gmail.com>"
-__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
-
-from bpy.props import (
- BoolProperty,
- FloatProperty,
- FloatVectorProperty,
-)
-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="Texture Projection",
- default=True)
- enable_uvbb = BoolProperty(
- 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, _):
- layout = self.layout
-
- 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 are new to 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_props.py b/uv_magic_uv/muv_props.py
deleted file mode 100644
index c0a7d961..00000000
--- a/uv_magic_uv/muv_props.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# <pep8-80 compliant>
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-__author__ = "Nutti <nutti.metro@gmail.com>"
-__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
-
-import bpy
-from bpy.props import (
- FloatProperty,
- EnumProperty,
- BoolProperty,
-)
-
-
-DEBUG = False
-
-
-def get_loaded_texture_name(_, __):
- items = [(key, key, "") for key in bpy.data.images.keys()]
- items.append(("None", "None", ""))
- return items
-
-
-# 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():
- src_uvs = []
- src_pin_uvs = []
- src_seams = []
-
-
-class MUV_CPUVSelSeqProps():
- src_uvs = []
- src_pin_uvs = []
- src_seams = []
-
-
-class MUV_TransUVProps():
- topology_copied = []
-
-
-class MUV_UVBBProps():
- uv_info_ini = []
- ctrl_points_ini = []
- ctrl_points = []
- running = False
-
-
-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_uniform_scaling = BoolProperty(
- name="Uniform Scaling",
- description="Enable Uniform Scaling",
- default=False)
- scene.muv_texproj_tex_magnitude = FloatProperty(
- name="Magnitude",
- description="Texture Magnitude",
- default=0.5,
- min=0.0,
- max=100.0)
- scene.muv_texproj_tex_image = EnumProperty(
- name="Image",
- description="Texture Image",
- items=get_loaded_texture_name)
- scene.muv_texproj_tex_transparency = FloatProperty(
- name="Transparency",
- description="Texture Transparency",
- 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_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/op/__init__.py b/uv_magic_uv/op/__init__.py
new file mode 100644
index 00000000..0b93c96a
--- /dev/null
+++ b/uv_magic_uv/op/__init__.py
@@ -0,0 +1,72 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(align_uv)
+ importlib.reload(align_uv_cursor)
+ importlib.reload(copy_paste_uv)
+ importlib.reload(copy_paste_uv_object)
+ importlib.reload(copy_paste_uv_uvedit)
+ importlib.reload(flip_rotate_uv)
+ importlib.reload(mirror_uv)
+ importlib.reload(move_uv)
+ importlib.reload(pack_uv)
+ importlib.reload(preserve_uv_aspect)
+ importlib.reload(smooth_uv)
+ importlib.reload(texture_lock)
+ importlib.reload(texture_projection)
+ importlib.reload(texture_wrap)
+ importlib.reload(transfer_uv)
+ importlib.reload(unwrap_constraint)
+ importlib.reload(uv_bounding_box)
+ importlib.reload(uv_inspection)
+ importlib.reload(uv_sculpt)
+ importlib.reload(uvw)
+ importlib.reload(world_scale_uv)
+else:
+ from . import align_uv
+ from . import align_uv_cursor
+ from . import copy_paste_uv
+ from . import copy_paste_uv_object
+ from . import copy_paste_uv_uvedit
+ from . import flip_rotate_uv
+ from . import mirror_uv
+ from . import move_uv
+ from . import pack_uv
+ from . import preserve_uv_aspect
+ from . import smooth_uv
+ from . import texture_lock
+ from . import texture_projection
+ from . import texture_wrap
+ from . import transfer_uv
+ from . import unwrap_constraint
+ from . import uv_bounding_box
+ from . import uv_inspection
+ from . import uv_sculpt
+ from . import uvw
+ from . import world_scale_uv
+
+import bpy
diff --git a/uv_magic_uv/op/align_uv.py b/uv_magic_uv/op/align_uv.py
new file mode 100644
index 00000000..f90f02ff
--- /dev/null
+++ b/uv_magic_uv/op/align_uv.py
@@ -0,0 +1,784 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import math
+from math import atan2, tan, sin, cos
+
+import bpy
+import bmesh
+from mathutils import Vector
+from bpy.props import EnumProperty, BoolProperty
+
+from .. import common
+
+
+def get_closed_loop_sequences(bm, uv_layer):
+ sel_faces = [f for f in bm.faces if f.select]
+
+ # get candidate loops
+ cand_loops = []
+ for f in sel_faces:
+ for l in f.loops:
+ if l[uv_layer].select:
+ cand_loops.append(l)
+
+ if len(cand_loops) < 2:
+ return None, "More than 2 UVs must be selected"
+
+ first_loop = cand_loops[0]
+ isl_info = common.get_island_info_from_bmesh(bm, False)
+ loop_pairs = common.get_loop_pairs(first_loop, uv_layer)
+ loop_pairs, err = common.sort_loop_pairs(uv_layer, loop_pairs, True)
+ if not loop_pairs:
+ return None, err
+ loop_seqs, err = common.get_loop_sequence_internal(uv_layer, loop_pairs,
+ isl_info, True)
+ if not loop_seqs:
+ return None, err
+
+ return loop_seqs, ""
+
+
+# get sum vertex length of loop sequences
+def get_loop_vert_len(loops):
+ length = 0
+ for l1, l2 in zip(loops[:-1], loops[1:]):
+ diff = l2.vert.co - l1.vert.co
+ length = length + abs(diff.length)
+
+ return length
+
+
+# get sum uv length of loop sequences
+def get_loop_uv_len(loops, uv_layer):
+ length = 0
+ for l1, l2 in zip(loops[:-1], loops[1:]):
+ diff = l2[uv_layer].uv - l1[uv_layer].uv
+ length = length + abs(diff.length)
+
+ return length
+
+
+# get center/radius of circle by 3 vertices
+def get_circle(v):
+ alpha = atan2((v[0].y - v[1].y), (v[0].x - v[1].x)) + math.pi / 2
+ beta = atan2((v[1].y - v[2].y), (v[1].x - v[2].x)) + math.pi / 2
+ ex = (v[0].x + v[1].x) / 2.0
+ ey = (v[0].y + v[1].y) / 2.0
+ fx = (v[1].x + v[2].x) / 2.0
+ fy = (v[1].y + v[2].y) / 2.0
+ cx = (ey - fy - ex * tan(alpha) + fx * tan(beta)) / \
+ (tan(beta) - tan(alpha))
+ cy = ey - (ex - cx) * tan(alpha)
+ center = Vector((cx, cy))
+
+ r = v[0] - center
+ radian = r.length
+
+ return center, radian
+
+
+# get position on circle with same arc length
+def calc_v_on_circle(v, center, radius):
+ base = v[0]
+ theta = atan2(base.y - center.y, base.x - center.x)
+ new_v = []
+ for i in range(len(v)):
+ angle = theta + i * 2 * math.pi / len(v)
+ new_v.append(Vector((center.x + radius * sin(angle),
+ center.y + radius * cos(angle))))
+
+ return new_v
+
+
+class MUV_AUVCircle(bpy.types.Operator):
+
+ bl_idname = "uv.muv_auv_circle"
+ bl_label = "Circle"
+ bl_description = "Align UV coordinates to Circle"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ transmission = BoolProperty(
+ name="Transmission",
+ description="Align linked UVs",
+ default=False
+ )
+ select = BoolProperty(
+ name="Select",
+ description="Select UVs which are aligned",
+ default=False
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'EDIT_MESH'
+
+ def execute(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ # loop_seqs[horizontal][vertical][loop]
+ loop_seqs, error = get_closed_loop_sequences(bm, uv_layer)
+ if not loop_seqs:
+ self.report({'WARNING'}, error)
+ return {'CANCELLED'}
+
+ # get circle and new UVs
+ uvs = [hseq[0][0][uv_layer].uv.copy() for hseq in loop_seqs]
+ c, r = get_circle(uvs[0:3])
+ new_uvs = calc_v_on_circle(uvs, c, r)
+
+ # check center UV of circle
+ center = loop_seqs[0][-1][0].vert
+ for hseq in loop_seqs[1:]:
+ if len(hseq[-1]) != 1:
+ self.report({'WARNING'}, "Last face must be triangle")
+ return {'CANCELLED'}
+ if hseq[-1][0].vert != center:
+ self.report({'WARNING'}, "Center must be identical")
+ return {'CANCELLED'}
+
+ # align to circle
+ if self.transmission:
+ for hidx, hseq in enumerate(loop_seqs):
+ for vidx, pair in enumerate(hseq):
+ all_ = int((len(hseq) + 1) / 2)
+ r = (all_ - int((vidx + 1) / 2)) / all_
+ pair[0][uv_layer].uv = c + (new_uvs[hidx] - c) * r
+ if self.select:
+ pair[0][uv_layer].select = True
+
+ if len(pair) < 2:
+ continue
+ # for quad polygon
+ next_hidx = (hidx + 1) % len(loop_seqs)
+ pair[1][uv_layer].uv = c + ((new_uvs[next_hidx]) - c) * r
+ if self.select:
+ pair[1][uv_layer].select = True
+ else:
+ for hidx, hseq in enumerate(loop_seqs):
+ pair = hseq[0]
+ pair[0][uv_layer].uv = new_uvs[hidx]
+ pair[1][uv_layer].uv = new_uvs[(hidx + 1) % len(loop_seqs)]
+ if self.select:
+ pair[0][uv_layer].select = True
+ pair[1][uv_layer].select = True
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
+
+
+# get horizontal differential of UV influenced by mesh vertex
+def get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pair_idx):
+ common.debug_print(
+ "vidx={0}, hidx={1}, pair_idx={2}".format(vidx, hidx, pair_idx))
+
+ # get total vertex length
+ hloops = []
+ for s in loop_seqs:
+ hloops.extend([s[vidx][0], s[vidx][1]])
+ vert_total_hlen = get_loop_vert_len(hloops)
+ common.debug_print(vert_total_hlen)
+
+ # target vertex length
+ hloops = []
+ for s in loop_seqs[:hidx]:
+ hloops.extend([s[vidx][0], s[vidx][1]])
+ for pidx, l in enumerate(loop_seqs[hidx][vidx]):
+ if pidx > pair_idx:
+ break
+ hloops.append(l)
+ vert_hlen = get_loop_vert_len(hloops)
+ common.debug_print(vert_hlen)
+
+ # get total UV length
+ # uv_all_hdiff = loop_seqs[-1][0][-1][uv_layer].uv -
+ # loop_seqs[0][0][0][uv_layer].uv
+ uv_total_hlen = loop_seqs[-1][vidx][-1][uv_layer].uv -\
+ loop_seqs[0][vidx][0][uv_layer].uv
+ common.debug_print(uv_total_hlen)
+
+ return uv_total_hlen * vert_hlen / vert_total_hlen
+
+
+# get vertical differential of UV influenced by mesh vertex
+def get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pair_idx):
+ common.debug_print(
+ "vidx={0}, hidx={1}, pair_idx={2}".format(vidx, hidx, pair_idx))
+
+ # get total vertex length
+ hloops = []
+ for s in loop_seqs[hidx]:
+ hloops.append(s[pair_idx])
+ vert_total_hlen = get_loop_vert_len(hloops)
+ common.debug_print(vert_total_hlen)
+
+ # target vertex length
+ hloops = []
+ for s in loop_seqs[hidx][:vidx + 1]:
+ hloops.append(s[pair_idx])
+ vert_hlen = get_loop_vert_len(hloops)
+ common.debug_print(vert_hlen)
+
+ # get total UV length
+ # uv_all_hdiff = loop_seqs[0][-1][pair_idx][uv_layer].uv - \
+ # loop_seqs[0][0][pair_idx][uv_layer].uv
+ uv_total_hlen = loop_seqs[hidx][-1][pair_idx][uv_layer].uv -\
+ loop_seqs[hidx][0][pair_idx][uv_layer].uv
+ common.debug_print(uv_total_hlen)
+
+ return uv_total_hlen * vert_hlen / vert_total_hlen
+
+
+# get horizontal differential of UV no influenced
+def get_hdiff_uv(uv_layer, loop_seqs, hidx):
+ base_uv = loop_seqs[0][0][0][uv_layer].uv.copy()
+ h_uv = loop_seqs[-1][0][1][uv_layer].uv.copy() - base_uv
+
+ return hidx * h_uv / len(loop_seqs)
+
+
+# get vertical differential of UV no influenced
+def get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx):
+ base_uv = loop_seqs[0][0][0][uv_layer].uv.copy()
+ v_uv = loop_seqs[0][-1][0][uv_layer].uv.copy() - base_uv
+
+ hseq = loop_seqs[hidx]
+ return int((vidx + 1) / 2) * v_uv / (len(hseq) / 2)
+
+
+class MUV_AUVStraighten(bpy.types.Operator):
+
+ bl_idname = "uv.muv_auv_straighten"
+ bl_label = "Straighten"
+ bl_description = "Straighten UV coordinates"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ transmission = BoolProperty(
+ name="Transmission",
+ description="Align linked UVs",
+ default=False
+ )
+ select = BoolProperty(
+ name="Select",
+ description="Select UVs which are aligned",
+ default=False
+ )
+ vertical = BoolProperty(
+ name="Vert-Infl (Vertical)",
+ description="Align vertical direction influenced "
+ "by mesh vertex proportion",
+ default=False
+ )
+ horizontal = BoolProperty(
+ name="Vert-Infl (Horizontal)",
+ description="Align horizontal direction influenced "
+ "by mesh vertex proportion",
+ default=False
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'EDIT_MESH'
+
+ # selected and paralleled UV loop sequence will be aligned
+ def __align_w_transmission(self, loop_seqs, uv_layer):
+ base_uv = loop_seqs[0][0][0][uv_layer].uv.copy()
+
+ # calculate diff UVs
+ diff_uvs = []
+ # hseq[vertical][loop]
+ for hidx, hseq in enumerate(loop_seqs):
+ # pair[loop]
+ diffs = []
+ for vidx in range(0, len(hseq), 2):
+ if self.horizontal:
+ hdiff_uvs = [
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 0),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 1),
+ ]
+ else:
+ hdiff_uvs = [
+ get_hdiff_uv(uv_layer, loop_seqs, hidx),
+ get_hdiff_uv(uv_layer, loop_seqs, hidx + 1),
+ get_hdiff_uv(uv_layer, loop_seqs, hidx),
+ get_hdiff_uv(uv_layer, loop_seqs, hidx + 1)
+ ]
+ if self.vertical:
+ vdiff_uvs = [
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 0),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 1),
+ ]
+ else:
+ vdiff_uvs = [
+ get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx),
+ get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx),
+ get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx),
+ get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx)
+ ]
+ diffs.append([hdiff_uvs, vdiff_uvs])
+ diff_uvs.append(diffs)
+
+ # update UV
+ for hseq, diffs in zip(loop_seqs, diff_uvs):
+ for vidx in range(0, len(hseq), 2):
+ loops = [
+ hseq[vidx][0], hseq[vidx][1],
+ hseq[vidx + 1][0], hseq[vidx + 1][1]
+ ]
+ for l, hdiff, vdiff in zip(loops, diffs[int(vidx / 2)][0],
+ diffs[int(vidx / 2)][1]):
+ l[uv_layer].uv = base_uv + hdiff + vdiff
+ if self.select:
+ l[uv_layer].select = True
+
+ # only selected UV loop sequence will be aligned
+ def __align_wo_transmission(self, loop_seqs, uv_layer):
+ base_uv = loop_seqs[0][0][0][uv_layer].uv.copy()
+
+ h_uv = loop_seqs[-1][0][1][uv_layer].uv.copy() - base_uv
+ for hidx, hseq in enumerate(loop_seqs):
+ # only selected loop pair is targeted
+ pair = hseq[0]
+ hdiff_uv_0 = hidx * h_uv / len(loop_seqs)
+ hdiff_uv_1 = (hidx + 1) * h_uv / len(loop_seqs)
+ pair[0][uv_layer].uv = base_uv + hdiff_uv_0
+ pair[1][uv_layer].uv = base_uv + hdiff_uv_1
+ if self.select:
+ pair[0][uv_layer].select = True
+ pair[1][uv_layer].select = True
+
+ def __align(self, loop_seqs, uv_layer):
+ if self.transmission:
+ self.__align_w_transmission(loop_seqs, uv_layer)
+ else:
+ self.__align_wo_transmission(loop_seqs, uv_layer)
+
+ def execute(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ # loop_seqs[horizontal][vertical][loop]
+ loop_seqs, error = common.get_loop_sequences(bm, uv_layer)
+ if not loop_seqs:
+ self.report({'WARNING'}, error)
+ return {'CANCELLED'}
+
+ # align
+ self.__align(loop_seqs, uv_layer)
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
+
+
+class MUV_AUVAxis(bpy.types.Operator):
+
+ bl_idname = "uv.muv_auv_axis"
+ bl_label = "XY-Axis"
+ bl_description = "Align UV to XY-axis"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ transmission = BoolProperty(
+ name="Transmission",
+ description="Align linked UVs",
+ default=False
+ )
+ select = BoolProperty(
+ name="Select",
+ description="Select UVs which are aligned",
+ default=False
+ )
+ vertical = BoolProperty(
+ name="Vert-Infl (Vertical)",
+ description="Align vertical direction influenced "
+ "by mesh vertex proportion",
+ default=False
+ )
+ horizontal = BoolProperty(
+ name="Vert-Infl (Horizontal)",
+ description="Align horizontal direction influenced "
+ "by mesh vertex proportion",
+ default=False
+ )
+ location = EnumProperty(
+ name="Location",
+ description="Align location",
+ items=[
+ ('LEFT_TOP', "Left/Top", "Align to Left or Top"),
+ ('MIDDLE', "Middle", "Align to middle"),
+ ('RIGHT_BOTTOM', "Right/Bottom", "Align to Right or Bottom")
+ ],
+ default='MIDDLE'
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'EDIT_MESH'
+
+ # get min/max of UV
+ def __get_uv_max_min(self, loop_seqs, uv_layer):
+ uv_max = Vector((-1000000.0, -1000000.0))
+ uv_min = Vector((1000000.0, 1000000.0))
+ for hseq in loop_seqs:
+ for l in hseq[0]:
+ uv = l[uv_layer].uv
+ uv_max.x = max(uv.x, uv_max.x)
+ uv_max.y = max(uv.y, uv_max.y)
+ uv_min.x = min(uv.x, uv_min.x)
+ uv_min.y = min(uv.y, uv_min.y)
+
+ return uv_max, uv_min
+
+ # get UV differentiation when UVs are aligned to X-axis
+ def __get_x_axis_align_diff_uvs(self, loop_seqs, uv_layer, uv_min,
+ width, height):
+ diff_uvs = []
+ for hidx, hseq in enumerate(loop_seqs):
+ pair = hseq[0]
+ luv0 = pair[0][uv_layer]
+ luv1 = pair[1][uv_layer]
+ target_uv0 = Vector((0.0, 0.0))
+ target_uv1 = Vector((0.0, 0.0))
+ if self.location == 'RIGHT_BOTTOM':
+ target_uv0.y = target_uv1.y = uv_min.y
+ elif self.location == 'MIDDLE':
+ target_uv0.y = target_uv1.y = uv_min.y + height * 0.5
+ elif self.location == 'LEFT_TOP':
+ target_uv0.y = target_uv1.y = uv_min.y + height
+ if luv0.uv.x < luv1.uv.x:
+ target_uv0.x = uv_min.x + hidx * width / len(loop_seqs)
+ target_uv1.x = uv_min.x + (hidx + 1) * width / len(loop_seqs)
+ else:
+ target_uv0.x = uv_min.x + (hidx + 1) * width / len(loop_seqs)
+ target_uv1.x = uv_min.x + hidx * width / len(loop_seqs)
+ diff_uvs.append([target_uv0 - luv0.uv, target_uv1 - luv1.uv])
+
+ return diff_uvs
+
+ # get UV differentiation when UVs are aligned to Y-axis
+ def __get_y_axis_align_diff_uvs(self, loop_seqs, uv_layer, uv_min,
+ width, height):
+ diff_uvs = []
+ for hidx, hseq in enumerate(loop_seqs):
+ pair = hseq[0]
+ luv0 = pair[0][uv_layer]
+ luv1 = pair[1][uv_layer]
+ target_uv0 = Vector((0.0, 0.0))
+ target_uv1 = Vector((0.0, 0.0))
+ if self.location == 'RIGHT_BOTTOM':
+ target_uv0.x = target_uv1.x = uv_min.x + width
+ elif self.location == 'MIDDLE':
+ target_uv0.x = target_uv1.x = uv_min.x + width * 0.5
+ elif self.location == 'LEFT_TOP':
+ target_uv0.x = target_uv1.x = uv_min.x
+ if luv0.uv.y < luv1.uv.y:
+ target_uv0.y = uv_min.y + hidx * height / len(loop_seqs)
+ target_uv1.y = uv_min.y + (hidx + 1) * height / len(loop_seqs)
+ else:
+ target_uv0.y = uv_min.y + (hidx + 1) * height / len(loop_seqs)
+ target_uv1.y = uv_min.y + hidx * height / len(loop_seqs)
+ diff_uvs.append([target_uv0 - luv0.uv, target_uv1 - luv1.uv])
+
+ return diff_uvs
+
+ # only selected UV loop sequence will be aligned along to X-axis
+ def __align_to_x_axis_wo_transmission(self, loop_seqs, uv_layer,
+ uv_min, width, height):
+ # reverse if the UV coordinate is not sorted by position
+ need_revese = loop_seqs[0][0][0][uv_layer].uv.x > \
+ loop_seqs[-1][0][0][uv_layer].uv.x
+ if need_revese:
+ loop_seqs.reverse()
+ for hidx, hseq in enumerate(loop_seqs):
+ for vidx, pair in enumerate(hseq):
+ tmp = loop_seqs[hidx][vidx][0]
+ loop_seqs[hidx][vidx][0] = loop_seqs[hidx][vidx][1]
+ loop_seqs[hidx][vidx][1] = tmp
+
+ # get UV differential
+ diff_uvs = self.__get_x_axis_align_diff_uvs(loop_seqs, uv_layer,
+ uv_min, width, height)
+
+ # update UV
+ for hseq, duv in zip(loop_seqs, diff_uvs):
+ pair = hseq[0]
+ luv0 = pair[0][uv_layer]
+ luv1 = pair[1][uv_layer]
+ luv0.uv = luv0.uv + duv[0]
+ luv1.uv = luv1.uv + duv[1]
+
+ # only selected UV loop sequence will be aligned along to Y-axis
+ def __align_to_y_axis_wo_transmission(self, loop_seqs, uv_layer,
+ uv_min, width, height):
+ # reverse if the UV coordinate is not sorted by position
+ need_revese = loop_seqs[0][0][0][uv_layer].uv.y > \
+ loop_seqs[-1][0][0][uv_layer].uv.y
+ if need_revese:
+ loop_seqs.reverse()
+ for hidx, hseq in enumerate(loop_seqs):
+ for vidx, pair in enumerate(hseq):
+ tmp = loop_seqs[hidx][vidx][0]
+ loop_seqs[hidx][vidx][0] = loop_seqs[hidx][vidx][1]
+ loop_seqs[hidx][vidx][1] = tmp
+
+ # get UV differential
+ diff_uvs = self.__get_y_axis_align_diff_uvs(loop_seqs, uv_layer,
+ uv_min, width, height)
+
+ # update UV
+ for hseq, duv in zip(loop_seqs, diff_uvs):
+ pair = hseq[0]
+ luv0 = pair[0][uv_layer]
+ luv1 = pair[1][uv_layer]
+ luv0.uv = luv0.uv + duv[0]
+ luv1.uv = luv1.uv + duv[1]
+
+ # selected and paralleled UV loop sequence will be aligned along to X-axis
+ def __align_to_x_axis_w_transmission(self, loop_seqs, uv_layer,
+ uv_min, width, height):
+ # reverse if the UV coordinate is not sorted by position
+ need_revese = loop_seqs[0][0][0][uv_layer].uv.x > \
+ loop_seqs[-1][0][0][uv_layer].uv.x
+ if need_revese:
+ loop_seqs.reverse()
+ for hidx, hseq in enumerate(loop_seqs):
+ for vidx in range(len(hseq)):
+ tmp = loop_seqs[hidx][vidx][0]
+ loop_seqs[hidx][vidx][0] = loop_seqs[hidx][vidx][1]
+ loop_seqs[hidx][vidx][1] = tmp
+
+ # get offset UVs when the UVs are aligned to X-axis
+ align_diff_uvs = self.__get_x_axis_align_diff_uvs(loop_seqs, uv_layer,
+ uv_min, width,
+ height)
+ base_uv = loop_seqs[0][0][0][uv_layer].uv.copy()
+ offset_uvs = []
+ for hseq, aduv in zip(loop_seqs, align_diff_uvs):
+ luv0 = hseq[0][0][uv_layer]
+ luv1 = hseq[0][1][uv_layer]
+ offset_uvs.append([luv0.uv + aduv[0] - base_uv,
+ luv1.uv + aduv[1] - base_uv])
+
+ # get UV differential
+ diff_uvs = []
+ # hseq[vertical][loop]
+ for hidx, hseq in enumerate(loop_seqs):
+ # pair[loop]
+ diffs = []
+ for vidx in range(0, len(hseq), 2):
+ if self.horizontal:
+ hdiff_uvs = [
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 0),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 1),
+ ]
+ hdiff_uvs[0].y = hdiff_uvs[0].y + offset_uvs[hidx][0].y
+ hdiff_uvs[1].y = hdiff_uvs[1].y + offset_uvs[hidx][1].y
+ hdiff_uvs[2].y = hdiff_uvs[2].y + offset_uvs[hidx][0].y
+ hdiff_uvs[3].y = hdiff_uvs[3].y + offset_uvs[hidx][1].y
+ else:
+ hdiff_uvs = [
+ offset_uvs[hidx][0],
+ offset_uvs[hidx][1],
+ offset_uvs[hidx][0],
+ offset_uvs[hidx][1],
+ ]
+ if self.vertical:
+ vdiff_uvs = [
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 0),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 1),
+ ]
+ else:
+ vdiff_uvs = [
+ get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx),
+ get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx),
+ get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx),
+ get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx)
+ ]
+ diffs.append([hdiff_uvs, vdiff_uvs])
+ diff_uvs.append(diffs)
+
+ # update UV
+ for hseq, diffs in zip(loop_seqs, diff_uvs):
+ for vidx in range(0, len(hseq), 2):
+ loops = [
+ hseq[vidx][0], hseq[vidx][1],
+ hseq[vidx + 1][0], hseq[vidx + 1][1]
+ ]
+ for l, hdiff, vdiff in zip(loops, diffs[int(vidx / 2)][0],
+ diffs[int(vidx / 2)][1]):
+ l[uv_layer].uv = base_uv + hdiff + vdiff
+ if self.select:
+ l[uv_layer].select = True
+
+ # selected and paralleled UV loop sequence will be aligned along to Y-axis
+ def __align_to_y_axis_w_transmission(self, loop_seqs, uv_layer,
+ uv_min, width, height):
+ # reverse if the UV coordinate is not sorted by position
+ need_revese = loop_seqs[0][0][0][uv_layer].uv.y > \
+ loop_seqs[-1][0][-1][uv_layer].uv.y
+ if need_revese:
+ loop_seqs.reverse()
+ for hidx, hseq in enumerate(loop_seqs):
+ for vidx in range(len(hseq)):
+ tmp = loop_seqs[hidx][vidx][0]
+ loop_seqs[hidx][vidx][0] = loop_seqs[hidx][vidx][1]
+ loop_seqs[hidx][vidx][1] = tmp
+
+ # get offset UVs when the UVs are aligned to Y-axis
+ align_diff_uvs = self.__get_y_axis_align_diff_uvs(loop_seqs, uv_layer,
+ uv_min, width,
+ height)
+ base_uv = loop_seqs[0][0][0][uv_layer].uv.copy()
+ offset_uvs = []
+ for hseq, aduv in zip(loop_seqs, align_diff_uvs):
+ luv0 = hseq[0][0][uv_layer]
+ luv1 = hseq[0][1][uv_layer]
+ offset_uvs.append([luv0.uv + aduv[0] - base_uv,
+ luv1.uv + aduv[1] - base_uv])
+
+ # get UV differential
+ diff_uvs = []
+ # hseq[vertical][loop]
+ for hidx, hseq in enumerate(loop_seqs):
+ # pair[loop]
+ diffs = []
+ for vidx in range(0, len(hseq), 2):
+ if self.horizontal:
+ hdiff_uvs = [
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 0),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 1),
+ ]
+ hdiff_uvs[0].x = hdiff_uvs[0].x + offset_uvs[hidx][0].x
+ hdiff_uvs[1].x = hdiff_uvs[1].x + offset_uvs[hidx][1].x
+ hdiff_uvs[2].x = hdiff_uvs[2].x + offset_uvs[hidx][0].x
+ hdiff_uvs[3].x = hdiff_uvs[3].x + offset_uvs[hidx][1].x
+ else:
+ hdiff_uvs = [
+ offset_uvs[hidx][0],
+ offset_uvs[hidx][1],
+ offset_uvs[hidx][0],
+ offset_uvs[hidx][1],
+ ]
+ if self.vertical:
+ vdiff_uvs = [
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 0),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
+ hidx, 1),
+ ]
+ else:
+ vdiff_uvs = [
+ get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx),
+ get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx),
+ get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx),
+ get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx)
+ ]
+ diffs.append([hdiff_uvs, vdiff_uvs])
+ diff_uvs.append(diffs)
+
+ # update UV
+ for hseq, diffs in zip(loop_seqs, diff_uvs):
+ for vidx in range(0, len(hseq), 2):
+ loops = [
+ hseq[vidx][0], hseq[vidx][1],
+ hseq[vidx + 1][0], hseq[vidx + 1][1]
+ ]
+ for l, hdiff, vdiff in zip(loops, diffs[int(vidx / 2)][0],
+ diffs[int(vidx / 2)][1]):
+ l[uv_layer].uv = base_uv + hdiff + vdiff
+ if self.select:
+ l[uv_layer].select = True
+
+ def __align(self, loop_seqs, uv_layer, uv_min, width, height):
+ # align along to x-axis
+ if width > height:
+ if self.transmission:
+ self.__align_to_x_axis_w_transmission(loop_seqs, uv_layer,
+ uv_min, width, height)
+ else:
+ self.__align_to_x_axis_wo_transmission(loop_seqs, uv_layer,
+ uv_min, width, height)
+ # align along to y-axis
+ else:
+ if self.transmission:
+ self.__align_to_y_axis_w_transmission(loop_seqs, uv_layer,
+ uv_min, width, height)
+ else:
+ self.__align_to_y_axis_wo_transmission(loop_seqs, uv_layer,
+ uv_min, width, height)
+
+ def execute(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ # loop_seqs[horizontal][vertical][loop]
+ loop_seqs, error = common.get_loop_sequences(bm, uv_layer)
+ if not loop_seqs:
+ self.report({'WARNING'}, error)
+ return {'CANCELLED'}
+
+ # get height and width
+ uv_max, uv_min = self.__get_uv_max_min(loop_seqs, uv_layer)
+ width = uv_max.x - uv_min.x
+ height = uv_max.y - uv_min.y
+
+ self.__align(loop_seqs, uv_layer, uv_min, width, height)
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
diff --git a/uv_magic_uv/op/align_uv_cursor.py b/uv_magic_uv/op/align_uv_cursor.py
new file mode 100644
index 00000000..b33dc68e
--- /dev/null
+++ b/uv_magic_uv/op/align_uv_cursor.py
@@ -0,0 +1,154 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+from mathutils import Vector
+from bpy.props import EnumProperty
+import bmesh
+
+from .. import common
+
+
+class MUV_AUVCAlignOps(bpy.types.Operator):
+
+ bl_idname = "uv.muv_auvc_align"
+ bl_label = "Align"
+ bl_description = "Align cursor to the center of UV island"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ position = EnumProperty(
+ items=(
+ ('CENTER', "Center", "Align to Center"),
+ ('LEFT_TOP', "Left Top", "Align to Left Top"),
+ ('LEFT_MIDDLE', "Left Middle", "Align to Left Middle"),
+ ('LEFT_BOTTOM', "Left Bottom", "Align to Left Bottom"),
+ ('MIDDLE_TOP', "Middle Top", "Align to Middle Top"),
+ ('MIDDLE_BOTTOM', "Middle Bottom", "Align to Middle Bottom"),
+ ('RIGHT_TOP', "Right Top", "Align to Right Top"),
+ ('RIGHT_MIDDLE', "Right Middle", "Align to Right Middle"),
+ ('RIGHT_BOTTOM', "Right Bottom", "Align to Right Bottom")
+ ),
+ name="Position",
+ description="Align position",
+ default='CENTER'
+ )
+ base = EnumProperty(
+ items=(
+ ('TEXTURE', "Texture", "Align based on Texture"),
+ ('UV', "UV", "Align to UV"),
+ ('UV_SEL', "UV (Selected)", "Align to Selected UV")
+ ),
+ name="Base",
+ description="Align base",
+ default='TEXTURE'
+ )
+
+ def execute(self, context):
+ area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
+ 'IMAGE_EDITOR')
+ bd_size = common.get_uvimg_editor_board_size(area)
+
+ if self.base == 'UV':
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if not bm.loops.layers.uv:
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+
+ max_ = Vector((-10000000.0, -10000000.0))
+ min_ = Vector((10000000.0, 10000000.0))
+ for f in bm.faces:
+ if not f.select:
+ continue
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ max_.x = max(max_.x, uv.x)
+ max_.y = max(max_.y, uv.y)
+ min_.x = min(min_.x, uv.x)
+ min_.y = min(min_.y, uv.y)
+ center = Vector(((max_.x + min_.x) / 2.0, (max_.y + min_.y) / 2.0))
+
+ elif self.base == 'UV_SEL':
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if not bm.loops.layers.uv:
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+
+ max_ = Vector((-10000000.0, -10000000.0))
+ min_ = Vector((10000000.0, 10000000.0))
+ for f in bm.faces:
+ if not f.select:
+ continue
+ for l in f.loops:
+ if not l[uv_layer].select:
+ continue
+ uv = l[uv_layer].uv
+ max_.x = max(max_.x, uv.x)
+ max_.y = max(max_.y, uv.y)
+ min_.x = min(min_.x, uv.x)
+ min_.y = min(min_.y, uv.y)
+ center = Vector(((max_.x + min_.x) / 2.0, (max_.y + min_.y) / 2.0))
+
+ elif self.base == 'TEXTURE':
+ min_ = Vector((0.0, 0.0))
+ max_ = Vector((1.0, 1.0))
+ center = Vector((0.5, 0.5))
+ else:
+ self.report({'ERROR'}, "Unknown Operation")
+
+ if self.position == 'CENTER':
+ cx = center.x * bd_size[0]
+ cy = center.y * bd_size[1]
+ elif self.position == 'LEFT_TOP':
+ cx = min_.x * bd_size[0]
+ cy = max_.y * bd_size[1]
+ elif self.position == 'LEFT_MIDDLE':
+ cx = min_.x * bd_size[0]
+ cy = center.y * bd_size[1]
+ elif self.position == 'LEFT_BOTTOM':
+ cx = min_.x * bd_size[0]
+ cy = min_.y * bd_size[1]
+ elif self.position == 'MIDDLE_TOP':
+ cx = center.x * bd_size[0]
+ cy = max_.y * bd_size[1]
+ elif self.position == 'MIDDLE_BOTTOM':
+ cx = center.x * bd_size[0]
+ cy = min_.y * bd_size[1]
+ elif self.position == 'RIGHT_TOP':
+ cx = max_.x * bd_size[0]
+ cy = max_.y * bd_size[1]
+ elif self.position == 'RIGHT_MIDDLE':
+ cx = max_.x * bd_size[0]
+ cy = center.y * bd_size[1]
+ elif self.position == 'RIGHT_BOTTOM':
+ cx = max_.x * bd_size[0]
+ cy = min_.y * bd_size[1]
+ else:
+ self.report({'ERROR'}, "Unknown Operation")
+
+ space.cursor_location = Vector((cx, cy))
+
+ return {'FINISHED'}
diff --git a/uv_magic_uv/muv_cpuv_ops.py b/uv_magic_uv/op/copy_paste_uv.py
index 82f043c6..3aac3f6d 100644
--- a/uv_magic_uv/muv_cpuv_ops.py
+++ b/uv_magic_uv/op/copy_paste_uv.py
@@ -18,10 +18,13 @@
#
# ##### END GPL LICENSE BLOCK #####
-__author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester"
+__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import math
+from math import atan2, sin, cos
import bpy
import bmesh
@@ -31,16 +34,9 @@ from bpy.props import (
IntProperty,
EnumProperty,
)
-from . import muv_common
-
+from mathutils import Vector
-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
+from .. import common
class MUV_CPUVCopyUV(bpy.types.Operator):
@@ -64,7 +60,7 @@ class MUV_CPUVCopyUV(bpy.types.Operator):
{'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:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
# get UV layer
@@ -174,7 +170,7 @@ class MUV_CPUVPasteUV(bpy.types.Operator):
{'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:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
# get UV layer
@@ -273,46 +269,158 @@ class MUV_CPUVPasteUVMenu(bpy.types.Menu):
bl_description = "Paste UV coordinate"
def draw(self, context):
+ sc = context.scene
layout = self.layout
# create sub menu
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
uv_maps = bm.loops.layers.uv.keys()
- layout.operator(
- MUV_CPUVPasteUV.bl_idname,
- text="[Default]", icon="IMAGE_COL").uv_map = ""
+ ops = layout.operator(MUV_CPUVPasteUV.bl_idname, text="[Default]")
+ ops.uv_map = ""
+ ops.copy_seams = sc.muv_cpuv_copy_seams
+ ops.strategy = sc.muv_cpuv_strategy
for m in uv_maps:
- layout.operator(
- MUV_CPUVPasteUV.bl_idname,
- text=m, icon="IMAGE_COL").uv_map = m
+ ops = layout.operator(MUV_CPUVPasteUV.bl_idname, text=m)
+ ops.uv_map = m
+ ops.copy_seams = sc.muv_cpuv_copy_seams
+ ops.strategy = sc.muv_cpuv_strategy
-class MUV_CPUVObjCopyUV(bpy.types.Operator):
+class MUV_CPUVIECopyUV(bpy.types.Operator):
"""
- Operation class: Copy UV coordinate per object
+ Operation class: Copy UV coordinate on UV/Image Editor
"""
- bl_idname = "object.muv_cpuv_obj_copy_uv"
+ bl_idname = "uv.muv_cpuv_ie_copy_uv"
bl_label = "Copy UV"
- bl_description = "Copy UV coordinate"
+ bl_description = "Copy UV coordinate (only selected in UV/Image Editor)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'EDIT_MESH'
+
+ def execute(self, context):
+ props = context.scene.muv_props.cpuv
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ for face in bm.faces:
+ if not face.select:
+ continue
+ skip = False
+ for l in face.loops:
+ if not l[uv_layer].select:
+ skip = True
+ break
+ if skip:
+ continue
+ props.src_uvs.append([l[uv_layer].uv.copy() for l in face.loops])
+
+ return {'FINISHED'}
+
+
+class MUV_CPUVIEPasteUV(bpy.types.Operator):
+ """
+ Operation class: Paste UV coordinate on UV/Image Editor
+ """
+
+ bl_idname = "uv.muv_cpuv_ie_paste_uv"
+ bl_label = "Paste UV"
+ bl_description = "Paste UV coordinate (only selected in UV/Image Editor)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'EDIT_MESH'
+
+ def execute(self, context):
+ props = context.scene.muv_props.cpuv
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ dest_uvs = []
+ dest_face_indices = []
+ for face in bm.faces:
+ if not face.select:
+ continue
+ skip = False
+ for l in face.loops:
+ if not l[uv_layer].select:
+ skip = True
+ break
+ if skip:
+ continue
+ dest_face_indices.append(face.index)
+ uvs = [l[uv_layer].uv.copy() for l in face.loops]
+ dest_uvs.append(uvs)
+
+ for suvs, duvs in zip(props.src_uvs, dest_uvs):
+ src_diff = suvs[1] - suvs[0]
+ dest_diff = duvs[1] - duvs[0]
+
+ src_base = suvs[0]
+ dest_base = duvs[0]
+
+ src_rad = atan2(src_diff.y, src_diff.x)
+ dest_rad = atan2(dest_diff.y, dest_diff.x)
+ if src_rad < dest_rad:
+ radian = dest_rad - src_rad
+ elif src_rad > dest_rad:
+ radian = math.pi * 2 - (src_rad - dest_rad)
+ else: # src_rad == dest_rad
+ radian = 0.0
+
+ ratio = dest_diff.length / src_diff.length
+ break
+
+ for suvs, fidx in zip(props.src_uvs, dest_face_indices):
+ for l, suv in zip(bm.faces[fidx].loops, suvs):
+ base = suv - src_base
+ radian_ref = atan2(base.y, base.x)
+ radian_fin = (radian + radian_ref)
+ length = base.length
+ turn = Vector((length * cos(radian_fin),
+ length * sin(radian_fin)))
+ target_uv = Vector((turn.x * ratio, turn.y * ratio)) + \
+ dest_base
+ l[uv_layer].uv = target_uv
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
+
+
+class MUV_CPUVSelSeqCopyUV(bpy.types.Operator):
+ """
+ Operation class: Copy UV coordinate by selection sequence
+ """
+
+ bl_idname = "uv.muv_cpuv_selseq_copy_uv"
+ bl_label = "Copy UV (Selection Sequence) (Operation)"
+ bl_description = "Copy UV data by selection sequence (Operation)"
bl_options = {'REGISTER', 'UNDO'}
uv_map = StringProperty(options={'HIDDEN'})
- @memorize_view_3d_mode
def execute(self, context):
- props = context.scene.muv_props.cpuv_obj
+ props = context.scene.muv_props.cpuv_selseq
if self.uv_map == "":
- self.report({'INFO'}, "Copy UV coordinate per object")
+ self.report({'INFO'}, "Copy UV coordinate (selection sequence)")
else:
self.report(
{'INFO'},
- "Copy UV coordinate per object (UV map:%s)" % (self.uv_map))
- bpy.ops.object.mode_set(mode='EDIT')
-
+ "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:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
# get UV layer
@@ -329,171 +437,210 @@ class MUV_CPUVObjCopyUV(bpy.types.Operator):
props.src_uvs = []
props.src_pin_uvs = []
props.src_seams = []
- 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]
- seams = [l.edge.seam for l in face.loops]
- props.src_uvs.append(uvs)
- props.src_pin_uvs.append(pin_uvs)
- props.src_seams.append(seams)
-
- self.report({'INFO'}, "%s's UV coordinates are copied" % (obj.name))
+ for hist in bm.select_history:
+ if isinstance(hist, bmesh.types.BMFace) and hist.select:
+ uvs = [l[uv_layer].uv.copy() for l in hist.loops]
+ pin_uvs = [l[uv_layer].pin_uv for l in hist.loops]
+ seams = [l.edge.seam for l in hist.loops]
+ props.src_uvs.append(uvs)
+ props.src_pin_uvs.append(pin_uvs)
+ props.src_seams.append(seams)
+ if not props.src_uvs or not props.src_pin_uvs:
+ self.report({'WARNING'}, "No faces are selected")
+ return {'CANCELLED'}
+ self.report({'INFO'}, "%d face(s) are selected" % len(props.src_uvs))
return {'FINISHED'}
-class MUV_CPUVObjCopyUVMenu(bpy.types.Menu):
+class MUV_CPUVSelSeqCopyUVMenu(bpy.types.Menu):
"""
- Menu class: Copy UV coordinate per object
+ Menu class: Copy UV coordinate by selection sequence
"""
- bl_idname = "object.muv_cpuv_obj_copy_uv_menu"
- bl_label = "Copy UV"
- bl_description = "Copy UV coordinate per object"
+ bl_idname = "uv.muv_cpuv_selseq_copy_uv_menu"
+ bl_label = "Copy UV (Selection Sequence)"
+ bl_description = "Copy UV coordinate by selection sequence"
- def draw(self, _):
+ def draw(self, context):
layout = self.layout
- # create sub menu
- uv_maps = bpy.context.active_object.data.uv_textures.keys()
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_maps = bm.loops.layers.uv.keys()
layout.operator(
- MUV_CPUVObjCopyUV.bl_idname,
+ MUV_CPUVSelSeqCopyUV.bl_idname,
text="[Default]", icon="IMAGE_COL").uv_map = ""
for m in uv_maps:
layout.operator(
- MUV_CPUVObjCopyUV.bl_idname,
+ MUV_CPUVSelSeqCopyUV.bl_idname,
text=m, icon="IMAGE_COL").uv_map = m
-class MUV_CPUVObjPasteUV(bpy.types.Operator):
+class MUV_CPUVSelSeqPasteUV(bpy.types.Operator):
"""
- Operation class: Paste UV coordinate per object
+ Operation class: Paste UV coordinate by selection sequence
"""
- bl_idname = "object.muv_cpuv_obj_paste_uv"
- bl_label = "Paste UV"
- bl_description = "Paste UV coordinate"
+ bl_idname = "uv.muv_cpuv_selseq_paste_uv"
+ bl_label = "Paste UV (Selection Sequence) (Operation)"
+ bl_description = "Paste UV coordinate by selection sequence (Operation)"
bl_options = {'REGISTER', 'UNDO'}
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')
+ ],
+ default="N_M"
+ )
+ flip_copied_uv = BoolProperty(
+ name="Flip Copied UV",
+ description="Flip Copied UV...",
+ default=False
+ )
+ rotate_copied_uv = IntProperty(
+ default=0,
+ name="Rotate Copied UV",
+ min=0,
+ max=30
+ )
copy_seams = BoolProperty(
name="Copy Seams",
description="Copy Seams",
default=True
)
- @memorize_view_3d_mode
def execute(self, context):
- props = context.scene.muv_props.cpuv_obj
+ props = context.scene.muv_props.cpuv_selseq
if not props.src_uvs or not props.src_pin_uvs:
self.report({'WARNING'}, "Need copy UV at first")
return {'CANCELLED'}
+ if self.uv_map == "":
+ self.report({'INFO'}, "Paste UV coordinate (selection sequence)")
+ else:
+ self.report(
+ {'INFO'},
+ "Paste UV coordinate (selection sequence) (UV map:%s)"
+ % (self.uv_map))
- 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()
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
- if (self.uv_map == "" or
- self.uv_map not in bm.loops.layers.uv.keys()):
- self.report({'INFO'}, "Paste UV coordinate per object")
- else:
+ # get UV layer
+ if self.uv_map == "":
+ if not bm.loops.layers.uv:
self.report(
- {'INFO'},
- "Paste UV coordinate per object (UV map: %s)"
- % (self.uv_map))
-
- # get UV layer
- if (self.uv_map == "" or
- self.uv_map not 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_seams = []
- 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]
- seams = [l.edge.seam for l in face.loops]
+ {'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_seams = []
+ dest_face_indices = []
+ for hist in bm.select_history:
+ if isinstance(hist, bmesh.types.BMFace) and hist.select:
+ dest_face_indices.append(hist.index)
+ uvs = [l[uv_layer].uv.copy() for l in hist.loops]
+ pin_uvs = [l[uv_layer].pin_uv for l in hist.loops]
+ seams = [l.edge.seam for l in hist.loops]
dest_uvs.append(uvs)
dest_pin_uvs.append(pin_uvs)
dest_seams.append(seams)
- 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'}
+ if not dest_uvs or not dest_pin_uvs:
+ self.report({'WARNING'}, "No faces are selected")
+ return {'CANCELLED'}
+ 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)))
+ return {'CANCELLED'}
- # paste
- for i, idx in enumerate(dest_face_indices):
+ # paste
+ for i, idx in enumerate(dest_face_indices):
+ suv = None
+ spuv = None
+ ss = None
+ duv = None
+ if self.strategy == 'N_N':
suv = props.src_uvs[i]
spuv = props.src_pin_uvs[i]
ss = props.src_seams[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]
- ss_fr = [s for s in ss]
- # paste UVs
- for l, suv, spuv, ss in zip(
- bm.faces[idx].loops, suvs_fr, spuvs_fr, ss_fr):
- l[uv_layer].uv = suv
- l[uv_layer].pin_uv = spuv
- if self.copy_seams is True:
- l.edge.seam = ss
-
- bmesh.update_edit_mesh(obj.data)
- if self.copy_seams is True:
- obj.data.show_edge_seams = True
+ elif self.strategy == 'N_M':
+ suv = props.src_uvs[i % len(props.src_uvs)]
+ spuv = props.src_pin_uvs[i % len(props.src_pin_uvs)]
+ ss = props.src_seams[i % len(props.src_seams)]
+ 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]
+ ss_fr = [s for s in ss]
+ # flip UVs
+ if self.flip_copied_uv is True:
+ suvs_fr.reverse()
+ spuvs_fr.reverse()
+ ss_fr.reverse()
+ # rotate UVs
+ for _ in range(self.rotate_copied_uv):
+ uv = suvs_fr.pop()
+ pin_uv = spuvs_fr.pop()
+ s = ss_fr.pop()
+ suvs_fr.insert(0, uv)
+ spuvs_fr.insert(0, pin_uv)
+ ss_fr.insert(0, s)
+ # paste UVs
+ for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr,
+ spuvs_fr, ss_fr):
+ l[uv_layer].uv = suv
+ l[uv_layer].pin_uv = spuv
+ if self.copy_seams is True:
+ l.edge.seam = ss
- self.report(
- {'INFO'}, "%s's UV coordinates are pasted" % (obj.name))
+ self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs))
+
+ bmesh.update_edit_mesh(obj.data)
+ if self.copy_seams is True:
+ obj.data.show_edge_seams = True
return {'FINISHED'}
-class MUV_CPUVObjPasteUVMenu(bpy.types.Menu):
+class MUV_CPUVSelSeqPasteUVMenu(bpy.types.Menu):
"""
- Menu class: Paste UV coordinate per object
+ Menu class: Paste UV coordinate by selection sequence
"""
- bl_idname = "object.muv_cpuv_obj_paste_uv_menu"
- bl_label = "Paste UV"
- bl_description = "Paste UV coordinate per object"
+ bl_idname = "uv.muv_cpuv_selseq_paste_uv_menu"
+ bl_label = "Paste UV (Selection Sequence)"
+ bl_description = "Paste UV coordinate by selection sequence"
- def draw(self, _):
+ def draw(self, context):
+ sc = context.scene
layout = self.layout
# create sub menu
- uv_maps = []
- for obj in bpy.data.objects:
- if hasattr(obj.data, "uv_textures") and obj.select:
- uv_maps.extend(obj.data.uv_textures.keys())
- uv_maps = list(set(uv_maps))
- layout.operator(
- MUV_CPUVObjPasteUV.bl_idname,
- text="[Default]", icon="IMAGE_COL").uv_map = ""
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_maps = bm.loops.layers.uv.keys()
+ ops = layout.operator(MUV_CPUVSelSeqPasteUV.bl_idname,
+ text="[Default]")
+ ops.uv_map = ""
+ ops.copy_seams = sc.muv_cpuv_copy_seams
+ ops.strategy = sc.muv_cpuv_strategy
for m in uv_maps:
- layout.operator(
- MUV_CPUVObjPasteUV.bl_idname,
- text=m, icon="IMAGE_COL").uv_map = m
+ ops = layout.operator(MUV_CPUVSelSeqPasteUV.bl_idname, text=m)
+ ops.uv_map = m
+ ops.copy_seams = sc.muv_cpuv_copy_seams
+ ops.strategy = sc.muv_cpuv_strategy
diff --git a/uv_magic_uv/op/copy_paste_uv_object.py b/uv_magic_uv/op/copy_paste_uv_object.py
new file mode 100644
index 00000000..eb42d99a
--- /dev/null
+++ b/uv_magic_uv/op/copy_paste_uv_object.py
@@ -0,0 +1,252 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+import bmesh
+from bpy.props import (
+ StringProperty,
+ BoolProperty,
+)
+
+from .. import 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_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 = 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:%s)" % (self.uv_map))
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ # get UV layer
+ 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 = []
+ props.src_seams = []
+ 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]
+ seams = [l.edge.seam for l in face.loops]
+ props.src_uvs.append(uvs)
+ props.src_pin_uvs.append(pin_uvs)
+ props.src_seams.append(seams)
+
+ 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, _):
+ layout = self.layout
+ # create sub menu
+ uv_maps = bpy.context.active_object.data.uv_textures.keys()
+ layout.operator(MUV_CPUVObjCopyUV.bl_idname, text="[Default]")\
+ .uv_map = ""
+ for m in uv_maps:
+ layout.operator(MUV_CPUVObjCopyUV.bl_idname, text=m).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 = StringProperty(options={'HIDDEN'})
+ copy_seams = BoolProperty(
+ name="Copy Seams",
+ description="Copy Seams",
+ default=True
+ )
+
+ @memorize_view_3d_mode
+ def execute(self, context):
+ props = context.scene.muv_props.cpuv_obj
+ if not props.src_uvs or not props.src_pin_uvs:
+ self.report({'WARNING'}, "Need copy UV at first")
+ return {'CANCELLED'}
+
+ for o in bpy.data.objects:
+ if not hasattr(o.data, "uv_textures") or not o.select:
+ continue
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.context.scene.objects.active = o
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ if (self.uv_map == "" or
+ self.uv_map not 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
+ self.uv_map not 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_seams = []
+ 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]
+ seams = [l.edge.seam for l in face.loops]
+ dest_uvs.append(uvs)
+ dest_pin_uvs.append(pin_uvs)
+ dest_seams.append(seams)
+ 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]
+ ss = props.src_seams[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]
+ ss_fr = [s for s in ss]
+ # paste UVs
+ for l, suv, spuv, ss in zip(
+ bm.faces[idx].loops, suvs_fr, spuvs_fr, ss_fr):
+ l[uv_layer].uv = suv
+ l[uv_layer].pin_uv = spuv
+ if self.copy_seams is True:
+ l.edge.seam = ss
+
+ bmesh.update_edit_mesh(obj.data)
+ if self.copy_seams is True:
+ obj.data.show_edge_seams = True
+
+ 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):
+ sc = context.scene
+ layout = self.layout
+ # create sub menu
+ uv_maps = []
+ for obj in bpy.data.objects:
+ if hasattr(obj.data, "uv_textures") and obj.select:
+ uv_maps.extend(obj.data.uv_textures.keys())
+ uv_maps = list(set(uv_maps))
+ ops = layout.operator(MUV_CPUVObjPasteUV.bl_idname, text="[Default]")
+ ops.uv_map = ""
+ ops.copy_seams = sc.muv_cpuv_copy_seams
+ for m in uv_maps:
+ ops = layout.operator(MUV_CPUVObjPasteUV.bl_idname, text=m)
+ ops.uv_map = m
+ ops.copy_seams = sc.muv_cpuv_copy_seams
diff --git a/uv_magic_uv/op/copy_paste_uv_uvedit.py b/uv_magic_uv/op/copy_paste_uv_uvedit.py
new file mode 100644
index 00000000..5b64505e
--- /dev/null
+++ b/uv_magic_uv/op/copy_paste_uv_uvedit.py
@@ -0,0 +1,144 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import math
+from math import atan2, sin, cos
+
+import bpy
+import bmesh
+from mathutils import Vector
+
+from .. import common
+
+
+class MUV_CPUVIECopyUV(bpy.types.Operator):
+ """
+ Operation class: Copy UV coordinate on UV/Image Editor
+ """
+
+ bl_idname = "uv.muv_cpuv_ie_copy_uv"
+ bl_label = "Copy UV"
+ bl_description = "Copy UV coordinate (only selected in UV/Image Editor)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'EDIT_MESH'
+
+ def execute(self, context):
+ props = context.scene.muv_props.cpuv
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ for face in bm.faces:
+ if not face.select:
+ continue
+ skip = False
+ for l in face.loops:
+ if not l[uv_layer].select:
+ skip = True
+ break
+ if skip:
+ continue
+ props.src_uvs.append([l[uv_layer].uv.copy() for l in face.loops])
+
+ return {'FINISHED'}
+
+
+class MUV_CPUVIEPasteUV(bpy.types.Operator):
+ """
+ Operation class: Paste UV coordinate on UV/Image Editor
+ """
+
+ bl_idname = "uv.muv_cpuv_ie_paste_uv"
+ bl_label = "Paste UV"
+ bl_description = "Paste UV coordinate (only selected in UV/Image Editor)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'EDIT_MESH'
+
+ def execute(self, context):
+ props = context.scene.muv_props.cpuv
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ dest_uvs = []
+ dest_face_indices = []
+ for face in bm.faces:
+ if not face.select:
+ continue
+ skip = False
+ for l in face.loops:
+ if not l[uv_layer].select:
+ skip = True
+ break
+ if skip:
+ continue
+ dest_face_indices.append(face.index)
+ uvs = [l[uv_layer].uv.copy() for l in face.loops]
+ dest_uvs.append(uvs)
+
+ for suvs, duvs in zip(props.src_uvs, dest_uvs):
+ src_diff = suvs[1] - suvs[0]
+ dest_diff = duvs[1] - duvs[0]
+
+ src_base = suvs[0]
+ dest_base = duvs[0]
+
+ src_rad = atan2(src_diff.y, src_diff.x)
+ dest_rad = atan2(dest_diff.y, dest_diff.x)
+ if src_rad < dest_rad:
+ radian = dest_rad - src_rad
+ elif src_rad > dest_rad:
+ radian = math.pi * 2 - (src_rad - dest_rad)
+ else: # src_rad == dest_rad
+ radian = 0.0
+
+ ratio = dest_diff.length / src_diff.length
+ break
+
+ for suvs, fidx in zip(props.src_uvs, dest_face_indices):
+ for l, suv in zip(bm.faces[fidx].loops, suvs):
+ base = suv - src_base
+ radian_ref = atan2(base.y, base.x)
+ radian_fin = (radian + radian_ref)
+ length = base.length
+ turn = Vector((length * cos(radian_fin),
+ length * sin(radian_fin)))
+ target_uv = Vector((turn.x * ratio, turn.y * ratio)) + \
+ dest_base
+ l[uv_layer].uv = target_uv
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
diff --git a/uv_magic_uv/muv_fliprot_ops.py b/uv_magic_uv/op/flip_rotate_uv.py
index 334eb14c..907c77c4 100644
--- a/uv_magic_uv/muv_fliprot_ops.py
+++ b/uv_magic_uv/op/flip_rotate_uv.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
import bpy
import bmesh
@@ -29,7 +29,8 @@ from bpy.props import (
BoolProperty,
IntProperty,
)
-from . import muv_common
+
+from .. import common
class MUV_FlipRot(bpy.types.Operator):
@@ -63,7 +64,7 @@ class MUV_FlipRot(bpy.types.Operator):
self.report({'INFO'}, "Flip/Rotate UV")
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
# get UV layer
diff --git a/uv_magic_uv/muv_mirroruv_ops.py b/uv_magic_uv/op/mirror_uv.py
index 63eb9bd5..d1014c73 100644
--- a/uv_magic_uv/muv_mirroruv_ops.py
+++ b/uv_magic_uv/op/mirror_uv.py
@@ -20,8 +20,8 @@
__author__ = "Keith (Wahooney) Boshoff, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
import bpy
from bpy.props import (
@@ -30,7 +30,8 @@ from bpy.props import (
)
import bmesh
from mathutils import Vector
-from . import muv_common
+
+from .. import common
class MUV_MirrorUV(bpy.types.Operator):
@@ -113,7 +114,7 @@ class MUV_MirrorUV(bpy.types.Operator):
error = self.error
axis = self.axis
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
if not bm.loops.layers.uv:
self.report({'WARNING'}, "Object must have more than one UV map")
diff --git a/uv_magic_uv/muv_mvuv_ops.py b/uv_magic_uv/op/move_uv.py
index 28346270..e0ac418f 100644
--- a/uv_magic_uv/muv_mvuv_ops.py
+++ b/uv_magic_uv/op/move_uv.py
@@ -20,8 +20,8 @@
__author__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
import bpy
import bmesh
@@ -64,6 +64,7 @@ class MUV_MVUV(bpy.types.Operator):
return context.edit_object
def modal(self, context, event):
+ props = context.scene.muv_props.mvuv
if self.__first_time is True:
self.__prev_mouse = Vector((
event.mouse_region_x, event.mouse_region_y))
@@ -84,7 +85,7 @@ class MUV_MVUV(bpy.types.Operator):
event.mouse_region_x, event.mouse_region_y))
# check if operation is started
- if self.__running is True:
+ if self.__running:
if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
self.__running = False
return {'RUNNING_MODAL'}
@@ -110,16 +111,20 @@ class MUV_MVUV(bpy.types.Operator):
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
+ props.running = False
return {'FINISHED'}
# confirmed
if event.type == confirm_btn and event.value == 'PRESS':
+ props.running = False
return {'FINISHED'}
return {'RUNNING_MODAL'}
def execute(self, context):
- self.__first_time = True
+ props = context.scene.muv_props.mvuv
+ props.running = True
self.__running = True
+ self.__first_time = True
context.window_manager.modal_handler_add(self)
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/op/pack_uv.py
index f663e662..692fa93e 100644
--- a/uv_magic_uv/muv_packuv_ops.py
+++ b/uv_magic_uv/op/pack_uv.py
@@ -20,11 +20,10 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
from math import fabs
-from collections import defaultdict
import bpy
import bmesh
@@ -36,7 +35,7 @@ from bpy.props import (
)
from mathutils import Vector
-from . import muv_common
+from .. import common
class MUV_PackUV(bpy.types.Operator):
@@ -69,23 +68,21 @@ class MUV_PackUV(bpy.types.Operator):
min=0.000001,
max=0.1,
default=(0.001, 0.001),
- size=2)
+ 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)
+ size=2
+ )
- def __init__(self):
- self.__face_to_verts = defaultdict(set)
- self.__vert_to_faces = defaultdict(set)
-
- def execute(self, _):
- obj = bpy.context.active_object
+ def execute(self, context):
+ obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
if not bm.loops.layers.uv:
self.report({'WARNING'}, "Object must have more than one UV map")
@@ -93,17 +90,7 @@ class MUV_PackUV(bpy.types.Operator):
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:
- id_ = l[uv_layer].uv.to_tuple(5), l.vert.index
- self.__face_to_verts[f.index].add(id_)
- self.__vert_to_faces[id_].add(f.index)
-
- # Group island
- uv_island_lists = self.__get_island(bm)
- island_info = self.__get_island_info(uv_layer, uv_island_lists)
+ island_info = common.get_island_info(obj)
num_group = self.__group_island(island_info)
loop_lists = [l for f in bm.faces for l in f.loops]
@@ -183,13 +170,17 @@ class MUV_PackUV(bpy.types.Operator):
dsx = isl_2['size'].x - isl_1['size'].x
dsy = isl_2['size'].y - isl_1['size'].y
center_x_matched = (
- fabs(dcx) < self.allowable_center_deviation[0])
+ fabs(dcx) < self.allowable_center_deviation[0]
+ )
center_y_matched = (
- fabs(dcy) < self.allowable_center_deviation[1])
+ fabs(dcy) < self.allowable_center_deviation[1]
+ )
size_x_matched = (
- fabs(dsx) < self.allowable_size_deviation[0])
+ fabs(dsx) < self.allowable_size_deviation[0]
+ )
size_y_matched = (
- fabs(dsy) < self.allowable_size_deviation[1])
+ 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'])
@@ -214,75 +205,3 @@ class MUV_PackUV(bpy.types.Operator):
num_group = num_group + 1
return num_group
-
- def __get_island_info(self, uv_layer, islands):
- """
- get information about each island
- """
-
- island_info = []
- for isl in islands:
- info = {}
- max_uv = Vector((-10000000.0, -10000000.0))
- min_uv = Vector((10000000.0, 10000000.0))
- ave_uv = Vector((0.0, 0.0))
- num_uv = 0
- for face in isl:
- n = 0
- a = Vector((0.0, 0.0))
- for l in face['face'].loops:
- uv = l[uv_layer].uv
- if uv.x > max_uv.x:
- max_uv.x = uv.x
- if uv.y > max_uv.y:
- max_uv.y = uv.y
- if uv.x < min_uv.x:
- min_uv.x = uv.x
- if uv.y < min_uv.y:
- min_uv.y = uv.y
- a = a + uv
- n = n + 1
- ave_uv = ave_uv + a
- num_uv = num_uv + n
- a = a / n
- face['ave_uv'] = a
- ave_uv = ave_uv / num_uv
-
- info['center'] = ave_uv
- info['size'] = max_uv - min_uv
- info['num_uv'] = num_uv
- info['group'] = -1
- info['faces'] = isl
-
- island_info.append(info)
-
- return island_info
-
- def __parse_island(self, bm, face_idx, faces_left, island):
- """
- Parse island
- """
-
- if face_idx in faces_left:
- faces_left.remove(face_idx)
- island.append({'face': bm.faces[face_idx]})
- for v in self.__face_to_verts[face_idx]:
- connected_faces = self.__vert_to_faces[v]
- if connected_faces:
- for cf in connected_faces:
- self.__parse_island(bm, cf, faces_left, island)
-
- def __get_island(self, bm):
- """
- Get island list
- """
-
- uv_island_lists = []
- faces_left = set(self.__face_to_verts.keys())
- while faces_left:
- current_island = []
- face_idx = list(faces_left)[0]
- self.__parse_island(bm, face_idx, faces_left, current_island)
- uv_island_lists.append(current_island)
-
- return uv_island_lists
diff --git a/uv_magic_uv/muv_preserve_uv_aspect.py b/uv_magic_uv/op/preserve_uv_aspect.py
index 68e75f74..9838aec6 100644
--- a/uv_magic_uv/muv_preserve_uv_aspect.py
+++ b/uv_magic_uv/op/preserve_uv_aspect.py
@@ -20,14 +20,15 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
import bpy
import bmesh
from bpy.props import StringProperty, EnumProperty
from mathutils import Vector
-from . import muv_common
+
+from .. import common
class MUV_PreserveUVAspect(bpy.types.Operator):
@@ -71,7 +72,7 @@ class MUV_PreserveUVAspect(bpy.types.Operator):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
if not bm.loops.layers.uv:
@@ -202,22 +203,3 @@ class MUV_PreserveUVAspect(bpy.types.Operator):
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, _):
- layout = self.layout
-
- # create sub menu
- for key in bpy.data.images.keys():
- layout.operator(
- MUV_PreserveUVAspect.bl_idname,
- text=key, icon="IMAGE_COL").dest_img_name = key
diff --git a/uv_magic_uv/op/smooth_uv.py b/uv_magic_uv/op/smooth_uv.py
new file mode 100644
index 00000000..6a120d08
--- /dev/null
+++ b/uv_magic_uv/op/smooth_uv.py
@@ -0,0 +1,215 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+import bmesh
+from bpy.props import BoolProperty, FloatProperty
+
+from .. import common
+
+
+class MUV_AUVSmooth(bpy.types.Operator):
+
+ bl_idname = "uv.muv_auv_smooth"
+ bl_label = "Smooth"
+ bl_description = "Smooth UV coordinates"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ transmission = BoolProperty(
+ name="Transmission",
+ description="Smooth linked UVs",
+ default=False
+ )
+ mesh_infl = FloatProperty(
+ name="Mesh Influence",
+ description="Influence rate of mesh vertex",
+ min=0.0,
+ max=1.0,
+ default=0.0
+ )
+ select = BoolProperty(
+ name="Select",
+ description="Select UVs which are smoothed",
+ default=False
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'EDIT_MESH'
+
+ def __smooth_wo_transmission(self, loop_seqs, uv_layer):
+ # calculate path length
+ loops = []
+ for hseq in loop_seqs:
+ loops.extend([hseq[0][0], hseq[0][1]])
+ full_vlen = 0
+ accm_vlens = [0.0]
+ full_uvlen = 0
+ accm_uvlens = [0.0]
+ orig_uvs = [loop_seqs[0][0][0][uv_layer].uv.copy()]
+ for l1, l2 in zip(loops[:-1], loops[1:]):
+ diff_v = l2.vert.co - l1.vert.co
+ full_vlen = full_vlen + diff_v.length
+ accm_vlens.append(full_vlen)
+ diff_uv = l2[uv_layer].uv - l1[uv_layer].uv
+ full_uvlen = full_uvlen + diff_uv.length
+ accm_uvlens.append(full_uvlen)
+ orig_uvs.append(l2[uv_layer].uv.copy())
+
+ for hidx, hseq in enumerate(loop_seqs):
+ pair = hseq[0]
+ for pidx, l in enumerate(pair):
+ if self.select:
+ l[uv_layer].select = True
+
+ # ignore start/end loop
+ if (hidx == 0 and pidx == 0) or\
+ ((hidx == len(loop_seqs) - 1) and (pidx == len(pair) - 1)):
+ continue
+
+ # calculate target path length
+ # target = no influenced * (1 - infl) + influenced * infl
+ tgt_noinfl = full_uvlen * (hidx + pidx) / (len(loop_seqs))
+ tgt_infl = full_uvlen * accm_vlens[hidx * 2 + pidx] / full_vlen
+ target_length = tgt_noinfl * (1 - self.mesh_infl) + \
+ tgt_infl * self.mesh_infl
+
+ # get target UV
+ for i in range(len(accm_uvlens[:-1])):
+ # get line segment which UV will be placed
+ if ((accm_uvlens[i] <= target_length) and
+ (accm_uvlens[i + 1] > target_length)):
+ tgt_seg_len = target_length - accm_uvlens[i]
+ seg_len = accm_uvlens[i + 1] - accm_uvlens[i]
+ uv1 = orig_uvs[i]
+ uv2 = orig_uvs[i + 1]
+ target_uv = uv1 + (uv2 - uv1) * tgt_seg_len / seg_len
+ break
+ else:
+ self.report({'ERROR'}, "Failed to get target UV")
+ return {'CANCELLED'}
+
+ # update UV
+ l[uv_layer].uv = target_uv
+
+ def __smooth_w_transmission(self, loop_seqs, uv_layer):
+ # calculate path length
+ loops = []
+ for vidx in range(len(loop_seqs[0])):
+ ls = []
+ for hseq in loop_seqs:
+ ls.extend(hseq[vidx])
+ loops.append(ls)
+
+ orig_uvs = []
+ accm_vlens = []
+ full_vlens = []
+ accm_uvlens = []
+ full_uvlens = []
+ for ls in loops:
+ full_v = 0.0
+ accm_v = [0.0]
+ full_uv = 0.0
+ accm_uv = [0.0]
+ uvs = [ls[0][uv_layer].uv.copy()]
+ for l1, l2 in zip(ls[:-1], ls[1:]):
+ diff_v = l2.vert.co - l1.vert.co
+ full_v = full_v + diff_v.length
+ accm_v.append(full_v)
+ diff_uv = l2[uv_layer].uv - l1[uv_layer].uv
+ full_uv = full_uv + diff_uv.length
+ accm_uv.append(full_uv)
+ uvs.append(l2[uv_layer].uv.copy())
+ accm_vlens.append(accm_v)
+ full_vlens.append(full_v)
+ accm_uvlens.append(accm_uv)
+ full_uvlens.append(full_uv)
+ orig_uvs.append(uvs)
+
+ for hidx, hseq in enumerate(loop_seqs):
+ for vidx, (pair, uvs, accm_v, full_v, accm_uv, full_uv)\
+ in enumerate(zip(hseq, orig_uvs, accm_vlens, full_vlens,
+ accm_uvlens, full_uvlens)):
+ for pidx, l in enumerate(pair):
+ if self.select:
+ l[uv_layer].select = True
+
+ # ignore start/end loop
+ if hidx == 0 and pidx == 0:
+ continue
+ if hidx == len(loop_seqs) - 1 and pidx == len(pair) - 1:
+ continue
+
+ # calculate target path length
+ # target = no influenced * (1 - infl) + influenced * infl
+ tgt_noinfl = full_uv * (hidx + pidx) / (len(loop_seqs))
+ tgt_infl = full_uv * accm_v[hidx * 2 + pidx] / full_v
+ target_length = tgt_noinfl * (1 - self.mesh_infl) + \
+ tgt_infl * self.mesh_infl
+
+ # get target UV
+ for i in range(len(accm_uv[:-1])):
+ # get line segment to be placed
+ if ((accm_uv[i] <= target_length) and
+ (accm_uv[i + 1] > target_length)):
+ tgt_seg_len = target_length - accm_uv[i]
+ seg_len = accm_uv[i + 1] - accm_uv[i]
+ uv1 = uvs[i]
+ uv2 = uvs[i + 1]
+ target_uv = uv1 +\
+ (uv2 - uv1) * tgt_seg_len / seg_len
+ break
+ else:
+ self.report({'ERROR'}, "Failed to get target UV")
+ return {'CANCELLED'}
+
+ # update UV
+ l[uv_layer].uv = target_uv
+
+ def __smooth(self, loop_seqs, uv_layer):
+ if self.transmission:
+ self.__smooth_w_transmission(loop_seqs, uv_layer)
+ else:
+ self.__smooth_wo_transmission(loop_seqs, uv_layer)
+
+ def execute(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ # loop_seqs[horizontal][vertical][loop]
+ loop_seqs, error = common.get_loop_sequences(bm, uv_layer)
+ if not loop_seqs:
+ self.report({'WARNING'}, error)
+ return {'CANCELLED'}
+
+ # smooth
+ self.__smooth(loop_seqs, uv_layer)
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
diff --git a/uv_magic_uv/muv_texlock_ops.py b/uv_magic_uv/op/texture_lock.py
index bfc95129..b0be3534 100644
--- a/uv_magic_uv/muv_texlock_ops.py
+++ b/uv_magic_uv/op/texture_lock.py
@@ -20,20 +20,18 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
import math
-from math import (
- atan2, cos,
- sqrt, sin, fabs,
-)
+from math import atan2, cos, sqrt, sin, fabs
import bpy
import bmesh
from mathutils import Vector
from bpy.props import BoolProperty
-from . import muv_common
+
+from .. import common
def get_vco(verts_orig, loop):
@@ -195,7 +193,7 @@ class MUV_TexLockStart(bpy.types.Operator):
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:
+ if common.check_version(2, 73, 0) >= 0:
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
@@ -224,13 +222,15 @@ class MUV_TexLockStop(bpy.types.Operator):
connect = BoolProperty(
name="Connect UV",
- default=True)
+ default=True
+ )
def execute(self, context):
- props = context.scene.muv_props.texlock
+ sc = context.scene
+ props = sc.muv_props.texlock
obj = bpy.context.active_object
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
@@ -297,14 +297,14 @@ class MUV_TexLockUpdater(bpy.types.Operator):
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:
+ if 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'}
+ return
uv_layer = bm.loops.layers.uv.verify()
verts = [v.index for v in bm.verts if v.select]
@@ -313,7 +313,7 @@ class MUV_TexLockUpdater(bpy.types.Operator):
for vidx, v_orig in zip(verts, verts_orig):
if vidx != v_orig["vidx"]:
self.report({'ERROR'}, "Internal Error")
- return {"CANCELLED"}
+ return
v = bm.verts[vidx]
link_loops = get_link_loops(v)
@@ -336,7 +336,7 @@ class MUV_TexLockUpdater(bpy.types.Operator):
v_orig["moved"] = True
bmesh.update_edit_mesh(obj.data)
- muv_common.redraw_all_areas()
+ 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]
@@ -395,7 +395,7 @@ class MUV_TexLockIntrStart(bpy.types.Operator):
obj = bpy.context.active_object
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
diff --git a/uv_magic_uv/muv_texproj_ops.py b/uv_magic_uv/op/texture_projection.py
index ffa4e789..9c2dc521 100644
--- a/uv_magic_uv/muv_texproj_ops.py
+++ b/uv_magic_uv/op/texture_projection.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
from collections import namedtuple
@@ -31,7 +31,7 @@ import bmesh
import mathutils
from bpy_extras import view3d_utils
-from . import muv_common
+from .. import common
Rect = namedtuple('Rect', 'x0 y0 x1 y1')
@@ -237,28 +237,28 @@ class MUV_TexProjProject(bpy.types.Operator):
def execute(self, context):
sc = context.scene
- if context.mode != "EDIT_MESH":
- self.report({'WARNING'}, "Mesh must be in Edit mode")
- return {'CANCELLED'}
-
if sc.muv_texproj_tex_image == "None":
self.report({'WARNING'}, "No textures are selected")
return {'CANCELLED'}
- _, region, space = muv_common.get_space(
+ _, region, space = common.get_space(
'VIEW_3D', 'WINDOW', 'VIEW_3D')
# get faces to be texture projected
obj = context.active_object
world_mat = obj.matrix_world
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
# get UV and texture layer
if not bm.loops.layers.uv:
- self.report({'WARNING'}, "Object must have more than one UV map")
- return {'CANCELLED'}
+ if sc.muv_texproj_assign_uvmap:
+ bm.loops.layers.uv.new()
+ else:
+ self.report({'WARNING'},
+ "Object must have more than one UV map")
+ return {'CANCELLED'}
uv_layer = bm.loops.layers.uv.verify()
tex_layer = bm.faces.layers.tex.verify()
@@ -290,50 +290,7 @@ class MUV_TexProjProject(bpy.types.Operator):
l[uv_layer].uv = v_canvas[i].to_2d()
i = i + 1
- muv_common.redraw_all_areas()
+ common.redraw_all_areas()
bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
-
-
-class OBJECT_PT_TP(bpy.types.Panel):
- """
- Panel class: Texture Projection Menu on Property Panel on View3D
- """
-
- bl_label = "Texture Projection"
- bl_description = "Texture Projection Menu"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_context = 'mesh_edit'
-
- @classmethod
- def poll(cls, context):
- prefs = context.user_preferences.addons["uv_magic_uv"].preferences
- return prefs.enable_texproj
-
- def draw_header(self, _):
- layout = self.layout
- layout.label(text="", icon='IMAGE_COL')
-
- def draw(self, context):
- sc = context.scene
- layout = self.layout
- props = sc.muv_props.texproj
- if props.running is False:
- layout.operator(
- MUV_TexProjStart.bl_idname, text="Start", icon='PLAY')
- else:
- 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/op/texture_wrap.py b/uv_magic_uv/op/texture_wrap.py
new file mode 100644
index 00000000..91b06704
--- /dev/null
+++ b/uv_magic_uv/op/texture_wrap.py
@@ -0,0 +1,212 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+import bmesh
+
+from .. import common
+
+
+class MUV_TexWrapRefer(bpy.types.Operator):
+ """
+ Operation class: Refer UV
+ """
+
+ bl_idname = "uv.muv_texwrap_refer"
+ bl_label = "Refer"
+ bl_description = "Refer UV"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ props = context.scene.muv_props.texwrap
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ if not bm.loops.layers.uv:
+ self.report({'WARNING'}, "Object must have more than one UV map")
+ return {'CANCELLED'}
+
+ sel_faces = [f for f in bm.faces if f.select]
+ if len(sel_faces) != 1:
+ self.report({'WARNING'}, "Must select only one face")
+ return {'CANCELLED'}
+
+ props.ref_face_index = sel_faces[0].index
+ props.ref_obj = obj
+
+ return {'FINISHED'}
+
+
+class MUV_TexWrapSet(bpy.types.Operator):
+ """
+ Operation class: Set UV
+ """
+
+ bl_idname = "uv.muv_texwrap_set"
+ bl_label = "Set"
+ bl_description = "Set UV"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ sc = context.scene
+ props = sc.muv_props.texwrap
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ if not bm.loops.layers.uv:
+ self.report({'WARNING'}, "Object must have more than one UV map")
+ return {'CANCELLED'}
+ uv_layer = bm.loops.layers.uv.verify()
+
+ if sc.muv_texwrap_selseq:
+ sel_faces = []
+ for hist in bm.select_history:
+ if isinstance(hist, bmesh.types.BMFace) and hist.select:
+ sel_faces.append(hist)
+ if not sel_faces:
+ self.report({'WARNING'}, "Must select more than one face")
+ return {'CANCELLED'}
+ else:
+ sel_faces = [f for f in bm.faces if f.select]
+ if len(sel_faces) != 1:
+ self.report({'WARNING'}, "Must select only one face")
+ return {'CANCELLED'}
+
+ ref_face_index = props.ref_face_index
+ for face in sel_faces:
+ tgt_face_index = face.index
+ if ref_face_index == tgt_face_index:
+ self.report({'WARNING'}, "Must select different face")
+ return {'CANCELLED'}
+
+ if props.ref_obj != obj:
+ self.report({'WARNING'}, "Object must be same")
+ return {'CANCELLED'}
+
+ ref_face = bm.faces[ref_face_index]
+ tgt_face = bm.faces[tgt_face_index]
+
+ # get common vertices info
+ common_verts = []
+ for sl in ref_face.loops:
+ for dl in tgt_face.loops:
+ if sl.vert == dl.vert:
+ info = {"vert": sl.vert, "ref_loop": sl,
+ "tgt_loop": dl}
+ common_verts.append(info)
+ break
+
+ if len(common_verts) != 2:
+ self.report({'WARNING'},
+ "2 verticies must be shared among faces")
+ return {'CANCELLED'}
+
+ # get reference other vertices info
+ ref_other_verts = []
+ for sl in ref_face.loops:
+ for ci in common_verts:
+ if sl.vert == ci["vert"]:
+ break
+ else:
+ info = {"vert": sl.vert, "loop": sl}
+ ref_other_verts.append(info)
+
+ if not ref_other_verts:
+ self.report({'WARNING'}, "More than 1 vertex must be unshared")
+ return {'CANCELLED'}
+
+ # get reference info
+ ref_info = {}
+ cv0 = common_verts[0]["vert"].co
+ cv1 = common_verts[1]["vert"].co
+ cuv0 = common_verts[0]["ref_loop"][uv_layer].uv
+ cuv1 = common_verts[1]["ref_loop"][uv_layer].uv
+ ov0 = ref_other_verts[0]["vert"].co
+ ouv0 = ref_other_verts[0]["loop"][uv_layer].uv
+ ref_info["vert_vdiff"] = cv1 - cv0
+ ref_info["uv_vdiff"] = cuv1 - cuv0
+ ref_info["vert_hdiff"], _ = common.diff_point_to_segment(
+ cv0, cv1, ov0)
+ ref_info["uv_hdiff"], _ = common.diff_point_to_segment(
+ cuv0, cuv1, ouv0)
+
+ # get target other vertices info
+ tgt_other_verts = []
+ for dl in tgt_face.loops:
+ for ci in common_verts:
+ if dl.vert == ci["vert"]:
+ break
+ else:
+ info = {"vert": dl.vert, "loop": dl}
+ tgt_other_verts.append(info)
+
+ if not tgt_other_verts:
+ self.report({'WARNING'}, "More than 1 vertex must be unshared")
+ return {'CANCELLED'}
+
+ # get target info
+ for info in tgt_other_verts:
+ cv0 = common_verts[0]["vert"].co
+ cv1 = common_verts[1]["vert"].co
+ cuv0 = common_verts[0]["ref_loop"][uv_layer].uv
+ ov = info["vert"].co
+ info["vert_hdiff"], x = common.diff_point_to_segment(
+ cv0, cv1, ov)
+ info["vert_vdiff"] = x - common_verts[0]["vert"].co
+
+ # calclulate factor
+ fact_h = -info["vert_hdiff"].length / \
+ ref_info["vert_hdiff"].length
+ fact_v = info["vert_vdiff"].length / \
+ ref_info["vert_vdiff"].length
+ duv_h = ref_info["uv_hdiff"] * fact_h
+ duv_v = ref_info["uv_vdiff"] * fact_v
+
+ # get target UV
+ info["target_uv"] = cuv0 + duv_h + duv_v
+
+ # apply to common UVs
+ for info in common_verts:
+ info["tgt_loop"][uv_layer].uv = \
+ info["ref_loop"][uv_layer].uv.copy()
+ # apply to other UVs
+ for info in tgt_other_verts:
+ info["loop"][uv_layer].uv = info["target_uv"]
+
+ common.debug_print("===== Target Other Verticies =====")
+ common.debug_print(tgt_other_verts)
+
+ bmesh.update_edit_mesh(obj.data)
+
+ ref_face_index = tgt_face_index
+
+ if sc.muv_texwrap_set_and_refer:
+ props.ref_face_index = tgt_face_index
+
+ return {'FINISHED'}
diff --git a/uv_magic_uv/muv_transuv_ops.py b/uv_magic_uv/op/transfer_uv.py
index ed0a3c46..fd1b45e4 100644
--- a/uv_magic_uv/muv_transuv_ops.py
+++ b/uv_magic_uv/op/transfer_uv.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>, Mifth, MaxRobinot"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
from collections import OrderedDict
@@ -29,8 +29,7 @@ import bpy
import bmesh
from bpy.props import BoolProperty
-from . import muv_props
-from . import muv_common
+from .. import common
class MUV_TransUVCopy(bpy.types.Operator):
@@ -48,7 +47,7 @@ class MUV_TransUVCopy(bpy.types.Operator):
props = context.scene.muv_props.transuv
active_obj = context.scene.objects.active
bm = bmesh.from_edit_mesh(active_obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
# get UV layer
@@ -115,7 +114,7 @@ class MUV_TransUVPaste(bpy.types.Operator):
props = context.scene.muv_props.transuv
active_obj = context.scene.objects.active
bm = bmesh.from_edit_mesh(active_obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
# get UV layer
@@ -291,19 +290,19 @@ def parse_faces(
vert1 = sorted_edge.verts[0]
vert2 = sorted_edge.verts[1]
- muv_common.debug_print(face_stuff[0], vert1, vert2)
+ common.debug_print(face_stuff[0], vert1, vert2)
if face_stuff[0].index(vert1) > face_stuff[0].index(vert2):
vert1 = sorted_edge.verts[1]
vert2 = sorted_edge.verts[0]
- muv_common.debug_print(shared_face.verts, vert1, vert2)
+ common.debug_print(shared_face.verts, vert1, vert2)
new_face_stuff = get_other_verts_edges(
shared_face, vert1, vert2, sorted_edge, uv_layer)
all_sorted_faces[shared_face] = new_face_stuff
used_verts.update(shared_face.verts)
used_edges.update(shared_face.edges)
- if muv_props.DEBUG:
+ if common.DEBUG:
shared_face.select = True # test which faces are parsed
new_shared_faces.append(shared_face)
diff --git a/uv_magic_uv/muv_unwrapconst_ops.py b/uv_magic_uv/op/unwrap_constraint.py
index 1a691119..311b2c35 100644
--- a/uv_magic_uv/muv_unwrapconst_ops.py
+++ b/uv_magic_uv/op/unwrap_constraint.py
@@ -18,8 +18,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
import bpy
import bmesh
@@ -28,7 +28,8 @@ from bpy.props import (
EnumProperty,
FloatProperty,
)
-from . import muv_common
+
+from .. import common
class MUV_UnwrapConstraint(bpy.types.Operator):
@@ -74,18 +75,21 @@ class MUV_UnwrapConstraint(bpy.types.Operator):
u_const = BoolProperty(
name="U-Constraint",
description="Keep UV U-axis coordinate",
- default=False)
+ default=False
+ )
v_const = BoolProperty(
name="V-Constraint",
description="Keep UV V-axis coordinate",
- default=False)
+ default=False
+ )
def execute(self, _):
obj = bpy.context.active_object
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
+ # bpy.ops.uv.unwrap() makes one UV map at least
if not bm.loops.layers.uv:
self.report({'WARNING'}, "Object must have more than one UV map")
return {'CANCELLED'}
diff --git a/uv_magic_uv/muv_uvbb_ops.py b/uv_magic_uv/op/uv_bounding_box.py
index 4f7b0631..04aa6110 100644
--- a/uv_magic_uv/muv_uvbb_ops.py
+++ b/uv_magic_uv/op/uv_bounding_box.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
from enum import IntEnum
import math
@@ -31,7 +31,7 @@ import bgl
import mathutils
import bmesh
-from . import muv_common
+from .. import common
MAX_VALUE = 100000.0
@@ -602,17 +602,23 @@ class MUV_UVBBUpdater(bpy.types.Operator):
"""
Get UV coordinate
"""
+ sc = context.scene
obj = context.active_object
uv_info = []
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
if not bm.loops.layers.uv:
return None
uv_layer = bm.loops.layers.uv.verify()
for f in bm.faces:
- if f.select:
- for i, l in enumerate(f.loops):
+ if not f.select:
+ continue
+ for i, l in enumerate(f.loops):
+ if sc.muv_uvbb_boundary == 'UV_SEL':
+ if l[uv_layer].select:
+ uv_info.append((f.index, i, l[uv_layer].uv.copy()))
+ elif sc.muv_uvbb_boundary == 'UV':
uv_info.append((f.index, i, l[uv_layer].uv.copy()))
if not uv_info:
return None
@@ -661,7 +667,7 @@ class MUV_UVBBUpdater(bpy.types.Operator):
"""
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
if not bm.loops.layers.uv:
return
@@ -683,7 +689,7 @@ class MUV_UVBBUpdater(bpy.types.Operator):
def modal(self, context, event):
props = context.scene.muv_props.uvbb
- muv_common.redraw_all_areas()
+ common.redraw_all_areas()
if props.running is False:
self.__handle_remove(context)
return {'FINISHED'}
@@ -717,37 +723,3 @@ class MUV_UVBBUpdater(bpy.types.Operator):
props.running = True
return {'RUNNING_MODAL'}
-
-
-class IMAGE_PT_MUV_UVBB(bpy.types.Panel):
- """
- Panel class: UV Bounding Box Menu on Property Panel on UV/ImageEditor
- """
-
- bl_space_type = 'IMAGE_EDITOR'
- bl_region_type = 'UI'
- bl_label = "UV Bounding Box"
- bl_context = 'mesh_edit'
-
- @classmethod
- def poll(cls, context):
- prefs = context.user_preferences.addons["uv_magic_uv"].preferences
- return prefs.enable_uvbb
-
- def draw_header(self, _):
- layout = self.layout
- layout.label(text="", icon='IMAGE_COL')
-
- def draw(self, context):
- sc = context.scene
- props = sc.muv_props.uvbb
- layout = self.layout
- if props.running is False:
- layout.operator(
- MUV_UVBBUpdater.bl_idname, text="Display UV Bounding Box",
- icon='PLAY')
- else:
- layout.operator(
- MUV_UVBBUpdater.bl_idname, text="Hide UV Bounding Box",
- icon='PAUSE')
- layout.prop(sc, "muv_uvbb_uniform_scaling", text="Uniform Scaling")
diff --git a/uv_magic_uv/op/uv_inspection.py b/uv_magic_uv/op/uv_inspection.py
new file mode 100644
index 00000000..0e8778f3
--- /dev/null
+++ b/uv_magic_uv/op/uv_inspection.py
@@ -0,0 +1,623 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+import bmesh
+import bgl
+from mathutils import Vector
+
+from .. import common
+
+
+def is_polygon_same(points1, points2):
+ if len(points1) != len(points2):
+ return False
+
+ pts1 = points1.as_list()
+ pts2 = points2.as_list()
+
+ for p1 in pts1:
+ for p2 in pts2:
+ diff = p2 - p1
+ if diff.length < 0.0000001:
+ pts2.remove(p2)
+ break
+ else:
+ return False
+
+ return True
+
+
+def is_segment_intersect(start1, end1, start2, end2):
+ seg1 = end1 - start1
+ seg2 = end2 - start2
+
+ a1 = -seg1.y
+ b1 = seg1.x
+ d1 = -(a1 * start1.x + b1 * start1.y)
+
+ a2 = -seg2.y
+ b2 = seg2.x
+ d2 = -(a2 * start2.x + b2 * start2.y)
+
+ seg1_line2_start = a2 * start1.x + b2 * start1.y + d2
+ seg1_line2_end = a2 * end1.x + b2 * end1.y + d2
+
+ seg2_line1_start = a1 * start2.x + b1 * start2.y + d1
+ seg2_line1_end = a1 * end2.x + b1 * end2.y + d1
+
+ if (seg1_line2_start * seg1_line2_end >= 0) or \
+ (seg2_line1_start * seg2_line1_end >= 0):
+ return False, None
+
+ u = seg1_line2_start / (seg1_line2_start - seg1_line2_end)
+ out = start1 + u * seg1
+
+ return True, out
+
+
+class RingBuffer:
+ def __init__(self, arr):
+ self.__buffer = arr.copy()
+ self.__pointer = 0
+
+ def __repr__(self):
+ return repr(self.__buffer)
+
+ def __len__(self):
+ return len(self.__buffer)
+
+ def insert(self, val, offset=0):
+ self.__buffer.insert(self.__pointer + offset, val)
+
+ def head(self):
+ return self.__buffer[0]
+
+ def tail(self):
+ return self.__buffer[-1]
+
+ def get(self, offset=0):
+ size = len(self.__buffer)
+ val = self.__buffer[(self.__pointer + offset) % size]
+ return val
+
+ def next(self):
+ size = len(self.__buffer)
+ self.__pointer = (self.__pointer + 1) % size
+
+ def reset(self):
+ self.__pointer = 0
+
+ def find(self, obj):
+ try:
+ idx = self.__buffer.index(obj)
+ except ValueError:
+ return None
+ return self.__buffer[idx]
+
+ def find_and_next(self, obj):
+ size = len(self.__buffer)
+ idx = self.__buffer.index(obj)
+ self.__pointer = (idx + 1) % size
+
+ def find_and_set(self, obj):
+ idx = self.__buffer.index(obj)
+ self.__pointer = idx
+
+ def as_list(self):
+ return self.__buffer.copy()
+
+ def reverse(self):
+ self.__buffer.reverse()
+ self.reset()
+
+
+# clip: reference polygon
+# subject: tested polygon
+def do_weiler_atherton_cliping(clip, subject, uv_layer, mode):
+
+ clip_uvs = RingBuffer([l[uv_layer].uv.copy() for l in clip.loops])
+ if is_polygon_flipped(clip_uvs):
+ clip_uvs.reverse()
+ subject_uvs = RingBuffer([l[uv_layer].uv.copy() for l in subject.loops])
+ if is_polygon_flipped(subject_uvs):
+ subject_uvs.reverse()
+
+ common.debug_print("===== Clip UV List =====")
+ common.debug_print(clip_uvs)
+ common.debug_print("===== Subject UV List =====")
+ common.debug_print(subject_uvs)
+
+ # check if clip and subject is overlapped completely
+ if is_polygon_same(clip_uvs, subject_uvs):
+ polygons = [subject_uvs.as_list()]
+ common.debug_print("===== Polygons Overlapped Completely =====")
+ common.debug_print(polygons)
+ return True, polygons
+
+ # check if subject is in clip
+ if is_points_in_polygon(subject_uvs, clip_uvs):
+ polygons = [subject_uvs.as_list()]
+ return True, polygons
+
+ # check if clip is in subject
+ if is_points_in_polygon(clip_uvs, subject_uvs):
+ polygons = [subject_uvs.as_list()]
+ return True, polygons
+
+ # check if clip and subject is overlapped partially
+ intersections = []
+ while True:
+ subject_uvs.reset()
+ while True:
+ uv_start1 = clip_uvs.get()
+ uv_end1 = clip_uvs.get(1)
+ uv_start2 = subject_uvs.get()
+ uv_end2 = subject_uvs.get(1)
+ intersected, point = is_segment_intersect(uv_start1, uv_end1,
+ uv_start2, uv_end2)
+ if intersected:
+ clip_uvs.insert(point, 1)
+ subject_uvs.insert(point, 1)
+ intersections.append([point,
+ [clip_uvs.get(), clip_uvs.get(1)]])
+ subject_uvs.next()
+ if subject_uvs.get() == subject_uvs.head():
+ break
+ clip_uvs.next()
+ if clip_uvs.get() == clip_uvs.head():
+ break
+
+ common.debug_print("===== Intersection List =====")
+ common.debug_print(intersections)
+
+ # no intersection, so subject and clip is not overlapped
+ if not intersections:
+ return False, None
+
+ def get_intersection_pair(intersections, key):
+ for sect in intersections:
+ if sect[0] == key:
+ return sect[1]
+
+ return None
+
+ # make enter/exit pair
+ subject_uvs.reset()
+ subject_entering = []
+ subject_exiting = []
+ clip_entering = []
+ clip_exiting = []
+ intersect_uv_list = []
+ while True:
+ pair = get_intersection_pair(intersections, subject_uvs.get())
+ if pair:
+ sub = subject_uvs.get(1) - subject_uvs.get(-1)
+ inter = pair[1] - pair[0]
+ cross = sub.x * inter.y - inter.x * sub.y
+ if cross < 0:
+ subject_entering.append(subject_uvs.get())
+ clip_exiting.append(subject_uvs.get())
+ else:
+ subject_exiting.append(subject_uvs.get())
+ clip_entering.append(subject_uvs.get())
+ intersect_uv_list.append(subject_uvs.get())
+
+ subject_uvs.next()
+ if subject_uvs.get() == subject_uvs.head():
+ break
+
+ common.debug_print("===== Enter List =====")
+ common.debug_print(clip_entering)
+ common.debug_print(subject_entering)
+ common.debug_print("===== Exit List =====")
+ common.debug_print(clip_exiting)
+ common.debug_print(subject_exiting)
+
+ # for now, can't handle the situation when fulfill all below conditions
+ # * two faces have common edge
+ # * each face is intersected
+ # * Show Mode is "Part"
+ # so for now, ignore this situation
+ if len(subject_entering) != len(subject_exiting):
+ if mode == 'FACE':
+ polygons = [subject_uvs.as_list()]
+ return True, polygons
+ return False, None
+
+ def traverse(current_list, entering, exiting, poly, current, other_list):
+ result = current_list.find(current)
+ if not result:
+ return None
+ if result != current:
+ print("Internal Error")
+ return None
+
+ # enter
+ if entering.count(current) >= 1:
+ entering.remove(current)
+
+ current_list.find_and_next(current)
+ current = current_list.get()
+
+ while exiting.count(current) == 0:
+ poly.append(current.copy())
+ current_list.find_and_next(current)
+ current = current_list.get()
+
+ # exit
+ poly.append(current.copy())
+ exiting.remove(current)
+
+ other_list.find_and_set(current)
+ return other_list.get()
+
+ # Traverse
+ polygons = []
+ current_uv_list = subject_uvs
+ other_uv_list = clip_uvs
+ current_entering = subject_entering
+ current_exiting = subject_exiting
+
+ poly = []
+ current_uv = current_entering[0]
+
+ while True:
+ current_uv = traverse(current_uv_list, current_entering,
+ current_exiting, poly, current_uv, other_uv_list)
+
+ if current_uv_list == subject_uvs:
+ current_uv_list = clip_uvs
+ other_uv_list = subject_uvs
+ current_entering = clip_entering
+ current_exiting = clip_exiting
+ common.debug_print("-- Next: Clip --")
+ else:
+ current_uv_list = subject_uvs
+ other_uv_list = clip_uvs
+ current_entering = subject_entering
+ current_exiting = subject_exiting
+ common.debug_print("-- Next: Subject --")
+
+ common.debug_print(clip_entering)
+ common.debug_print(clip_exiting)
+ common.debug_print(subject_entering)
+ common.debug_print(subject_exiting)
+
+ if not clip_entering and not clip_exiting \
+ and not subject_entering and not subject_exiting:
+ break
+
+ polygons.append(poly)
+
+ common.debug_print("===== Polygons Overlapped Partially =====")
+ common.debug_print(polygons)
+
+ return True, polygons
+
+
+class MUV_UVInspRenderer(bpy.types.Operator):
+ """
+ Operation class: Render UV Inspection
+ No operation (only rendering)
+ """
+
+ bl_idname = "uv.muv_uvinsp_renderer"
+ bl_description = "Render overlapped/flipped UVs"
+ bl_label = "Overlapped/Flipped UV renderer"
+
+ __handle = None
+
+ @staticmethod
+ def handle_add(obj, context):
+ sie = bpy.types.SpaceImageEditor
+ MUV_UVInspRenderer.__handle = sie.draw_handler_add(
+ MUV_UVInspRenderer.draw, (obj, context), 'WINDOW', 'POST_PIXEL')
+
+ @staticmethod
+ def handle_remove():
+ if MUV_UVInspRenderer.__handle is not None:
+ bpy.types.SpaceImageEditor.draw_handler_remove(
+ MUV_UVInspRenderer.__handle, 'WINDOW')
+ MUV_UVInspRenderer.__handle = None
+
+ @staticmethod
+ def draw(_, context):
+ sc = context.scene
+ props = sc.muv_props.uvinsp
+ prefs = context.user_preferences.addons["uv_magic_uv"].preferences
+
+ # OpenGL configuration
+ bgl.glEnable(bgl.GL_BLEND)
+
+ # render overlapped UV
+ if sc.muv_uvinsp_show_overlapped:
+ color = prefs.uvinsp_overlapped_color
+ for info in props.overlapped_info:
+ if sc.muv_uvinsp_show_mode == 'PART':
+ for poly in info["polygons"]:
+ bgl.glBegin(bgl.GL_TRIANGLE_FAN)
+ bgl.glColor4f(color[0], color[1], color[2], color[3])
+ for uv in poly:
+ x, y = context.region.view2d.view_to_region(
+ uv.x, uv.y)
+ bgl.glVertex2f(x, y)
+ bgl.glEnd()
+ elif sc.muv_uvinsp_show_mode == 'FACE':
+ bgl.glBegin(bgl.GL_TRIANGLE_FAN)
+ bgl.glColor4f(color[0], color[1], color[2], color[3])
+ for uv in info["subject_uvs"]:
+ x, y = context.region.view2d.view_to_region(uv.x, uv.y)
+ bgl.glVertex2f(x, y)
+ bgl.glEnd()
+
+ # render flipped UV
+ if sc.muv_uvinsp_show_flipped:
+ color = prefs.uvinsp_flipped_color
+ for info in props.flipped_info:
+ if sc.muv_uvinsp_show_mode == 'PART':
+ for poly in info["polygons"]:
+ bgl.glBegin(bgl.GL_TRIANGLE_FAN)
+ bgl.glColor4f(color[0], color[1], color[2], color[3])
+ for uv in poly:
+ x, y = context.region.view2d.view_to_region(
+ uv.x, uv.y)
+ bgl.glVertex2f(x, y)
+ bgl.glEnd()
+ elif sc.muv_uvinsp_show_mode == 'FACE':
+ bgl.glBegin(bgl.GL_TRIANGLE_FAN)
+ bgl.glColor4f(color[0], color[1], color[2], color[3])
+ for uv in info["uvs"]:
+ x, y = context.region.view2d.view_to_region(uv.x, uv.y)
+ bgl.glVertex2f(x, y)
+ bgl.glEnd()
+
+
+def is_polygon_flipped(points):
+ area = 0.0
+ for i in range(len(points)):
+ uv1 = points.get(i)
+ uv2 = points.get(i + 1)
+ a = uv1.x * uv2.y - uv1.y * uv2.x
+ area = area + a
+ if area < 0:
+ # clock-wise
+ return True
+ return False
+
+
+def is_point_in_polygon(point, subject_points):
+ count = 0
+ for i in range(len(subject_points)):
+ uv_start1 = subject_points.get(i)
+ uv_end1 = subject_points.get(i + 1)
+ uv_start2 = point
+ uv_end2 = Vector((1000000.0, point.y))
+ intersected, _ = is_segment_intersect(uv_start1, uv_end1,
+ uv_start2, uv_end2)
+ if intersected:
+ count = count + 1
+
+ return count % 2
+
+
+def is_points_in_polygon(points, subject_points):
+ for i in range(len(points)):
+ internal = is_point_in_polygon(points.get(i), subject_points)
+ if not internal:
+ return False
+
+ return True
+
+
+def get_overlapped_uv_info(bm, faces, uv_layer, mode):
+ # at first, check island overlapped
+ isl = common.get_island_info_from_faces(bm, faces, uv_layer)
+ overlapped_isl_pairs = []
+ for i, i1 in enumerate(isl):
+ for i2 in isl[i + 1:]:
+ if (i1["max"].x < i2["min"].x) or (i2["max"].x < i1["min"].x) or \
+ (i1["max"].y < i2["min"].y) or (i2["max"].y < i1["min"].y):
+ continue
+ overlapped_isl_pairs.append([i1, i2])
+
+ # next, check polygon overlapped
+ overlapped_uvs = []
+ for oip in overlapped_isl_pairs:
+ for clip in oip[0]["faces"]:
+ f_clip = clip["face"]
+ for subject in oip[1]["faces"]:
+ f_subject = subject["face"]
+
+ # fast operation, apply bounding box algorithm
+ if (clip["max_uv"].x < subject["min_uv"].x) or \
+ (subject["max_uv"].x < clip["min_uv"].x) or \
+ (clip["max_uv"].y < subject["min_uv"].y) or \
+ (subject["max_uv"].y < clip["min_uv"].y):
+ continue
+
+ # slow operation, apply Weiler-Atherton cliping algorithm
+ result, polygons = do_weiler_atherton_cliping(f_clip,
+ f_subject,
+ uv_layer, mode)
+ if result:
+ subject_uvs = [l[uv_layer].uv.copy()
+ for l in f_subject.loops]
+ overlapped_uvs.append({"clip_face": f_clip,
+ "subject_face": f_subject,
+ "subject_uvs": subject_uvs,
+ "polygons": polygons})
+
+ return overlapped_uvs
+
+
+def get_flipped_uv_info(faces, uv_layer):
+ flipped_uvs = []
+ for f in faces:
+ polygon = RingBuffer([l[uv_layer].uv.copy() for l in f.loops])
+ if is_polygon_flipped(polygon):
+ uvs = [l[uv_layer].uv.copy() for l in f.loops]
+ flipped_uvs.append({"face": f, "uvs": uvs,
+ "polygons": [polygon.as_list()]})
+
+ return flipped_uvs
+
+
+def update_uvinsp_info(context):
+ sc = context.scene
+ props = sc.muv_props.uvinsp
+
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ if context.tool_settings.use_uv_select_sync:
+ sel_faces = [f for f in bm.faces]
+ else:
+ sel_faces = [f for f in bm.faces if f.select]
+ props.overlapped_info = get_overlapped_uv_info(bm, sel_faces, uv_layer,
+ sc.muv_uvinsp_show_mode)
+ props.flipped_info = get_flipped_uv_info(sel_faces, uv_layer)
+
+
+class MUV_UVInspUpdate(bpy.types.Operator):
+ """
+ Operation class: Update
+ """
+
+ bl_idname = "uv.muv_uvinsp_update"
+ bl_label = "Update"
+ bl_description = "Update Overlapped/Flipped UV"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ update_uvinsp_info(context)
+
+ if context.area:
+ context.area.tag_redraw()
+
+ return {'FINISHED'}
+
+
+class MUV_UVInspDisplay(bpy.types.Operator):
+ """
+ Operation class: Display
+ """
+
+ bl_idname = "uv.muv_uvinsp_display"
+ bl_label = "Display"
+ bl_description = "Display Overlapped/Flipped UV"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ sc = context.scene
+ props = sc.muv_props.uvinsp
+ if not props.display_running:
+ update_uvinsp_info(context)
+ MUV_UVInspRenderer.handle_add(self, context)
+ props.display_running = True
+ else:
+ MUV_UVInspRenderer.handle_remove()
+ props.display_running = False
+
+ if context.area:
+ context.area.tag_redraw()
+
+ return {'FINISHED'}
+
+
+class MUV_UVInspSelectOverlapped(bpy.types.Operator):
+ """
+ Operation class: Select faces which have overlapped UVs
+ """
+
+ bl_idname = "uv.muv_uvinsp_select_overlapped"
+ bl_label = "Overlapped"
+ bl_description = "Select faces which have overlapped UVs"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ if context.tool_settings.use_uv_select_sync:
+ sel_faces = [f for f in bm.faces]
+ else:
+ sel_faces = [f for f in bm.faces if f.select]
+
+ overlapped_info = get_overlapped_uv_info(bm, sel_faces, uv_layer,
+ 'FACE')
+
+ for info in overlapped_info:
+ if context.tool_settings.use_uv_select_sync:
+ info["subject_face"].select = True
+ else:
+ for l in info["subject_face"].loops:
+ l[uv_layer].select = True
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
+
+
+class MUV_UVInspSelectFlipped(bpy.types.Operator):
+ """
+ Operation class: Select faces which have flipped UVs
+ """
+
+ bl_idname = "uv.muv_uvinsp_select_flipped"
+ bl_label = "Flipped"
+ bl_description = "Select faces which have flipped UVs"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ if context.tool_settings.use_uv_select_sync:
+ sel_faces = [f for f in bm.faces]
+ else:
+ sel_faces = [f for f in bm.faces if f.select]
+
+ flipped_info = get_flipped_uv_info(sel_faces, uv_layer)
+
+ for info in flipped_info:
+ if context.tool_settings.use_uv_select_sync:
+ info["face"].select = True
+ else:
+ for l in info["face"].loops:
+ l[uv_layer].select = True
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
diff --git a/uv_magic_uv/op/uv_sculpt.py b/uv_magic_uv/op/uv_sculpt.py
new file mode 100644
index 00000000..6133b2a2
--- /dev/null
+++ b/uv_magic_uv/op/uv_sculpt.py
@@ -0,0 +1,355 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+from math import pi, cos, tan, sin
+
+import bpy
+import bmesh
+import bgl
+from mathutils import Vector
+from bpy_extras import view3d_utils
+from mathutils.bvhtree import BVHTree
+from mathutils.geometry import barycentric_transform
+
+from .. import common
+
+
+class MUV_UVSculptRenderer(bpy.types.Operator):
+ """
+ Operation class: Render Brush
+ """
+
+ bl_idname = "uv.muv_uvsculpt_renderer"
+ bl_label = "Brush Renderer"
+ bl_description = "Brush Renderer in View3D"
+
+ __handle = None
+
+ @staticmethod
+ def handle_add(obj, context):
+ if MUV_UVSculptRenderer.__handle is None:
+ sv = bpy.types.SpaceView3D
+ MUV_UVSculptRenderer.__handle = sv.draw_handler_add(
+ MUV_UVSculptRenderer.draw_brush,
+ (obj, context), "WINDOW", "POST_PIXEL")
+
+ @staticmethod
+ def handle_remove():
+ if MUV_UVSculptRenderer.__handle is not None:
+ sv = bpy.types.SpaceView3D
+ sv.draw_handler_remove(
+ MUV_UVSculptRenderer.__handle, "WINDOW")
+ MUV_UVSculptRenderer.__handle = None
+
+ @staticmethod
+ def draw_brush(obj, context):
+ sc = context.scene
+ prefs = context.user_preferences.addons["uv_magic_uv"].preferences
+
+ num_segment = 180
+ theta = 2 * pi / num_segment
+ fact_t = tan(theta)
+ fact_r = cos(theta)
+ color = prefs.uvsculpt_brush_color
+
+ bgl.glBegin(bgl.GL_LINE_STRIP)
+ bgl.glColor4f(color[0], color[1], color[2], color[3])
+ x = sc.muv_uvsculpt_radius * cos(0.0)
+ y = sc.muv_uvsculpt_radius * sin(0.0)
+ for _ in range(num_segment):
+ bgl.glVertex2f(x + obj.current_mco.x, y + obj.current_mco.y)
+ tx = -y
+ ty = x
+ x = x + tx * fact_t
+ y = y + ty * fact_t
+ x = x * fact_r
+ y = y * fact_r
+ bgl.glEnd()
+
+
+class MUV_UVSculptOps(bpy.types.Operator):
+ """
+ Operation class: UV Sculpt in View3D
+ """
+
+ bl_idname = "uv.muv_uvsculpt_ops"
+ bl_label = "UV Sculpt"
+ bl_description = "UV Sculpt in View3D"
+ bl_options = {'REGISTER'}
+
+ def __init__(self):
+ self.__timer = None
+ self.__loop_info = []
+ self.__stroking = False
+ self.current_mco = Vector((0.0, 0.0))
+ self.__initial_mco = Vector((0.0, 0.0))
+
+ def __get_strength(self, p, len_, factor):
+ f = factor
+
+ if p > len_:
+ return 0.0
+
+ if p < 0.0:
+ return f
+
+ return (len_ - p) * f / len_
+
+ def __stroke_init(self, context, _):
+ sc = context.scene
+
+ self.__initial_mco = self.current_mco
+
+ # get influenced UV
+ obj = context.active_object
+ world_mat = obj.matrix_world
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
+ _, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
+
+ self.__loop_info = []
+ for f in bm.faces:
+ if not f.select:
+ continue
+ for i, l in enumerate(f.loops):
+ loc_2d = view3d_utils.location_3d_to_region_2d(
+ region, space.region_3d, world_mat * l.vert.co)
+ diff = loc_2d - self.__initial_mco
+ if diff.length < sc.muv_uvsculpt_radius:
+ info = {
+ "face_idx": f.index,
+ "loop_idx": i,
+ "initial_vco": l.vert.co.copy(),
+ "initial_vco_2d": loc_2d,
+ "initial_uv": l[uv_layer].uv.copy(),
+ "strength": self.__get_strength(
+ diff.length, sc.muv_uvsculpt_radius,
+ sc.muv_uvsculpt_strength)
+ }
+ self.__loop_info.append(info)
+
+ def __stroke_apply(self, context, _):
+ sc = context.scene
+ obj = context.active_object
+ world_mat = obj.matrix_world
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
+ mco = self.current_mco
+
+ if sc.muv_uvsculpt_tools == 'GRAB':
+ for info in self.__loop_info:
+ diff_uv = (mco - self.__initial_mco) * info["strength"]
+ l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
+ l[uv_layer].uv = info["initial_uv"] + diff_uv / 100.0
+
+ elif sc.muv_uvsculpt_tools == 'PINCH':
+ _, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
+ loop_info = []
+ for f in bm.faces:
+ if not f.select:
+ continue
+ for i, l in enumerate(f.loops):
+ loc_2d = view3d_utils.location_3d_to_region_2d(
+ region, space.region_3d, world_mat * l.vert.co)
+ diff = loc_2d - self.__initial_mco
+ if diff.length < sc.muv_uvsculpt_radius:
+ info = {
+ "face_idx": f.index,
+ "loop_idx": i,
+ "initial_vco": l.vert.co.copy(),
+ "initial_vco_2d": loc_2d,
+ "initial_uv": l[uv_layer].uv.copy(),
+ "strength": self.__get_strength(
+ diff.length, sc.muv_uvsculpt_radius,
+ sc.muv_uvsculpt_strength)
+ }
+ loop_info.append(info)
+
+ # mouse coordinate to UV coordinate
+ ray_vec = view3d_utils.region_2d_to_vector_3d(region,
+ space.region_3d, mco)
+ ray_vec.normalize()
+ ray_orig = view3d_utils.region_2d_to_origin_3d(region,
+ space.region_3d,
+ mco)
+ ray_tgt = ray_orig + ray_vec * 1000000.0
+ mwi = world_mat.inverted()
+ ray_orig_obj = mwi * ray_orig
+ ray_tgt_obj = mwi * ray_tgt
+ ray_dir_obj = ray_tgt_obj - ray_orig_obj
+ ray_dir_obj.normalize()
+ tree = BVHTree.FromBMesh(bm)
+ loc, _, fidx, _ = tree.ray_cast(ray_orig_obj, ray_dir_obj)
+ if not loc:
+ return
+ loops = [l for l in bm.faces[fidx].loops]
+ uvs = [Vector((l[uv_layer].uv.x, l[uv_layer].uv.y, 0.0))
+ for l in loops]
+ target_uv = barycentric_transform(
+ loc, loops[0].vert.co, loops[1].vert.co, loops[2].vert.co,
+ uvs[0], uvs[1], uvs[2])
+ target_uv = Vector((target_uv.x, target_uv.y))
+
+ # move to target UV coordinate
+ for info in loop_info:
+ l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
+ if sc.muv_uvsculpt_pinch_invert:
+ diff_uv = (l[uv_layer].uv - target_uv) * info["strength"]
+ else:
+ diff_uv = (target_uv - l[uv_layer].uv) * info["strength"]
+ l[uv_layer].uv = l[uv_layer].uv + diff_uv / 10.0
+
+ elif sc.muv_uvsculpt_tools == 'RELAX':
+ _, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
+
+ # get vertex and loop relation
+ vert_db = {}
+ for f in bm.faces:
+ for l in f.loops:
+ if l.vert in vert_db:
+ vert_db[l.vert]["loops"].append(l)
+ else:
+ vert_db[l.vert] = {"loops": [l]}
+
+ # get relaxation information
+ for k in vert_db.keys():
+ d = vert_db[k]
+ d["uv_sum"] = Vector((0.0, 0.0))
+ d["uv_count"] = 0
+
+ for l in d["loops"]:
+ ln = l.link_loop_next
+ lp = l.link_loop_prev
+ d["uv_sum"] = d["uv_sum"] + ln[uv_layer].uv
+ d["uv_sum"] = d["uv_sum"] + lp[uv_layer].uv
+ d["uv_count"] = d["uv_count"] + 2
+ d["uv_p"] = d["uv_sum"] / d["uv_count"]
+ d["uv_b"] = d["uv_p"] - d["loops"][0][uv_layer].uv
+ for k in vert_db.keys():
+ d = vert_db[k]
+ d["uv_sum_b"] = Vector((0.0, 0.0))
+ for l in d["loops"]:
+ ln = l.link_loop_next
+ lp = l.link_loop_prev
+ dn = vert_db[ln.vert]
+ dp = vert_db[lp.vert]
+ d["uv_sum_b"] = d["uv_sum_b"] + dn["uv_b"] + dp["uv_b"]
+
+ # apply
+ for f in bm.faces:
+ if not f.select:
+ continue
+ for i, l in enumerate(f.loops):
+ loc_2d = view3d_utils.location_3d_to_region_2d(
+ region, space.region_3d, world_mat * l.vert.co)
+ diff = loc_2d - self.__initial_mco
+ if diff.length >= sc.muv_uvsculpt_radius:
+ continue
+ db = vert_db[l.vert]
+ strength = self.__get_strength(diff.length,
+ sc.muv_uvsculpt_radius,
+ sc.muv_uvsculpt_strength)
+
+ base = (1.0 - strength) * l[uv_layer].uv
+ if sc.muv_uvsculpt_relax_method == 'HC':
+ t = 0.5 * (db["uv_b"] + db["uv_sum_b"] / d["uv_count"])
+ diff = strength * (db["uv_p"] - t)
+ target_uv = base + diff
+ elif sc.muv_uvsculpt_relax_method == 'LAPLACIAN':
+ diff = strength * db["uv_p"]
+ target_uv = base + diff
+ else:
+ continue
+
+ l[uv_layer].uv = target_uv
+
+ bmesh.update_edit_mesh(obj.data)
+
+ def __stroke_exit(self, context, _):
+ sc = context.scene
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
+ mco = self.current_mco
+
+ if sc.muv_uvsculpt_tools == 'GRAB':
+ for info in self.__loop_info:
+ diff_uv = (mco - self.__initial_mco) * info["strength"]
+ l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
+ l[uv_layer].uv = info["initial_uv"] + diff_uv / 100.0
+
+ bmesh.update_edit_mesh(obj.data)
+
+ def modal(self, context, event):
+ props = context.scene.muv_props.uvsculpt
+
+ if context.area:
+ context.area.tag_redraw()
+
+ if not props.running:
+ if self.__timer is not None:
+ MUV_UVSculptRenderer.handle_remove()
+ context.window_manager.event_timer_remove(self.__timer)
+ self.__timer = None
+ return {'FINISHED'}
+
+ self.current_mco = Vector((event.mouse_region_x, event.mouse_region_y))
+
+ if event.type == 'LEFTMOUSE':
+ if event.value == 'PRESS':
+ if not self.__stroking:
+ self.__stroke_init(context, event)
+ self.__stroking = True
+ elif event.value == 'RELEASE':
+ if self.__stroking:
+ self.__stroke_exit(context, event)
+ self.__stroking = False
+ elif event.type == 'MOUSEMOVE':
+ if self.__stroking:
+ self.__stroke_apply(context, event)
+ elif event.type == 'TIMER':
+ if self.__stroking:
+ self.__stroke_apply(context, event)
+
+ return {'PASS_THROUGH'}
+
+ def invoke(self, context, _):
+ props = context.scene.muv_props.uvsculpt
+
+ if context.area:
+ context.area.tag_redraw()
+
+ if props.running:
+ props.running = False
+ return {'FINISHED'}
+
+ props.running = True
+ 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_UVSculptRenderer.handle_add(self, context)
+
+ return {'RUNNING_MODAL'}
diff --git a/uv_magic_uv/muv_uvw_ops.py b/uv_magic_uv/op/uvw.py
index eb366e97..37d88a53 100644
--- a/uv_magic_uv/muv_uvw_ops.py
+++ b/uv_magic_uv/op/uvw.py
@@ -20,9 +20,8 @@
__author__ = "Alexander Milovsky, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
-
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
from math import sin, cos, pi
@@ -30,11 +29,12 @@ import bpy
import bmesh
from bpy.props import (
FloatProperty,
- FloatVectorProperty
+ FloatVectorProperty,
+ BoolProperty
)
from mathutils import Vector
-from . import muv_common
+from .. import common
class MUV_UVWBoxMap(bpy.types.Operator):
@@ -62,6 +62,11 @@ class MUV_UVWBoxMap(bpy.types.Operator):
default=1.0,
precision=4
)
+ assign_uvmap = BoolProperty(
+ name="Assign UVMap",
+ description="Assign UVMap when no UVmaps are available",
+ default=True
+ )
@classmethod
def poll(cls, context):
@@ -71,15 +76,17 @@ class MUV_UVWBoxMap(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if 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'}
-
+ if self.assign_uvmap:
+ bm.loops.layers.uv.new()
+ else:
+ self.report(
+ {'WARNING'}, "Object must have more than one UV map")
+ return {'CANCELLED'}
uv_layer = bm.loops.layers.uv.verify()
scale = 1.0 / self.size
@@ -168,6 +175,11 @@ class MUV_UVWBestPlanerMap(bpy.types.Operator):
default=1.0,
precision=4
)
+ assign_uvmap = BoolProperty(
+ name="Assign UVMap",
+ description="Assign UVMap when no UVmaps are available",
+ default=True
+ )
@classmethod
def poll(cls, context):
@@ -177,14 +189,17 @@ class MUV_UVWBestPlanerMap(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if 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'}
+ if self.assign_uvmap:
+ bm.loops.layers.uv.new()
+ else:
+ self.report(
+ {'WARNING'}, "Object must have more than one UV map")
+ return {'CANCELLED'}
uv_layer = bm.loops.layers.uv.verify()
diff --git a/uv_magic_uv/muv_wsuv_ops.py b/uv_magic_uv/op/world_scale_uv.py
index 4ee8b4f9..f1539ddb 100644
--- a/uv_magic_uv/muv_wsuv_ops.py
+++ b/uv_magic_uv/op/world_scale_uv.py
@@ -20,43 +20,32 @@
__author__ = "McBuff, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "4.5"
-__date__ = "19 Nov 2017"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+from math import sqrt
import bpy
import bmesh
from mathutils import Vector
-from bpy.props import (
- FloatProperty,
- BoolProperty,
- EnumProperty
-)
-from . import muv_common
+from bpy.props import EnumProperty
+from .. import 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
+def measure_wsuv_info(obj):
+ mesh_area = common.measure_mesh_area(obj)
+ uv_area = common.measure_uv_area(obj)
- scale = 0.0
- if dv.magnitude > 0.00000001:
- scale = duv.magnitude / dv.magnitude
+ if not uv_area:
+ return None, None, None
- return scale
+ if mesh_area == 0.0:
+ density = 0.0
+ else:
+ density = sqrt(uv_area) / sqrt(mesh_area)
-
-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
+ return uv_area, mesh_area, density
class MUV_WSUVMeasure(bpy.types.Operator):
@@ -70,30 +59,22 @@ class MUV_WSUVMeasure(bpy.types.Operator):
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()
+ sc = context.scene
+ obj = context.active_object
- if not bm.loops.layers.uv:
- self.report({'WARNING'}, "Object must have more than one UV map")
+ uv_area, mesh_area, density = measure_wsuv_info(obj)
+ if not uv_area:
+ self.report({'WARNING'},
+ "Object must have more than one UV map and texture")
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)
+ sc.muv_wsuv_src_uv_area = uv_area
+ sc.muv_wsuv_src_mesh_area = mesh_area
+ sc.muv_wsuv_src_density = density
- props.ref_scale = scale / len(sel_faces)
-
- self.report(
- {'INFO'}, "Average face size: {0}".format(props.ref_scale))
+ self.report({'INFO'},
+ "UV Area: {0}, Mesh Area: {1}, Texel Density: {2}"
+ .format(uv_area, mesh_area, density))
return {'FINISHED'}
@@ -108,16 +89,6 @@ class MUV_WSUVApply(bpy.types.Operator):
bl_description = "Apply scaled UV based on scale calculation"
bl_options = {'REGISTER', 'UNDO'}
- proportional_scaling = BoolProperty(
- name="Proportional Scaling",
- default=True
- )
- scaling_factor = FloatProperty(
- name="Scaling Factor",
- default=1.0,
- max=1000.0,
- min=0.00001
- )
origin = EnumProperty(
name="Origin",
description="Aspect Origin",
@@ -139,43 +110,38 @@ class MUV_WSUVApply(bpy.types.Operator):
def draw(self, _):
layout = self.layout
- row = layout.row()
- row.prop(self, "proportional_scaling")
- row = layout.row()
- row.prop(self, "scaling_factor")
- if self.proportional_scaling:
- row.enabled = False
+ layout.prop(self, "origin")
def execute(self, context):
- props = context.scene.muv_props.wsuv
- obj = bpy.context.active_object
+ sc = context.scene
+ obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
- if muv_common.check_version(2, 73, 0) >= 0:
+ if 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)
+ uv_area, mesh_area, density = measure_wsuv_info(obj)
+ if not uv_area:
+ self.report({'WARNING'},
+ "Object must have more than one UV map and texture")
+ return {'CANCELLED'}
- self.report(
- {'INFO'}, "Average face size: {0}".format(scale))
+ uv_layer = bm.loops.layers.uv.verify()
- if self.proportional_scaling:
- factor = props.ref_scale / scale
- else:
- factor = self.scaling_factor
+ if sc.muv_wsuv_mode == 'PROPORTIONAL':
+ tgt_density = sc.muv_wsuv_src_density * sqrt(mesh_area) / \
+ sqrt(sc.muv_wsuv_src_mesh_area)
+ elif sc.muv_wsuv_mode == 'SCALING':
+ tgt_density = sc.muv_wsuv_src_density * sc.muv_wsuv_scaling_factor
+ elif sc.muv_wsuv_mode == 'USER':
+ tgt_density = sc.muv_wsuv_tgt_density
+ elif sc.muv_wsuv_mode == 'CONSTANT':
+ tgt_density = sc.muv_wsuv_src_density
+
+ factor = tgt_density / density
# calculate origin
if self.origin == 'CENTER':
diff --git a/uv_magic_uv/preferences.py b/uv_magic_uv/preferences.py
new file mode 100644
index 00000000..eb86804e
--- /dev/null
+++ b/uv_magic_uv/preferences.py
@@ -0,0 +1,216 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+from bpy.props import (
+ FloatProperty,
+ FloatVectorProperty,
+)
+from bpy.types import AddonPreferences
+
+
+class MUV_Preferences(AddonPreferences):
+ """Preferences class: Preferences for this add-on"""
+
+ bl_idname = __package__
+
+ # for UV Sculpt
+ uvsculpt_brush_color = FloatVectorProperty(
+ name="Color",
+ description="Color",
+ default=(1.0, 0.4, 0.4, 1.0),
+ min=0.0,
+ max=1.0,
+ size=4,
+ subtype='COLOR'
+ )
+
+ # for Overlapped UV
+ uvinsp_overlapped_color = FloatVectorProperty(
+ name="Color",
+ description="Color",
+ default=(0.0, 0.0, 1.0, 0.3),
+ min=0.0,
+ max=1.0,
+ size=4,
+ subtype='COLOR'
+ )
+
+ # for Flipped UV
+ uvinsp_flipped_color = FloatVectorProperty(
+ name="Color",
+ description="Color",
+ default=(1.0, 0.0, 0.0, 0.3),
+ min=0.0,
+ max=1.0,
+ size=4,
+ subtype='COLOR'
+ )
+
+ # for Texture Projection
+ 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, _):
+ layout = self.layout
+
+ layout.label("[Configuration]")
+
+ layout.label("UV Sculpt:")
+ sp = layout.split(percentage=0.05)
+ col = sp.column() # spacer
+ sp = sp.split(percentage=0.3)
+ col = sp.column()
+ col.label("Brush Color:")
+ col.prop(self, "uvsculpt_brush_color", text="")
+
+ layout.separator()
+
+ layout.label("UV Inspection:")
+ sp = layout.split(percentage=0.05)
+ col = sp.column() # spacer
+ sp = sp.split(percentage=0.3)
+ col = sp.column()
+ col.label("Overlapped UV Color:")
+ col.prop(self, "uvinsp_overlapped_color", text="")
+ sp = sp.split(percentage=0.45)
+ col = sp.column()
+ col.label("Flipped UV Color:")
+ col.prop(self, "uvinsp_flipped_color", text="")
+
+ layout.separator()
+
+ layout.label("Texture Projection:")
+ sp = layout.split(percentage=0.05)
+ col = sp.column() # spacer
+ sp = sp.split(percentage=0.3)
+ col = sp.column()
+ col.prop(self, "texproj_canvas_padding")
+
+ layout.separator()
+
+ layout.label("UV Bounding Box:")
+ 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("--------------------------------------")
+
+ 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 are new to this add-on.")
+ column.label("https://github.com/nutti/Magic-UV/wiki/Tutorial")
+
+ layout.label("--------------------------------------")
+
+ layout.label("[Location]")
+
+ row = layout.row(align=True)
+ sp = row.split(percentage=0.5)
+ sp.label("3D View > Tool shelf > Copy/Paste UV (Object mode)")
+ sp = sp.split(percentage=1.0)
+ col = sp.column(align=True)
+ col.label("Copy/Paste UV (Among objects)")
+
+ row = layout.row(align=True)
+ sp = row.split(percentage=0.5)
+ sp.label("3D View > Tool shelf > Copy/Paste UV (Edit mode)")
+ sp = sp.split(percentage=1.0)
+ col = sp.column(align=True)
+ col.label("Copy/Paste UV (Among faces in 3D View)")
+ col.label("Transfer UV")
+
+ row = layout.row(align=True)
+ sp = row.split(percentage=0.5)
+ sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)")
+ sp = sp.split(percentage=1.0)
+ col = sp.column(align=True)
+ col.label("Flip/Rotate UV")
+ col.label("Mirror UV")
+ col.label("Move UV")
+ col.label("World Scale UV")
+ col.label("Preserve UV Aspect")
+ col.label("Texture Lock")
+ col.label("Texture Wrap")
+ col.label("UV Sculpt")
+
+ row = layout.row(align=True)
+ sp = row.split(percentage=0.5)
+ sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)")
+ sp = sp.split(percentage=1.0)
+ col = sp.column(align=True)
+ col.label("Unwrap Constraint")
+ col.label("Texture Projection")
+ col.label("UVW")
+
+ row = layout.row(align=True)
+ sp = row.split(percentage=0.5)
+ sp.label("UV/Image Editor > Tool shelf > Copy/Paste UV")
+ sp = sp.split(percentage=1.0)
+ col = sp.column(align=True)
+ col.label("Copy/Paste UV (Among faces in UV/Image Editor)")
+
+ row = layout.row(align=True)
+ sp = row.split(percentage=0.5)
+ sp.label("UV/Image Editor > Tool shelf > UV Manipulation")
+ sp = sp.split(percentage=1.0)
+ col = sp.column(align=True)
+ col.label("Align UV")
+ col.label("Smooth UV")
+ col.label("Select UV")
+ col.label("Pack UV (Extension)")
+
+ row = layout.row(align=True)
+ sp = row.split(percentage=0.5)
+ sp.label("UV/Image Editor > Tool shelf > Editor Enhancement")
+ sp = sp.split(percentage=1.0)
+ col = sp.column(align=True)
+ col.label("Align UV Cursor")
+ col.label("UV Cursor Location")
+ col.label("UV Bounding Box")
+ col.label("UV Inspection")
diff --git a/uv_magic_uv/properites.py b/uv_magic_uv/properites.py
new file mode 100644
index 00000000..f40e9f1f
--- /dev/null
+++ b/uv_magic_uv/properites.py
@@ -0,0 +1,755 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+from bpy.props import (
+ FloatProperty,
+ EnumProperty,
+ BoolProperty,
+ FloatVectorProperty,
+ IntProperty
+)
+from mathutils import Vector
+
+from . import common
+
+
+def get_loaded_texture_name(_, __):
+ items = [(key, key, "") for key in bpy.data.images.keys()]
+ items.append(("None", "None", ""))
+ return items
+
+
+# Properties used in this add-on.
+class MUV_Properties():
+ cpuv = None
+ cpuv_obj = None
+ cpuv_selseq = None
+ transuv = None
+ uvbb = None
+ texlock = None
+ texproj = None
+ texwrap = None
+ mvuv = None
+ uvinsp = None
+ uvsculpt = 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.texlock = MUV_TexLockProps()
+ self.texproj = MUV_TexProjProps()
+ self.texwrap = MUV_TexWrapProps()
+ self.mvuv = MUV_MVUVProps()
+ self.uvinsp = MUV_UVInspProps()
+ self.uvsculpt = MUV_UVSculptProps()
+
+
+class MUV_CPUVProps():
+ src_uvs = []
+ src_pin_uvs = []
+ src_seams = []
+
+
+class MUV_CPUVSelSeqProps():
+ src_uvs = []
+ src_pin_uvs = []
+ src_seams = []
+
+
+class MUV_TransUVProps():
+ topology_copied = []
+
+
+class MUV_TexProjProps():
+ running = False
+
+
+class MUV_UVBBProps():
+ uv_info_ini = []
+ ctrl_points_ini = []
+ ctrl_points = []
+ running = False
+
+
+class MUV_TexLockProps():
+ verts_orig = None
+ intr_verts_orig = None
+ intr_running = False
+
+
+class MUV_TexWrapProps():
+ ref_face_index = -1
+ ref_obj = None
+
+
+class MUV_MVUVProps():
+ running = False
+
+
+class MUV_UVInspProps():
+ display_running = False
+ overlapped_info = []
+ flipped_info = []
+
+
+class MUV_UVSculptProps():
+ running = False
+
+
+def init_props(scene):
+ scene.muv_props = MUV_Properties()
+
+ # UV Sculpt
+ scene.muv_uvsculpt_enabled = BoolProperty(
+ name="UV Sculpt",
+ description="UV Sculpt is enabled",
+ default=False
+ )
+ scene.muv_uvsculpt_radius = IntProperty(
+ name="Radius",
+ description="Radius of the brush",
+ min=1,
+ max=500,
+ default=30
+ )
+ scene.muv_uvsculpt_strength = FloatProperty(
+ name="Strength",
+ description="How powerful the effect of the brush when applied",
+ min=0.0,
+ max=1.0,
+ default=0.03,
+ )
+ scene.muv_uvsculpt_tools = EnumProperty(
+ name="Tools",
+ description="Select Tools for the UV sculpt brushes",
+ items=[
+ ('GRAB', "Grab", "Grab UVs"),
+ ('RELAX', "Relax", "Relax UVs"),
+ ('PINCH', "Pinch", "Pinch UVs")
+ ],
+ default='GRAB'
+ )
+ scene.muv_uvsculpt_show_brush = BoolProperty(
+ name="Show Brush",
+ description="Show Brush",
+ default=True
+ )
+ scene.muv_uvsculpt_pinch_invert = BoolProperty(
+ name="Invert",
+ description="Pinch UV to invert direction",
+ default=False
+ )
+ scene.muv_uvsculpt_relax_method = EnumProperty(
+ name="Method",
+ description="Algorithm used for relaxation",
+ items=[
+ ('HC', "HC", "Use HC method for relaxation"),
+ ('LAPLACIAN', "Laplacian", "Use laplacian method for relaxation")
+ ],
+ default='HC'
+ )
+
+ # Texture Wrap
+ scene.muv_texwrap_enabled = BoolProperty(
+ name="Texture Wrap",
+ description="Texture Wrap is enabled",
+ default=False
+ )
+ scene.muv_texwrap_set_and_refer = BoolProperty(
+ name="Set and Refer",
+ description="Refer and set UV",
+ default=True
+ )
+ scene.muv_texwrap_selseq = BoolProperty(
+ name="Selection Sequence",
+ description="Set UV sequentially",
+ default=False
+ )
+
+ # UV inspection
+ scene.muv_seluv_enabled = BoolProperty(
+ name="Select UV Enabled",
+ description="Select UV is enabled",
+ default=False
+ )
+ scene.muv_uvinsp_enabled = BoolProperty(
+ name="UV Inspection Enabled",
+ description="UV Inspection is enabled",
+ default=False
+ )
+ scene.muv_uvinsp_show_overlapped = BoolProperty(
+ name="Overlapped",
+ description="Show overlapped UVs",
+ default=False
+ )
+ scene.muv_uvinsp_show_flipped = BoolProperty(
+ name="Flipped",
+ description="Show flipped UVs",
+ default=False
+ )
+ scene.muv_uvinsp_show_mode = EnumProperty(
+ name="Mode",
+ description="Show mode",
+ items=[
+ ('PART', "Part", "Show only overlapped/flipped part"),
+ ('FACE', "Face", "Show overlapped/flipped face")
+ ],
+ default='PART'
+ )
+
+ # Align UV
+ scene.muv_auv_enabled = BoolProperty(
+ name="Aline UV Enabled",
+ description="Align UV is enabled",
+ default=False
+ )
+ scene.muv_auv_transmission = BoolProperty(
+ name="Transmission",
+ description="Align linked UVs",
+ default=False
+ )
+ scene.muv_auv_select = BoolProperty(
+ name="Select",
+ description="Select UVs which are aligned",
+ default=False
+ )
+ scene.muv_auv_vertical = BoolProperty(
+ name="Vert-Infl (Vertical)",
+ description="Align vertical direction influenced "
+ "by mesh vertex proportion",
+ default=False
+ )
+ scene.muv_auv_horizontal = BoolProperty(
+ name="Vert-Infl (Horizontal)",
+ description="Align horizontal direction influenced "
+ "by mesh vertex proportion",
+ default=False
+ )
+ scene.muv_auv_location = EnumProperty(
+ name="Location",
+ description="Align location",
+ items=[
+ ('LEFT_TOP', "Left/Top", "Align to Left or Top"),
+ ('MIDDLE', "Middle", "Align to middle"),
+ ('RIGHT_BOTTOM', "Right/Bottom", "Align to Right or Bottom")
+ ],
+ default='MIDDLE'
+ )
+
+ # Smooth UV
+ scene.muv_smuv_enabled = BoolProperty(
+ name="Smooth UV Enabled",
+ description="Smooth UV is enabled",
+ default=False
+ )
+ scene.muv_smuv_transmission = BoolProperty(
+ name="Transmission",
+ description="Smooth linked UVs",
+ default=False
+ )
+ scene.muv_smuv_mesh_infl = FloatProperty(
+ name="Mesh Influence",
+ description="Influence rate of mesh vertex",
+ min=0.0,
+ max=1.0,
+ default=0.0
+ )
+ scene.muv_smuv_select = BoolProperty(
+ name="Select",
+ description="Select UVs which are smoothed",
+ default=False
+ )
+
+ # UV Bounding Box
+ scene.muv_uvbb_enabled = BoolProperty(
+ name="UV Bounding Box Enabled",
+ description="UV Bounding Box is enabled",
+ default=False
+ )
+ scene.muv_uvbb_uniform_scaling = BoolProperty(
+ name="Uniform Scaling",
+ description="Enable Uniform Scaling",
+ default=False
+ )
+ scene.muv_uvbb_boundary = EnumProperty(
+ name="Boundary",
+ description="Boundary",
+ default='UV_SEL',
+ items=[
+ ('UV', "UV", "Boundary is decided by UV"),
+ ('UV_SEL', "UV (Selected)", "Boundary is decided by Selected UV")
+ ]
+ )
+
+ # Pack UV
+ scene.muv_packuv_enabled = BoolProperty(
+ name="Pack UV Enabled",
+ description="Pack UV is enabled",
+ default=False
+ )
+ scene.muv_packuv_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
+ )
+ scene.muv_packuv_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
+ )
+
+ # Move UV
+ scene.muv_mvuv_enabled = BoolProperty(
+ name="Move UV Enabled",
+ description="Move UV is enabled",
+ default=False
+ )
+
+ # UVW
+ scene.muv_uvw_enabled = BoolProperty(
+ name="UVW Enabled",
+ description="UVW is enabled",
+ default=False
+ )
+ scene.muv_uvw_assign_uvmap = BoolProperty(
+ name="Assign UVMap",
+ description="Assign UVMap when no UVmaps are available",
+ default=True
+ )
+
+ # Texture Projection
+ scene.muv_texproj_enabled = BoolProperty(
+ name="Texture Projection Enabled",
+ description="Texture Projection is enabled",
+ default=False
+ )
+ scene.muv_texproj_tex_magnitude = FloatProperty(
+ name="Magnitude",
+ description="Texture Magnitude",
+ default=0.5,
+ min=0.0,
+ max=100.0
+ )
+ scene.muv_texproj_tex_image = EnumProperty(
+ name="Image",
+ description="Texture Image",
+ items=get_loaded_texture_name
+ )
+ scene.muv_texproj_tex_transparency = FloatProperty(
+ name="Transparency",
+ description="Texture Transparency",
+ 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
+ )
+ scene.muv_texproj_assign_uvmap = BoolProperty(
+ name="Assign UVMap",
+ description="Assign UVMap when no UVmaps are available",
+ default=True
+ )
+
+ # Texture Lock
+ scene.muv_texlock_enabled = BoolProperty(
+ name="Texture Lock Enabled",
+ description="Texture Lock is enabled",
+ default=False
+ )
+ scene.muv_texlock_connect = BoolProperty(
+ name="Connect UV",
+ default=True
+ )
+
+ # World Scale UV
+ scene.muv_wsuv_enabled = BoolProperty(
+ name="World Scale UV Enabled",
+ description="World Scale UV is enabled",
+ default=False
+ )
+ scene.muv_wsuv_src_mesh_area = FloatProperty(
+ name="Mesh Area",
+ description="Source Mesh Area",
+ default=0.0,
+ min=0.0
+ )
+ scene.muv_wsuv_src_uv_area = FloatProperty(
+ name="UV Area",
+ description="Source UV Area",
+ default=0.0,
+ min=0.0
+ )
+ scene.muv_wsuv_src_density = FloatProperty(
+ name="Density",
+ description="Source Texel Density",
+ default=0.0,
+ min=0.0
+ )
+ scene.muv_wsuv_tgt_density = FloatProperty(
+ name="Density",
+ description="Target Texel Density",
+ default=0.0,
+ min=0.0
+ )
+ scene.muv_wsuv_mode = EnumProperty(
+ name="Mode",
+ description="Density calculation mode",
+ items=[
+ ('PROPORTIONAL', 'Proportional', 'Scale proportionally by mesh'),
+ ('SCALING', 'Scaling', 'Specify scale factor'),
+ ('USER', 'User', 'Specify density'),
+ ('CONSTANT', 'Constant', 'Constant density')
+ ],
+ default='CONSTANT'
+ )
+ scene.muv_wsuv_scaling_factor = FloatProperty(
+ name="Scaling Factor",
+ default=1.0,
+ max=1000.0,
+ min=0.00001
+ )
+ scene.muv_wsuv_origin = EnumProperty(
+ name="Origin",
+ description="Aspect Origin",
+ items=[
+ ('CENTER', 'Center', 'Center'),
+ ('LEFT_TOP', 'Left Top', 'Left Bottom'),
+ ('LEFT_CENTER', 'Left Center', 'Left Center'),
+ ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'),
+ ('CENTER_TOP', 'Center Top', 'Center Top'),
+ ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'),
+ ('RIGHT_TOP', 'Right Top', 'Right Top'),
+ ('RIGHT_CENTER', 'Right Center', 'Right Center'),
+ ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom')
+
+ ],
+ default='CENTER'
+ )
+
+ # Unwrap Constraint
+ scene.muv_unwrapconst_enabled = BoolProperty(
+ name="Unwrap Constraint Enabled",
+ description="Unwrap Constraint is enabled",
+ default=False
+ )
+ scene.muv_unwrapconst_u_const = BoolProperty(
+ name="U-Constraint",
+ description="Keep UV U-axis coordinate",
+ default=False
+ )
+ scene.muv_unwrapconst_v_const = BoolProperty(
+ name="V-Constraint",
+ description="Keep UV V-axis coordinate",
+ default=False
+ )
+
+ # Preserve UV Aspect
+ scene.muv_preserve_uv_enabled = BoolProperty(
+ name="Preserve UV Aspect Enabled",
+ description="Preserve UV Aspect is enabled",
+ default=False
+ )
+ scene.muv_preserve_uv_tex_image = EnumProperty(
+ name="Image",
+ description="Texture Image",
+ items=get_loaded_texture_name
+ )
+ scene.muv_preserve_uv_origin = EnumProperty(
+ name="Origin",
+ description="Aspect Origin",
+ items=[
+ ('CENTER', 'Center', 'Center'),
+ ('LEFT_TOP', 'Left Top', 'Left Bottom'),
+ ('LEFT_CENTER', 'Left Center', 'Left Center'),
+ ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'),
+ ('CENTER_TOP', 'Center Top', 'Center Top'),
+ ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'),
+ ('RIGHT_TOP', 'Right Top', 'Right Top'),
+ ('RIGHT_CENTER', 'Right Center', 'Right Center'),
+ ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom')
+
+ ],
+ default="CENTER"
+ )
+
+ # Flip/Rotate UV
+ scene.muv_fliprot_enabled = BoolProperty(
+ name="Flip/Rotate UV Enabled",
+ description="Flip/Rotate UV is enabled",
+ default=False
+ )
+ scene.muv_fliprot_seams = BoolProperty(
+ name="Seams",
+ description="Seams",
+ default=True
+ )
+
+ # Mirror UV
+ scene.muv_mirroruv_enabled = BoolProperty(
+ name="Mirror UV Enabled",
+ description="Mirror UV is enabled",
+ default=False
+ )
+ scene.muv_mirroruv_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'
+ )
+
+ # Copy/Paste UV
+ scene.muv_cpuv_enabled = BoolProperty(
+ name="Copy/Paste UV Enabled",
+ description="Copy/Paste UV is enabled",
+ default=False
+ )
+ scene.muv_cpuv_copy_seams = BoolProperty(
+ name="Copy Seams",
+ description="Copy Seams",
+ default=True
+ )
+ scene.muv_cpuv_mode = EnumProperty(
+ items=[
+ ('DEFAULT', "Default", "Default Mode"),
+ ('SEL_SEQ', "Selection Sequence", "Selection Sequence Mode")
+ ],
+ name="Copy/Paste UV Mode",
+ description="Copy/Paste UV Mode",
+ default='DEFAULT'
+ )
+ scene.muv_cpuv_strategy = EnumProperty(
+ name="Strategy",
+ description="Paste Strategy",
+ items=[
+ ('N_N', 'N:N', 'Number of faces must be equal to source'),
+ ('N_M', 'N:M', 'Number of faces must not be equal to source')
+ ],
+ default='N_M'
+ )
+
+ # Transfer UV
+ scene.muv_transuv_enabled = BoolProperty(
+ name="Transfer UV Enabled",
+ description="Transfer UV is enabled",
+ default=False
+ )
+ scene.muv_transuv_invert_normals = BoolProperty(
+ name="Invert Normals",
+ description="Invert Normals",
+ default=False
+ )
+ scene.muv_transuv_copy_seams = BoolProperty(
+ name="Copy Seams",
+ description="Copy Seams",
+ default=True
+ )
+
+ # Align UV Cursor
+ def auvc_get_cursor_loc(self):
+ area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
+ 'IMAGE_EDITOR')
+ bd_size = common.get_uvimg_editor_board_size(area)
+ loc = space.cursor_location
+ if bd_size[0] < 0.000001:
+ cx = 0.0
+ else:
+ cx = loc[0] / bd_size[0]
+ if bd_size[1] < 0.000001:
+ cy = 0.0
+ else:
+ cy = loc[1] / bd_size[1]
+ self['muv_auvc_cursor_loc'] = Vector((cx, cy))
+ return self.get('muv_auvc_cursor_loc', (0.0, 0.0))
+
+ def auvc_set_cursor_loc(self, value):
+ self['muv_auvc_cursor_loc'] = value
+ area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
+ 'IMAGE_EDITOR')
+ bd_size = common.get_uvimg_editor_board_size(area)
+ cx = bd_size[0] * value[0]
+ cy = bd_size[1] * value[1]
+ space.cursor_location = Vector((cx, cy))
+
+ scene.muv_auvc_enabled = BoolProperty(
+ name="Align UV Cursor Enabled",
+ description="Align UV Cursor is enabled",
+ default=False
+ )
+ scene.muv_auvc_cursor_loc = FloatVectorProperty(
+ name="UV Cursor Location",
+ size=2,
+ precision=4,
+ soft_min=-1.0,
+ soft_max=1.0,
+ step=1,
+ default=(0.000, 0.000),
+ get=auvc_get_cursor_loc,
+ set=auvc_set_cursor_loc
+ )
+ scene.muv_auvc_align_menu = EnumProperty(
+ name="Align Method",
+ description="Align Method",
+ default='TEXTURE',
+ items=[
+ ('TEXTURE', "Texture", "Align to texture"),
+ ('UV', "UV", "Align to UV"),
+ ('UV_SEL', "UV (Selected)", "Align to Selected UV")
+ ]
+ )
+
+
+def clear_props(scene):
+ del scene.muv_props
+
+ # UV Sculpt
+ del scene.muv_uvsculpt_enabled
+ del scene.muv_uvsculpt_radius
+ del scene.muv_uvsculpt_strength
+ del scene.muv_uvsculpt_tools
+ del scene.muv_uvsculpt_show_brush
+ del scene.muv_uvsculpt_pinch_invert
+ del scene.muv_uvsculpt_relax_method
+
+ # Texture Wrap
+ del scene.muv_texwrap_enabled
+ del scene.muv_texwrap_set_and_refer
+ del scene.muv_texwrap_selseq
+
+ # UV Inspection
+ del scene.muv_seluv_enabled
+ del scene.muv_uvinsp_enabled
+ del scene.muv_uvinsp_show_overlapped
+ del scene.muv_uvinsp_show_flipped
+ del scene.muv_uvinsp_show_mode
+
+ # Align UV
+ del scene.muv_auv_enabled
+ del scene.muv_auv_transmission
+ del scene.muv_auv_select
+ del scene.muv_auv_vertical
+ del scene.muv_auv_horizontal
+ del scene.muv_auv_location
+
+ # Smooth UV
+ del scene.muv_smuv_enabled
+ del scene.muv_smuv_transmission
+ del scene.muv_smuv_mesh_infl
+ del scene.muv_smuv_select
+
+ # UV Bounding Box
+ del scene.muv_uvbb_enabled
+ del scene.muv_uvbb_uniform_scaling
+ del scene.muv_uvbb_boundary
+
+ # Pack UV
+ del scene.muv_packuv_enabled
+ del scene.muv_packuv_allowable_center_deviation
+ del scene.muv_packuv_allowable_size_deviation
+
+ # Move UV
+ del scene.muv_mvuv_enabled
+
+ # UVW
+ del scene.muv_uvw_enabled
+ del scene.muv_uvw_assign_uvmap
+
+ # Texture Projection
+ del scene.muv_texproj_enabled
+ 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
+ del scene.muv_texproj_assign_uvmap
+
+ # Texture Lock
+ del scene.muv_texlock_enabled
+ del scene.muv_texlock_connect
+
+ # World Scale UV
+ del scene.muv_wsuv_enabled
+ del scene.muv_wsuv_src_mesh_area
+ del scene.muv_wsuv_src_uv_area
+ del scene.muv_wsuv_src_density
+ del scene.muv_wsuv_tgt_density
+ del scene.muv_wsuv_mode
+ del scene.muv_wsuv_scaling_factor
+ del scene.muv_wsuv_origin
+
+ # Unwrap Constraint
+ del scene.muv_unwrapconst_enabled
+ del scene.muv_unwrapconst_u_const
+ del scene.muv_unwrapconst_v_const
+
+ # Preserve UV Aspect
+ del scene.muv_preserve_uv_enabled
+ del scene.muv_preserve_uv_tex_image
+ del scene.muv_preserve_uv_origin
+
+ # Flip/Rotate UV
+ del scene.muv_fliprot_enabled
+ del scene.muv_fliprot_seams
+
+ # Mirror UV
+ del scene.muv_mirroruv_enabled
+ del scene.muv_mirroruv_axis
+
+ # Copy/Paste UV
+ del scene.muv_cpuv_enabled
+ del scene.muv_cpuv_copy_seams
+ del scene.muv_cpuv_mode
+ del scene.muv_cpuv_strategy
+
+ # Transfer UV
+ del scene.muv_transuv_enabled
+ del scene.muv_transuv_invert_normals
+ del scene.muv_transuv_copy_seams
+
+ # Align UV Cursor
+ del scene.muv_auvc_enabled
+ del scene.muv_auvc_cursor_loc
+ del scene.muv_auvc_align_menu
diff --git a/uv_magic_uv/ui/__init__.py b/uv_magic_uv/ui/__init__.py
new file mode 100644
index 00000000..00af3e06
--- /dev/null
+++ b/uv_magic_uv/ui/__init__.py
@@ -0,0 +1,44 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(view3d_copy_paste_uv_objectmode)
+ importlib.reload(view3d_copy_paste_uv_editmode)
+ importlib.reload(view3d_uv_manipulation)
+ importlib.reload(view3d_uv_mapping)
+ importlib.reload(uvedit_copy_paste_uv)
+ importlib.reload(uvedit_uv_manipulation)
+ importlib.reload(uvedit_editor_enhance)
+else:
+ from . import view3d_copy_paste_uv_objectmode
+ from . import view3d_copy_paste_uv_editmode
+ from . import view3d_uv_manipulation
+ from . import view3d_uv_mapping
+ from . import uvedit_copy_paste_uv
+ from . import uvedit_uv_manipulation
+ from . import uvedit_editor_enhance
+
+import bpy
diff --git a/uv_magic_uv/ui/uvedit_copy_paste_uv.py b/uv_magic_uv/ui/uvedit_copy_paste_uv.py
new file mode 100644
index 00000000..87b23fed
--- /dev/null
+++ b/uv_magic_uv/ui/uvedit_copy_paste_uv.py
@@ -0,0 +1,54 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+
+from ..op import copy_paste_uv_uvedit
+
+
+class IMAGE_PT_MUV_CPUV(bpy.types.Panel):
+ """
+ Panel class: Copy/Paste UV on Property Panel on UV/ImageEditor
+ """
+
+ bl_space_type = 'IMAGE_EDITOR'
+ bl_region_type = 'TOOLS'
+ bl_label = "Copy/Paste UV"
+ bl_category = "Magic UV"
+ bl_context = 'mesh_edit'
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw_header(self, _):
+ layout = self.layout
+ layout.label(text="", icon='IMAGE_COL')
+
+ def draw(self, _):
+ layout = self.layout
+
+ row = layout.row(align=True)
+ row.operator(copy_paste_uv_uvedit.MUV_CPUVIECopyUV.bl_idname,
+ text="Copy")
+ row.operator(copy_paste_uv_uvedit.MUV_CPUVIEPasteUV.bl_idname,
+ text="Paste")
diff --git a/uv_magic_uv/ui/uvedit_editor_enhance.py b/uv_magic_uv/ui/uvedit_editor_enhance.py
new file mode 100644
index 00000000..dfe30978
--- /dev/null
+++ b/uv_magic_uv/ui/uvedit_editor_enhance.py
@@ -0,0 +1,136 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+
+from ..op import align_uv_cursor
+from ..op import uv_bounding_box
+from ..op import uv_inspection
+
+
+class IMAGE_PT_MUV_EE(bpy.types.Panel):
+ """
+ Panel class: UV/Image Editor Enhancement
+ """
+
+ bl_space_type = 'IMAGE_EDITOR'
+ bl_region_type = 'TOOLS'
+ bl_label = "Editor Enhancement"
+ bl_category = "Magic UV"
+ bl_context = 'mesh_edit'
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw_header(self, _):
+ layout = self.layout
+ layout.label(text="", icon='IMAGE_COL')
+
+ def draw(self, context):
+ layout = self.layout
+ sc = context.scene
+ props = sc.muv_props
+
+ box = layout.box()
+ box.prop(sc, "muv_auvc_enabled", text="Align UV Cursor")
+ if sc.muv_auvc_enabled:
+ box.prop(sc, "muv_auvc_align_menu", expand=True)
+
+ col = box.column(align=True)
+
+ row = col.row(align=True)
+ ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
+ text="Left Top")
+ ops.position = 'LEFT_TOP'
+ ops.base = sc.muv_auvc_align_menu
+ ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
+ text="Middle Top")
+ ops.position = 'MIDDLE_TOP'
+ ops.base = sc.muv_auvc_align_menu
+ ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
+ text="Right Top")
+ ops.position = 'RIGHT_TOP'
+ ops.base = sc.muv_auvc_align_menu
+
+ row = col.row(align=True)
+ ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
+ text="Left Middle")
+ ops.position = 'LEFT_MIDDLE'
+ ops.base = sc.muv_auvc_align_menu
+ ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
+ text="Center")
+ ops.position = 'CENTER'
+ ops.base = sc.muv_auvc_align_menu
+ ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
+ text="Right Middle")
+ ops.position = 'RIGHT_MIDDLE'
+ ops.base = sc.muv_auvc_align_menu
+
+ row = col.row(align=True)
+ ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
+ text="Left Bottom")
+ ops.position = 'LEFT_BOTTOM'
+ ops.base = sc.muv_auvc_align_menu
+ ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
+ text="Middle Bottom")
+ ops.position = 'MIDDLE_BOTTOM'
+ ops.base = sc.muv_auvc_align_menu
+ ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
+ text="Right Bottom")
+ ops.position = 'RIGHT_BOTTOM'
+ ops.base = sc.muv_auvc_align_menu
+
+ box = layout.box()
+ box.prop(sc, "muv_uvcloc_enabled", text="UV Cursor Location")
+ if sc.muv_uvcloc_enabled:
+ box.prop(sc, "muv_auvc_cursor_loc", text="")
+
+ box = layout.box()
+ box.prop(sc, "muv_uvbb_enabled", text="UV Bounding Box")
+ if sc.muv_uvbb_enabled:
+ if props.uvbb.running is False:
+ box.operator(uv_bounding_box.MUV_UVBBUpdater.bl_idname,
+ text="Display", icon='PLAY')
+ else:
+ box.operator(uv_bounding_box.MUV_UVBBUpdater.bl_idname,
+ text="Hide", icon='PAUSE')
+ box.prop(sc, "muv_uvbb_uniform_scaling", text="Uniform Scaling")
+ box.prop(sc, "muv_uvbb_boundary", text="Boundary")
+
+ box = layout.box()
+ box.prop(sc, "muv_uvinsp_enabled", text="UV Inspection")
+ if sc.muv_uvinsp_enabled:
+ row = box.row()
+ if not sc.muv_props.uvinsp.display_running:
+ row.operator(uv_inspection.MUV_UVInspDisplay.bl_idname,
+ text="Display", icon='PLAY')
+ else:
+ row.operator(uv_inspection.MUV_UVInspDisplay.bl_idname,
+ text="Hide", icon='PAUSE')
+ row.operator(uv_inspection.MUV_UVInspUpdate.bl_idname,
+ text="Update")
+ row = box.row()
+ row.prop(sc, "muv_uvinsp_show_overlapped")
+ row.prop(sc, "muv_uvinsp_show_flipped")
+ row = box.row()
+ row.prop(sc, "muv_uvinsp_show_mode")
diff --git a/uv_magic_uv/ui/uvedit_uv_manipulation.py b/uv_magic_uv/ui/uvedit_uv_manipulation.py
new file mode 100644
index 00000000..2231cdf4
--- /dev/null
+++ b/uv_magic_uv/ui/uvedit_uv_manipulation.py
@@ -0,0 +1,117 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+
+from ..op import uv_inspection
+from ..op import align_uv
+from ..op import smooth_uv
+from ..op import pack_uv
+
+
+class IMAGE_PT_MUV_UVManip(bpy.types.Panel):
+ """
+ Panel class: UV Manipulation on Property Panel on UV/ImageEditor
+ """
+
+ bl_space_type = 'IMAGE_EDITOR'
+ bl_region_type = 'TOOLS'
+ bl_label = "UV Manipulation"
+ bl_category = "Magic UV"
+ bl_context = 'mesh_edit'
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw_header(self, _):
+ layout = self.layout
+ layout.label(text="", icon='IMAGE_COL')
+
+ def draw(self, context):
+ sc = context.scene
+ layout = self.layout
+
+ box = layout.box()
+ box.prop(sc, "muv_auv_enabled", text="Align UV")
+ if sc.muv_auv_enabled:
+ col = box.column()
+ row = col.row(align=True)
+ ops = row.operator(align_uv.MUV_AUVCircle.bl_idname, text="Circle")
+ ops.transmission = sc.muv_auv_transmission
+ ops.select = sc.muv_auv_select
+ ops = row.operator(align_uv.MUV_AUVStraighten.bl_idname,
+ text="Straighten")
+ ops.transmission = sc.muv_auv_transmission
+ ops.select = sc.muv_auv_select
+ ops.vertical = sc.muv_auv_vertical
+ ops.horizontal = sc.muv_auv_horizontal
+ row = col.row()
+ ops = row.operator(align_uv.MUV_AUVAxis.bl_idname, text="XY-axis")
+ ops.transmission = sc.muv_auv_transmission
+ ops.select = sc.muv_auv_select
+ ops.vertical = sc.muv_auv_vertical
+ ops.horizontal = sc.muv_auv_horizontal
+ ops.location = sc.muv_auv_location
+ row.prop(sc, "muv_auv_location", text="")
+
+ col = box.column(align=True)
+ row = col.row(align=True)
+ row.prop(sc, "muv_auv_transmission", text="Transmission")
+ row.prop(sc, "muv_auv_select", text="Select")
+ row = col.row(align=True)
+ row.prop(sc, "muv_auv_vertical", text="Vertical")
+ row.prop(sc, "muv_auv_horizontal", text="Horizontal")
+
+ box = layout.box()
+ box.prop(sc, "muv_smuv_enabled", text="Smooth UV")
+ if sc.muv_smuv_enabled:
+ ops = box.operator(smooth_uv.MUV_AUVSmooth.bl_idname,
+ text="Smooth")
+ ops.transmission = sc.muv_smuv_transmission
+ ops.select = sc.muv_smuv_select
+ ops.mesh_infl = sc.muv_smuv_mesh_infl
+ col = box.column(align=True)
+ row = col.row(align=True)
+ row.prop(sc, "muv_smuv_transmission", text="Transmission")
+ row.prop(sc, "muv_smuv_select", text="Select")
+ col.prop(sc, "muv_smuv_mesh_infl", text="Mesh Influence")
+
+ box = layout.box()
+ box.prop(sc, "muv_seluv_enabled", text="Select UV")
+ if sc.muv_seluv_enabled:
+ row = box.row(align=True)
+ row.operator(uv_inspection.MUV_UVInspSelectOverlapped.bl_idname)
+ row.operator(uv_inspection.MUV_UVInspSelectFlipped.bl_idname)
+
+ box = layout.box()
+ box.prop(sc, "muv_packuv_enabled", text="Pack UV (Extension)")
+ if sc.muv_packuv_enabled:
+ ops = box.operator(pack_uv.MUV_PackUV.bl_idname, text="Pack UV")
+ ops.allowable_center_deviation = \
+ sc.muv_packuv_allowable_center_deviation
+ ops.allowable_size_deviation = \
+ sc.muv_packuv_allowable_size_deviation
+ box.label("Allowable Center Deviation:")
+ box.prop(sc, "muv_packuv_allowable_center_deviation", text="")
+ box.label("Allowable Size Deviation:")
+ box.prop(sc, "muv_packuv_allowable_size_deviation", text="")
diff --git a/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py b/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py
new file mode 100644
index 00000000..530b1797
--- /dev/null
+++ b/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py
@@ -0,0 +1,81 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+
+from ..op import copy_paste_uv
+from ..op import transfer_uv
+
+
+class OBJECT_PT_MUV_CPUV(bpy.types.Panel):
+ """
+ Panel class: Copy/Paste UV on Property Panel on View3D
+ """
+
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+ bl_label = "Copy/Paste UV"
+ bl_category = "Magic UV"
+ bl_context = 'mesh_edit'
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw_header(self, _):
+ layout = self.layout
+ layout.label(text="", icon='IMAGE_COL')
+
+ def draw(self, context):
+ sc = context.scene
+ layout = self.layout
+
+ box = layout.box()
+ box.prop(sc, "muv_cpuv_enabled", text="Copy/Paste UV")
+ if sc.muv_cpuv_enabled:
+ row = box.row(align=True)
+ if sc.muv_cpuv_mode == 'DEFAULT':
+ row.menu(copy_paste_uv.MUV_CPUVCopyUVMenu.bl_idname,
+ text="Copy")
+ row.menu(copy_paste_uv.MUV_CPUVPasteUVMenu.bl_idname,
+ text="Paste")
+ elif sc.muv_cpuv_mode == 'SEL_SEQ':
+ row.menu(copy_paste_uv.MUV_CPUVSelSeqCopyUVMenu.bl_idname,
+ text="Copy")
+ row.menu(copy_paste_uv.MUV_CPUVSelSeqPasteUVMenu.bl_idname,
+ text="Paste")
+ box.prop(sc, "muv_cpuv_mode", expand=True)
+ box.prop(sc, "muv_cpuv_copy_seams", text="Seams")
+ box.prop(sc, "muv_cpuv_strategy", text="Strategy")
+
+ box = layout.box()
+ box.prop(sc, "muv_transuv_enabled", text="Transfer UV")
+ if sc.muv_transuv_enabled:
+ row = box.row(align=True)
+ row.operator(transfer_uv.MUV_TransUVCopy.bl_idname, text="Copy")
+ ops = row.operator(transfer_uv.MUV_TransUVPaste.bl_idname,
+ text="Paste")
+ ops.invert_normals = sc.muv_transuv_invert_normals
+ ops.copy_seams = sc.muv_transuv_copy_seams
+ row = box.row()
+ row.prop(sc, "muv_transuv_invert_normals", text="Invert Normals")
+ row.prop(sc, "muv_transuv_copy_seams", text="Seams")
diff --git a/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py b/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py
new file mode 100644
index 00000000..5aa968f2
--- /dev/null
+++ b/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py
@@ -0,0 +1,56 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+
+from ..op import copy_paste_uv_object
+
+
+class OBJECT_PT_MUV_CPUVObj(bpy.types.Panel):
+ """
+ Panel class: Copy/Paste UV on Property Panel on View3D
+ """
+
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+ bl_label = "Copy/Paste UV"
+ bl_category = "Magic UV"
+ bl_context = 'objectmode'
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw_header(self, _):
+ layout = self.layout
+ layout.label(text="", icon='IMAGE_COL')
+
+ def draw(self, context):
+ sc = context.scene
+ layout = self.layout
+
+ row = layout.row(align=True)
+ row.menu(copy_paste_uv_object.MUV_CPUVObjCopyUVMenu.bl_idname,
+ text="Copy")
+ row.menu(copy_paste_uv_object.MUV_CPUVObjPasteUVMenu.bl_idname,
+ text="Paste")
+ layout.prop(sc, "muv_cpuv_copy_seams", text="Copy Seams")
diff --git a/uv_magic_uv/ui/view3d_uv_manipulation.py b/uv_magic_uv/ui/view3d_uv_manipulation.py
new file mode 100644
index 00000000..76e0d3aa
--- /dev/null
+++ b/uv_magic_uv/ui/view3d_uv_manipulation.py
@@ -0,0 +1,180 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+
+from ..op import flip_rotate_uv
+from ..op import mirror_uv
+from ..op import move_uv
+from ..op import preserve_uv_aspect
+from ..op import texture_lock
+from ..op import texture_wrap
+from ..op import uv_sculpt
+from ..op import world_scale_uv
+
+
+class OBJECT_PT_MUV_UVManip(bpy.types.Panel):
+ """
+ Panel class: UV Manipulation on Property Panel on View3D
+ """
+
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+ bl_label = "UV Manipulation"
+ bl_category = "Magic UV"
+ bl_context = 'mesh_edit'
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw_header(self, _):
+ layout = self.layout
+ layout.label(text="", icon='IMAGE_COL')
+
+ def draw(self, context):
+ sc = context.scene
+ props = sc.muv_props
+ layout = self.layout
+
+ box = layout.box()
+ box.prop(sc, "muv_fliprot_enabled", text="Flip/Rotate UV")
+ if sc.muv_fliprot_enabled:
+ row = box.row()
+ ops = row.operator(flip_rotate_uv.MUV_FlipRot.bl_idname,
+ text="Flip/Rotate")
+ ops.seams = sc.muv_fliprot_seams
+ row.prop(sc, "muv_fliprot_seams", text="Seams")
+
+ box = layout.box()
+ box.prop(sc, "muv_mirroruv_enabled", text="Mirror UV")
+ if sc.muv_mirroruv_enabled:
+ row = box.row()
+ ops = row.operator(mirror_uv.MUV_MirrorUV.bl_idname, text="Mirror")
+ ops.axis = sc.muv_mirroruv_axis
+ row.prop(sc, "muv_mirroruv_axis", text="")
+
+ box = layout.box()
+ box.prop(sc, "muv_mvuv_enabled", text="Move UV")
+ if sc.muv_mvuv_enabled:
+ col = box.column()
+ col.operator(move_uv.MUV_MVUV.bl_idname, icon='PLAY', text="Start")
+ if props.mvuv.running:
+ col.enabled = False
+ else:
+ col.enabled = True
+
+ box = layout.box()
+ box.prop(sc, "muv_wsuv_enabled", text="World Scale UV")
+ if sc.muv_wsuv_enabled:
+ row = box.row(align=True)
+ row.operator(world_scale_uv.MUV_WSUVMeasure.bl_idname,
+ text="Measure")
+ ops = row.operator(world_scale_uv.MUV_WSUVApply.bl_idname,
+ text="Apply")
+ ops.origin = sc.muv_wsuv_origin
+ box.label("Source:")
+ sp = box.split(percentage=0.7)
+ col = sp.column(align=True)
+ col.prop(sc, "muv_wsuv_src_mesh_area", text="Mesh Area")
+ col.prop(sc, "muv_wsuv_src_uv_area", text="UV Area")
+ col.prop(sc, "muv_wsuv_src_density", text="Density")
+ col.enabled = False
+ sp = sp.split(percentage=1.0)
+ col = sp.column(align=True)
+ col.label("cm x cm")
+ col.label("px x px")
+ col.label("px/cm")
+ col.enabled = False
+ sp = box.split(percentage=0.3)
+ sp.label("Mode:")
+ sp = sp.split(percentage=1.0)
+ col = sp.column()
+ col.prop(sc, "muv_wsuv_mode", text="")
+ if sc.muv_wsuv_mode == 'USER':
+ col.prop(sc, "muv_wsuv_tgt_density", text="Density")
+ if sc.muv_wsuv_mode == 'SCALING':
+ col.prop(sc, "muv_wsuv_scaling_factor", text="Scaling Factor")
+ box.prop(sc, "muv_wsuv_origin", text="Origin")
+
+ box = layout.box()
+ box.prop(sc, "muv_preserve_uv_enabled", text="Preserve UV Aspect")
+ if sc.muv_preserve_uv_enabled:
+ row = box.row()
+ ops = row.operator(
+ preserve_uv_aspect.MUV_PreserveUVAspect.bl_idname,
+ text="Change Image")
+ ops.dest_img_name = sc.muv_preserve_uv_tex_image
+ ops.origin = sc.muv_preserve_uv_origin
+ row.prop(sc, "muv_preserve_uv_tex_image", text="")
+ box.prop(sc, "muv_preserve_uv_origin", text="Origin")
+
+ box = layout.box()
+ box.prop(sc, "muv_texlock_enabled", text="Texture Lock")
+ if sc.muv_texlock_enabled:
+ row = box.row(align=True)
+ col = row.column(align=True)
+ col.label("Normal Mode:")
+ col = row.column(align=True)
+ col.operator(texture_lock.MUV_TexLockStart.bl_idname, text="Lock")
+ ops = col.operator(texture_lock.MUV_TexLockStop.bl_idname,
+ text="Unlock")
+ ops.connect = sc.muv_texlock_connect
+ col.prop(sc, "muv_texlock_connect", text="Connect")
+
+ row = box.row(align=True)
+ row.label("Interactive Mode:")
+ if not props.texlock.intr_running:
+ row.operator(texture_lock.MUV_TexLockIntrStart.bl_idname,
+ icon='PLAY', text="Start")
+ else:
+ row.operator(texture_lock.MUV_TexLockIntrStop.bl_idname,
+ icon="PAUSE", text="Stop")
+
+ box = layout.box()
+ box.prop(sc, "muv_texwrap_enabled", text="Texture Wrap")
+ if sc.muv_texwrap_enabled:
+ row = box.row(align=True)
+ row.operator(texture_wrap.MUV_TexWrapRefer.bl_idname, text="Refer")
+ row.operator(texture_wrap.MUV_TexWrapSet.bl_idname, text="Set")
+ box.prop(sc, "muv_texwrap_set_and_refer")
+ box.prop(sc, "muv_texwrap_selseq")
+
+ box = layout.box()
+ box.prop(sc, "muv_uvsculpt_enabled", text="UV Sculpt")
+ if sc.muv_uvsculpt_enabled:
+ if not props.uvsculpt.running:
+ box.operator(uv_sculpt.MUV_UVSculptOps.bl_idname,
+ icon='PLAY', text="Start")
+ else:
+ box.operator(uv_sculpt.MUV_UVSculptOps.bl_idname,
+ icon='PAUSE', text="Stop")
+ col = box.column()
+ col.label("Brush:")
+ col.prop(sc, "muv_uvsculpt_radius")
+ col.prop(sc, "muv_uvsculpt_strength")
+ box.prop(sc, "muv_uvsculpt_tools")
+ if sc.muv_uvsculpt_tools == 'PINCH':
+ box.prop(sc, "muv_uvsculpt_pinch_invert")
+ elif sc.muv_uvsculpt_tools == 'RELAX':
+ box.prop(sc, "muv_uvsculpt_relax_method")
+ box.prop(sc, "muv_uvsculpt_show_brush")
diff --git a/uv_magic_uv/ui/view3d_uv_mapping.py b/uv_magic_uv/ui/view3d_uv_mapping.py
new file mode 100644
index 00000000..77c60c9e
--- /dev/null
+++ b/uv_magic_uv/ui/view3d_uv_mapping.py
@@ -0,0 +1,99 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.0"
+__date__ = "16 Feb 2018"
+
+import bpy
+
+from ..op import texture_projection
+from ..op import unwrap_constraint
+from ..op import uvw
+
+
+class OBJECT_PT_MUV_UVMapping(bpy.types.Panel):
+ """
+ Panel class: UV Mapping on Property Panel on View3D
+ """
+
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+ bl_label = "UV Mapping"
+ bl_category = "Magic UV"
+ bl_context = 'mesh_edit'
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw_header(self, _):
+ layout = self.layout
+ layout.label(text="", icon='IMAGE_COL')
+
+ def draw(self, context):
+ sc = context.scene
+ props = sc.muv_props
+ layout = self.layout
+
+ box = layout.box()
+ box.prop(sc, "muv_unwrapconst_enabled", text="Unwrap Constraint")
+ if sc.muv_unwrapconst_enabled:
+ ops = box.operator(
+ unwrap_constraint.MUV_UnwrapConstraint.bl_idname,
+ text="Unwrap")
+ ops.u_const = sc.muv_unwrapconst_u_const
+ ops.v_const = sc.muv_unwrapconst_v_const
+ row = box.row(align=True)
+ row.prop(sc, "muv_unwrapconst_u_const", text="U-Constraint")
+ row.prop(sc, "muv_unwrapconst_v_const", text="V-Constraint")
+
+ box = layout.box()
+ box.prop(sc, "muv_texproj_enabled", text="Texture Projection")
+ if sc.muv_texproj_enabled:
+ row = box.row()
+ if not props.texproj.running:
+ row.operator(texture_projection.MUV_TexProjStart.bl_idname,
+ text="Start", icon='PLAY')
+ else:
+ row.operator(texture_projection.MUV_TexProjStop.bl_idname,
+ text="Stop", icon='PAUSE')
+ row.prop(sc, "muv_texproj_tex_image", text="")
+ box.prop(sc, "muv_texproj_tex_transparency", text="Transparency")
+ col = box.column(align=True)
+ row = col.row()
+ row.prop(sc, "muv_texproj_adjust_window", text="Adjust Window")
+ if not sc.muv_texproj_adjust_window:
+ row.prop(sc, "muv_texproj_tex_magnitude", text="Magnitude")
+ col.prop(sc, "muv_texproj_apply_tex_aspect",
+ text="Texture Aspect Ratio")
+ col.prop(sc, "muv_texproj_assign_uvmap", text="Assign UVMap")
+ if props.texproj.running:
+ box.operator(texture_projection.MUV_TexProjProject.bl_idname,
+ text="Project")
+
+ box = layout.box()
+ box.prop(sc, "muv_uvw_enabled", text="UVW")
+ if sc.muv_uvw_enabled:
+ row = box.row(align=True)
+ ops = row.operator(uvw.MUV_UVWBoxMap.bl_idname, text="Box")
+ ops.assign_uvmap = sc.muv_uvw_assign_uvmap
+ ops = row.operator(uvw.MUV_UVWBestPlanerMap.bl_idname,
+ text="Best Planner")
+ ops.assign_uvmap = sc.muv_uvw_assign_uvmap
+ box.prop(sc, "muv_uvw_assign_uvmap", text="Assign UVMap")