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
This commit is contained in:
nutti 2018-02-16 21:04:36 +09:00
parent bfab29085c
commit fdc914d653
39 changed files with 5628 additions and 1380 deletions

View File

@ -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__":

592
uv_magic_uv/common.py Normal file
View File

@ -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, ""

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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)")

View File

@ -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

View File

@ -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

784
uv_magic_uv/op/align_uv.py Normal file
View File

@ -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'}

View File

@ -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'}

View File

@ -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
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.scene.objects.active = o
bpy.ops.object.mode_set(mode='EDIT')
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
if muv_common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
if (self.uv_map == "" or
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))
{'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 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]
# 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
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
bmesh.update_edit_mesh(obj.data)
if self.copy_seams is True:
obj.data.show_edge_seams = True
self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs))
self.report(
{'INFO'}, "%s's UV coordinates are pasted" % (obj.name))
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

View File

@ -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

View File

@ -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'}

View File

@ -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

View File

@ -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")

View File

@ -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'}

View File

@ -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

View File

@ -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

215
uv_magic_uv/op/smooth_uv.py Normal file
View File

@ -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'}

View File

@ -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()

View File

@ -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")

View File

@ -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'}

View File

@ -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)

View File

@ -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'}

View File

@ -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")

View File

@ -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'}

355
uv_magic_uv/op/uv_sculpt.py Normal file
View File

@ -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'}

View File

@ -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()

View File

@ -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()
def measure_wsuv_info(obj):
mesh_area = common.measure_mesh_area(obj)
uv_area = common.measure_uv_area(obj)
dv = v1 - v0
duv = uv1 - uv0
if not uv_area:
return None, None, None
scale = 0.0
if dv.magnitude > 0.00000001:
scale = duv.magnitude / dv.magnitude
if mesh_area == 0.0:
density = 0.0
else:
density = sqrt(uv_area) / sqrt(mesh_area)
return scale
def calc_face_scale(uv_layer, face):
es = 0.0
for i, l in enumerate(face.loops[1:]):
es = es + calc_edge_scale(uv_layer, face.loops[i], l)
return es
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]
sc.muv_wsuv_src_uv_area = uv_area
sc.muv_wsuv_src_mesh_area = mesh_area
sc.muv_wsuv_src_density = density
# measure average face size
scale = 0.0
for f in sel_faces:
scale = scale + calc_face_scale(uv_layer, f)
props.ref_scale = scale / len(sel_faces)
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':

216
uv_magic_uv/preferences.py Normal file
View File

@ -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")

755
uv_magic_uv/properites.py Normal file
View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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")

View File

@ -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="")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")