uv_magic_uv commit to addons release: T51064
Initial commit to addons release. Task: T51064
This commit is contained in:
parent
d9c25b4390
commit
8a17c01627
|
@ -0,0 +1,126 @@
|
|||
# <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.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
|
||||
bl_info = {
|
||||
"name": "Magic UV",
|
||||
"author": "Nutti, Mifth, Jace Priester, kgeogeo, mem, Keith (Wahooney) Boshoff, McBuff, MaxRobinot",
|
||||
"version": (4, 3),
|
||||
"blender": (2, 77, 0),
|
||||
"location": "See Add-ons Preferences",
|
||||
"description": "UV Manipulator Tools. See Add-ons Preferences for details",
|
||||
"warning": "",
|
||||
"support": "COMMUNITY",
|
||||
"wiki_url": "https://github.com/nutti/Magic-UV/wikil",
|
||||
"tracker_url": "https://github.com/nutti/Copy-And-Paste-UV",
|
||||
"category": "UV"
|
||||
}
|
||||
|
||||
if "bpy" in locals():
|
||||
import imp
|
||||
imp.reload(muv_preferences)
|
||||
imp.reload(muv_menu)
|
||||
imp.reload(muv_common)
|
||||
imp.reload(muv_props)
|
||||
imp.reload(muv_cpuv_ops)
|
||||
imp.reload(muv_cpuv_selseq_ops)
|
||||
imp.reload(muv_fliprot_ops)
|
||||
imp.reload(muv_transuv_ops)
|
||||
imp.reload(muv_uvbb_ops)
|
||||
imp.reload(muv_mvuv_ops)
|
||||
imp.reload(muv_texproj_ops)
|
||||
imp.reload(muv_packuv_ops)
|
||||
imp.reload(muv_texlock_ops)
|
||||
imp.reload(muv_mirroruv_ops)
|
||||
imp.reload(muv_wsuv_ops)
|
||||
imp.reload(muv_unwrapconst_ops)
|
||||
imp.reload(muv_preserve_uv_aspect)
|
||||
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
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def view3d_uvmap_menu_fn(self, context):
|
||||
self.layout.separator()
|
||||
self.layout.menu(muv_menu.MUV_CPUVMenu.bl_idname, icon="PLUGIN")
|
||||
self.layout.operator(muv_fliprot_ops.MUV_FlipRot.bl_idname, icon="PLUGIN")
|
||||
self.layout.menu(muv_menu.MUV_TransUVMenu.bl_idname, icon="PLUGIN")
|
||||
self.layout.operator(muv_mvuv_ops.MUV_MVUV.bl_idname, icon="PLUGIN")
|
||||
self.layout.menu(muv_menu.MUV_TexLockMenu.bl_idname, icon="PLUGIN")
|
||||
self.layout.operator(
|
||||
muv_mirroruv_ops.MUV_MirrorUV.bl_idname, icon="PLUGIN")
|
||||
self.layout.menu(muv_menu.MUV_WSUVMenu.bl_idname, icon="PLUGIN")
|
||||
self.layout.operator(
|
||||
muv_unwrapconst_ops.MUV_UnwrapConstraint.bl_idname, icon='PLUGIN')
|
||||
self.layout.menu(
|
||||
muv_preserve_uv_aspect.MUV_PreserveUVAspectMenu.bl_idname,
|
||||
icon='PLUGIN')
|
||||
|
||||
|
||||
def image_uvs_menu_fn(self, context):
|
||||
self.layout.separator()
|
||||
self.layout.operator(muv_packuv_ops.MUV_PackUV.bl_idname, icon="PLUGIN")
|
||||
|
||||
|
||||
def view3d_object_menu_fn(self, context):
|
||||
self.layout.separator()
|
||||
self.layout.menu(muv_menu.MUV_CPUVObjMenu.bl_idname, icon="PLUGIN")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
bpy.types.VIEW3D_MT_uv_map.append(view3d_uvmap_menu_fn)
|
||||
bpy.types.IMAGE_MT_uvs.append(image_uvs_menu_fn)
|
||||
bpy.types.VIEW3D_MT_object.append(view3d_object_menu_fn)
|
||||
muv_props.init_props(bpy.types.Scene)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
bpy.types.VIEW3D_MT_uv_map.remove(view3d_uvmap_menu_fn)
|
||||
bpy.types.IMAGE_MT_uvs.remove(image_uvs_menu_fn)
|
||||
bpy.types.VIEW3D_MT_object.remove(view3d_object_menu_fn)
|
||||
muv_props.clear_props(bpy.types.Scene)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -0,0 +1,86 @@
|
|||
# <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.3"
|
||||
__date__ = "1 Apr 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
|
||||
else:
|
||||
if bpy.app.version[1] > minor:
|
||||
return 1
|
||||
else:
|
||||
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)
|
|
@ -0,0 +1,455 @@
|
|||
# <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__ = "4.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from bpy.props import StringProperty, BoolProperty, IntProperty, EnumProperty
|
||||
from . import muv_common
|
||||
|
||||
|
||||
def memorize_view_3d_mode(fn):
|
||||
def __memorize_view_3d_mode(self, context):
|
||||
mode_orig = bpy.context.object.mode
|
||||
result = fn(self, context)
|
||||
bpy.ops.object.mode_set(mode=mode_orig)
|
||||
return result
|
||||
return __memorize_view_3d_mode
|
||||
|
||||
|
||||
class MUV_CPUVCopyUV(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Copy UV coordinate
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_cpuv_copy_uv"
|
||||
bl_label = "Copy UV (Operation)"
|
||||
bl_description = "Copy UV coordinate (Operation)"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
uv_map = StringProperty(options={'HIDDEN'})
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.cpuv
|
||||
if self.uv_map == "":
|
||||
self.report({'INFO'}, "Copy UV coordinate")
|
||||
else:
|
||||
self.report(
|
||||
{'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:
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
# get UV layer
|
||||
if self.uv_map == "":
|
||||
if not bm.loops.layers.uv:
|
||||
self.report(
|
||||
{'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
else:
|
||||
uv_layer = bm.loops.layers.uv[self.uv_map]
|
||||
|
||||
# get selected face
|
||||
props.src_uvs = []
|
||||
props.src_pin_uvs = []
|
||||
for face in bm.faces:
|
||||
if face.select:
|
||||
uvs = [l[uv_layer].uv.copy() for l in face.loops]
|
||||
pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
|
||||
props.src_uvs.append(uvs)
|
||||
props.src_pin_uvs.append(pin_uvs)
|
||||
if len(props.src_uvs) == 0 or len(props.src_pin_uvs) == 0:
|
||||
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_CPUVCopyUVMenu(bpy.types.Menu):
|
||||
"""
|
||||
Menu class: Copy UV coordinate
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_cpuv_copy_uv_menu"
|
||||
bl_label = "Copy UV"
|
||||
bl_description = "Copy UV coordinate"
|
||||
|
||||
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_CPUVCopyUV.bl_idname,
|
||||
text="[Default]",
|
||||
icon="PLUGIN"
|
||||
).uv_map = ""
|
||||
for m in uv_maps:
|
||||
layout.operator(
|
||||
MUV_CPUVCopyUV.bl_idname,
|
||||
text=m,
|
||||
icon="PLUGIN"
|
||||
).uv_map = m
|
||||
|
||||
|
||||
class MUV_CPUVPasteUV(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Paste UV coordinate
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_cpuv_paste_uv"
|
||||
bl_label = "Paste UV (Operation)"
|
||||
bl_description = "Paste UV coordinate (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
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.cpuv
|
||||
if len(props.src_uvs) == 0 or len(props.src_pin_uvs) == 0:
|
||||
self.report({'WARNING'}, "Need copy UV at first")
|
||||
return {'CANCELLED'}
|
||||
if self.uv_map == "":
|
||||
self.report({'INFO'}, "Paste UV coordinate")
|
||||
else:
|
||||
self.report(
|
||||
{'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:
|
||||
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_face_indices = []
|
||||
for face in bm.faces:
|
||||
if face.select:
|
||||
dest_face_indices.append(face.index)
|
||||
uvs = [l[uv_layer].uv.copy() for l in face.loops]
|
||||
pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
|
||||
dest_uvs.append(uvs)
|
||||
dest_pin_uvs.append(pin_uvs)
|
||||
if len(dest_uvs) == 0 or len(dest_pin_uvs) == 0:
|
||||
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" +
|
||||
"(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
|
||||
duv = None
|
||||
if self.strategy == 'N_N':
|
||||
suv = props.src_uvs[i]
|
||||
spuv = props.src_pin_uvs[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)]
|
||||
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]
|
||||
# flip UVs
|
||||
if self.flip_copied_uv is True:
|
||||
suvs_fr.reverse()
|
||||
spuvs_fr.reverse()
|
||||
# rotate UVs
|
||||
for _ in range(self.rotate_copied_uv):
|
||||
uv = suvs_fr.pop()
|
||||
pin_uv = spuvs_fr.pop()
|
||||
suvs_fr.insert(0, uv)
|
||||
spuvs_fr.insert(0, pin_uv)
|
||||
# paste UVs
|
||||
for l, suv, spuv in zip(bm.faces[idx].loops, suvs_fr, spuvs_fr):
|
||||
l[uv_layer].uv = suv
|
||||
l[uv_layer].pin_uv = spuv
|
||||
self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs))
|
||||
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_CPUVPasteUVMenu(bpy.types.Menu):
|
||||
"""
|
||||
Menu class: Paste UV coordinate
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_cpuv_paste_uv_menu"
|
||||
bl_label = "Paste UV"
|
||||
bl_description = "Paste UV coordinate"
|
||||
|
||||
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_CPUVPasteUV.bl_idname,
|
||||
text="[Default]", icon="PLUGIN").uv_map = ""
|
||||
for m in uv_maps:
|
||||
layout.operator(
|
||||
MUV_CPUVPasteUV.bl_idname,
|
||||
text=m, icon="PLUGIN").uv_map = m
|
||||
|
||||
|
||||
class MUV_CPUVObjCopyUV(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Copy UV coordinate per object
|
||||
"""
|
||||
|
||||
bl_idname = "object.muv_cpuv_obj_copy_uv"
|
||||
bl_label = "Copy UV"
|
||||
bl_description = "Copy UV coordinate"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
uv_map = 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 muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
# get UV layer
|
||||
if self.uv_map == "":
|
||||
if not bm.loops.layers.uv:
|
||||
self.report(
|
||||
{'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
else:
|
||||
uv_layer = bm.loops.layers.uv[self.uv_map]
|
||||
|
||||
# get selected face
|
||||
props.src_uvs = []
|
||||
props.src_pin_uvs = []
|
||||
for face in bm.faces:
|
||||
uvs = [l[uv_layer].uv.copy() for l in face.loops]
|
||||
pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
|
||||
props.src_uvs.append(uvs)
|
||||
props.src_pin_uvs.append(pin_uvs)
|
||||
|
||||
self.report({'INFO'}, "%s's UV coordinates are copied" % (obj.name))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_CPUVObjCopyUVMenu(bpy.types.Menu):
|
||||
"""
|
||||
Menu class: Copy UV coordinate per object
|
||||
"""
|
||||
|
||||
bl_idname = "object.muv_cpuv_obj_copy_uv_menu"
|
||||
bl_label = "Copy UV"
|
||||
bl_description = "Copy UV coordinate per object"
|
||||
|
||||
def draw(self, _):
|
||||
layout = self.layout
|
||||
# create sub menu
|
||||
uv_maps = bpy.context.active_object.data.uv_textures.keys()
|
||||
layout.operator(
|
||||
MUV_CPUVObjCopyUV.bl_idname,
|
||||
text="[Default]", icon="PLUGIN").uv_map = ""
|
||||
for m in uv_maps:
|
||||
layout.operator(
|
||||
MUV_CPUVObjCopyUV.bl_idname,
|
||||
text=m, icon="PLUGIN").uv_map = m
|
||||
|
||||
|
||||
class MUV_CPUVObjPasteUV(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Paste UV coordinate per object
|
||||
"""
|
||||
|
||||
bl_idname = "object.muv_cpuv_obj_paste_uv"
|
||||
bl_label = "Paste UV"
|
||||
bl_description = "Paste UV coordinate"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
uv_map = StringProperty(options={'HIDDEN'})
|
||||
|
||||
@memorize_view_3d_mode
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.cpuv_obj
|
||||
if len(props.src_uvs) == 0 or len(props.src_pin_uvs) == 0:
|
||||
self.report({'WARNING'}, "Need copy UV at first")
|
||||
return {'CANCELLED'}
|
||||
|
||||
for o in bpy.data.objects:
|
||||
if not hasattr(o.data, "uv_textures") or not o.select:
|
||||
continue
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.context.scene.objects.active = o
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
if (self.uv_map == "" or
|
||||
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_face_indices = []
|
||||
for face in bm.faces:
|
||||
dest_face_indices.append(face.index)
|
||||
uvs = [l[uv_layer].uv.copy() for l in face.loops]
|
||||
pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
|
||||
dest_uvs.append(uvs)
|
||||
dest_pin_uvs.append(pin_uvs)
|
||||
if len(props.src_uvs) != len(dest_uvs):
|
||||
self.report(
|
||||
{'WARNING'},
|
||||
"Number of faces is different from copied "
|
||||
+ "(src:%d, dest:%d)"
|
||||
% (len(props.src_uvs), len(dest_uvs))
|
||||
)
|
||||
return {'CANCELLED'}
|
||||
|
||||
# paste
|
||||
for i, idx in enumerate(dest_face_indices):
|
||||
suv = props.src_uvs[i]
|
||||
spuv = props.src_pin_uvs[i]
|
||||
duv = dest_uvs[i]
|
||||
if len(suv) != len(duv):
|
||||
self.report({'WARNING'}, "Some faces are different size")
|
||||
return {'CANCELLED'}
|
||||
suvs_fr = [uv for uv in suv]
|
||||
spuvs_fr = [pin_uv for pin_uv in spuv]
|
||||
# paste UVs
|
||||
for l, suv, spuv in zip(
|
||||
bm.faces[idx].loops, suvs_fr, spuvs_fr):
|
||||
l[uv_layer].uv = suv
|
||||
l[uv_layer].pin_uv = spuv
|
||||
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
self.report(
|
||||
{'INFO'}, "%s's UV coordinates are pasted" % (obj.name))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_CPUVObjPasteUVMenu(bpy.types.Menu):
|
||||
"""
|
||||
Menu class: Paste UV coordinate per object
|
||||
"""
|
||||
|
||||
bl_idname = "object.muv_cpuv_obj_paste_uv_menu"
|
||||
bl_label = "Paste UV"
|
||||
bl_description = "Paste UV coordinate per object"
|
||||
|
||||
def draw(self, _):
|
||||
layout = self.layout
|
||||
# create sub menu
|
||||
uv_maps = []
|
||||
for obj in bpy.data.objects:
|
||||
if hasattr(obj.data, "uv_textures") and obj.select:
|
||||
uv_maps.extend(obj.data.uv_textures.keys())
|
||||
uv_maps = list(set(uv_maps))
|
||||
layout.operator(
|
||||
MUV_CPUVObjPasteUV.bl_idname,
|
||||
text="[Default]", icon="PLUGIN").uv_map = ""
|
||||
for m in uv_maps:
|
||||
layout.operator(
|
||||
MUV_CPUVObjPasteUV.bl_idname,
|
||||
text=m, icon="PLUGIN").uv_map = m
|
|
@ -0,0 +1,249 @@
|
|||
# <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.3"
|
||||
__date__ = "1 Apr 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 = []
|
||||
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]
|
||||
props.src_uvs.append(uvs)
|
||||
props.src_pin_uvs.append(pin_uvs)
|
||||
if len(props.src_uvs) == 0 or len(props.src_pin_uvs) == 0:
|
||||
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="PLUGIN").uv_map = ""
|
||||
for m in uv_maps:
|
||||
layout.operator(
|
||||
MUV_CPUVSelSeqCopyUV.bl_idname,
|
||||
text=m, icon="PLUGIN").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)
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.cpuv_selseq
|
||||
if len(props.src_uvs) == 0 or len(props.src_pin_uvs) == 0:
|
||||
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_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]
|
||||
dest_uvs.append(uvs)
|
||||
dest_pin_uvs.append(pin_uvs)
|
||||
if len(dest_uvs) == 0 or len(dest_pin_uvs) == 0:
|
||||
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
|
||||
duv = None
|
||||
if self.strategy == 'N_N':
|
||||
suv = props.src_uvs[i]
|
||||
spuv = props.src_pin_uvs[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)]
|
||||
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]
|
||||
# flip UVs
|
||||
if self.flip_copied_uv is True:
|
||||
suvs_fr.reverse()
|
||||
spuvs_fr.reverse()
|
||||
# rotate UVs
|
||||
for _ in range(self.rotate_copied_uv):
|
||||
uv = suvs_fr.pop()
|
||||
pin_uv = spuvs_fr.pop()
|
||||
suvs_fr.insert(0, uv)
|
||||
spuvs_fr.insert(0, pin_uv)
|
||||
# paste UVs
|
||||
for l, suv, spuv in zip(bm.faces[idx].loops, suvs_fr, spuvs_fr):
|
||||
l[uv_layer].uv = suv
|
||||
l[uv_layer].pin_uv = spuv
|
||||
|
||||
self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs))
|
||||
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_CPUVSelSeqPasteUVMenu(bpy.types.Menu):
|
||||
"""
|
||||
Menu class: Paste UV coordinate by selection sequence
|
||||
"""
|
||||
|
||||
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="PLUGIN").uv_map = ""
|
||||
for m in uv_maps:
|
||||
layout.operator(
|
||||
MUV_CPUVSelSeqPasteUV.bl_idname,
|
||||
text=m, icon="PLUGIN").uv_map = m
|
|
@ -0,0 +1,107 @@
|
|||
# <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.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from bpy.props import BoolProperty, IntProperty
|
||||
from . import muv_common
|
||||
|
||||
|
||||
class MUV_FlipRot(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Flip and Rotate UV coordinate
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_fliprot"
|
||||
bl_label = "Flip/Rotate UV"
|
||||
bl_description = "Flip/Rotate UV coordinate"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
flip = BoolProperty(
|
||||
name="Flip UV",
|
||||
description="Flip UV...",
|
||||
default=False
|
||||
)
|
||||
rotate = IntProperty(
|
||||
default=0,
|
||||
name="Rotate UV",
|
||||
min=0,
|
||||
max=30
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
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:
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
# get UV layer
|
||||
if not bm.loops.layers.uv:
|
||||
self.report({'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
# get selected face
|
||||
dest_uvs = []
|
||||
dest_pin_uvs = []
|
||||
dest_face_indices = []
|
||||
for face in bm.faces:
|
||||
if face.select:
|
||||
dest_face_indices.append(face.index)
|
||||
uvs = [l[uv_layer].uv.copy() for l in face.loops]
|
||||
pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
|
||||
dest_uvs.append(uvs)
|
||||
dest_pin_uvs.append(pin_uvs)
|
||||
if len(dest_uvs) == 0 or len(dest_pin_uvs) == 0:
|
||||
self.report({'WARNING'}, "No faces are selected")
|
||||
return {'CANCELLED'}
|
||||
self.report({'INFO'}, "%d face(s) are selected" % len(dest_uvs))
|
||||
|
||||
# paste
|
||||
for idx, duvs, dpuvs in zip(dest_face_indices, dest_uvs, dest_pin_uvs):
|
||||
duvs_fr = [uv for uv in duvs]
|
||||
dpuvs_fr = [pin_uv for pin_uv in dpuvs]
|
||||
# flip UVs
|
||||
if self.flip is True:
|
||||
duvs_fr.reverse()
|
||||
dpuvs_fr.reverse()
|
||||
# rotate UVs
|
||||
for _ in range(self.rotate):
|
||||
uv = duvs_fr.pop()
|
||||
pin_uv = dpuvs_fr.pop()
|
||||
duvs_fr.insert(0, uv)
|
||||
dpuvs_fr.insert(0, pin_uv)
|
||||
# paste UVs
|
||||
for l, duv, dpuv in zip(bm.faces[idx].loops, duvs_fr, dpuvs_fr):
|
||||
l[uv_layer].uv = duv
|
||||
l[uv_layer].pin_uv = dpuv
|
||||
|
||||
self.report({'INFO'}, "%d face(s) are flipped/rotated" % len(dest_uvs))
|
||||
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
return {'FINISHED'}
|
|
@ -0,0 +1,122 @@
|
|||
# <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.3"
|
||||
__date__ = "1 Apr 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
|
||||
|
||||
|
||||
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="PLUGIN")
|
||||
self.layout.menu(
|
||||
muv_cpuv_ops.MUV_CPUVPasteUVMenu.bl_idname, icon="PLUGIN")
|
||||
self.layout.menu(
|
||||
muv_cpuv_selseq_ops.MUV_CPUVSelSeqCopyUVMenu.bl_idname,
|
||||
icon="PLUGIN")
|
||||
self.layout.menu(
|
||||
muv_cpuv_selseq_ops.MUV_CPUVSelSeqPasteUVMenu.bl_idname,
|
||||
icon="PLUGIN")
|
||||
|
||||
|
||||
class MUV_CPUVObjMenu(bpy.types.Menu):
|
||||
"""
|
||||
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="PLUGIN")
|
||||
self.layout.menu(
|
||||
muv_cpuv_ops.MUV_CPUVObjPasteUVMenu.bl_idname, icon="PLUGIN")
|
||||
|
||||
|
||||
class MUV_TransUVMenu(bpy.types.Menu):
|
||||
"""
|
||||
Menu class: Master menu of Transfer UV coordinate
|
||||
"""
|
||||
|
||||
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="PLUGIN")
|
||||
self.layout.operator(
|
||||
muv_transuv_ops.MUV_TransUVPaste.bl_idname, icon="PLUGIN")
|
||||
|
||||
|
||||
class MUV_TexLockMenu(bpy.types.Menu):
|
||||
"""
|
||||
Menu class: Master menu of Texture Lock
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_texlock_menu"
|
||||
bl_label = "Texture Lock"
|
||||
bl_description = "Lock texture when vertices of mesh (Preserve UV)"
|
||||
|
||||
def draw(self, _):
|
||||
self.layout.operator(
|
||||
muv_texlock_ops.MUV_TexLockStart.bl_idname, icon="PLUGIN")
|
||||
self.layout.operator(
|
||||
muv_texlock_ops.MUV_TexLockStop.bl_idname, icon="PLUGIN")
|
||||
self.layout.operator(
|
||||
muv_texlock_ops.MUV_TexLockIntrStart.bl_idname, icon="PLUGIN")
|
||||
self.layout.operator(
|
||||
muv_texlock_ops.MUV_TexLockIntrStop.bl_idname, icon="PLUGIN")
|
||||
|
||||
|
||||
class MUV_WSUVMenu(bpy.types.Menu):
|
||||
"""
|
||||
Menu class: Master menu of world scale UV
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_wsuv_menu"
|
||||
bl_label = "World Scale UV"
|
||||
bl_description = ""
|
||||
|
||||
def draw(self, _):
|
||||
self.layout.operator(
|
||||
muv_wsuv_ops.MUV_WSUVMeasure.bl_idname, icon="PLUGIN")
|
||||
self.layout.operator(
|
||||
muv_wsuv_ops.MUV_WSUVApply.bl_idname, icon="PLUGIN")
|
|
@ -0,0 +1,152 @@
|
|||
# <pep8-80 compliant>
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
__author__ = "Keith (Wahooney) Boshoff, Nutti <nutti.metro@gmail.com>"
|
||||
__status__ = "production"
|
||||
__version__ = "4.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.props import EnumProperty, FloatProperty
|
||||
import bmesh
|
||||
from mathutils import Vector
|
||||
from . import muv_common
|
||||
|
||||
|
||||
class MUV_MirrorUV(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Mirror UV
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_mirror_uv"
|
||||
bl_label = "Mirror UV"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
axis = EnumProperty(
|
||||
items=(
|
||||
('X', "X", "Mirror Along X axis"),
|
||||
('Y', "Y", "Mirror Along Y axis"),
|
||||
('Z', "Z", "Mirror Along Z axis")
|
||||
),
|
||||
name="Axis",
|
||||
description="Mirror Axis",
|
||||
default='X'
|
||||
)
|
||||
error = FloatProperty(
|
||||
name="Error",
|
||||
description="Error threshold",
|
||||
default=0.001,
|
||||
min=0.0,
|
||||
max=100.0,
|
||||
soft_min=0.0,
|
||||
soft_max=1.0
|
||||
)
|
||||
|
||||
def __is_vector_similar(self, v1, v2, error):
|
||||
"""
|
||||
Check if two vectors are similar, within an error threshold
|
||||
"""
|
||||
within_err_x = abs(v2.x - v1.x) < error
|
||||
within_err_y = abs(v2.y - v1.y) < error
|
||||
within_err_z = abs(v2.z - v1.z) < error
|
||||
|
||||
return within_err_x and within_err_y and within_err_z
|
||||
|
||||
def __mirror_uvs(self, uv_layer, src, dst, axis, error):
|
||||
"""
|
||||
Copy UV coordinates from one UV face to another
|
||||
"""
|
||||
for sl in src.loops:
|
||||
suv = sl[uv_layer].uv.copy()
|
||||
svco = sl.vert.co.copy()
|
||||
for dl in dst.loops:
|
||||
dvco = dl.vert.co.copy()
|
||||
if axis == 'X':
|
||||
dvco.x = -dvco.x
|
||||
elif axis == 'Y':
|
||||
dvco.y = -dvco.y
|
||||
elif axis == 'Z':
|
||||
dvco.z = -dvco.z
|
||||
|
||||
if self.__is_vector_similar(svco, dvco, error):
|
||||
dl[uv_layer].uv = suv.copy()
|
||||
|
||||
def __get_face_center(self, face):
|
||||
"""
|
||||
Get center coordinate of the face
|
||||
"""
|
||||
center = Vector((0.0, 0.0, 0.0))
|
||||
for v in face.verts:
|
||||
center = center + v.co
|
||||
|
||||
return center / len(face.verts)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj and obj.type == 'MESH'
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
error = self.error
|
||||
axis = self.axis
|
||||
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.faces.ensure_lookup_table()
|
||||
if not bm.loops.layers.uv:
|
||||
self.report({'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
faces = [f for f in bm.faces if f.select]
|
||||
for f_dst in faces:
|
||||
count = len(f_dst.verts)
|
||||
for f_src in bm.faces:
|
||||
# check if this is a candidate to do mirror UV
|
||||
if f_src.index == f_dst.index:
|
||||
continue
|
||||
if count != len(f_src.verts):
|
||||
continue
|
||||
|
||||
# test if the vertices x values are the same sign
|
||||
dst = self.__get_face_center(f_dst)
|
||||
src = self.__get_face_center(f_src)
|
||||
if (dst.x > 0 and src.x > 0) or (dst.x < 0 and src.x < 0):
|
||||
continue
|
||||
|
||||
# invert source axis
|
||||
if axis == 'X':
|
||||
src.x = -src.x
|
||||
elif axis == 'Y':
|
||||
src.y = -src.z
|
||||
elif axis == 'Z':
|
||||
src.z = -src.z
|
||||
|
||||
# do mirror UV
|
||||
if self.__is_vector_similar(dst, src, error):
|
||||
self.__mirror_uvs(
|
||||
uv_layer, f_src, f_dst, self.axis, self.error)
|
||||
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
return {'FINISHED'}
|
|
@ -0,0 +1,126 @@
|
|||
# <pep8-80 compliant>
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
__author__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>"
|
||||
__status__ = "production"
|
||||
__version__ = "4.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
class MUV_MVUV(bpy.types.Operator):
|
||||
"""
|
||||
Operator class: Move UV from View3D
|
||||
"""
|
||||
|
||||
bl_idname = "view3d.muv_mvuv"
|
||||
bl_label = "Move the UV from View3D"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def __init__(self):
|
||||
self.__topology_dict = []
|
||||
self.__prev_mouse = Vector((0.0, 0.0))
|
||||
self.__offset_uv = Vector((0.0, 0.0))
|
||||
self.__prev_offset_uv = Vector((0.0, 0.0))
|
||||
self.__first_time = True
|
||||
self.__ini_uvs = []
|
||||
self.__running = False
|
||||
|
||||
def __find_uv(self, context):
|
||||
bm = bmesh.from_edit_mesh(context.object.data)
|
||||
topology_dict = []
|
||||
uvs = []
|
||||
active_uv = bm.loops.layers.uv.active
|
||||
for fidx, f in enumerate(bm.faces):
|
||||
for vidx, v in enumerate(f.verts):
|
||||
if v.select:
|
||||
uvs.append(f.loops[vidx][active_uv].uv.copy())
|
||||
topology_dict.append([fidx, vidx])
|
||||
|
||||
return topology_dict, uvs
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.edit_object
|
||||
|
||||
def modal(self, context, event):
|
||||
if self.__first_time is True:
|
||||
self.__prev_mouse = Vector((
|
||||
event.mouse_region_x, event.mouse_region_y))
|
||||
self.__first_time = False
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
# move UV
|
||||
div = 10000
|
||||
self.__offset_uv += Vector((
|
||||
(event.mouse_region_x - self.__prev_mouse.x) / div,
|
||||
(event.mouse_region_y - self.__prev_mouse.y) / div))
|
||||
ouv = self.__offset_uv
|
||||
pouv = self.__prev_offset_uv
|
||||
vec = Vector((ouv.x - ouv.y, ouv.x + ouv.y))
|
||||
dv = vec - pouv
|
||||
self.__prev_offset_uv = vec
|
||||
self.__prev_mouse = Vector((
|
||||
event.mouse_region_x, event.mouse_region_y))
|
||||
|
||||
# check if operation is started
|
||||
if self.__running is True:
|
||||
if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
|
||||
self.__running = False
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
# update UV
|
||||
obj = context.object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
active_uv = bm.loops.layers.uv.active
|
||||
for fidx, vidx in self.__topology_dict:
|
||||
l = bm.faces[fidx].loops[vidx]
|
||||
l[active_uv].uv = l[active_uv].uv + dv
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
# check mouse preference
|
||||
if context.user_preferences.inputs.select_mouse == 'RIGHT':
|
||||
confirm_btn = 'LEFTMOUSE'
|
||||
cancel_btn = 'RIGHTMOUSE'
|
||||
else:
|
||||
confirm_btn = 'RIGHTMOUSE'
|
||||
cancel_btn = 'LEFTMOUSE'
|
||||
|
||||
# cancelled
|
||||
if event.type == cancel_btn and event.value == 'PRESS':
|
||||
for (fidx, vidx), uv in zip(self.__topology_dict, self.__ini_uvs):
|
||||
bm.faces[fidx].loops[vidx][active_uv].uv = uv
|
||||
return {'FINISHED'}
|
||||
# confirmed
|
||||
if event.type == confirm_btn and event.value == 'PRESS':
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def execute(self, context):
|
||||
self.__first_time = True
|
||||
self.__running = True
|
||||
context.window_manager.modal_handler_add(self)
|
||||
self.__topology_dict, self.__ini_uvs = self.__find_uv(context)
|
||||
return {'RUNNING_MODAL'}
|
|
@ -0,0 +1,285 @@
|
|||
# <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.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
|
||||
from math import fabs
|
||||
from collections import defaultdict
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import mathutils
|
||||
from bpy.props import FloatProperty, FloatVectorProperty, BoolProperty
|
||||
from mathutils import Vector
|
||||
|
||||
from . import muv_common
|
||||
|
||||
|
||||
class MUV_PackUV(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Pack UV with same UV islands are integrated
|
||||
Island matching algorithm
|
||||
- Same center of UV island
|
||||
- Same size of UV island
|
||||
- Same number of UV
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_packuv"
|
||||
bl_label = "Pack UV"
|
||||
bl_description = "Pack UV (Same UV Islands are integrated)"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
rotate = BoolProperty(
|
||||
name="Rotate",
|
||||
description="Rotate option used by default pack UV function",
|
||||
default=False)
|
||||
margin = FloatProperty(
|
||||
name="Margin",
|
||||
description="Margin used by default pack UV function",
|
||||
min=0,
|
||||
max=1,
|
||||
default=0.001)
|
||||
allowable_center_deviation = FloatVectorProperty(
|
||||
name="Allowable Center Deviation",
|
||||
description="Allowable center deviation to judge same UV island",
|
||||
min=0.000001,
|
||||
max=0.1,
|
||||
default=(0.001, 0.001),
|
||||
size=2)
|
||||
allowable_size_deviation = FloatVectorProperty(
|
||||
name="Allowable Size Deviation",
|
||||
description="Allowable sizse deviation to judge same UV island",
|
||||
min=0.000001,
|
||||
max=0.1,
|
||||
default=(0.001, 0.001),
|
||||
size=2)
|
||||
|
||||
def __init__(self):
|
||||
self.__face_to_verts = defaultdict(set)
|
||||
self.__vert_to_faces = defaultdict(set)
|
||||
|
||||
def execute(self, _):
|
||||
obj = bpy.context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.faces.ensure_lookup_table()
|
||||
if not bm.loops.layers.uv:
|
||||
self.report({'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
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)
|
||||
num_group = self.__group_island(island_info)
|
||||
|
||||
loop_lists = [l for f in bm.faces for l in f.loops]
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
# pack UV
|
||||
for gidx in range(num_group):
|
||||
group = list(filter(
|
||||
lambda i, idx=gidx: i['group'] == idx, island_info))
|
||||
for f in group[0]['faces']:
|
||||
f['face'].select = True
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
bpy.ops.uv.select_all(action='SELECT')
|
||||
bpy.ops.uv.pack_islands(rotate=self.rotate, margin=self.margin)
|
||||
|
||||
# copy/paste UV among same islands
|
||||
for gidx in range(num_group):
|
||||
group = list(filter(
|
||||
lambda i, idx=gidx: i['group'] == idx, island_info))
|
||||
if len(group) <= 1:
|
||||
continue
|
||||
for g in group[1:]:
|
||||
for (src_face, dest_face) in zip(
|
||||
group[0]['sorted'], g['sorted']):
|
||||
for (src_loop, dest_loop) in zip(
|
||||
src_face['face'].loops, dest_face['face'].loops):
|
||||
loop_lists[dest_loop.index][uv_layer].uv = loop_lists[
|
||||
src_loop.index][uv_layer].uv
|
||||
|
||||
# restore face/UV selection
|
||||
bpy.ops.uv.select_all(action='DESELECT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
for f in selected_faces:
|
||||
f.select = True
|
||||
bpy.ops.uv.select_all(action='SELECT')
|
||||
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def __sort_island_faces(self, kd, uvs, isl1, isl2):
|
||||
"""
|
||||
Sort faces in island
|
||||
"""
|
||||
|
||||
sorted_faces = []
|
||||
for f in isl1['sorted']:
|
||||
_, idx, _ = kd.find(
|
||||
Vector((f['ave_uv'].x, f['ave_uv'].y, 0.0)))
|
||||
sorted_faces.append(isl2['faces'][uvs[idx]['face_idx']])
|
||||
return sorted_faces
|
||||
|
||||
def __group_island(self, island_info):
|
||||
"""
|
||||
Group island
|
||||
"""
|
||||
|
||||
num_group = 0
|
||||
while True:
|
||||
# search islands which is not parsed yet
|
||||
isl_1 = None
|
||||
for isl_1 in island_info:
|
||||
if isl_1['group'] == -1:
|
||||
break
|
||||
else:
|
||||
break # all faces are parsed
|
||||
if isl_1 is None:
|
||||
break
|
||||
isl_1['group'] = num_group
|
||||
isl_1['sorted'] = isl_1['faces']
|
||||
|
||||
# search same island
|
||||
for isl_2 in island_info:
|
||||
if isl_2['group'] == -1:
|
||||
dcx = isl_2['center'].x - isl_1['center'].x
|
||||
dcy = isl_2['center'].y - isl_1['center'].y
|
||||
dsx = isl_2['size'].x - isl_1['size'].x
|
||||
dsy = isl_2['size'].y - isl_1['size'].y
|
||||
center_x_matched = (
|
||||
fabs(dcx) < self.allowable_center_deviation[0])
|
||||
center_y_matched = (
|
||||
fabs(dcy) < self.allowable_center_deviation[1])
|
||||
size_x_matched = (
|
||||
fabs(dsx) < self.allowable_size_deviation[0])
|
||||
size_y_matched = (
|
||||
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'])
|
||||
# are islands have same?
|
||||
if center_matched and size_matched and num_uv_matched:
|
||||
isl_2['group'] = num_group
|
||||
kd = mathutils.kdtree.KDTree(len(isl_2['faces']))
|
||||
uvs = [
|
||||
{
|
||||
'uv': Vector(
|
||||
(f['ave_uv'].x, f['ave_uv'].y, 0.0)
|
||||
),
|
||||
'face_idx': fidx
|
||||
} for fidx, f in enumerate(isl_2['faces'])
|
||||
]
|
||||
for i, uv in enumerate(uvs):
|
||||
kd.insert(uv['uv'], i)
|
||||
kd.balance()
|
||||
# sort faces for copy/paste UV
|
||||
isl_2['sorted'] = self.__sort_island_faces(
|
||||
kd, uvs, isl_1, isl_2)
|
||||
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 len(faces_left) > 0:
|
||||
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
|
|
@ -0,0 +1,141 @@
|
|||
# <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.3"
|
||||
__date__ = "1 Apr 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 know about this add-on.")
|
||||
column.label("https://github.com/nutti/Magic-UV/wiki/Tutorial")
|
||||
|
||||
layout.label("Location:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
sp = row.split(percentage=0.3)
|
||||
sp.label("View3D > U")
|
||||
sp = sp.split(percentage=1.0)
|
||||
col = sp.column(align=True)
|
||||
col.label("Copy/Paste UV Coordinates")
|
||||
col.label("Copy/Paste UV Coordinates (by selection sequence)")
|
||||
col.label("Flip/Rotate UVs")
|
||||
col.label("Transfer UV")
|
||||
col.label("Move UV from 3D View")
|
||||
col.label("Texture Lock")
|
||||
col.label("Mirror UV")
|
||||
col.label("World Scale UV")
|
||||
col.label("Unwrap Constraint")
|
||||
col.label("Preserve UV Aspect")
|
||||
|
||||
row = layout.row(align=True)
|
||||
sp = row.split(percentage=0.3)
|
||||
sp.label("View3D > Object")
|
||||
sp = sp.split(percentage=1.0)
|
||||
col = sp.column(align=True)
|
||||
col.label("Copy/Paste UV Coordinates (Among same objects)")
|
||||
|
||||
row = layout.row(align=True)
|
||||
sp = row.split(percentage=0.3)
|
||||
sp.label("ImageEditor > Property Panel")
|
||||
sp = sp.split(percentage=1.0)
|
||||
col = sp.column(align=True)
|
||||
col.label("Manipulate UV with Bounding Box in UV Editor")
|
||||
|
||||
row = layout.row(align=True)
|
||||
sp = row.split(percentage=0.3)
|
||||
sp.label("View3D > Property Panel")
|
||||
sp = sp.split(percentage=1.0)
|
||||
col = sp.column(align=True)
|
||||
col.label("Texture Projection")
|
||||
|
||||
row = layout.row(align=True)
|
||||
sp = row.split(percentage=0.3)
|
||||
sp.label("ImageEditor > UVs")
|
||||
sp = sp.split(percentage=1.0)
|
||||
col = sp.column(align=True)
|
||||
col.label("Pack UV (with same UV island packing)")
|
|
@ -0,0 +1,119 @@
|
|||
# <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.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from bpy.props import StringProperty
|
||||
from mathutils import Vector
|
||||
from . import muv_common
|
||||
|
||||
|
||||
class MUV_PreserveUVAspect(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Preserve UV Aspect
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_preserve_uv_aspect"
|
||||
bl_label = "Preserve UV Aspect"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
dest_img_name = StringProperty(options={'HIDDEN'})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj and obj.type == 'MESH'
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
if not bm.loops.layers.uv:
|
||||
self.report({'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
tex_layer = bm.faces.layers.tex.verify()
|
||||
|
||||
sel_faces = [f for f in bm.faces if f.select]
|
||||
dest_img = bpy.data.images[self.dest_img_name]
|
||||
|
||||
info = {}
|
||||
|
||||
for f in sel_faces:
|
||||
if not f[tex_layer].image in info.keys():
|
||||
info[f[tex_layer].image] = {}
|
||||
info[f[tex_layer].image]['faces'] = []
|
||||
info[f[tex_layer].image]['faces'].append(f)
|
||||
|
||||
for img in info:
|
||||
src_img = img
|
||||
ratio = Vector((
|
||||
dest_img.size[0] / src_img.size[0],
|
||||
dest_img.size[1] / src_img.size[1]))
|
||||
origin = Vector((100000.0, 100000.0))
|
||||
for f in info[img]['faces']:
|
||||
for l in f.loops:
|
||||
uv = l[uv_layer].uv
|
||||
origin.x = min(uv.x, origin.x)
|
||||
origin.y = min(uv.y, origin.y)
|
||||
info[img]['ratio'] = ratio
|
||||
info[img]['origin'] = origin
|
||||
|
||||
for img in info:
|
||||
for f in info[img]['faces']:
|
||||
f[tex_layer].image = dest_img
|
||||
for l in f.loops:
|
||||
uv = l[uv_layer].uv
|
||||
diff = uv - info[img]['origin']
|
||||
diff.x = diff.x / info[img]['ratio'].x
|
||||
diff.y = diff.y / info[img]['ratio'].y
|
||||
uv.x = origin.x + diff.x
|
||||
uv.y = origin.y + diff.y
|
||||
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_PreserveUVAspectMenu(bpy.types.Menu):
|
||||
"""
|
||||
Menu class: Preserve UV Aspect
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_preserve_uv_aspect_menu"
|
||||
bl_label = "Preserve UV Aspect"
|
||||
bl_description = "Preserve UV Aspect"
|
||||
|
||||
def draw(self, _):
|
||||
layout = self.layout
|
||||
# create sub menu
|
||||
for key in bpy.data.images.keys():
|
||||
layout.operator(
|
||||
MUV_PreserveUVAspect.bl_idname,
|
||||
text=key, icon="PLUGIN").dest_img_name = key
|
|
@ -0,0 +1,143 @@
|
|||
# <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.3"
|
||||
__date__ = "1 Apr 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 = []
|
||||
|
||||
|
||||
class MUV_CPUVSelSeqProps():
|
||||
src_uvs = []
|
||||
src_pin_uvs = []
|
||||
|
||||
|
||||
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,431 @@
|
|||
# <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.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
import math
|
||||
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
|
||||
|
||||
|
||||
def get_vco(verts_orig, loop):
|
||||
"""
|
||||
Get vertex original coordinate from loop
|
||||
"""
|
||||
for vo in verts_orig:
|
||||
if vo["vidx"] == loop.vert.index and vo["moved"] is False:
|
||||
return vo["vco"]
|
||||
return loop.vert.co
|
||||
|
||||
|
||||
def get_link_loops(vert):
|
||||
"""
|
||||
Get loop linked to vertex
|
||||
"""
|
||||
link_loops = []
|
||||
for f in vert.link_faces:
|
||||
adj_loops = []
|
||||
for loop in f.loops:
|
||||
# self loop
|
||||
if loop.vert == vert:
|
||||
l = loop
|
||||
# linked loop
|
||||
else:
|
||||
for e in loop.vert.link_edges:
|
||||
if e.other_vert(loop.vert) == vert:
|
||||
adj_loops.append(loop)
|
||||
if len(adj_loops) < 2:
|
||||
return None
|
||||
|
||||
link_loops.append({"l": l, "l0": adj_loops[0], "l1": adj_loops[1]})
|
||||
return link_loops
|
||||
|
||||
|
||||
def get_ini_geom(link_loop, uv_layer, verts_orig, v_orig):
|
||||
"""
|
||||
Get initial geometory
|
||||
(Get interior angle of face in vertex/UV space)
|
||||
"""
|
||||
u = link_loop["l"][uv_layer].uv
|
||||
v0 = get_vco(verts_orig, link_loop["l0"])
|
||||
u0 = link_loop["l0"][uv_layer].uv
|
||||
v1 = get_vco(verts_orig, link_loop["l1"])
|
||||
u1 = link_loop["l1"][uv_layer].uv
|
||||
|
||||
# get interior angle of face in vertex space
|
||||
v0v1 = v1 - v0
|
||||
v0v = v_orig["vco"] - v0
|
||||
v1v = v_orig["vco"] - v1
|
||||
theta0 = v0v1.angle(v0v)
|
||||
theta1 = v0v1.angle(-v1v)
|
||||
if (theta0 + theta1) > math.pi:
|
||||
theta0 = v0v1.angle(-v0v)
|
||||
theta1 = v0v1.angle(v1v)
|
||||
|
||||
# get interior angle of face in UV space
|
||||
u0u1 = u1 - u0
|
||||
u0u = u - u0
|
||||
u1u = u - u1
|
||||
phi0 = u0u1.angle(u0u)
|
||||
phi1 = u0u1.angle(-u1u)
|
||||
if (phi0 + phi1) > math.pi:
|
||||
phi0 = u0u1.angle(-u0u)
|
||||
phi1 = u0u1.angle(u1u)
|
||||
|
||||
# get direction of linked UV coordinate
|
||||
# this will be used to judge whether angle is more or less than 180 degree
|
||||
dir0 = u0u1.cross(u0u) > 0
|
||||
dir1 = u0u1.cross(u1u) > 0
|
||||
|
||||
return {
|
||||
"theta0": theta0,
|
||||
"theta1": theta1,
|
||||
"phi0": phi0,
|
||||
"phi1": phi1,
|
||||
"dir0": dir0,
|
||||
"dir1": dir1}
|
||||
|
||||
|
||||
def get_target_uv(link_loop, uv_layer, verts_orig, v, ini_geom):
|
||||
"""
|
||||
Get target UV coordinate
|
||||
"""
|
||||
v0 = get_vco(verts_orig, link_loop["l0"])
|
||||
lo0 = link_loop["l0"]
|
||||
v1 = get_vco(verts_orig, link_loop["l1"])
|
||||
lo1 = link_loop["l1"]
|
||||
|
||||
# get interior angle of face in vertex space
|
||||
v0v1 = v1 - v0
|
||||
v0v = v.co - v0
|
||||
v1v = v.co - v1
|
||||
theta0 = v0v1.angle(v0v)
|
||||
theta1 = v0v1.angle(-v1v)
|
||||
if (theta0 + theta1) > math.pi:
|
||||
theta0 = v0v1.angle(-v0v)
|
||||
theta1 = v0v1.angle(v1v)
|
||||
|
||||
# calculate target interior angle in UV space
|
||||
phi0 = theta0 * ini_geom["phi0"] / ini_geom["theta0"]
|
||||
phi1 = theta1 * ini_geom["phi1"] / ini_geom["theta1"]
|
||||
|
||||
uv0 = lo0[uv_layer].uv
|
||||
uv1 = lo1[uv_layer].uv
|
||||
|
||||
# calculate target vertex coordinate from target interior angle
|
||||
tuv0, tuv1 = calc_tri_vert(uv0, uv1, phi0, phi1)
|
||||
|
||||
# target UV coordinate depends on direction, so judge using direction of
|
||||
# linked UV coordinate
|
||||
u0u1 = uv1 - uv0
|
||||
u0u = tuv0 - uv0
|
||||
u1u = tuv0 - uv1
|
||||
dir0 = u0u1.cross(u0u) > 0
|
||||
dir1 = u0u1.cross(u1u) > 0
|
||||
if (ini_geom["dir0"] != dir0) or (ini_geom["dir1"] != dir1):
|
||||
return tuv1
|
||||
|
||||
return tuv0
|
||||
|
||||
|
||||
def calc_tri_vert(v0, v1, angle0, angle1):
|
||||
"""
|
||||
Calculate rest coordinate from other coordinates and angle of end
|
||||
"""
|
||||
angle = math.pi - angle0 - angle1
|
||||
|
||||
alpha = atan2(v1.y - v0.y, v1.x - v0.x)
|
||||
d = (v1.x - v0.x) / cos(alpha)
|
||||
a = d * sin(angle0) / sin(angle)
|
||||
b = d * sin(angle1) / sin(angle)
|
||||
s = (a + b + d) / 2.0
|
||||
if fabs(d) < 0.0000001:
|
||||
xd = 0
|
||||
yd = 0
|
||||
else:
|
||||
xd = (b * b - a * a + d * d) / (2 * d)
|
||||
yd = 2 * sqrt(s * (s - a) * (s - b) * (s - d)) / d
|
||||
x1 = xd * cos(alpha) - yd * sin(alpha) + v0.x
|
||||
y1 = xd * sin(alpha) + yd * cos(alpha) + v0.y
|
||||
x2 = xd * cos(alpha) + yd * sin(alpha) + v0.x
|
||||
y2 = xd * sin(alpha) - yd * cos(alpha) + v0.y
|
||||
|
||||
return Vector((x1, y1)), Vector((x2, y2))
|
||||
|
||||
|
||||
class MUV_TexLockStart(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Start Texture Lock
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_texlock_start"
|
||||
bl_label = "Start"
|
||||
bl_description = "Start Texture Lock"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.texlock
|
||||
obj = bpy.context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
if not bm.loops.layers.uv:
|
||||
self.report(
|
||||
{'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
|
||||
props.verts_orig = [
|
||||
{"vidx": v.index, "vco": v.co.copy(), "moved": False}
|
||||
for v in bm.verts if v.select]
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_TexLockStop(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Stop Texture Lock
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_texlock_stop"
|
||||
bl_label = "Stop"
|
||||
bl_description = "Start Texture Lock"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
connect = BoolProperty(
|
||||
name="Connect UV",
|
||||
default=True)
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.texlock
|
||||
obj = bpy.context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
if not bm.loops.layers.uv:
|
||||
self.report(
|
||||
{'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
verts = [v.index for v in bm.verts if v.select]
|
||||
verts_orig = props.verts_orig
|
||||
|
||||
# move UV followed by vertex coordinate
|
||||
for vidx, v_orig in zip(verts, verts_orig):
|
||||
if vidx != v_orig["vidx"]:
|
||||
self.report({'ERROR'}, "Internal Error")
|
||||
return {"CANCELLED"}
|
||||
|
||||
v = bm.verts[vidx]
|
||||
link_loops = get_link_loops(v)
|
||||
|
||||
result = []
|
||||
|
||||
for ll in link_loops:
|
||||
ini_geom = get_ini_geom(ll, uv_layer, verts_orig, v_orig)
|
||||
target_uv = get_target_uv(
|
||||
ll, uv_layer, verts_orig, v, ini_geom)
|
||||
result.append({"l": ll["l"], "uv": target_uv})
|
||||
|
||||
# connect other face's UV
|
||||
if self.connect:
|
||||
ave = Vector((0.0, 0.0))
|
||||
for r in result:
|
||||
ave = ave + r["uv"]
|
||||
ave = ave / len(result)
|
||||
for r in result:
|
||||
r["l"][uv_layer].uv = ave
|
||||
else:
|
||||
for r in result:
|
||||
r["l"][uv_layer].uv = r["uv"]
|
||||
v_orig["moved"] = True
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_TexLockUpdater(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Texture locking updater
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_texlock_updater"
|
||||
bl_label = "Texture Lock Updater"
|
||||
bl_description = "Texture Lock Updater"
|
||||
|
||||
def __init__(self):
|
||||
self.__timer = None
|
||||
|
||||
def __update_uv(self, context):
|
||||
"""
|
||||
Update UV when vertex coordinates are changed
|
||||
"""
|
||||
props = context.scene.muv_props.texlock
|
||||
obj = bpy.context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
if not bm.loops.layers.uv:
|
||||
self.report({'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
verts = [v.index for v in bm.verts if v.select]
|
||||
verts_orig = props.intr_verts_orig
|
||||
|
||||
for vidx, v_orig in zip(verts, verts_orig):
|
||||
if vidx != v_orig["vidx"]:
|
||||
self.report({'ERROR'}, "Internal Error")
|
||||
return {"CANCELLED"}
|
||||
|
||||
v = bm.verts[vidx]
|
||||
link_loops = get_link_loops(v)
|
||||
|
||||
result = []
|
||||
for ll in link_loops:
|
||||
ini_geom = get_ini_geom(ll, uv_layer, verts_orig, v_orig)
|
||||
target_uv = get_target_uv(
|
||||
ll, uv_layer, verts_orig, v, ini_geom)
|
||||
result.append({"l": ll["l"], "uv": target_uv})
|
||||
|
||||
# UV connect option is always true, because it raises
|
||||
# unexpected behavior
|
||||
ave = Vector((0.0, 0.0))
|
||||
for r in result:
|
||||
ave = ave + r["uv"]
|
||||
ave = ave / len(result)
|
||||
for r in result:
|
||||
r["l"][uv_layer].uv = ave
|
||||
v_orig["moved"] = True
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
muv_common.redraw_all_areas()
|
||||
props.intr_verts_orig = [
|
||||
{"vidx": v.index, "vco": v.co.copy(), "moved": False}
|
||||
for v in bm.verts if v.select]
|
||||
|
||||
def modal(self, context, event):
|
||||
props = context.scene.muv_props.texlock
|
||||
if context.area:
|
||||
context.area.tag_redraw()
|
||||
if props.intr_running is False:
|
||||
self.__handle_remove(context)
|
||||
return {'FINISHED'}
|
||||
if event.type == 'TIMER':
|
||||
self.__update_uv(context)
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def __handle_add(self, context):
|
||||
if self.__timer is None:
|
||||
self.__timer = context.window_manager.event_timer_add(
|
||||
0.10, context.window)
|
||||
context.window_manager.modal_handler_add(self)
|
||||
|
||||
def __handle_remove(self, context):
|
||||
if self.__timer is not None:
|
||||
context.window_manager.event_timer_remove(self.__timer)
|
||||
self.__timer = None
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.texlock
|
||||
if props.intr_running is False:
|
||||
self.__handle_add(context)
|
||||
props.intr_running = True
|
||||
return {'RUNNING_MODAL'}
|
||||
else:
|
||||
props.intr_running = False
|
||||
if context.area:
|
||||
context.area.tag_redraw()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_TexLockIntrStart(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Start texture locking (Interactive mode)
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_texlock_intr_start"
|
||||
bl_label = "Texture Lock Start (Interactive mode)"
|
||||
bl_description = "Texture Lock Start (Realtime UV update)"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.texlock
|
||||
if props.intr_running is True:
|
||||
return {'CANCELLED'}
|
||||
|
||||
obj = bpy.context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
if not bm.loops.layers.uv:
|
||||
self.report({'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
|
||||
props.intr_verts_orig = [
|
||||
{"vidx": v.index, "vco": v.co.copy(), "moved": False}
|
||||
for v in bm.verts if v.select]
|
||||
|
||||
bpy.ops.uv.muv_texlock_updater()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Texture lock (Stop, Interactive mode)
|
||||
class MUV_TexLockIntrStop(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Stop texture locking (interactive mode)
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_texlock_intr_stop"
|
||||
bl_label = "Texture Lock Stop (Interactive mode)"
|
||||
bl_description = "Texture Lock Stop (Realtime UV update)"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.texlock
|
||||
if props.intr_running is False:
|
||||
return {'CANCELLED'}
|
||||
|
||||
bpy.ops.uv.muv_texlock_updater()
|
||||
|
||||
return {'FINISHED'}
|
|
@ -0,0 +1,328 @@
|
|||
# <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.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
import bpy
|
||||
import bgl
|
||||
import bmesh
|
||||
import mathutils
|
||||
from bpy_extras import view3d_utils
|
||||
|
||||
from . import muv_common
|
||||
|
||||
|
||||
Rect = namedtuple('Rect', 'x0 y0 x1 y1')
|
||||
Rect2 = namedtuple('Rect2', 'x y width height')
|
||||
|
||||
|
||||
def get_canvas(context, magnitude):
|
||||
"""
|
||||
Get canvas to be renderred texture
|
||||
"""
|
||||
sc = context.scene
|
||||
prefs = context.user_preferences.addons["uv_magic_uv"].preferences
|
||||
|
||||
region_w = context.region.width
|
||||
region_h = context.region.height
|
||||
canvas_w = region_w - prefs.texproj_canvas_padding[0] * 2.0
|
||||
canvas_h = region_h - prefs.texproj_canvas_padding[1] * 2.0
|
||||
|
||||
img = bpy.data.images[sc.muv_texproj_tex_image]
|
||||
tex_w = img.size[0]
|
||||
tex_h = img.size[1]
|
||||
|
||||
center_x = region_w * 0.5
|
||||
center_y = region_h * 0.5
|
||||
|
||||
if sc.muv_texproj_adjust_window:
|
||||
ratio_x = canvas_w / tex_w
|
||||
ratio_y = canvas_h / tex_h
|
||||
if sc.muv_texproj_apply_tex_aspect:
|
||||
ratio = ratio_y if ratio_x > ratio_y else ratio_x
|
||||
len_x = ratio * tex_w
|
||||
len_y = ratio * tex_h
|
||||
else:
|
||||
len_x = canvas_w
|
||||
len_y = canvas_h
|
||||
else:
|
||||
if sc.muv_texproj_apply_tex_aspect:
|
||||
len_x = tex_w * magnitude
|
||||
len_y = tex_h * magnitude
|
||||
else:
|
||||
len_x = region_w * magnitude
|
||||
len_y = region_h * magnitude
|
||||
|
||||
x0 = int(center_x - len_x * 0.5)
|
||||
y0 = int(center_y - len_y * 0.5)
|
||||
x1 = int(center_x + len_x * 0.5)
|
||||
y1 = int(center_y + len_y * 0.5)
|
||||
|
||||
return Rect(x0, y0, x1, y1)
|
||||
|
||||
|
||||
def rect_to_rect2(rect):
|
||||
"""
|
||||
Convert Rect1 to Rect2
|
||||
"""
|
||||
|
||||
return Rect2(rect.x0, rect.y0, rect.x1 - rect.x0, rect.y1 - rect.y0)
|
||||
|
||||
|
||||
def region_to_canvas(rg_vec, canvas):
|
||||
"""
|
||||
Convert screen region to canvas
|
||||
"""
|
||||
|
||||
cv_rect = rect_to_rect2(canvas)
|
||||
cv_vec = mathutils.Vector()
|
||||
cv_vec.x = (rg_vec.x - cv_rect.x) / cv_rect.width
|
||||
cv_vec.y = (rg_vec.y - cv_rect.y) / cv_rect.height
|
||||
|
||||
return cv_vec
|
||||
|
||||
|
||||
class MUV_TexProjRenderer(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Render selected texture
|
||||
No operation (only rendering texture)
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_texproj_renderer"
|
||||
bl_description = "Render selected texture"
|
||||
bl_label = "Texture renderer"
|
||||
|
||||
__handle = None
|
||||
|
||||
@staticmethod
|
||||
def handle_add(obj, context):
|
||||
MUV_TexProjRenderer.__handle = bpy.types.SpaceView3D.draw_handler_add(
|
||||
MUV_TexProjRenderer.draw_texture,
|
||||
(obj, context), 'WINDOW', 'POST_PIXEL')
|
||||
|
||||
@staticmethod
|
||||
def handle_remove():
|
||||
if MUV_TexProjRenderer.__handle is not None:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(
|
||||
MUV_TexProjRenderer.__handle, 'WINDOW')
|
||||
MUV_TexProjRenderer.__handle = None
|
||||
|
||||
@staticmethod
|
||||
def draw_texture(_, context):
|
||||
sc = context.scene
|
||||
|
||||
# no textures are selected
|
||||
if sc.muv_texproj_tex_image == "None":
|
||||
return
|
||||
|
||||
# get texture to be renderred
|
||||
img = bpy.data.images[sc.muv_texproj_tex_image]
|
||||
|
||||
# setup rendering region
|
||||
rect = get_canvas(context, sc.muv_texproj_tex_magnitude)
|
||||
positions = [
|
||||
[rect.x0, rect.y0],
|
||||
[rect.x0, rect.y1],
|
||||
[rect.x1, rect.y1],
|
||||
[rect.x1, rect.y0]
|
||||
]
|
||||
tex_coords = [
|
||||
[0.0, 0.0],
|
||||
[0.0, 1.0],
|
||||
[1.0, 1.0],
|
||||
[1.0, 0.0]
|
||||
]
|
||||
|
||||
# OpenGL configuration
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glEnable(bgl.GL_TEXTURE_2D)
|
||||
if img.bindcode:
|
||||
bind = img.bindcode[0]
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, bind)
|
||||
bgl.glTexParameteri(
|
||||
bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR)
|
||||
bgl.glTexParameteri(
|
||||
bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR)
|
||||
bgl.glTexEnvi(
|
||||
bgl.GL_TEXTURE_ENV, bgl.GL_TEXTURE_ENV_MODE, bgl.GL_MODULATE)
|
||||
|
||||
# render texture
|
||||
bgl.glBegin(bgl.GL_QUADS)
|
||||
bgl.glColor4f(1.0, 1.0, 1.0, sc.muv_texproj_tex_transparency)
|
||||
for (v1, v2), (u, v) in zip(positions, tex_coords):
|
||||
bgl.glTexCoord2f(u, v)
|
||||
bgl.glVertex2f(v1, v2)
|
||||
bgl.glEnd()
|
||||
|
||||
|
||||
class MUV_TexProjStart(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Start Texture Projection
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_texproj_start"
|
||||
bl_label = "Start Texture Projection"
|
||||
bl_description = "Start Texture Projection"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.texproj
|
||||
if props.running is False:
|
||||
MUV_TexProjRenderer.handle_add(self, context)
|
||||
props.running = True
|
||||
if context.area:
|
||||
context.area.tag_redraw()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_TexProjStop(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Stop Texture Projection
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_texproj_stop"
|
||||
bl_label = "Stop Texture Projection"
|
||||
bl_description = "Stop Texture Projection"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.texproj
|
||||
if props.running is True:
|
||||
MUV_TexProjRenderer.handle_remove()
|
||||
props.running = False
|
||||
if context.area:
|
||||
context.area.tag_redraw()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_TexProjProject(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Project texture
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_texproj_project"
|
||||
bl_label = "Project Texture"
|
||||
bl_description = "Project Texture"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
sc = context.scene
|
||||
|
||||
if sc.muv_texproj_tex_image == "None":
|
||||
self.report({'WARNING'}, "No textures are selected")
|
||||
return {'CANCELLED'}
|
||||
_, region, space = muv_common.get_space(
|
||||
'VIEW_3D', 'WINDOW', 'VIEW_3D')
|
||||
|
||||
# get faces to be texture projected
|
||||
obj = context.active_object
|
||||
world_mat = obj.matrix_world
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_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'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
tex_layer = bm.faces.layers.tex.verify()
|
||||
|
||||
sel_faces = [f for f in bm.faces if f.select]
|
||||
|
||||
# transform 3d space to screen region
|
||||
v_screen = [
|
||||
view3d_utils.location_3d_to_region_2d(
|
||||
region,
|
||||
space.region_3d,
|
||||
world_mat * l.vert.co)
|
||||
for f in sel_faces for l in f.loops
|
||||
]
|
||||
|
||||
# transform screen region to canvas
|
||||
v_canvas = [
|
||||
region_to_canvas(
|
||||
v,
|
||||
get_canvas(bpy.context, sc.muv_texproj_tex_magnitude))
|
||||
for v in v_screen
|
||||
]
|
||||
|
||||
# project texture to object
|
||||
i = 0
|
||||
for f in sel_faces:
|
||||
f[tex_layer].image = bpy.data.images[sc.muv_texproj_tex_image]
|
||||
for l in f.loops:
|
||||
l[uv_layer].uv = v_canvas[i].to_2d()
|
||||
i = i + 1
|
||||
|
||||
muv_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='PLUGIN')
|
||||
|
||||
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,345 @@
|
|||
# <pep8-80 compliant>
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
__author__ = "Nutti <nutti.metro@gmail.com>, Mifth, MaxRobinot"
|
||||
__status__ = "production"
|
||||
__version__ = "4.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from bpy.props import BoolProperty
|
||||
|
||||
from . import muv_props
|
||||
from . import muv_common
|
||||
|
||||
|
||||
class MUV_TransUVCopy(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Transfer UV copy
|
||||
Topological based copy
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_transuv_copy"
|
||||
bl_label = "Transfer UV Copy"
|
||||
bl_description = "Transfer UV Copy (Topological based copy)"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
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:
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
# get UV layer
|
||||
if not bm.loops.layers.uv:
|
||||
self.report({'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
props.topology_copied.clear()
|
||||
|
||||
# get selected faces
|
||||
active_face = bm.faces.active
|
||||
sel_faces = [face for face in bm.faces if face.select]
|
||||
if len(sel_faces) != 2:
|
||||
self.report({'WARNING'}, "Two faces must be selected")
|
||||
return {'CANCELLED'}
|
||||
if not active_face or active_face not in sel_faces:
|
||||
self.report({'WARNING'}, "Two faces must be active")
|
||||
return {'CANCELLED'}
|
||||
|
||||
# parse all faces according to selection
|
||||
active_face_nor = active_face.normal.copy()
|
||||
all_sorted_faces = main_parse(
|
||||
self, uv_layer, sel_faces, active_face,
|
||||
active_face_nor)
|
||||
|
||||
if all_sorted_faces:
|
||||
for face_data in all_sorted_faces.values():
|
||||
uv_loops = face_data[2]
|
||||
uvs = [l.uv.copy() for l in uv_loops]
|
||||
pin_uvs = [l.pin_uv for l in uv_loops]
|
||||
props.topology_copied.append([uvs, pin_uvs])
|
||||
|
||||
bmesh.update_edit_mesh(active_obj.data)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_TransUVPaste(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Transfer UV paste
|
||||
Topological based paste
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_transuv_paste"
|
||||
bl_label = "Transfer UV Paste"
|
||||
bl_description = "Transfer UV Paste (Topological based paste)"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
invert_normals = BoolProperty(
|
||||
name="Invert Normals",
|
||||
description="Invert Normals",
|
||||
default=False)
|
||||
|
||||
def execute(self, context):
|
||||
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:
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
# get UV layer
|
||||
if not bm.loops.layers.uv:
|
||||
self.report({'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
# get selection history
|
||||
all_sel_faces = [
|
||||
e for e in bm.select_history
|
||||
if isinstance(e, bmesh.types.BMFace) and e.select]
|
||||
if len(all_sel_faces) % 2 != 0:
|
||||
self.report({'WARNING'}, "Two faces must be selected")
|
||||
return {'CANCELLED'}
|
||||
|
||||
# parse selection history
|
||||
for i, _ in enumerate(all_sel_faces):
|
||||
if i > 0 and i % 2 != 0:
|
||||
sel_faces = [all_sel_faces[i - 1], all_sel_faces[i]]
|
||||
active_face = all_sel_faces[i]
|
||||
|
||||
# parse all faces according to selection history
|
||||
active_face_nor = active_face.normal.copy()
|
||||
if self.invert_normals:
|
||||
active_face_nor.negate()
|
||||
all_sorted_faces = main_parse(
|
||||
self, uv_layer, sel_faces, active_face,
|
||||
active_face_nor)
|
||||
|
||||
if all_sorted_faces:
|
||||
# check amount of copied/pasted faces
|
||||
if len(all_sorted_faces) != len(props.topology_copied):
|
||||
self.report(
|
||||
{'WARNING'},
|
||||
"Mesh has different amount of faces"
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
for i, face_data in enumerate(all_sorted_faces.values()):
|
||||
copied_data = props.topology_copied[i]
|
||||
|
||||
# check amount of copied/pasted verts
|
||||
if len(copied_data[0]) != len(face_data[2]):
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
# select problematic face
|
||||
list(all_sorted_faces.keys())[i].select = True
|
||||
self.report(
|
||||
{'WARNING'},
|
||||
"Face have different amount of vertices"
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
for j, uvloop in enumerate(face_data[2]):
|
||||
uvloop.uv = copied_data[0][j]
|
||||
uvloop.pin_uv = copied_data[1][j]
|
||||
|
||||
bmesh.update_edit_mesh(active_obj.data)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def main_parse(
|
||||
self, uv_layer, sel_faces,
|
||||
active_face, active_face_nor):
|
||||
all_sorted_faces = OrderedDict() # This is the main stuff
|
||||
|
||||
used_verts = set()
|
||||
used_edges = set()
|
||||
|
||||
faces_to_parse = []
|
||||
|
||||
# get shared edge of two faces
|
||||
cross_edges = []
|
||||
for edge in active_face.edges:
|
||||
if edge in sel_faces[0].edges and edge in sel_faces[1].edges:
|
||||
cross_edges.append(edge)
|
||||
|
||||
# parse two selected faces
|
||||
if cross_edges and len(cross_edges) == 1:
|
||||
shared_edge = cross_edges[0]
|
||||
vert1 = None
|
||||
vert2 = None
|
||||
|
||||
dot_n = active_face_nor.normalized()
|
||||
edge_vec_1 = (shared_edge.verts[1].co - shared_edge.verts[0].co)
|
||||
edge_vec_len = edge_vec_1.length
|
||||
edge_vec_1 = edge_vec_1.normalized()
|
||||
|
||||
af_center = active_face.calc_center_median()
|
||||
af_vec = shared_edge.verts[0].co + (edge_vec_1 * (edge_vec_len * 0.5))
|
||||
af_vec = (af_vec - af_center).normalized()
|
||||
|
||||
if af_vec.cross(edge_vec_1).dot(dot_n) > 0:
|
||||
vert1 = shared_edge.verts[0]
|
||||
vert2 = shared_edge.verts[1]
|
||||
else:
|
||||
vert1 = shared_edge.verts[1]
|
||||
vert2 = shared_edge.verts[0]
|
||||
|
||||
# get active face stuff and uvs
|
||||
face_stuff = get_other_verts_edges(
|
||||
active_face, vert1, vert2, shared_edge, uv_layer)
|
||||
all_sorted_faces[active_face] = face_stuff
|
||||
used_verts.update(active_face.verts)
|
||||
used_edges.update(active_face.edges)
|
||||
|
||||
# get first selected face stuff and uvs as they share shared_edge
|
||||
second_face = sel_faces[0]
|
||||
if second_face is active_face:
|
||||
second_face = sel_faces[1]
|
||||
face_stuff = get_other_verts_edges(
|
||||
second_face, vert1, vert2, shared_edge, uv_layer)
|
||||
all_sorted_faces[second_face] = face_stuff
|
||||
used_verts.update(second_face.verts)
|
||||
used_edges.update(second_face.edges)
|
||||
|
||||
# first Grow
|
||||
faces_to_parse.append(active_face)
|
||||
faces_to_parse.append(second_face)
|
||||
|
||||
else:
|
||||
self.report({'WARNING'}, "Two faces should share one edge")
|
||||
return None
|
||||
|
||||
# parse all faces
|
||||
while True:
|
||||
new_parsed_faces = []
|
||||
if not faces_to_parse:
|
||||
break
|
||||
for face in faces_to_parse:
|
||||
face_stuff = all_sorted_faces.get(face)
|
||||
new_faces = parse_faces(
|
||||
face, face_stuff, used_verts, used_edges, all_sorted_faces,
|
||||
uv_layer)
|
||||
if new_faces == 'CANCELLED':
|
||||
self.report({'WARNING'}, "More than 2 faces share edge")
|
||||
return None
|
||||
|
||||
new_parsed_faces += new_faces
|
||||
faces_to_parse = new_parsed_faces
|
||||
|
||||
return all_sorted_faces
|
||||
|
||||
|
||||
def parse_faces(
|
||||
check_face, face_stuff, used_verts, used_edges, all_sorted_faces,
|
||||
uv_layer):
|
||||
"""recurse faces around the new_grow only"""
|
||||
|
||||
new_shared_faces = []
|
||||
for sorted_edge in face_stuff[1]:
|
||||
shared_faces = sorted_edge.link_faces
|
||||
if shared_faces:
|
||||
if len(shared_faces) > 2:
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
for face_sel in shared_faces:
|
||||
face_sel.select = True
|
||||
shared_faces = []
|
||||
return 'CANCELLED'
|
||||
|
||||
clear_shared_faces = get_new_shared_faces(
|
||||
check_face, sorted_edge, shared_faces, all_sorted_faces.keys())
|
||||
if clear_shared_faces:
|
||||
shared_face = clear_shared_faces[0]
|
||||
# get vertices of the edge
|
||||
vert1 = sorted_edge.verts[0]
|
||||
vert2 = sorted_edge.verts[1]
|
||||
|
||||
muv_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)
|
||||
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:
|
||||
shared_face.select = True # test which faces are parsed
|
||||
|
||||
new_shared_faces.append(shared_face)
|
||||
|
||||
return new_shared_faces
|
||||
|
||||
|
||||
def get_new_shared_faces(orig_face, shared_edge, check_faces, used_faces):
|
||||
shared_faces = []
|
||||
|
||||
for face in check_faces:
|
||||
is_shared_edge = shared_edge in face.edges
|
||||
not_used = face not in used_faces
|
||||
not_orig = face is not orig_face
|
||||
not_hide = face.hide is False
|
||||
if is_shared_edge and not_used and not_orig and not_hide:
|
||||
shared_faces.append(face)
|
||||
|
||||
return shared_faces
|
||||
|
||||
|
||||
def get_other_verts_edges(face, vert1, vert2, first_edge, uv_layer):
|
||||
face_edges = [first_edge]
|
||||
face_verts = [vert1, vert2]
|
||||
face_loops = []
|
||||
|
||||
other_edges = [edge for edge in face.edges if edge not in face_edges]
|
||||
|
||||
for _ in range(len(other_edges)):
|
||||
found_edge = None
|
||||
# get sorted verts and edges
|
||||
for edge in other_edges:
|
||||
if face_verts[-1] in edge.verts:
|
||||
other_vert = edge.other_vert(face_verts[-1])
|
||||
|
||||
if other_vert not in face_verts:
|
||||
face_verts.append(other_vert)
|
||||
|
||||
found_edge = edge
|
||||
if found_edge not in face_edges:
|
||||
face_edges.append(edge)
|
||||
break
|
||||
|
||||
other_edges.remove(found_edge)
|
||||
|
||||
# get sorted uvs
|
||||
for vert in face_verts:
|
||||
for loop in face.loops:
|
||||
if loop.vert is vert:
|
||||
face_loops.append(loop[uv_layer])
|
||||
break
|
||||
|
||||
return [face_verts, face_edges, face_loops]
|
|
@ -0,0 +1,117 @@
|
|||
# ##### 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.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from bpy.props import BoolProperty, EnumProperty, FloatProperty
|
||||
from . import muv_common
|
||||
|
||||
|
||||
class MUV_UnwrapConstraint(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Unwrap with constrain UV coordinate
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_unwrap_constraint"
|
||||
bl_label = "Unwrap Constraint"
|
||||
bl_description = "Unwrap while keeping uv coordinate"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
# property for original unwrap
|
||||
method = EnumProperty(
|
||||
name="Method",
|
||||
description="Unwrapping method",
|
||||
items=[
|
||||
('ANGLE_BASED', 'Angle Based', 'Angle Based'),
|
||||
('CONFORMAL', 'Conformal', 'Conformal')
|
||||
],
|
||||
default='ANGLE_BASED')
|
||||
fill_holes = BoolProperty(
|
||||
name="Fill Holes",
|
||||
description="Virtual fill holes in meshes before unwrapping",
|
||||
default=True)
|
||||
correct_aspect = BoolProperty(
|
||||
name="Correct Aspect",
|
||||
description="Map UVs taking image aspect ratio into account",
|
||||
default=True)
|
||||
use_subsurf_data = BoolProperty(
|
||||
name="Use Subsurf Modifier",
|
||||
description="""Map UVs taking vertex position after subsurf
|
||||
into account""",
|
||||
default=False)
|
||||
margin = FloatProperty(
|
||||
name="Margin",
|
||||
description="Space between islands",
|
||||
max=1.0,
|
||||
min=0.0,
|
||||
default=0.001)
|
||||
|
||||
# property for this operation
|
||||
u_const = BoolProperty(
|
||||
name="U-Constraint",
|
||||
description="Keep UV U-axis coordinate",
|
||||
default=False)
|
||||
v_const = BoolProperty(
|
||||
name="V-Constraint",
|
||||
description="Keep UV V-axis coordinate",
|
||||
default=False)
|
||||
|
||||
def execute(self, _):
|
||||
obj = bpy.context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
if not bm.loops.layers.uv:
|
||||
self.report({'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
# get original UV coordinate
|
||||
faces = [f for f in bm.faces if f.select]
|
||||
uv_list = []
|
||||
for f in faces:
|
||||
uvs = [l[uv_layer].uv.copy() for l in f.loops]
|
||||
uv_list.append(uvs)
|
||||
|
||||
# unwrap
|
||||
bpy.ops.uv.unwrap(
|
||||
method=self.method,
|
||||
fill_holes=self.fill_holes,
|
||||
correct_aspect=self.correct_aspect,
|
||||
use_subsurf_data=self.use_subsurf_data,
|
||||
margin=self.margin)
|
||||
|
||||
# when U/V-Constraint is checked, revert original coordinate
|
||||
for f, uvs in zip(faces, uv_list):
|
||||
for l, uv in zip(f.loops, uvs):
|
||||
if self.u_const:
|
||||
l[uv_layer].uv.x = uv.x
|
||||
if self.v_const:
|
||||
l[uv_layer].uv.y = uv.y
|
||||
|
||||
# update mesh
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
return {'FINISHED'}
|
|
@ -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__ = "4.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
|
||||
from enum import IntEnum
|
||||
import math
|
||||
|
||||
import bpy
|
||||
import bgl
|
||||
import mathutils
|
||||
import bmesh
|
||||
|
||||
from . import muv_common
|
||||
|
||||
|
||||
MAX_VALUE = 100000.0
|
||||
|
||||
|
||||
class MUV_UVBBCmd():
|
||||
"""
|
||||
Custom class: Base class of command
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.op = 'NONE' # operation
|
||||
|
||||
def to_matrix(self):
|
||||
# mat = I
|
||||
mat = mathutils.Matrix()
|
||||
mat.identity()
|
||||
return mat
|
||||
|
||||
|
||||
class MUV_UVBBTranslationCmd(MUV_UVBBCmd):
|
||||
"""
|
||||
Custom class: Translation operation
|
||||
"""
|
||||
|
||||
def __init__(self, ix, iy):
|
||||
super().__init__()
|
||||
self.op = 'TRANSLATION'
|
||||
self.__x = ix # current x
|
||||
self.__y = iy # current y
|
||||
self.__ix = ix # initial x
|
||||
self.__iy = iy # initial y
|
||||
|
||||
def to_matrix(self):
|
||||
# mat = Mt
|
||||
dx = self.__x - self.__ix
|
||||
dy = self.__y - self.__iy
|
||||
return mathutils.Matrix.Translation((dx, dy, 0))
|
||||
|
||||
def set(self, x, y):
|
||||
self.__x = x
|
||||
self.__y = y
|
||||
|
||||
|
||||
class MUV_UVBBRotationCmd(MUV_UVBBCmd):
|
||||
"""
|
||||
Custom class: Rotation operation
|
||||
"""
|
||||
|
||||
def __init__(self, ix, iy, cx, cy):
|
||||
super().__init__()
|
||||
self.op = 'ROTATION'
|
||||
self.__x = ix # current x
|
||||
self.__y = iy # current y
|
||||
self.__cx = cx # center of rotation x
|
||||
self.__cy = cy # center of rotation y
|
||||
dx = self.__x - self.__cx
|
||||
dy = self.__y - self.__cy
|
||||
self.__iangle = math.atan2(dy, dx) # initial rotation angle
|
||||
|
||||
def to_matrix(self):
|
||||
# mat = Mt * Mr * Mt^-1
|
||||
dx = self.__x - self.__cx
|
||||
dy = self.__y - self.__cy
|
||||
angle = math.atan2(dy, dx) - self.__iangle
|
||||
mti = mathutils.Matrix.Translation((-self.__cx, -self.__cy, 0.0))
|
||||
mr = mathutils.Matrix.Rotation(angle, 4, 'Z')
|
||||
mt = mathutils.Matrix.Translation((self.__cx, self.__cy, 0.0))
|
||||
return mt * mr * mti
|
||||
|
||||
def set(self, x, y):
|
||||
self.__x = x
|
||||
self.__y = y
|
||||
|
||||
|
||||
class MUV_UVBBScalingCmd(MUV_UVBBCmd):
|
||||
"""
|
||||
Custom class: Scaling operation
|
||||
"""
|
||||
|
||||
def __init__(self, ix, iy, ox, oy, dir_x, dir_y, mat):
|
||||
super().__init__()
|
||||
self.op = 'SCALING'
|
||||
self.__ix = ix # initial x
|
||||
self.__iy = iy # initial y
|
||||
self.__x = ix # current x
|
||||
self.__y = iy # current y
|
||||
self.__ox = ox # origin of scaling x
|
||||
self.__oy = oy # origin of scaling y
|
||||
self.__dir_x = dir_x # direction of scaling x
|
||||
self.__dir_y = dir_y # direction of scaling y
|
||||
self.__mat = mat
|
||||
# initial origin of scaling = M(to original transform) * (ox, oy)
|
||||
iov = mat * mathutils.Vector((ox, oy, 0.0))
|
||||
self.__iox = iov.x # initial origin of scaling X
|
||||
self.__ioy = iov.y # initial origin of scaling y
|
||||
|
||||
def to_matrix(self):
|
||||
"""
|
||||
mat = M(to original transform)^-1 * Mt(to origin) * Ms *
|
||||
Mt(to origin)^-1 * M(to original transform)
|
||||
"""
|
||||
m = self.__mat
|
||||
mi = self.__mat.inverted()
|
||||
mtoi = mathutils.Matrix.Translation((-self.__iox, -self.__ioy, 0.0))
|
||||
mto = mathutils.Matrix.Translation((self.__iox, self.__ioy, 0.0))
|
||||
# every point must be transformed to origin
|
||||
t = m * mathutils.Vector((self.__ix, self.__iy, 0.0))
|
||||
tix, tiy = t.x, t.y
|
||||
t = m * mathutils.Vector((self.__ox, self.__oy, 0.0))
|
||||
tox, toy = t.x, t.y
|
||||
t = m * mathutils.Vector((self.__x, self.__y, 0.0))
|
||||
tx, ty = t.x, t.y
|
||||
ms = mathutils.Matrix()
|
||||
ms.identity()
|
||||
if self.__dir_x == 1:
|
||||
ms[0][0] = (tx - tox) * self.__dir_x / (tix - tox)
|
||||
if self.__dir_y == 1:
|
||||
ms[1][1] = (ty - toy) * self.__dir_y / (tiy - toy)
|
||||
return mi * mto * ms * mtoi * m
|
||||
|
||||
def set(self, x, y):
|
||||
self.__x = x
|
||||
self.__y = y
|
||||
|
||||
|
||||
class MUV_UVBBUniformScalingCmd(MUV_UVBBCmd):
|
||||
"""
|
||||
Custom class: Uniform Scaling operation
|
||||
"""
|
||||
|
||||
def __init__(self, ix, iy, ox, oy, mat):
|
||||
super().__init__()
|
||||
self.op = 'SCALING'
|
||||
self.__ix = ix # initial x
|
||||
self.__iy = iy # initial y
|
||||
self.__x = ix # current x
|
||||
self.__y = iy # current y
|
||||
self.__ox = ox # origin of scaling x
|
||||
self.__oy = oy # origin of scaling y
|
||||
self.__mat = mat
|
||||
# initial origin of scaling = M(to original transform) * (ox, oy)
|
||||
iov = mat * mathutils.Vector((ox, oy, 0.0))
|
||||
self.__iox = iov.x # initial origin of scaling x
|
||||
self.__ioy = iov.y # initial origin of scaling y
|
||||
self.__dir_x = 1
|
||||
self.__dir_y = 1
|
||||
|
||||
def to_matrix(self):
|
||||
"""
|
||||
mat = M(to original transform)^-1 * Mt(to origin) * Ms *
|
||||
Mt(to origin)^-1 * M(to original transform)
|
||||
"""
|
||||
m = self.__mat
|
||||
mi = self.__mat.inverted()
|
||||
mtoi = mathutils.Matrix.Translation((-self.__iox, -self.__ioy, 0.0))
|
||||
mto = mathutils.Matrix.Translation((self.__iox, self.__ioy, 0.0))
|
||||
# every point must be transformed to origin
|
||||
t = m * mathutils.Vector((self.__ix, self.__iy, 0.0))
|
||||
tix, tiy = t.x, t.y
|
||||
t = m * mathutils.Vector((self.__ox, self.__oy, 0.0))
|
||||
tox, toy = t.x, t.y
|
||||
t = m * mathutils.Vector((self.__x, self.__y, 0.0))
|
||||
tx, ty = t.x, t.y
|
||||
ms = mathutils.Matrix()
|
||||
ms.identity()
|
||||
tir = math.sqrt((tix - tox) * (tix - tox) + (tiy - toy) * (tiy - toy))
|
||||
tr = math.sqrt((tx - tox) * (tx - tox) + (ty - toy) * (ty - toy))
|
||||
|
||||
sr = tr / tir
|
||||
|
||||
if ((tx - tox) * (tix - tox)) > 0:
|
||||
self.__dir_x = 1
|
||||
else:
|
||||
self.__dir_x = -1
|
||||
if ((ty - toy) * (tiy - toy)) > 0:
|
||||
self.__dir_y = 1
|
||||
else:
|
||||
self.__dir_y = -1
|
||||
|
||||
ms[0][0] = sr * self.__dir_x
|
||||
ms[1][1] = sr * self.__dir_y
|
||||
|
||||
return mi * mto * ms * mtoi * m
|
||||
|
||||
def set(self, x, y):
|
||||
self.__x = x
|
||||
self.__y = y
|
||||
|
||||
|
||||
class MUV_UVBBCmdExecuter():
|
||||
"""
|
||||
Custom class: manage command history and execute command
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.__cmd_list = [] # history
|
||||
self.__cmd_list_redo = [] # redo list
|
||||
|
||||
def execute(self, begin=0, end=-1):
|
||||
"""
|
||||
create matrix from history
|
||||
"""
|
||||
mat = mathutils.Matrix()
|
||||
mat.identity()
|
||||
for i, cmd in enumerate(self.__cmd_list):
|
||||
if begin <= i and (end == -1 or i <= end):
|
||||
mat = cmd.to_matrix() * mat
|
||||
return mat
|
||||
|
||||
def undo_size(self):
|
||||
"""
|
||||
get history size
|
||||
"""
|
||||
return len(self.__cmd_list)
|
||||
|
||||
def top(self):
|
||||
"""
|
||||
get top of history
|
||||
"""
|
||||
if len(self.__cmd_list) <= 0:
|
||||
return None
|
||||
return self.__cmd_list[-1]
|
||||
|
||||
def append(self, cmd):
|
||||
"""
|
||||
append command
|
||||
"""
|
||||
self.__cmd_list.append(cmd)
|
||||
self.__cmd_list_redo = []
|
||||
|
||||
def undo(self):
|
||||
"""
|
||||
undo command
|
||||
"""
|
||||
if len(self.__cmd_list) <= 0:
|
||||
return
|
||||
self.__cmd_list_redo.append(self.__cmd_list.pop())
|
||||
|
||||
def redo(self):
|
||||
"""
|
||||
redo command
|
||||
"""
|
||||
if len(self.__cmd_list_redo) <= 0:
|
||||
return
|
||||
self.__cmd_list.append(self.__cmd_list_redo.pop())
|
||||
|
||||
def pop(self):
|
||||
if len(self.__cmd_list) <= 0:
|
||||
return None
|
||||
return self.__cmd_list.pop()
|
||||
|
||||
def push(self, cmd):
|
||||
self.__cmd_list.append(cmd)
|
||||
|
||||
|
||||
class MUV_UVBBRenderer(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Render UV bounding box
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_uvbb_renderer"
|
||||
bl_label = "UV Bounding Box Renderer"
|
||||
bl_description = "Bounding Box Renderer about UV in Image Editor"
|
||||
|
||||
__handle = None
|
||||
|
||||
@staticmethod
|
||||
def handle_add(obj, context):
|
||||
if MUV_UVBBRenderer.__handle is None:
|
||||
sie = bpy.types.SpaceImageEditor
|
||||
MUV_UVBBRenderer.__handle = sie.draw_handler_add(
|
||||
MUV_UVBBRenderer.draw_bb,
|
||||
(obj, context), "WINDOW", "POST_PIXEL")
|
||||
|
||||
@staticmethod
|
||||
def handle_remove():
|
||||
if MUV_UVBBRenderer.__handle is not None:
|
||||
sie = bpy.types.SpaceImageEditor
|
||||
sie.draw_handler_remove(
|
||||
MUV_UVBBRenderer.__handle, "WINDOW")
|
||||
MUV_UVBBRenderer.__handle = None
|
||||
|
||||
@staticmethod
|
||||
def __draw_ctrl_point(context, pos):
|
||||
"""
|
||||
Draw control point
|
||||
"""
|
||||
prefs = context.user_preferences.addons["uv_magic_uv"].preferences
|
||||
cp_size = prefs.uvbb_cp_size
|
||||
offset = cp_size / 2
|
||||
verts = [
|
||||
[pos.x - offset, pos.y - offset],
|
||||
[pos.x - offset, pos.y + offset],
|
||||
[pos.x + offset, pos.y + offset],
|
||||
[pos.x + offset, pos.y - offset]
|
||||
]
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glBegin(bgl.GL_QUADS)
|
||||
bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
|
||||
for (x, y) in verts:
|
||||
bgl.glVertex2f(x, y)
|
||||
bgl.glEnd()
|
||||
|
||||
@staticmethod
|
||||
def draw_bb(_, context):
|
||||
"""
|
||||
Draw bounding box
|
||||
"""
|
||||
props = context.scene.muv_props.uvbb
|
||||
for cp in props.ctrl_points:
|
||||
MUV_UVBBRenderer.__draw_ctrl_point(
|
||||
context, mathutils.Vector(
|
||||
context.region.view2d.view_to_region(cp.x, cp.y)))
|
||||
|
||||
|
||||
class MUV_UVBBState(IntEnum):
|
||||
"""
|
||||
Enum: State definition used by MUV_UVBBStateMgr
|
||||
"""
|
||||
NONE = 0
|
||||
TRANSLATING = 1
|
||||
SCALING_1 = 2
|
||||
SCALING_2 = 3
|
||||
SCALING_3 = 4
|
||||
SCALING_4 = 5
|
||||
SCALING_5 = 6
|
||||
SCALING_6 = 7
|
||||
SCALING_7 = 8
|
||||
SCALING_8 = 9
|
||||
ROTATING = 10
|
||||
UNIFORM_SCALING_1 = 11
|
||||
UNIFORM_SCALING_2 = 12
|
||||
UNIFORM_SCALING_3 = 13
|
||||
UNIFORM_SCALING_4 = 14
|
||||
|
||||
|
||||
class MUV_UVBBStateBase():
|
||||
"""
|
||||
Custom class: Base class of state
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def update(self, context, event, ctrl_points, mouse_view):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class MUV_UVBBStateNone(MUV_UVBBStateBase):
|
||||
"""
|
||||
Custom class:
|
||||
No state
|
||||
Wait for event from mouse
|
||||
"""
|
||||
|
||||
def __init__(self, cmd_exec):
|
||||
super().__init__()
|
||||
self.__cmd_exec = cmd_exec
|
||||
|
||||
def update(self, context, event, ctrl_points, mouse_view):
|
||||
"""
|
||||
Update state
|
||||
"""
|
||||
prefs = context.user_preferences.addons["uv_magic_uv"].preferences
|
||||
cp_react_size = prefs.uvbb_cp_react_size
|
||||
is_uscaling = context.scene.muv_uvbb_uniform_scaling
|
||||
if event.type == 'LEFTMOUSE':
|
||||
if event.value == 'PRESS':
|
||||
x, y = context.region.view2d.view_to_region(
|
||||
mouse_view.x, mouse_view.y)
|
||||
for i, p in enumerate(ctrl_points):
|
||||
px, py = context.region.view2d.view_to_region(p.x, p.y)
|
||||
in_cp_x = (px + cp_react_size > x and
|
||||
px - cp_react_size < x)
|
||||
in_cp_y = (py + cp_react_size > y and
|
||||
py - cp_react_size < y)
|
||||
if in_cp_x and in_cp_y:
|
||||
if is_uscaling:
|
||||
arr = [1, 3, 6, 8]
|
||||
if i in arr:
|
||||
return (
|
||||
MUV_UVBBState.UNIFORM_SCALING_1
|
||||
+ arr.index(i)
|
||||
)
|
||||
else:
|
||||
return MUV_UVBBState.TRANSLATING + i
|
||||
|
||||
return MUV_UVBBState.NONE
|
||||
|
||||
|
||||
class MUV_UVBBStateTranslating(MUV_UVBBStateBase):
|
||||
"""
|
||||
Custom class: Translating state
|
||||
"""
|
||||
|
||||
def __init__(self, cmd_exec, ctrl_points):
|
||||
super().__init__()
|
||||
self.__cmd_exec = cmd_exec
|
||||
ix, iy = ctrl_points[0].x, ctrl_points[0].y
|
||||
self.__cmd_exec.append(MUV_UVBBTranslationCmd(ix, iy))
|
||||
|
||||
def update(self, context, event, ctrl_points, mouse_view):
|
||||
if event.type == 'LEFTMOUSE':
|
||||
if event.value == 'RELEASE':
|
||||
return MUV_UVBBState.NONE
|
||||
if event.type == 'MOUSEMOVE':
|
||||
x, y = mouse_view.x, mouse_view.y
|
||||
self.__cmd_exec.top().set(x, y)
|
||||
return MUV_UVBBState.TRANSLATING
|
||||
|
||||
|
||||
class MUV_UVBBStateScaling(MUV_UVBBStateBase):
|
||||
"""
|
||||
Custom class: Scaling state
|
||||
"""
|
||||
|
||||
def __init__(self, cmd_exec, state, ctrl_points):
|
||||
super().__init__()
|
||||
self.__state = state
|
||||
self.__cmd_exec = cmd_exec
|
||||
dir_x_list = [1, 1, 1, 0, 0, 1, 1, 1]
|
||||
dir_y_list = [1, 0, 1, 1, 1, 1, 0, 1]
|
||||
idx = state - 2
|
||||
ix, iy = ctrl_points[idx + 1].x, ctrl_points[idx + 1].y
|
||||
ox, oy = ctrl_points[8 - idx].x, ctrl_points[8 - idx].y
|
||||
dir_x, dir_y = dir_x_list[idx], dir_y_list[idx]
|
||||
mat = self.__cmd_exec.execute(end=self.__cmd_exec.undo_size())
|
||||
self.__cmd_exec.append(
|
||||
MUV_UVBBScalingCmd(ix, iy, ox, oy, dir_x, dir_y, mat.inverted()))
|
||||
|
||||
def update(self, context, event, ctrl_points, mouse_view):
|
||||
if event.type == 'LEFTMOUSE':
|
||||
if event.value == 'RELEASE':
|
||||
return MUV_UVBBState.NONE
|
||||
if event.type == 'MOUSEMOVE':
|
||||
x, y = mouse_view.x, mouse_view.y
|
||||
self.__cmd_exec.top().set(x, y)
|
||||
return self.__state
|
||||
|
||||
|
||||
class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase):
|
||||
"""
|
||||
Custom class: Uniform Scaling state
|
||||
"""
|
||||
|
||||
def __init__(self, cmd_exec, state, ctrl_points):
|
||||
super().__init__()
|
||||
self.__state = state
|
||||
self.__cmd_exec = cmd_exec
|
||||
icp_idx = [1, 3, 6, 8]
|
||||
ocp_idx = [8, 6, 3, 1]
|
||||
idx = state - MUV_UVBBState.UNIFORM_SCALING_1
|
||||
ix, iy = ctrl_points[icp_idx[idx]].x, ctrl_points[icp_idx[idx]].y
|
||||
ox, oy = ctrl_points[ocp_idx[idx]].x, ctrl_points[ocp_idx[idx]].y
|
||||
mat = self.__cmd_exec.execute(end=self.__cmd_exec.undo_size())
|
||||
self.__cmd_exec.append(MUV_UVBBUniformScalingCmd(
|
||||
ix, iy, ox, oy, mat.inverted()))
|
||||
|
||||
def update(self, context, event, ctrl_points, mouse_view):
|
||||
if event.type == 'LEFTMOUSE':
|
||||
if event.value == 'RELEASE':
|
||||
return MUV_UVBBState.NONE
|
||||
if event.type == 'MOUSEMOVE':
|
||||
x, y = mouse_view.x, mouse_view.y
|
||||
self.__cmd_exec.top().set(x, y)
|
||||
|
||||
return self.__state
|
||||
|
||||
|
||||
class MUV_UVBBStateRotating(MUV_UVBBStateBase):
|
||||
"""
|
||||
Custom class: Rotating state
|
||||
"""
|
||||
|
||||
def __init__(self, cmd_exec, ctrl_points):
|
||||
super().__init__()
|
||||
self.__cmd_exec = cmd_exec
|
||||
ix, iy = ctrl_points[9].x, ctrl_points[9].y
|
||||
ox, oy = ctrl_points[0].x, ctrl_points[0].y
|
||||
self.__cmd_exec.append(MUV_UVBBRotationCmd(ix, iy, ox, oy))
|
||||
|
||||
def update(self, context, event, ctrl_points, mouse_view):
|
||||
if event.type == 'LEFTMOUSE':
|
||||
if event.value == 'RELEASE':
|
||||
return MUV_UVBBState.NONE
|
||||
if event.type == 'MOUSEMOVE':
|
||||
x, y = mouse_view.x, mouse_view.y
|
||||
self.__cmd_exec.top().set(x, y)
|
||||
return MUV_UVBBState.ROTATING
|
||||
|
||||
|
||||
class MUV_UVBBStateMgr():
|
||||
"""
|
||||
Custom class: Manage state about this feature
|
||||
"""
|
||||
|
||||
def __init__(self, cmd_exec):
|
||||
self.__cmd_exec = cmd_exec # command executer
|
||||
self.__state = MUV_UVBBState.NONE # current state
|
||||
self.__state_obj = MUV_UVBBStateNone(self.__cmd_exec)
|
||||
|
||||
def __update_state(self, next_state, ctrl_points):
|
||||
"""
|
||||
Update state
|
||||
"""
|
||||
|
||||
if next_state == self.__state:
|
||||
return
|
||||
obj = None
|
||||
if next_state == MUV_UVBBState.TRANSLATING:
|
||||
obj = MUV_UVBBStateTranslating(self.__cmd_exec, ctrl_points)
|
||||
elif MUV_UVBBState.SCALING_1 <= next_state <= MUV_UVBBState.SCALING_8:
|
||||
obj = MUV_UVBBStateScaling(
|
||||
self.__cmd_exec, next_state, ctrl_points)
|
||||
elif next_state == MUV_UVBBState.ROTATING:
|
||||
obj = MUV_UVBBStateRotating(self.__cmd_exec, ctrl_points)
|
||||
elif next_state == MUV_UVBBState.NONE:
|
||||
obj = MUV_UVBBStateNone(self.__cmd_exec)
|
||||
elif (MUV_UVBBState.UNIFORM_SCALING_1 <= next_state
|
||||
<= MUV_UVBBState.UNIFORM_SCALING_4):
|
||||
obj = MUV_UVBBStateUniformScaling(
|
||||
self.__cmd_exec, next_state, ctrl_points)
|
||||
|
||||
if obj is not None:
|
||||
self.__state_obj = obj
|
||||
|
||||
self.__state = next_state
|
||||
|
||||
def update(self, context, ctrl_points, event):
|
||||
mouse_region = mathutils.Vector((
|
||||
event.mouse_region_x, event.mouse_region_y))
|
||||
mouse_view = mathutils.Vector((context.region.view2d.region_to_view(
|
||||
mouse_region.x, mouse_region.y)))
|
||||
next_state = self.__state_obj.update(
|
||||
context, event, ctrl_points, mouse_view)
|
||||
self.__update_state(next_state, ctrl_points)
|
||||
|
||||
|
||||
class MUV_UVBBUpdater(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Update state and handle event by modal function
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_uvbb_updater"
|
||||
bl_label = "UV Bounding Box Updater"
|
||||
bl_description = "Update UV Bounding Box"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def __init__(self):
|
||||
self.__timer = None
|
||||
self.__cmd_exec = MUV_UVBBCmdExecuter() # Command executer
|
||||
self.__state_mgr = MUV_UVBBStateMgr(self.__cmd_exec) # State Manager
|
||||
|
||||
def __handle_add(self, context):
|
||||
if self.__timer is None:
|
||||
self.__timer = context.window_manager.event_timer_add(
|
||||
0.1, context.window)
|
||||
context.window_manager.modal_handler_add(self)
|
||||
MUV_UVBBRenderer.handle_add(self, context)
|
||||
|
||||
def __handle_remove(self, context):
|
||||
MUV_UVBBRenderer.handle_remove()
|
||||
if self.__timer is not None:
|
||||
context.window_manager.event_timer_remove(self.__timer)
|
||||
self.__timer = None
|
||||
|
||||
def __get_uv_info(self, context):
|
||||
"""
|
||||
Get UV coordinate
|
||||
"""
|
||||
obj = context.active_object
|
||||
uv_info = []
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.faces.ensure_lookup_table()
|
||||
if not bm.loops.layers.uv:
|
||||
return None
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
for f in bm.faces:
|
||||
if f.select:
|
||||
for i, l in enumerate(f.loops):
|
||||
uv_info.append((f.index, i, l[uv_layer].uv.copy()))
|
||||
if len(uv_info) == 0:
|
||||
return None
|
||||
return uv_info
|
||||
|
||||
def __get_ctrl_point(self, uv_info_ini):
|
||||
"""
|
||||
Get control point
|
||||
"""
|
||||
left = MAX_VALUE
|
||||
right = -MAX_VALUE
|
||||
top = -MAX_VALUE
|
||||
bottom = MAX_VALUE
|
||||
|
||||
for info in uv_info_ini:
|
||||
uv = info[2]
|
||||
if uv.x < left:
|
||||
left = uv.x
|
||||
if uv.x > right:
|
||||
right = uv.x
|
||||
if uv.y < bottom:
|
||||
bottom = uv.y
|
||||
if uv.y > top:
|
||||
top = uv.y
|
||||
|
||||
points = [
|
||||
mathutils.Vector((
|
||||
(left + right) * 0.5, (top + bottom) * 0.5, 0.0
|
||||
)),
|
||||
mathutils.Vector((left, top, 0.0)),
|
||||
mathutils.Vector((left, (top + bottom) * 0.5, 0.0)),
|
||||
mathutils.Vector((left, bottom, 0.0)),
|
||||
mathutils.Vector(((left + right) * 0.5, top, 0.0)),
|
||||
mathutils.Vector(((left + right) * 0.5, bottom, 0.0)),
|
||||
mathutils.Vector((right, top, 0.0)),
|
||||
mathutils.Vector((right, (top + bottom) * 0.5, 0.0)),
|
||||
mathutils.Vector((right, bottom, 0.0)),
|
||||
mathutils.Vector(((left + right) * 0.5, top + 0.03, 0.0))
|
||||
]
|
||||
|
||||
return points
|
||||
|
||||
def __update_uvs(self, context, uv_info_ini, trans_mat):
|
||||
"""
|
||||
Update UV coordinate
|
||||
"""
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.faces.ensure_lookup_table()
|
||||
if not bm.loops.layers.uv:
|
||||
return
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
for info in uv_info_ini:
|
||||
fidx = info[0]
|
||||
lidx = info[1]
|
||||
uv = info[2]
|
||||
v = mathutils.Vector((uv.x, uv.y, 0.0))
|
||||
av = trans_mat * v
|
||||
bm.faces[fidx].loops[lidx][uv_layer].uv = mathutils.Vector(
|
||||
(av.x, av.y))
|
||||
|
||||
def __update_ctrl_point(self, ctrl_points_ini, trans_mat):
|
||||
"""
|
||||
Update control point
|
||||
"""
|
||||
return [trans_mat * cp for cp in ctrl_points_ini]
|
||||
|
||||
def modal(self, context, event):
|
||||
props = context.scene.muv_props.uvbb
|
||||
muv_common.redraw_all_areas()
|
||||
if props.running is False:
|
||||
self.__handle_remove(context)
|
||||
return {'FINISHED'}
|
||||
if event.type == 'TIMER':
|
||||
trans_mat = self.__cmd_exec.execute()
|
||||
self.__update_uvs(context, props.uv_info_ini, trans_mat)
|
||||
props.ctrl_points = self.__update_ctrl_point(
|
||||
props.ctrl_points_ini, trans_mat)
|
||||
|
||||
self.__state_mgr.update(context, props.ctrl_points, event)
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.uvbb
|
||||
|
||||
if props.running is True:
|
||||
props.running = False
|
||||
return {'FINISHED'}
|
||||
|
||||
props.uv_info_ini = self.__get_uv_info(context)
|
||||
if props.uv_info_ini is None:
|
||||
return {'CANCELLED'}
|
||||
props.ctrl_points_ini = self.__get_ctrl_point(props.uv_info_ini)
|
||||
trans_mat = self.__cmd_exec.execute()
|
||||
# Update is needed in order to display control point
|
||||
self.__update_uvs(context, props.uv_info_ini, trans_mat)
|
||||
props.ctrl_points = self.__update_ctrl_point(
|
||||
props.ctrl_points_ini, trans_mat)
|
||||
self.__handle_add(context)
|
||||
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='PLUGIN')
|
||||
|
||||
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,151 @@
|
|||
# <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__ = "McBuff, Nutti <nutti.metro@gmail.com>"
|
||||
__status__ = "production"
|
||||
__version__ = "4.3"
|
||||
__date__ = "1 Apr 2017"
|
||||
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from . import muv_common
|
||||
|
||||
|
||||
def calc_edge_scale(uv_layer, loop0, loop1):
|
||||
v0 = loop0.vert.co
|
||||
v1 = loop1.vert.co
|
||||
uv0 = loop0[uv_layer].uv.copy()
|
||||
uv1 = loop1[uv_layer].uv.copy()
|
||||
|
||||
dv = v1 - v0
|
||||
duv = uv1 - uv0
|
||||
|
||||
scale = 0.0
|
||||
if dv.magnitude > 0.00000001:
|
||||
scale = duv.magnitude / dv.magnitude
|
||||
|
||||
return scale
|
||||
|
||||
|
||||
def calc_face_scale(uv_layer, face):
|
||||
es = 0.0
|
||||
for i, l in enumerate(face.loops[1:]):
|
||||
es = es + calc_edge_scale(uv_layer, face.loops[i], l)
|
||||
|
||||
return es
|
||||
|
||||
|
||||
class MUV_WSUVMeasure(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Measure face size
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_wsuv_measure"
|
||||
bl_label = "Measure"
|
||||
bl_description = "Measure face size for scale calculation"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.wsuv
|
||||
obj = bpy.context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
if not bm.loops.layers.uv:
|
||||
self.report({'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
sel_faces = [f for f in bm.faces if f.select]
|
||||
|
||||
# measure average face size
|
||||
scale = 0.0
|
||||
for f in sel_faces:
|
||||
scale = scale + calc_face_scale(uv_layer, f)
|
||||
|
||||
props.ref_scale = scale / len(sel_faces)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MUV_WSUVApply(bpy.types.Operator):
|
||||
"""
|
||||
Operation class: Apply scaled UV
|
||||
"""
|
||||
|
||||
bl_idname = "uv.muv_wsuv_apply"
|
||||
bl_label = "Apply"
|
||||
bl_description = "Apply scaled UV based on scale calculation"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.muv_props.wsuv
|
||||
obj = bpy.context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if muv_common.check_version(2, 73, 0) >= 0:
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
if not bm.loops.layers.uv:
|
||||
self.report(
|
||||
{'WARNING'}, "Object must have more than one UV map")
|
||||
return {'CANCELLED'}
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
sel_faces = [f for f in bm.faces if f.select]
|
||||
|
||||
# measure average face size
|
||||
scale = 0.0
|
||||
for f in sel_faces:
|
||||
scale = scale + calc_face_scale(uv_layer, f)
|
||||
scale = scale / len(sel_faces)
|
||||
|
||||
ratio = props.ref_scale / scale
|
||||
|
||||
orig_area = bpy.context.area.type
|
||||
bpy.context.area.type = 'IMAGE_EDITOR'
|
||||
|
||||
# apply scaled UV
|
||||
bpy.ops.transform.resize(
|
||||
value=(ratio, ratio, ratio),
|
||||
constraint_axis=(False, False, False),
|
||||
constraint_orientation='GLOBAL',
|
||||
mirror=False,
|
||||
proportional='DISABLED',
|
||||
proportional_edit_falloff='SMOOTH',
|
||||
proportional_size=1,
|
||||
snap=False,
|
||||
snap_target='CLOSEST',
|
||||
snap_point=(0, 0, 0),
|
||||
snap_align=False,
|
||||
snap_normal=(0, 0, 0),
|
||||
texture_space=False,
|
||||
release_confirm=False)
|
||||
|
||||
bpy.context.area.type = orig_area
|
||||
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
return {'FINISHED'}
|
Loading…
Reference in New Issue