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:
parent
bfab29085c
commit
fdc914d653
|
@ -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__":
|
||||
|
|
|
@ -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, ""
|
|
@ -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)
|
|
@ -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
|
|
@ -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")
|
|
@ -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)")
|
|
@ -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
|
|
@ -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
|
|
@ -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'}
|
|
@ -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'}
|
|
@ -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
|
|
@ -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
|
|
@ -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'}
|
|
@ -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
|
|
@ -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")
|
|
@ -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'}
|
|
@ -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
|
|
@ -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
|
|
@ -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'}
|
|
@ -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()
|
|
@ -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")
|
|
@ -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'}
|
|
@ -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)
|
|
@ -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'}
|
|
@ -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")
|
|
@ -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'}
|
|
@ -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'}
|
|
@ -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()
|
||||
|
|
@ -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':
|
|
@ -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")
|
|
@ -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
|
|
@ -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
|
|
@ -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")
|
|
@ -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")
|
|
@ -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="")
|
|
@ -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")
|
|
@ -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")
|
|
@ -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")
|
|
@ -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")
|
Loading…
Reference in New Issue