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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'uv_magic_uv/impl')
-rw-r--r--uv_magic_uv/impl/__init__.py0
-rw-r--r--uv_magic_uv/impl/copy_paste_uv_impl.py271
-rw-r--r--uv_magic_uv/impl/copy_paste_uv_uvedit_impl.py166
-rw-r--r--uv_magic_uv/impl/flip_rotate_impl.py133
-rw-r--r--uv_magic_uv/impl/mirror_uv_impl.py158
-rw-r--r--uv_magic_uv/impl/move_uv_impl.py166
-rw-r--r--uv_magic_uv/impl/transfer_uv_impl.py330
-rw-r--r--uv_magic_uv/impl/uvw_impl.py154
8 files changed, 1378 insertions, 0 deletions
diff --git a/uv_magic_uv/impl/__init__.py b/uv_magic_uv/impl/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/uv_magic_uv/impl/__init__.py
diff --git a/uv_magic_uv/impl/copy_paste_uv_impl.py b/uv_magic_uv/impl/copy_paste_uv_impl.py
new file mode 100644
index 00000000..ed44637b
--- /dev/null
+++ b/uv_magic_uv/impl/copy_paste_uv_impl.py
@@ -0,0 +1,271 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+
+import bmesh
+
+from .. import common
+
+
+__all__ = [
+ 'is_valid_context',
+ 'get_copy_uv_layers',
+ 'get_paste_uv_layers',
+ 'get_src_face_info',
+ 'get_dest_face_info',
+ 'get_select_history_src_face_info',
+ 'get_select_history_dest_face_info',
+ 'paste_uv',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+def get_copy_uv_layers(ops_obj, bm, uv_map):
+ uv_layers = []
+ if uv_map == "__default":
+ if not bm.loops.layers.uv:
+ ops_obj.report(
+ {'WARNING'}, "Object must have more than one UV map")
+ return None
+ uv_layers.append(bm.loops.layers.uv.verify())
+ ops_obj.report({'INFO'}, "Copy UV coordinate")
+ elif uv_map == "__all":
+ for uv in bm.loops.layers.uv.keys():
+ uv_layers.append(bm.loops.layers.uv[uv])
+ ops_obj.report({'INFO'}, "Copy UV coordinate (UV map: ALL)")
+ else:
+ uv_layers.append(bm.loops.layers.uv[uv_map])
+ ops_obj.report(
+ {'INFO'}, "Copy UV coordinate (UV map:{})".format(uv_map))
+
+ return uv_layers
+
+
+def get_paste_uv_layers(ops_obj, obj, bm, src_info, uv_map):
+ uv_layers = []
+ if uv_map == "__default":
+ if not bm.loops.layers.uv:
+ ops_obj.report(
+ {'WARNING'}, "Object must have more than one UV map")
+ return None
+ uv_layers.append(bm.loops.layers.uv.verify())
+ ops_obj.report({'INFO'}, "Paste UV coordinate")
+ elif uv_map == "__new":
+ new_uv_map = common.create_new_uv_map(obj)
+ if not new_uv_map:
+ ops_obj.report({'WARNING'},
+ "Reached to the maximum number of UV map")
+ return None
+ uv_layers.append(bm.loops.layers.uv[new_uv_map.name])
+ ops_obj.report(
+ {'INFO'}, "Paste UV coordinate (UV map:{})".format(new_uv_map))
+ elif uv_map == "__all":
+ for src_layer in src_info.keys():
+ if src_layer not in bm.loops.layers.uv.keys():
+ new_uv_map = common.create_new_uv_map(obj, src_layer)
+ if not new_uv_map:
+ ops_obj.report({'WARNING'},
+ "Reached to the maximum number of UV map")
+ return None
+ uv_layers.append(bm.loops.layers.uv[src_layer])
+ ops_obj.report({'INFO'}, "Paste UV coordinate (UV map: ALL)")
+ else:
+ uv_layers.append(bm.loops.layers.uv[uv_map])
+ ops_obj.report(
+ {'INFO'}, "Paste UV coordinate (UV map:{})".format(uv_map))
+
+ return uv_layers
+
+
+def get_src_face_info(ops_obj, bm, uv_layers, only_select=False):
+ src_info = {}
+ for layer in uv_layers:
+ face_info = []
+ for face in bm.faces:
+ if not only_select or face.select:
+ info = {
+ "index": face.index,
+ "uvs": [l[layer].uv.copy() for l in face.loops],
+ "pin_uvs": [l[layer].pin_uv for l in face.loops],
+ "seams": [l.edge.seam for l in face.loops],
+ }
+ face_info.append(info)
+ if not face_info:
+ ops_obj.report({'WARNING'}, "No faces are selected")
+ return None
+ src_info[layer.name] = face_info
+
+ return src_info
+
+
+def get_dest_face_info(ops_obj, bm, uv_layers, src_info, strategy,
+ only_select=False):
+ dest_info = {}
+ for layer in uv_layers:
+ face_info = []
+ for face in bm.faces:
+ if not only_select or face.select:
+ info = {
+ "index": face.index,
+ "uvs": [l[layer].uv.copy() for l in face.loops],
+ }
+ face_info.append(info)
+ if not face_info:
+ ops_obj.report({'WARNING'}, "No faces are selected")
+ return None
+ key = list(src_info.keys())[0]
+ src_face_count = len(src_info[key])
+ dest_face_count = len(face_info)
+ if strategy == 'N_N' and src_face_count != dest_face_count:
+ ops_obj.report(
+ {'WARNING'},
+ "Number of selected faces is different from copied" +
+ "(src:{}, dest:{})"
+ .format(src_face_count, dest_face_count))
+ return None
+ dest_info[layer.name] = face_info
+
+ return dest_info
+
+
+def get_select_history_src_face_info(ops_obj, bm, uv_layers):
+ src_info = {}
+ for layer in uv_layers:
+ face_info = []
+ for hist in bm.select_history:
+ if isinstance(hist, bmesh.types.BMFace) and hist.select:
+ info = {
+ "index": hist.index,
+ "uvs": [l[layer].uv.copy() for l in hist.loops],
+ "pin_uvs": [l[layer].pin_uv for l in hist.loops],
+ "seams": [l.edge.seam for l in hist.loops],
+ }
+ face_info.append(info)
+ if not face_info:
+ ops_obj.report({'WARNING'}, "No faces are selected")
+ return None
+ src_info[layer.name] = face_info
+
+ return src_info
+
+
+def get_select_history_dest_face_info(ops_obj, bm, uv_layers, src_info,
+ strategy):
+ dest_info = {}
+ for layer in uv_layers:
+ face_info = []
+ for hist in bm.select_history:
+ if isinstance(hist, bmesh.types.BMFace) and hist.select:
+ info = {
+ "index": hist.index,
+ "uvs": [l[layer].uv.copy() for l in hist.loops],
+ }
+ face_info.append(info)
+ if not face_info:
+ ops_obj.report({'WARNING'}, "No faces are selected")
+ return None
+ key = list(src_info.keys())[0]
+ src_face_count = len(src_info[key])
+ dest_face_count = len(face_info)
+ if strategy == 'N_N' and src_face_count != dest_face_count:
+ ops_obj.report(
+ {'WARNING'},
+ "Number of selected faces is different from copied" +
+ "(src:{}, dest:{})"
+ .format(src_face_count, dest_face_count))
+ return None
+ dest_info[layer.name] = face_info
+
+ return dest_info
+
+
+def paste_uv(ops_obj, bm, src_info, dest_info, uv_layers, strategy, flip,
+ rotate, copy_seams):
+ for slayer_name, dlayer in zip(src_info.keys(), uv_layers):
+ src_faces = src_info[slayer_name]
+ dest_faces = dest_info[dlayer.name]
+
+ for idx, dinfo in enumerate(dest_faces):
+ sinfo = None
+ if strategy == 'N_N':
+ sinfo = src_faces[idx]
+ elif strategy == 'N_M':
+ sinfo = src_faces[idx % len(src_faces)]
+
+ suv = sinfo["uvs"]
+ spuv = sinfo["pin_uvs"]
+ ss = sinfo["seams"]
+ if len(sinfo["uvs"]) != len(dinfo["uvs"]):
+ ops_obj.report({'WARNING'}, "Some faces are different size")
+ return -1
+
+ suvs_fr = [uv for uv in suv]
+ spuvs_fr = [pin_uv for pin_uv in spuv]
+ ss_fr = [s for s in ss]
+
+ # flip UVs
+ if flip is True:
+ suvs_fr.reverse()
+ spuvs_fr.reverse()
+ ss_fr.reverse()
+
+ # rotate UVs
+ for _ in range(rotate):
+ uv = suvs_fr.pop()
+ pin_uv = spuvs_fr.pop()
+ s = ss_fr.pop()
+ suvs_fr.insert(0, uv)
+ spuvs_fr.insert(0, pin_uv)
+ ss_fr.insert(0, s)
+
+ # paste UVs
+ for l, suv, spuv, ss in zip(bm.faces[dinfo["index"]].loops,
+ suvs_fr, spuvs_fr, ss_fr):
+ l[dlayer].uv = suv
+ l[dlayer].pin_uv = spuv
+ if copy_seams is True:
+ l.edge.seam = ss
+
+ return 0
diff --git a/uv_magic_uv/impl/copy_paste_uv_uvedit_impl.py b/uv_magic_uv/impl/copy_paste_uv_uvedit_impl.py
new file mode 100644
index 00000000..f14a70d6
--- /dev/null
+++ b/uv_magic_uv/impl/copy_paste_uv_uvedit_impl.py
@@ -0,0 +1,166 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import math
+from math import atan2, sin, cos
+
+import bmesh
+from mathutils import Vector
+
+from .. import common
+
+
+__all__ = [
+ 'is_valid_context',
+ 'CopyUVImpl',
+ 'PasteUVImpl',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
+ # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
+ # after the execution
+ for space in context.area.spaces:
+ if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
+ break
+ else:
+ return False
+
+ return True
+
+
+class CopyUVImpl:
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
+ def execute(self, _, context):
+ props = context.scene.muv_props.copy_paste_uv_uvedit
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ props.src_uvs = []
+ for face in bm.faces:
+ if not face.select:
+ continue
+ skip = False
+ for l in face.loops:
+ if not l[uv_layer].select:
+ skip = True
+ break
+ if skip:
+ continue
+ props.src_uvs.append([l[uv_layer].uv.copy() for l in face.loops])
+
+ return {'FINISHED'}
+
+
+class PasteUVImpl:
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ sc = context.scene
+ props = sc.muv_props.copy_paste_uv_uvedit
+ if not props.src_uvs:
+ return False
+ return is_valid_context(context)
+
+ def execute(self, _, context):
+ props = context.scene.muv_props.copy_paste_uv_uvedit
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ dest_uvs = []
+ dest_face_indices = []
+ for face in bm.faces:
+ if not face.select:
+ continue
+ skip = False
+ for l in face.loops:
+ if not l[uv_layer].select:
+ skip = True
+ break
+ if skip:
+ continue
+ dest_face_indices.append(face.index)
+ uvs = [l[uv_layer].uv.copy() for l in face.loops]
+ dest_uvs.append(uvs)
+
+ for suvs, duvs in zip(props.src_uvs, dest_uvs):
+ src_diff = suvs[1] - suvs[0]
+ dest_diff = duvs[1] - duvs[0]
+
+ src_base = suvs[0]
+ dest_base = duvs[0]
+
+ src_rad = atan2(src_diff.y, src_diff.x)
+ dest_rad = atan2(dest_diff.y, dest_diff.x)
+ if src_rad < dest_rad:
+ radian = dest_rad - src_rad
+ elif src_rad > dest_rad:
+ radian = math.pi * 2 - (src_rad - dest_rad)
+ else: # src_rad == dest_rad
+ radian = 0.0
+
+ ratio = dest_diff.length / src_diff.length
+ break
+
+ for suvs, fidx in zip(props.src_uvs, dest_face_indices):
+ for l, suv in zip(bm.faces[fidx].loops, suvs):
+ base = suv - src_base
+ radian_ref = atan2(base.y, base.x)
+ radian_fin = (radian + radian_ref)
+ length = base.length
+ turn = Vector((length * cos(radian_fin),
+ length * sin(radian_fin)))
+ target_uv = Vector((turn.x * ratio, turn.y * ratio)) + \
+ dest_base
+ l[uv_layer].uv = target_uv
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
diff --git a/uv_magic_uv/impl/flip_rotate_impl.py b/uv_magic_uv/impl/flip_rotate_impl.py
new file mode 100644
index 00000000..f74bc256
--- /dev/null
+++ b/uv_magic_uv/impl/flip_rotate_impl.py
@@ -0,0 +1,133 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+
+__all__ = [
+ 'is_valid_context',
+ 'get_uv_layer',
+ 'get_src_face_info',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+def get_uv_layer(ops_obj, bm):
+ # get UV layer
+ if not bm.loops.layers.uv:
+ ops_obj.report({'WARNING'}, "Object must have more than one UV map")
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+
+ return uv_layer
+
+
+def get_src_face_info(ops_obj, bm, uv_layers, only_select=False):
+ src_info = {}
+ for layer in uv_layers:
+ face_info = []
+ for face in bm.faces:
+ if not only_select or face.select:
+ info = {
+ "index": face.index,
+ "uvs": [l[layer].uv.copy() for l in face.loops],
+ "pin_uvs": [l[layer].pin_uv for l in face.loops],
+ "seams": [l.edge.seam for l in face.loops],
+ }
+ face_info.append(info)
+ if not face_info:
+ ops_obj.report({'WARNING'}, "No faces are selected")
+ return None
+ src_info[layer.name] = face_info
+
+ return src_info
+
+
+def paste_uv(ops_obj, bm, src_info, dest_info, uv_layers, strategy, flip,
+ rotate, copy_seams):
+ for slayer_name, dlayer in zip(src_info.keys(), uv_layers):
+ src_faces = src_info[slayer_name]
+ dest_faces = dest_info[dlayer.name]
+
+ for idx, dinfo in enumerate(dest_faces):
+ sinfo = None
+ if strategy == 'N_N':
+ sinfo = src_faces[idx]
+ elif strategy == 'N_M':
+ sinfo = src_faces[idx % len(src_faces)]
+
+ suv = sinfo["uvs"]
+ spuv = sinfo["pin_uvs"]
+ ss = sinfo["seams"]
+ if len(sinfo["uvs"]) != len(dinfo["uvs"]):
+ ops_obj.report({'WARNING'}, "Some faces are different size")
+ return -1
+
+ suvs_fr = [uv for uv in suv]
+ spuvs_fr = [pin_uv for pin_uv in spuv]
+ ss_fr = [s for s in ss]
+
+ # flip UVs
+ if flip is True:
+ suvs_fr.reverse()
+ spuvs_fr.reverse()
+ ss_fr.reverse()
+
+ # rotate UVs
+ for _ in range(rotate):
+ uv = suvs_fr.pop()
+ pin_uv = spuvs_fr.pop()
+ s = ss_fr.pop()
+ suvs_fr.insert(0, uv)
+ spuvs_fr.insert(0, pin_uv)
+ ss_fr.insert(0, s)
+
+ # paste UVs
+ for l, suv, spuv, ss in zip(bm.faces[dinfo["index"]].loops,
+ suvs_fr, spuvs_fr, ss_fr):
+ l[dlayer].uv = suv
+ l[dlayer].pin_uv = spuv
+ if copy_seams is True:
+ l.edge.seam = ss
+
+ return 0
diff --git a/uv_magic_uv/impl/mirror_uv_impl.py b/uv_magic_uv/impl/mirror_uv_impl.py
new file mode 100644
index 00000000..e79fbc2c
--- /dev/null
+++ b/uv_magic_uv/impl/mirror_uv_impl.py
@@ -0,0 +1,158 @@
+# <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__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bmesh
+from mathutils import Vector
+
+from .. import common
+
+
+__all__ = [
+ 'is_valid_context',
+ 'is_vector_similar',
+ 'mirror_uvs',
+ 'get_face_center',
+ 'MirrorUVImpl',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+def is_vector_similar(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(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 is_vector_similar(svco, dvco, error):
+ dl[uv_layer].uv = suv.copy()
+
+
+def get_face_center(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)
+
+
+class MirrorUVImpl:
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
+ def execute(self, ops_obj, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+
+ error = ops_obj.error
+ axis = ops_obj.axis
+
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ if not bm.loops.layers.uv:
+ ops_obj.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 = get_face_center(f_dst)
+ src = 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 is_vector_similar(dst, src, error):
+ mirror_uvs(
+ uv_layer, f_src, f_dst, ops_obj.axis, ops_obj.error)
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
diff --git a/uv_magic_uv/impl/move_uv_impl.py b/uv_magic_uv/impl/move_uv_impl.py
new file mode 100644
index 00000000..ce507fba
--- /dev/null
+++ b/uv_magic_uv/impl/move_uv_impl.py
@@ -0,0 +1,166 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bmesh
+from mathutils import Vector
+
+from .. import common
+
+
+__all__ = [
+ 'is_valid_context',
+ 'find_uv',
+ 'MoveUVImpl',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+def find_uv(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
+
+
+class MoveUVImpl():
+ __running = False
+
+ def __init__(self):
+ self.__topology_dict = []
+ self.__prev_mouse = Vector((0.0, 0.0))
+ self.__offset_uv = Vector((0.0, 0.0))
+ self.__prev_offset_uv = Vector((0.0, 0.0))
+ self.__first_time = True
+ self.__ini_uvs = []
+ self.__operating = False
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return False
+ if cls.is_running(context):
+ return False
+ return is_valid_context(context)
+
+ @classmethod
+ def is_running(cls, _):
+ return cls.__running
+
+ 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 not self.__operating:
+ if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
+ self.__operating = True
+ 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
+ MoveUVImpl.__running = False
+ return {'FINISHED'}
+ # confirmed
+ if event.type == confirm_btn and event.value == 'PRESS':
+ MoveUVImpl.__running = False
+ return {'FINISHED'}
+
+ return {'RUNNING_MODAL'}
+
+ def execute(self, ops_obj, context):
+ MoveUVImpl.__running = True
+ self.__operating = False
+ self.__first_time = True
+
+ context.window_manager.modal_handler_add(ops_obj)
+ self.__topology_dict, self.__ini_uvs = find_uv(context)
+
+ if context.area:
+ context.area.tag_redraw()
+
+ return {'RUNNING_MODAL'}
diff --git a/uv_magic_uv/impl/transfer_uv_impl.py b/uv_magic_uv/impl/transfer_uv_impl.py
new file mode 100644
index 00000000..adc97352
--- /dev/null
+++ b/uv_magic_uv/impl/transfer_uv_impl.py
@@ -0,0 +1,330 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+from collections import OrderedDict
+
+import bpy
+import bmesh
+
+from .. import common
+
+
+__all__ = [
+ 'is_valid_context',
+ 'get_uv_layer',
+ 'main_parse',
+ 'parse_faces',
+ 'get_new_shared_faces',
+ 'get_other_verts_edges',
+ 'get_selected_src_faces',
+ 'paste_uv',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+def get_uv_layer(ops_obj, bm):
+ # get UV layer
+ if not bm.loops.layers.uv:
+ ops_obj.report({'WARNING'}, "Object must have more than one UV map")
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+
+ return uv_layer
+
+
+def main_parse(ops_obj, 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:
+ ops_obj.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 is None:
+ ops_obj.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 None
+
+ 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]
+
+ 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]
+
+ 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 common.is_debug_mode():
+ 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]
+
+
+def get_selected_src_faces(ops_obj, bm, uv_layer):
+ topology_copied = []
+
+ # get selected faces
+ active_face = bm.faces.active
+ sel_faces = [face for face in bm.faces if face.select]
+ if len(sel_faces) != 2:
+ ops_obj.report({'WARNING'}, "Two faces must be selected")
+ return None
+ if not active_face or active_face not in sel_faces:
+ ops_obj.report({'WARNING'}, "Two faces must be active")
+ return None
+
+ # parse all faces according to selection
+ active_face_nor = active_face.normal.copy()
+ all_sorted_faces = main_parse(ops_obj, uv_layer, sel_faces, active_face,
+ active_face_nor)
+
+ if all_sorted_faces:
+ for face_data in all_sorted_faces.values():
+ edges = face_data[1]
+ uv_loops = face_data[2]
+ uvs = [l.uv.copy() for l in uv_loops]
+ pin_uvs = [l.pin_uv for l in uv_loops]
+ seams = [e.seam for e in edges]
+ topology_copied.append([uvs, pin_uvs, seams])
+ else:
+ return None
+
+ return topology_copied
+
+
+def paste_uv(ops_obj, bm, uv_layer, src_faces, invert_normals, copy_seams):
+ # 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:
+ ops_obj.report({'WARNING'}, "Two faces must be selected")
+ return -1
+
+ # parse selection history
+ for i, _ in enumerate(all_sel_faces):
+ if (i == 0) or (i % 2 == 0):
+ continue
+ 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 invert_normals:
+ active_face_nor.negate()
+ all_sorted_faces = main_parse(ops_obj, 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(src_faces):
+ ops_obj.report({'WARNING'},
+ "Mesh has different amount of faces")
+ return -1
+
+ for j, face_data in enumerate(all_sorted_faces.values()):
+ copied_data = src_faces[j]
+
+ # 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())[j].select = True
+ ops_obj.report({'WARNING'},
+ "Face have different amount of vertices")
+ return 0
+
+ for k, (edge, uvloop) in enumerate(zip(face_data[1],
+ face_data[2])):
+ uvloop.uv = copied_data[0][k]
+ uvloop.pin_uv = copied_data[1][k]
+ if copy_seams:
+ edge.seam = copied_data[2][k]
+ else:
+ return -1
+
+ return 0
diff --git a/uv_magic_uv/impl/uvw_impl.py b/uv_magic_uv/impl/uvw_impl.py
new file mode 100644
index 00000000..e815f54f
--- /dev/null
+++ b/uv_magic_uv/impl/uvw_impl.py
@@ -0,0 +1,154 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+
+from math import sin, cos, pi
+
+from mathutils import Vector
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+def get_uv_layer(ops_obj, bm, assign_uvmap):
+ # get UV layer
+ if not bm.loops.layers.uv:
+ if assign_uvmap:
+ bm.loops.layers.uv.new()
+ else:
+ ops_obj.report({'WARNING'},
+ "Object must have more than one UV map")
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+
+ return uv_layer
+
+
+def apply_box_map(bm, uv_layer, size, offset, rotation, tex_aspect):
+ scale = 1.0 / size
+
+ sx = 1.0 * scale
+ sy = 1.0 * scale
+ sz = 1.0 * scale
+ ofx = offset[0]
+ ofy = offset[1]
+ ofz = offset[2]
+ rx = rotation[0] * pi / 180.0
+ ry = rotation[1] * pi / 180.0
+ rz = rotation[2] * pi / 180.0
+ aspect = tex_aspect
+
+ sel_faces = [f for f in bm.faces if f.select]
+
+ # update UV coordinate
+ for f in sel_faces:
+ n = f.normal
+ for l in f.loops:
+ co = l.vert.co
+ x = co.x * sx
+ y = co.y * sy
+ z = co.z * sz
+
+ # X-plane
+ if abs(n[0]) >= abs(n[1]) and abs(n[0]) >= abs(n[2]):
+ if n[0] >= 0.0:
+ u = (y - ofy) * cos(rx) + (z - ofz) * sin(rx)
+ v = -(y * aspect - ofy) * sin(rx) + \
+ (z * aspect - ofz) * cos(rx)
+ else:
+ u = -(y - ofy) * cos(rx) + (z - ofz) * sin(rx)
+ v = (y * aspect - ofy) * sin(rx) + \
+ (z * aspect - ofz) * cos(rx)
+ # Y-plane
+ elif abs(n[1]) >= abs(n[0]) and abs(n[1]) >= abs(n[2]):
+ if n[1] >= 0.0:
+ u = -(x - ofx) * cos(ry) + (z - ofz) * sin(ry)
+ v = (x * aspect - ofx) * sin(ry) + \
+ (z * aspect - ofz) * cos(ry)
+ else:
+ u = (x - ofx) * cos(ry) + (z - ofz) * sin(ry)
+ v = -(x * aspect - ofx) * sin(ry) + \
+ (z * aspect - ofz) * cos(ry)
+ # Z-plane
+ else:
+ if n[2] >= 0.0:
+ u = (x - ofx) * cos(rz) + (y - ofy) * sin(rz)
+ v = -(x * aspect - ofx) * sin(rz) + \
+ (y * aspect - ofy) * cos(rz)
+ else:
+ u = -(x - ofx) * cos(rz) - (y + ofy) * sin(rz)
+ v = -(x * aspect + ofx) * sin(rz) + \
+ (y * aspect - ofy) * cos(rz)
+
+ l[uv_layer].uv = Vector((u, v))
+
+
+def apply_planer_map(bm, uv_layer, size, offset, rotation, tex_aspect):
+ scale = 1.0 / size
+
+ sx = 1.0 * scale
+ sy = 1.0 * scale
+ ofx = offset[0]
+ ofy = offset[1]
+ rz = rotation * pi / 180.0
+ aspect = tex_aspect
+
+ sel_faces = [f for f in bm.faces if f.select]
+
+ # calculate average of normal
+ n_ave = Vector((0.0, 0.0, 0.0))
+ for f in sel_faces:
+ n_ave = n_ave + f.normal
+ q = n_ave.rotation_difference(Vector((0.0, 0.0, 1.0)))
+
+ # update UV coordinate
+ for f in sel_faces:
+ for l in f.loops:
+ co = q @ l.vert.co
+ x = co.x * sx
+ y = co.y * sy
+
+ u = x * cos(rz) - y * sin(rz) + ofx
+ v = -x * aspect * sin(rz) - y * aspect * cos(rz) + ofy
+
+ l[uv_layer].uv = Vector((u, v))