From 2cbb9e2b960ac343a6f5796e20d0efb443bd4a67 Mon Sep 17 00:00:00 2001
From: nutti <nutti.metro@gmail.com>
Date: Fri, 22 Apr 2022 16:21:10 +0900
Subject: [PATCH] Magic UV: Release v6.6

Added Features

* Copy/Paste UV Island

Updated Features

* Pack UV
  * Add options "Accurate Island Copy", "Stride", "Apply Pack UV"

Other Updates

* Add 'develop' branch to the update target of updater
* Make documents official
* Fix bugs
---
 magic_uv/__init__.py                          |  11 +-
 magic_uv/common.py                            | 137 +++++++++-
 magic_uv/lib/__init__.py                      |   4 +-
 magic_uv/lib/bglx.py                          |   2 +
 magic_uv/op/__init__.py                       |   4 +-
 magic_uv/op/align_uv.py                       |  16 +-
 magic_uv/op/align_uv_cursor.py                |  41 +--
 magic_uv/op/clip_uv.py                        |  16 +-
 magic_uv/op/copy_paste_uv.py                  |  12 +-
 magic_uv/op/copy_paste_uv_object.py           |  12 +-
 magic_uv/op/copy_paste_uv_uvedit.py           | 237 +++++++++++++++++-
 magic_uv/op/flip_rotate_uv.py                 |  12 +-
 magic_uv/op/mirror_uv.py                      |  25 +-
 magic_uv/op/move_uv.py                        |  12 +-
 magic_uv/op/pack_uv.py                        | 130 ++++++++--
 magic_uv/op/preserve_uv_aspect.py             |  12 +-
 magic_uv/op/select_uv.py                      |  24 +-
 magic_uv/op/smooth_uv.py                      |  16 +-
 magic_uv/op/texture_lock.py                   |  12 +-
 magic_uv/op/texture_projection.py             |  12 +-
 magic_uv/op/texture_wrap.py                   |  12 +-
 magic_uv/op/transfer_uv.py                    |  12 +-
 magic_uv/op/unwrap_constraint.py              |  14 +-
 magic_uv/op/uv_bounding_box.py                |  16 +-
 magic_uv/op/uv_inspection.py                  |  20 +-
 magic_uv/op/uv_sculpt.py                      |  12 +-
 magic_uv/op/uvw.py                            |  12 +-
 magic_uv/op/world_scale_uv.py                 |  20 +-
 magic_uv/preferences.py                       |   7 +-
 magic_uv/properties.py                        |   4 +-
 magic_uv/ui/IMAGE_MT_uvs.py                   |  17 +-
 magic_uv/ui/VIEW3D_MT_object.py               |   4 +-
 magic_uv/ui/VIEW3D_MT_uv_map.py               |   4 +-
 magic_uv/ui/__init__.py                       |   4 +-
 magic_uv/ui/uvedit_copy_paste_uv.py           |  21 +-
 magic_uv/ui/uvedit_editor_enhancement.py      |   4 +-
 magic_uv/ui/uvedit_uv_manipulation.py         |  12 +-
 magic_uv/ui/view3d_copy_paste_uv_editmode.py  |   4 +-
 .../ui/view3d_copy_paste_uv_objectmode.py     |   4 +-
 magic_uv/ui/view3d_uv_manipulation.py         |   4 +-
 magic_uv/ui/view3d_uv_mapping.py              |   4 +-
 magic_uv/utils/__init__.py                    |   6 +-
 magic_uv/utils/bl_class_registry.py           |   4 +-
 magic_uv/utils/compatibility.py               |   4 +-
 magic_uv/utils/graph.py                       | 152 +++++++++++
 magic_uv/utils/property_class_registry.py     |   4 +-
 46 files changed, 890 insertions(+), 238 deletions(-)
 create mode 100644 magic_uv/utils/graph.py

diff --git a/magic_uv/__init__.py b/magic_uv/__init__.py
index dc3c96416..883851075 100644
--- a/magic_uv/__init__.py
+++ b/magic_uv/__init__.py
@@ -4,16 +4,17 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 
 bl_info = {
     "name": "Magic UV",
-    "author": "Nutti, Mifth, Jace Priester, kgeogeo, mem, imdjs"
+    "author": "Nutti, Mifth, Jace Priester, kgeogeo, mem, imdjs, "
               "Keith (Wahooney) Boshoff, McBuff, MaxRobinot, "
-              "Alexander Milovsky, Dusan Stevanovic, MatthiasThDs",
-    "version": (6, 5, 0),
+              "Alexander Milovsky, Dusan Stevanovic, MatthiasThDs, "
+              "theCryingMan, PratikBorhade302",
+    "version": (6, 6, 0),
     "blender": (2, 80, 0),
     "location": "See Add-ons Preferences",
     "description": "UV Toolset. See Add-ons Preferences for details",
diff --git a/magic_uv/common.py b/magic_uv/common.py
index 4e6334083..034936c55 100644
--- a/magic_uv/common.py
+++ b/magic_uv/common.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 from collections import defaultdict
 from pprint import pprint
@@ -17,6 +17,7 @@ from mathutils import Vector
 import bmesh
 
 from .utils import compatibility as compat
+from .utils.graph import Graph, Node
 
 
 __DEBUG_MODE = False
@@ -286,6 +287,30 @@ def get_island_info(obj, only_selected=True):
     return get_island_info_from_bmesh(bm, only_selected)
 
 
+# Return island info.
+#
+# Format:
+#
+# [
+#   {
+#     faces: [
+#       {
+#         face: BMFace
+#         max_uv: Vector (2D)
+#         min_uv: Vector (2D)
+#         ave_uv: Vector (2D)
+#       },
+#       ...
+#     ]
+#     center: Vector (2D)
+#     size: Vector (2D)
+#     num_uv: int
+#     group: int
+#     max: Vector (2D)
+#     min: Vector (2D)
+#   },
+#   ...
+# ]
 def get_island_info_from_bmesh(bm, only_selected=True):
     if not bm.loops.layers.uv:
         return None
@@ -1184,12 +1209,22 @@ def __is_polygon_flipped(points):
 
 
 def __is_point_in_polygon(point, subject_points):
+    """Return true when point is inside of the polygon by using
+    'Crossing number algorithm'.
+    """
+
     count = 0
     for i in range(len(subject_points)):
         uv_start1 = subject_points.get(i)
         uv_end1 = subject_points.get(i + 1)
         uv_start2 = point
         uv_end2 = Vector((1000000.0, point.y))
+
+        # If the point exactly matches to the point of the polygon,
+        # this point is not in polygon.
+        if uv_start1.x == uv_start2.x and uv_start1.y == uv_start2.y:
+            return False
+
         intersected, _ = __is_segment_intersect(uv_start1, uv_end1,
                                                 uv_start2, uv_end2)
         if intersected:
@@ -1239,7 +1274,7 @@ def get_overlapped_uv_info(bm_list, faces_list, uv_layer_list,
             overlapped_uv_layer_pairs.append([uv_layer_1, uv_layer_2])
             overlapped_bm_paris.append([bm_1, bm_2])
 
-    # next, check polygon overlapped
+    # check polygon overlapped (inter UV islands)
     overlapped_uvs = []
     for oip, uvlp, bmp in zip(overlapped_isl_pairs,
                               overlapped_uv_layer_pairs,
@@ -1272,6 +1307,41 @@ def get_overlapped_uv_info(bm_list, faces_list, uv_layer_list,
                                            "subject_uvs": subject_uvs,
                                            "polygons": polygons})
 
+    # check polygon overlapped (intra UV island)
+    for info, uv_layer, bm in isl:
+        for i in range(len(info["faces"])):
+            clip = info["faces"][i]
+            f_clip = clip["face"]
+            clip_uvs = [l[uv_layer].uv.copy() for l in f_clip.loops]
+            for j in range(len(info["faces"])):
+                if j <= i:
+                    continue
+
+                subject = info["faces"][j]
+                f_subject = subject["face"]
+
+                # fast operation, apply bounding box algorithm
+                if (clip["max_uv"].x < subject["min_uv"].x) or \
+                   (subject["max_uv"].x < clip["min_uv"].x) or \
+                   (clip["max_uv"].y < subject["min_uv"].y) or \
+                   (subject["max_uv"].y < clip["min_uv"].y):
+                    continue
+
+                subject_uvs = [l[uv_layer].uv.copy() for l in f_subject.loops]
+                # slow operation, apply Weiler-Atherton cliping algorithm
+                result, polygons = \
+                    __do_weiler_atherton_cliping(clip_uvs, subject_uvs,
+                                                 mode, same_polygon_threshold)
+                if result:
+                    overlapped_uvs.append({"clip_bmesh": bm,
+                                           "subject_bmesh": bm,
+                                           "clip_face": f_clip,
+                                           "subject_face": f_subject,
+                                           "clip_uv_layer": uv_layer,
+                                           "subject_uv_layer": uv_layer,
+                                           "subject_uvs": subject_uvs,
+                                           "polygons": polygons})
+
     return overlapped_uvs
 
 
@@ -1308,3 +1378,64 @@ def __is_polygon_same(points1, points2, threshold):
             return False
 
     return True
+
+
+def _is_uv_loop_connected(l1, l2, uv_layer):
+    uv1 = l1[uv_layer].uv
+    uv2 = l2[uv_layer].uv
+    return uv1.x == uv2.x and uv1.y == uv2.y
+
+
+def create_uv_graph(loops, uv_layer):
+    # For looking up faster.
+    loop_index_to_loop = {}     # { loop index: loop }
+    for l in loops:
+        loop_index_to_loop[l.index] = l
+
+    # Setup relationship between uv_vert and loops.
+    # uv_vert is a representative of the loops which shares same
+    # UV coordinate.
+    uv_vert_to_loops = {}   # { uv_vert: loops belonged to uv_vert }
+    loop_to_uv_vert = {}    # { loop: uv_vert belonged to }
+    for l in loops:
+        found = False
+        for k in uv_vert_to_loops.keys():
+            if _is_uv_loop_connected(k, l, uv_layer):
+                uv_vert_to_loops[k].append(l)
+                loop_to_uv_vert[l] = k
+                found = True
+                break
+        if not found:
+            uv_vert_to_loops[l] = [l]
+            loop_to_uv_vert[l] = l
+
+    # Collect adjacent uv_vert.
+    uv_adj_verts = {}       # { uv_vert: adj uv_vert list }
+    for v, vs in uv_vert_to_loops.items():
+        uv_adj_verts[v] = []
+        for ll in vs:
+            ln = ll.link_loop_next
+            lp = ll.link_loop_prev
+            uv_adj_verts[v].append(loop_to_uv_vert[ln])
+            uv_adj_verts[v].append(loop_to_uv_vert[lp])
+        uv_adj_verts[v] = list(set(uv_adj_verts[v]))
+
+    # Setup uv_vert graph.
+    graph = Graph()
+    for v in uv_adj_verts.keys():
+        graph.add_node(
+            Node(v.index, {"uv_vert": v, "loops": uv_vert_to_loops[v]})
+        )
+    edges = []
+    for v, adjs in uv_adj_verts.items():
+        n1 = graph.get_node(v.index)
+        for a in adjs:
+            n2 = graph.get_node(a.index)
+            edges.append(tuple(sorted((n1.key, n2.key))))
+    edges = list(set(edges))
+    for e in edges:
+        n1 = graph.get_node(e[0])
+        n2 = graph.get_node(e[1])
+        graph.add_edge(n1, n2)
+
+    return graph
diff --git a/magic_uv/lib/__init__.py b/magic_uv/lib/__init__.py
index 76eaf4800..bccf4e170 100644
--- a/magic_uv/lib/__init__.py
+++ b/magic_uv/lib/__init__.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 if "bpy" in locals():
     import importlib
diff --git a/magic_uv/lib/bglx.py b/magic_uv/lib/bglx.py
index c1f696ab4..044141b69 100644
--- a/magic_uv/lib/bglx.py
+++ b/magic_uv/lib/bglx.py
@@ -1,5 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
+# <pep8-80 compliant>
+
 from threading import Lock
 
 import bgl
diff --git a/magic_uv/op/__init__.py b/magic_uv/op/__init__.py
index da77b17b3..223ed004a 100644
--- a/magic_uv/op/__init__.py
+++ b/magic_uv/op/__init__.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 if "bpy" in locals():
     import importlib
diff --git a/magic_uv/op/align_uv.py b/magic_uv/op/align_uv.py
index 9f606db9b..cf5a49deb 100644
--- a/magic_uv/op/align_uv.py
+++ b/magic_uv/op/align_uv.py
@@ -4,8 +4,8 @@
 
 __author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import math
 from math import atan2, tan, sin, cos
@@ -28,6 +28,12 @@ from .. import common
 
 
 def _is_valid_context(context):
+    # '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
+    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -36,12 +42,6 @@ def _is_valid_context(context):
     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
-    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/align_uv_cursor.py b/magic_uv/op/align_uv_cursor.py
index 2b7f1491b..696b7cb85 100644
--- a/magic_uv/op/align_uv_cursor.py
+++ b/magic_uv/op/align_uv_cursor.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 from mathutils import Vector
@@ -175,7 +175,8 @@ class MUV_OT_AlignUVCursor(bpy.types.Operator):
                     uv_layer = bm.loops.layers.uv.verify()
 
                     for f in bm.faces:
-                        if not f.select:
+                        if (not context.tool_settings.use_uv_select_sync and
+                                not f.select):
                             continue
                         for l in f.loops:
                             uv = l[uv_layer].uv
@@ -204,18 +205,30 @@ class MUV_OT_AlignUVCursor(bpy.types.Operator):
                         return None
                     uv_layer = bm.loops.layers.uv.verify()
 
-                    for f in bm.faces:
-                        if not f.select:
-                            continue
-                        for l in f.loops:
-                            if not l[uv_layer].select:
+                    if context.tool_settings.use_uv_select_sync:
+                        for v in bm.verts:
+                            if not v.select:
                                 continue
-                            uv = l[uv_layer].uv
-                            max_.x = max(max_.x, uv.x)
-                            max_.y = max(max_.y, uv.y)
-                            min_.x = min(min_.x, uv.x)
-                            min_.y = min(min_.y, uv.y)
-                            no_selected_face = False
+                            for l in v.link_loops:
+                                uv = l[uv_layer].uv
+                                max_.x = max(max_.x, uv.x)
+                                max_.y = max(max_.y, uv.y)
+                                min_.x = min(min_.x, uv.x)
+                                min_.y = min(min_.y, uv.y)
+                                no_selected_face = False
+                    else:
+                        for f in bm.faces:
+                            if not f.select:
+                                continue
+                            for l in f.loops:
+                                if not l[uv_layer].select:
+                                    continue
+                                uv = l[uv_layer].uv
+                                max_.x = max(max_.x, uv.x)
+                                max_.y = max(max_.y, uv.y)
+                                min_.x = min(min_.x, uv.x)
+                                min_.y = min(min_.y, uv.y)
+                                no_selected_face = False
             if no_selected_face:
                 max_ = Vector((1.0, 1.0))
                 min_ = Vector((0.0, 0.0))
diff --git a/magic_uv/op/clip_uv.py b/magic_uv/op/clip_uv.py
index c74755438..e3815453c 100644
--- a/magic_uv/op/clip_uv.py
+++ b/magic_uv/op/clip_uv.py
@@ -4,8 +4,8 @@
 
 __author__ = "Dusan Stevanovic, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 
 import math
@@ -22,6 +22,12 @@ from ..utils import compatibility as compat
 
 
 def _is_valid_context(context):
+    # '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
+    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -30,12 +36,6 @@ def _is_valid_context(context):
     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
-    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/copy_paste_uv.py b/magic_uv/op/copy_paste_uv.py
index 8ee83ad94..1496d67ac 100644
--- a/magic_uv/op/copy_paste_uv.py
+++ b/magic_uv/op/copy_paste_uv.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bmesh
 import bpy.utils
@@ -23,6 +23,10 @@ from ..utils import compatibility as compat
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     # Multiple objects editing mode is not supported in this feature.
     objs = common.get_uv_editable_objects(context)
     if len(objs) != 1:
@@ -32,10 +36,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/copy_paste_uv_object.py b/magic_uv/op/copy_paste_uv_object.py
index 4e5d500af..af5df07c3 100644
--- a/magic_uv/op/copy_paste_uv_object.py
+++ b/magic_uv/op/copy_paste_uv_object.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bmesh
 import bpy
@@ -28,6 +28,10 @@ from ..utils import compatibility as compat
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     # Multiple objects editing mode is not supported in this feature.
     objs = common.get_uv_editable_objects(context)
     if len(objs) != 1:
@@ -37,10 +41,6 @@ def _is_valid_context(context):
     if context.object.mode != 'OBJECT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/copy_paste_uv_uvedit.py b/magic_uv/op/copy_paste_uv_uvedit.py
index 7055915f9..733c30b32 100644
--- a/magic_uv/op/copy_paste_uv_uvedit.py
+++ b/magic_uv/op/copy_paste_uv_uvedit.py
@@ -4,8 +4,8 @@
 
 __author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import math
 from math import atan2, sin, cos
@@ -13,13 +13,22 @@ from math import atan2, sin, cos
 import bpy
 import bmesh
 from mathutils import Vector
+from bpy.props import BoolProperty
 
 from .. import common
 from ..utils.bl_class_registry import BlClassRegistry
 from ..utils.property_class_registry import PropertyClassRegistry
+from ..utils.graph import graph_is_isomorphic
+from ..utils import compatibility as compat
 
 
 def _is_valid_context(context):
+    # '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
+    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
+        return False
+
     # Multiple objects editing mode is not supported in this feature.
     objs = common.get_uv_editable_objects(context)
     if len(objs) != 1:
@@ -29,12 +38,6 @@ def _is_valid_context(context):
     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
-    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
-        return False
-
     return True
 
 
@@ -44,14 +47,33 @@ class _Properties:
 
     @classmethod
     def init_props(cls, scene):
-        class Props():
+        class CopyPastUVProps():
             src_uvs = None
 
-        scene.muv_props.copy_paste_uv_uvedit = Props()
+        class CopyPasteUVIslandProps():
+            # [
+            #   {
+            #     "bmesh": BMesh,
+            #     "uv_layer": UV Layer,
+            #     "island": UV Island,
+            #   }
+            # ]
+            src_data = []
+            src_objects = []
+
+        scene.muv_props.copy_paste_uv_uvedit = CopyPastUVProps()
+        scene.muv_props.copy_paste_uv_island = CopyPasteUVIslandProps()
+
+        scene.muv_copy_paste_uv_uvedit_unique_target = BoolProperty(
+            name="Unique Target",
+            description="Paste to the target uniquely",
+            default=False
+        )
 
     @classmethod
     def del_props(cls, scene):
         del scene.muv_props.copy_paste_uv_uvedit
+        del scene.muv_props.copy_paste_uv_island
 
 
 @BlClassRegistry()
@@ -182,3 +204,198 @@ class MUV_OT_CopyPasteUVUVEdit_PasteUV(bpy.types.Operator):
         bmesh.update_edit_mesh(obj.data)
 
         return {'FINISHED'}
+
+
+# Return selected/all count.
+#   If context.tool_settings.use_uv_select_sync is enabled:
+#      Return selected/all face count.
+#   If context.tool_settings.use_uv_select_sync is disabled:
+#      Return selected/all loop count.
+def get_counts(context, island, uv_layer):
+    selected_count = 0
+    all_count = 0
+    if context.tool_settings.use_uv_select_sync:
+        for f in island["faces"]:
+            all_count += 1
+            if f["face"].select:
+                selected_count += 1
+    else:
+        for f in island["faces"]:
+            for l in f["face"].loops:
+                all_count += 1
+                if l[uv_layer].select:
+                    selected_count += 1
+
+    return selected_count, all_count
+
+
+@BlClassRegistry()
+class MUV_OT_CopyPasteUVUVEdit_CopyUVIsland(bpy.types.Operator):
+    """
+    Operation class: Copy UV island on UV/Image Editor
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_uvedit_copy_uv_island"
+    bl_label = "Copy UV Island (UV/Image Editor)"
+    bl_description = "Copy UV island (only selected in UV/Image Editor)"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    @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):
+        sc = context.scene
+        props = sc.muv_props.copy_paste_uv_island
+
+        props.src_data = []
+        props.src_objects = []
+        objs = common.get_uv_editable_objects(context)
+        for obj in objs:
+            bm = bmesh.from_edit_mesh(obj.data)
+            uv_layer = bm.loops.layers.uv.verify()
+            if common.check_version(2, 73, 0) >= 0:
+                bm.verts.ensure_lookup_table()
+                bm.edges.ensure_lookup_table()
+                bm.faces.ensure_lookup_table()
+
+            if context.tool_settings.use_uv_select_sync:
+                islands = common.get_island_info_from_bmesh(
+                    bm, only_selected=False)
+            else:
+                islands = common.get_island_info_from_bmesh(
+                    bm, only_selected=True)
+            for isl in islands:
+                # Check if all UVs belonging to the island is selected.
+                selected_count, all_count = get_counts(context, isl, uv_layer)
+                if selected_count == 0:
+                    continue
+                if selected_count != all_count:
+                    self.report(
+                        {'WARNING'},
+                        "All UVs belonging to the island must be selected")
+                    return {'CANCELLED'}
+
+                data = {
+                    "bmesh": bm,
+                    "uv_layer": uv_layer,
+                    "island": isl
+                }
+                props.src_data.append(data)
+            props.src_objects.append(obj)
+
+        return {'FINISHED'}
+
+
+@BlClassRegistry()
+@compat.make_annotations
+class MUV_OT_CopyPasteUVUVEdit_PasteUVIsland(bpy.types.Operator):
+    """
+    Operation class: Paste UV island on UV/Image Editor
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_uvedit_paste_uv_island"
+    bl_label = "Paste UV Island (UV/Image Editor)"
+    bl_description = "Paste UV island (only selected in UV/Image Editor)"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    unique_target = BoolProperty(
+        name="Unique Target",
+        description="Paste to the target uniquely",
+        default=False
+    )
+
+    @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_island
+        if not props.src_data:
+            return False
+        return _is_valid_context(context)
+
+    def execute(self, context):
+        sc = context.scene
+        props = sc.muv_props.copy_paste_uv_island
+
+        src_data = props.src_data
+        src_objs = props.src_objects
+
+        bms_and_uv_layers = {}
+        for d in src_data:
+            bms_and_uv_layers[d["bmesh"]] = d["uv_layer"]
+        dst_data = []
+        for bm, uv_layer in bms_and_uv_layers.items():
+            if context.tool_settings.use_uv_select_sync:
+                islands = common.get_island_info_from_bmesh(
+                    bm, only_selected=False)
+            else:
+                islands = common.get_island_info_from_bmesh(
+                    bm, only_selected=True)
+            for isl in islands:
+                # Check if all UVs belonging to the island is selected.
+                selected_count, all_count = get_counts(context, isl, uv_layer)
+                if selected_count == 0:
+                    continue
+                if selected_count != all_count:
+                    self.report(
+                        {'WARNING'},
+                        "All UVs belonging to the island must be selected")
+                    return {'CANCELLED'}
+
+                dst_data.append(
+                    {
+                        "bm": bm,
+                        "uv_layer": uv_layer,
+                        "island": isl,
+                    }
+                )
+
+        used = []
+        for ddata in dst_data:
+            dst_loops = []
+            for f in ddata["island"]["faces"]:
+                for l in f["face"].loops:
+                    dst_loops.append(l)
+            dst_uv_layer = ddata["uv_layer"]
+
+            # Find a suitable island.
+            for sdata in src_data:
+                if self.unique_target and sdata in used:
+                    continue
+
+                src_loops = []
+                for f in sdata["island"]["faces"]:
+                    for l in f["face"].loops:
+                        src_loops.append(l)
+                src_uv_layer = sdata["uv_layer"]
+
+                # Create UV graph.
+                src_uv_graph = common.create_uv_graph(src_loops, src_uv_layer)
+                dst_uv_graph = common.create_uv_graph(dst_loops, dst_uv_layer)
+
+                # Check if the graph is isomorphic.
+                # If the graph is isomorphic, matching pair is returned.
+                result, pairs = graph_is_isomorphic(src_uv_graph, dst_uv_graph)
+                if result:
+                    # Paste UV island.
+                    for n1, n2 in pairs.items():
+                        uv1 = n1.value["uv_vert"][src_uv_layer].uv
+                        l2 = n2.value["loops"]
+                        for l in l2:
+                            l[dst_uv_layer].uv = uv1
+                    used.append(sdata)
+                    break
+            else:
+                self.report({'WARNING'}, "Island does not match")
+                return {'CANCELLED'}
+
+        for obj in src_objs:
+            bmesh.update_edit_mesh(obj.data)
+
+        return {'FINISHED'}
diff --git a/magic_uv/op/flip_rotate_uv.py b/magic_uv/op/flip_rotate_uv.py
index cb40ede88..c9a79420f 100644
--- a/magic_uv/op/flip_rotate_uv.py
+++ b/magic_uv/op/flip_rotate_uv.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 import bmesh
@@ -21,6 +21,10 @@ from ..utils import compatibility as compat
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -29,10 +33,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/mirror_uv.py b/magic_uv/op/mirror_uv.py
index dce3ca013..2f637535b 100644
--- a/magic_uv/op/mirror_uv.py
+++ b/magic_uv/op/mirror_uv.py
@@ -4,8 +4,8 @@
 
 __author__ = "Keith (Wahooney) Boshoff, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 from bpy.props import (
@@ -23,6 +23,10 @@ from .. import common
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -31,10 +35,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
@@ -251,15 +251,22 @@ class MUV_OT_MirrorUV(bpy.types.Operator):
                     # test if the vertices x values are the same sign
                     dst = _get_face_center(f_dst, transformed_verts)
                     src = _get_face_center(f_src, transformed_verts)
-                    if (dst.x > 0 and src.x > 0) or (dst.x < 0 and src.x < 0):
-                        continue
 
                     # invert source axis
                     if axis == 'X':
+                        if ((dst.x > 0 and src.x > 0) or
+                                (dst.x < 0 and src.x < 0)):
+                            continue
                         src.x = -src.x
                     elif axis == 'Y':
-                        src.y = -src.z
+                        if ((dst.y > 0 and src.y > 0) or
+                                (dst.y < 0 and src.y < 0)):
+                            continue
+                        src.y = -src.y
                     elif axis == 'Z':
+                        if ((dst.z > 0 and src.z > 0) or
+                                (dst.z < 0 and src.z < 0)):
+                            continue
                         src.z = -src.z
 
                     # do mirror UV
diff --git a/magic_uv/op/move_uv.py b/magic_uv/op/move_uv.py
index 76022d12d..b99a6b15d 100644
--- a/magic_uv/op/move_uv.py
+++ b/magic_uv/op/move_uv.py
@@ -4,8 +4,8 @@
 
 __author__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 from bpy.props import BoolProperty
@@ -18,6 +18,10 @@ from ..utils.property_class_registry import PropertyClassRegistry
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     # Multiple objects editing mode is not supported in this feature.
     objs = common.get_uv_editable_objects(context)
     if len(objs) != 1:
@@ -27,10 +31,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/pack_uv.py b/magic_uv/op/pack_uv.py
index 5eaceaf49..2163d4f39 100644
--- a/magic_uv/op/pack_uv.py
+++ b/magic_uv/op/pack_uv.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 from math import fabs
 
@@ -21,11 +21,18 @@ from mathutils import Vector
 
 from ..utils.bl_class_registry import BlClassRegistry
 from ..utils.property_class_registry import PropertyClassRegistry
+from ..utils.graph import graph_is_isomorphic
 from ..utils import compatibility as compat
 from .. import common
 
 
 def _is_valid_context(context):
+    # '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
+    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -34,12 +41,6 @@ def _is_valid_context(context):
     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
-    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
-        return False
-
     return True
 
 
@@ -137,15 +138,36 @@ class _Properties:
             min=0.000001,
             max=10.0,
             default=(0.001, 0.001),
-            size=2
+            size=2,
+            subtype='XYZ'
         )
         scene.muv_pack_uv_allowable_size_deviation = FloatVectorProperty(
             name="Allowable Size Deviation",
-            description="Allowable sizse deviation to judge same UV island",
+            description="Allowable sizes deviation to judge same UV island",
             min=0.000001,
             max=10.0,
             default=(0.001, 0.001),
-            size=2
+            size=2,
+            subtype='XYZ'
+        )
+        scene.muv_pack_uv_accurate_island_copy = BoolProperty(
+            name="Accurate Island Copy",
+            description="Copy islands topologically",
+            default=True
+        )
+        scene.muv_pack_uv_stride = FloatVectorProperty(
+            name="Stride",
+            description="Stride UV coordinates",
+            min=-100.0,
+            max=100.0,
+            default=(0.0, 0.0),
+            size=2,
+            subtype='XYZ'
+        )
+        scene.muv_pack_uv_apply_pack_uv = BoolProperty(
+            name="Apply Pack UV",
+            description="Apply Pack UV operation intrinsic to Blender itself",
+            default=True
         )
 
     @classmethod
@@ -153,6 +175,9 @@ class _Properties:
         del scene.muv_pack_uv_enabled
         del scene.muv_pack_uv_allowable_center_deviation
         del scene.muv_pack_uv_allowable_size_deviation
+        del scene.muv_pack_uv_accurate_island_copy
+        del scene.muv_pack_uv_stride
+        del scene.muv_pack_uv_apply_pack_uv
 
 
 @BlClassRegistry()
@@ -188,7 +213,8 @@ class MUV_OT_PackUV(bpy.types.Operator):
         min=0.000001,
         max=10.0,
         default=(0.001, 0.001),
-        size=2
+        size=2,
+        subtype='XYZ'
     )
     allowable_size_deviation = FloatVectorProperty(
         name="Allowable Size Deviation",
@@ -196,7 +222,27 @@ class MUV_OT_PackUV(bpy.types.Operator):
         min=0.000001,
         max=10.0,
         default=(0.001, 0.001),
-        size=2
+        size=2,
+        subtype='XYZ'
+    )
+    accurate_island_copy = BoolProperty(
+        name="Accurate Island Copy",
+        description="Copy islands topologically",
+        default=True
+    )
+    stride = FloatVectorProperty(
+        name="Stride",
+        description="Stride UV coordinates",
+        min=-100.0,
+        max=100.0,
+        default=(0.0, 0.0),
+        size=2,
+        subtype='XYZ'
+    )
+    apply_pack_uv = BoolProperty(
+        name="Apply Pack UV",
+        description="Apply Pack UV operation intrinsic to Blender itself",
+        default=True
     )
 
     @classmethod
@@ -249,7 +295,8 @@ class MUV_OT_PackUV(bpy.types.Operator):
         for obj in objs:
             bmesh.update_edit_mesh(obj.data)
         bpy.ops.uv.select_all(action='SELECT')
-        bpy.ops.uv.pack_islands(rotate=self.rotate, margin=self.margin)
+        if self.apply_pack_uv:
+            bpy.ops.uv.pack_islands(rotate=self.rotate, margin=self.margin)
 
         # copy/paste UV among same islands
         for gidx in range(num_group):
@@ -260,16 +307,57 @@ class MUV_OT_PackUV(bpy.types.Operator):
             src_bm = island_to_bm[group[0]["id"]]
             src_uv_layer = island_to_uv_layer[group[0]["id"]]
             src_loop_lists = bm_to_loop_lists[src_bm]
-            for g in group[1:]:
+
+            src_loops = []
+            for f in group[0]["faces"]:
+                for l in f["face"].loops:
+                    src_loops.append(l)
+
+            src_uv_graph = common.create_uv_graph(src_loops, src_uv_layer)
+
+            for stride_idx, g in enumerate(group[1:]):
                 dst_bm = island_to_bm[g["id"]]
                 dst_uv_layer = island_to_uv_layer[g["id"]]
                 dst_loop_lists = bm_to_loop_lists[dst_bm]
-                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):
-                        dst_loop_lists[dest_loop.index][dst_uv_layer].uv = \
-                            src_loop_lists[src_loop.index][src_uv_layer].uv
+
+                dst_loops = []
+                for f in g["faces"]:
+                    for l in f["face"].loops:
+                        dst_loops.append(l)
+
+                dst_uv_graph = common.create_uv_graph(dst_loops, dst_uv_layer)
+
+                uv_stride = Vector(((stride_idx + 1) * self.stride.x,
+                                    (stride_idx + 1) * self.stride.y))
+                if self.accurate_island_copy:
+                    # Check if the graph is isomorphic.
+                    # If the graph is isomorphic, matching pair is returned.
+                    result, pairs = graph_is_isomorphic(
+                        src_uv_graph, dst_uv_graph)
+                    if not result:
+                        self.report(
+                            {'WARNING'},
+                            "Island does not match. "
+                            "Disable 'Accurate Island Copy' and try again")
+                        return {'CANCELLED'}
+
+                    # Paste UV island.
+                    for n1, n2 in pairs.items():
+                        uv1 = n1.value["uv_vert"][src_uv_layer].uv
+                        l2 = n2.value["loops"]
+                        for l in l2:
+                            l[dst_uv_layer].uv = uv1 + uv_stride
+                else:
+                    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):
+                            src_lidx = src_loop.index
+                            dst_lidx = dest_loop.index
+                            dst_loop_lists[dst_lidx][dst_uv_layer].uv = \
+                                src_loop_lists[src_lidx][src_uv_layer].uv + \
+                                uv_stride
 
         # restore face/UV selection
         bpy.ops.uv.select_all(action='DESELECT')
diff --git a/magic_uv/op/preserve_uv_aspect.py b/magic_uv/op/preserve_uv_aspect.py
index 9d3cbddeb..32e0103f5 100644
--- a/magic_uv/op/preserve_uv_aspect.py
+++ b/magic_uv/op/preserve_uv_aspect.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 from bpy.props import StringProperty, EnumProperty, BoolProperty
@@ -19,6 +19,10 @@ from ..utils import compatibility as compat
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -27,10 +31,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/select_uv.py b/magic_uv/op/select_uv.py
index affc41e43..3e1f160c5 100644
--- a/magic_uv/op/select_uv.py
+++ b/magic_uv/op/select_uv.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 from bpy.props import BoolProperty, FloatProperty, EnumProperty
@@ -18,6 +18,12 @@ from ..utils import compatibility as compat
 
 
 def _is_valid_context(context):
+    # '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
+    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -26,12 +32,6 @@ def _is_valid_context(context):
     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
-    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
-        return False
-
     return True
 
 
@@ -49,8 +49,8 @@ class _Properties:
         scene.muv_select_uv_same_polygon_threshold = FloatProperty(
             name="Same Polygon Threshold",
             description="Threshold to distinguish same polygons",
-            default=0.000001,
-            min=0.000001,
+            default=0.00001,
+            min=0.00001,
             max=0.01,
             step=0.00001
         )
@@ -91,8 +91,8 @@ class MUV_OT_SelectUV_SelectOverlapped(bpy.types.Operator):
     same_polygon_threshold = FloatProperty(
         name="Same Polygon Threshold",
         description="Threshold to distinguish same polygons",
-        default=0.000001,
-        min=0.000001,
+        default=0.00001,
+        min=0.00001,
         max=0.01,
         step=0.00001
     )
diff --git a/magic_uv/op/smooth_uv.py b/magic_uv/op/smooth_uv.py
index 020bc78f8..b7c06c7c2 100644
--- a/magic_uv/op/smooth_uv.py
+++ b/magic_uv/op/smooth_uv.py
@@ -4,8 +4,8 @@
 
 __author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 from bpy.props import BoolProperty, FloatProperty
@@ -18,6 +18,12 @@ from ..utils import compatibility as compat
 
 
 def _is_valid_context(context):
+    # '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
+    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -26,12 +32,6 @@ def _is_valid_context(context):
     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
-    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/texture_lock.py b/magic_uv/op/texture_lock.py
index f54c94530..94d91e573 100644
--- a/magic_uv/op/texture_lock.py
+++ b/magic_uv/op/texture_lock.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import math
 from math import atan2, cos, sqrt, sin, fabs
@@ -172,6 +172,10 @@ def _calc_tri_vert(v0, v1, angle0, angle1):
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -180,10 +184,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/texture_projection.py b/magic_uv/op/texture_projection.py
index 912447c33..1288e9b79 100644
--- a/magic_uv/op/texture_projection.py
+++ b/magic_uv/op/texture_projection.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 from collections import namedtuple
 from math import sin, cos
@@ -139,6 +139,10 @@ def _create_affine_matrix(identity, scale, rotate, translate):
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -147,10 +151,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/texture_wrap.py b/magic_uv/op/texture_wrap.py
index 4f9c868d8..55e878120 100644
--- a/magic_uv/op/texture_wrap.py
+++ b/magic_uv/op/texture_wrap.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 from bpy.props import (
@@ -19,6 +19,10 @@ from ..utils.property_class_registry import PropertyClassRegistry
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     # Multiple objects editing mode is not supported in this feature.
     objs = common.get_uv_editable_objects(context)
     if len(objs) != 1:
@@ -28,10 +32,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/transfer_uv.py b/magic_uv/op/transfer_uv.py
index 029a5de35..4d332a55b 100644
--- a/magic_uv/op/transfer_uv.py
+++ b/magic_uv/op/transfer_uv.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>, Mifth, MaxRobinot"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 from collections import OrderedDict
 
@@ -20,6 +20,10 @@ from ..utils import compatibility as compat
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     # Multiple objects editing mode is not supported in this feature.
     objs = common.get_uv_editable_objects(context)
     if len(objs) != 1:
@@ -29,10 +33,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/unwrap_constraint.py b/magic_uv/op/unwrap_constraint.py
index da94c495d..06b33c277 100644
--- a/magic_uv/op/unwrap_constraint.py
+++ b/magic_uv/op/unwrap_constraint.py
@@ -1,9 +1,11 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
+# <pep8-80 compliant>
+
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 from bpy.props import (
@@ -20,6 +22,10 @@ from ..utils import compatibility as compat
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -28,10 +34,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/uv_bounding_box.py b/magic_uv/op/uv_bounding_box.py
index 436f66793..07519c248 100644
--- a/magic_uv/op/uv_bounding_box.py
+++ b/magic_uv/op/uv_bounding_box.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 from enum import IntEnum
 import math
@@ -30,6 +30,12 @@ MAX_VALUE = 100000.0
 
 
 def _is_valid_context(context):
+    # '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
+    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
+        return False
+
     obj = context.object
 
     # only edit mode is allowed to execute
@@ -40,12 +46,6 @@ def _is_valid_context(context):
     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
-    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/uv_inspection.py b/magic_uv/op/uv_inspection.py
index e974221e5..cfbdbacff 100644
--- a/magic_uv/op/uv_inspection.py
+++ b/magic_uv/op/uv_inspection.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import random
 from math import fabs
@@ -26,6 +26,12 @@ else:
 
 
 def _is_valid_context(context):
+    # '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
+    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -34,12 +40,6 @@ def _is_valid_context(context):
     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
-    if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
-        return False
-
     return True
 
 
@@ -156,8 +156,8 @@ class _Properties:
         scene.muv_uv_inspection_same_polygon_threshold = FloatProperty(
             name="Same Polygon Threshold",
             description="Threshold to distinguish same polygons",
-            default=0.000001,
-            min=0.000001,
+            default=0.00001,
+            min=0.00001,
             max=0.01,
             step=0.00001
         )
diff --git a/magic_uv/op/uv_sculpt.py b/magic_uv/op/uv_sculpt.py
index 24c76e130..d1cfc6909 100644
--- a/magic_uv/op/uv_sculpt.py
+++ b/magic_uv/op/uv_sculpt.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 from math import pi, cos, tan, sin
 
@@ -35,6 +35,10 @@ else:
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -43,10 +47,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/uvw.py b/magic_uv/op/uvw.py
index 516a5da89..24588b82a 100644
--- a/magic_uv/op/uvw.py
+++ b/magic_uv/op/uvw.py
@@ -4,8 +4,8 @@
 
 __author__ = "Alexander Milovsky, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 from math import sin, cos, pi
 
@@ -26,6 +26,10 @@ from ..utils import compatibility as compat
 
 
 def _is_valid_context(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -34,10 +38,6 @@ def _is_valid_context(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/op/world_scale_uv.py b/magic_uv/op/world_scale_uv.py
index 9c617ee40..41f258525 100644
--- a/magic_uv/op/world_scale_uv.py
+++ b/magic_uv/op/world_scale_uv.py
@@ -4,8 +4,8 @@
 
 __author__ = "McBuff, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 from math import sqrt
 
@@ -26,6 +26,10 @@ from ..utils import compatibility as compat
 
 
 def _is_valid_context_for_measure(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     # Multiple objects editing mode is not supported in this feature.
     objs = common.get_uv_editable_objects(context)
     if len(objs) != 1:
@@ -35,14 +39,14 @@ def _is_valid_context_for_measure(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
 def _is_valid_context_for_apply(context):
+    # only 'VIEW_3D' space is allowed to execute
+    if not common.is_valid_space(context, ['VIEW_3D']):
+        return False
+
     objs = common.get_uv_editable_objects(context)
     if not objs:
         return False
@@ -51,10 +55,6 @@ def _is_valid_context_for_apply(context):
     if context.object.mode != 'EDIT':
         return False
 
-    # only 'VIEW_3D' space is allowed to execute
-    if not common.is_valid_space(context, ['VIEW_3D']):
-        return False
-
     return True
 
 
diff --git a/magic_uv/preferences.py b/magic_uv/preferences.py
index 27785ad9a..d273f41ff 100644
--- a/magic_uv/preferences.py
+++ b/magic_uv/preferences.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 from bpy.props import (
@@ -119,6 +119,9 @@ def image_uvs_menu_fn(self, context):
     ops = layout.operator(MUV_OT_PackUV.bl_idname, text="Pack UV")
     ops.allowable_center_deviation = sc.muv_pack_uv_allowable_center_deviation
     ops.allowable_size_deviation = sc.muv_pack_uv_allowable_size_deviation
+    ops.accurate_island_copy = sc.muv_pack_uv_accurate_island_copy
+    ops.stride = sc.muv_pack_uv_stride
+    ops.apply_pack_uv = sc.muv_pack_uv_apply_pack_uv
     # Select UV
     layout.menu(MUV_MT_SelectUV.bl_idname, text="Select UV")
     # Smooth UV
diff --git a/magic_uv/properties.py b/magic_uv/properties.py
index 215069156..127fc6f26 100644
--- a/magic_uv/properties.py
+++ b/magic_uv/properties.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 
 from .utils.property_class_registry import PropertyClassRegistry
diff --git a/magic_uv/ui/IMAGE_MT_uvs.py b/magic_uv/ui/IMAGE_MT_uvs.py
index adad2fe91..0f94409b7 100644
--- a/magic_uv/ui/IMAGE_MT_uvs.py
+++ b/magic_uv/ui/IMAGE_MT_uvs.py
@@ -4,14 +4,16 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 
 from ..op.copy_paste_uv_uvedit import (
     MUV_OT_CopyPasteUVUVEdit_CopyUV,
     MUV_OT_CopyPasteUVUVEdit_PasteUV,
+    MUV_OT_CopyPasteUVUVEdit_CopyUVIsland,
+    MUV_OT_CopyPasteUVUVEdit_PasteUVIsland,
 )
 from ..op.align_uv_cursor import MUV_OT_AlignUVCursor
 from ..op.align_uv import (
@@ -42,13 +44,22 @@ class MUV_MT_CopyPasteUV_UVEdit(bpy.types.Menu):
     bl_label = "Copy/Paste UV"
     bl_description = "Copy and Paste UV coordinate among object"
 
-    def draw(self, _):
+    def draw(self, context):
         layout = self.layout
+        sc = context.scene
 
+        layout.label(text="Face")
         layout.operator(MUV_OT_CopyPasteUVUVEdit_CopyUV.bl_idname, text="Copy")
         layout.operator(MUV_OT_CopyPasteUVUVEdit_PasteUV.bl_idname,
                         text="Paste")
 
+        layout.label(text="Island")
+        layout.operator(MUV_OT_CopyPasteUVUVEdit_CopyUVIsland.bl_idname,
+                        text="Copy")
+        ops = layout.operator(MUV_OT_CopyPasteUVUVEdit_PasteUVIsland.bl_idname,
+                              text="Paste")
+        ops.unique_target = sc.muv_copy_paste_uv_uvedit_unique_target
+
 
 @BlClassRegistry()
 class MUV_MT_AlignUV(bpy.types.Menu):
diff --git a/magic_uv/ui/VIEW3D_MT_object.py b/magic_uv/ui/VIEW3D_MT_object.py
index 29d5d607c..e9349ee00 100644
--- a/magic_uv/ui/VIEW3D_MT_object.py
+++ b/magic_uv/ui/VIEW3D_MT_object.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 
diff --git a/magic_uv/ui/VIEW3D_MT_uv_map.py b/magic_uv/ui/VIEW3D_MT_uv_map.py
index ee99ccba6..544dabe47 100644
--- a/magic_uv/ui/VIEW3D_MT_uv_map.py
+++ b/magic_uv/ui/VIEW3D_MT_uv_map.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy.utils
 
diff --git a/magic_uv/ui/__init__.py b/magic_uv/ui/__init__.py
index 883e966ff..9b56e3aea 100644
--- a/magic_uv/ui/__init__.py
+++ b/magic_uv/ui/__init__.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 if "bpy" in locals():
     import importlib
diff --git a/magic_uv/ui/uvedit_copy_paste_uv.py b/magic_uv/ui/uvedit_copy_paste_uv.py
index 847b6e9a2..95da5be4d 100644
--- a/magic_uv/ui/uvedit_copy_paste_uv.py
+++ b/magic_uv/ui/uvedit_copy_paste_uv.py
@@ -4,14 +4,16 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 
 from ..op.copy_paste_uv_uvedit import (
     MUV_OT_CopyPasteUVUVEdit_CopyUV,
     MUV_OT_CopyPasteUVUVEdit_PasteUV,
+    MUV_OT_CopyPasteUVUVEdit_CopyUVIsland,
+    MUV_OT_CopyPasteUVUVEdit_PasteUVIsland,
 )
 from ..utils.bl_class_registry import BlClassRegistry
 from ..utils import compatibility as compat
@@ -34,9 +36,22 @@ class MUV_PT_UVEdit_CopyPasteUV(bpy.types.Panel):
         layout = self.layout
         layout.label(text="", icon=compat.icon('IMAGE'))
 
-    def draw(self, _):
+    def draw(self, context):
         layout = self.layout
+        sc = context.scene
 
+        layout.label(text="Face:")
         row = layout.row(align=True)
         row.operator(MUV_OT_CopyPasteUVUVEdit_CopyUV.bl_idname, text="Copy")
         row.operator(MUV_OT_CopyPasteUVUVEdit_PasteUV.bl_idname, text="Paste")
+
+        layout.separator()
+
+        layout.label(text="Island:")
+        row = layout.row(align=True)
+        row.operator(MUV_OT_CopyPasteUVUVEdit_CopyUVIsland.bl_idname,
+                     text="Copy")
+        ops = row.operator(MUV_OT_CopyPasteUVUVEdit_PasteUVIsland.bl_idname,
+                           text="Paste")
+        ops.unique_target = sc.muv_copy_paste_uv_uvedit_unique_target
+        layout.prop(sc, "muv_copy_paste_uv_uvedit_unique_target")
diff --git a/magic_uv/ui/uvedit_editor_enhancement.py b/magic_uv/ui/uvedit_editor_enhancement.py
index b73e5eb9d..0af607ecf 100644
--- a/magic_uv/ui/uvedit_editor_enhancement.py
+++ b/magic_uv/ui/uvedit_editor_enhancement.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 
diff --git a/magic_uv/ui/uvedit_uv_manipulation.py b/magic_uv/ui/uvedit_uv_manipulation.py
index 636a0acae..e538ea4d8 100644
--- a/magic_uv/ui/uvedit_uv_manipulation.py
+++ b/magic_uv/ui/uvedit_uv_manipulation.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 
@@ -184,10 +184,18 @@ class MUV_PT_UVEdit_UVManipulation(bpy.types.Panel):
                 sc.muv_pack_uv_allowable_center_deviation
             ops.allowable_size_deviation = \
                 sc.muv_pack_uv_allowable_size_deviation
+            ops.accurate_island_copy = \
+                sc.muv_pack_uv_accurate_island_copy
+            ops.stride = sc.muv_pack_uv_stride
+            ops.apply_pack_uv = sc.muv_pack_uv_apply_pack_uv
+            box.prop(sc, "muv_pack_uv_apply_pack_uv")
+            box.prop(sc, "muv_pack_uv_accurate_island_copy")
             box.label(text="Allowable Center Deviation:")
             box.prop(sc, "muv_pack_uv_allowable_center_deviation", text="")
             box.label(text="Allowable Size Deviation:")
             box.prop(sc, "muv_pack_uv_allowable_size_deviation", text="")
+            box.label(text="Stride:")
+            box.prop(sc, "muv_pack_uv_stride", text="")
 
         box = layout.box()
         box.prop(sc, "muv_clip_uv_enabled", text="Clip UV")
diff --git a/magic_uv/ui/view3d_copy_paste_uv_editmode.py b/magic_uv/ui/view3d_copy_paste_uv_editmode.py
index d0b52021b..49bbaac3f 100644
--- a/magic_uv/ui/view3d_copy_paste_uv_editmode.py
+++ b/magic_uv/ui/view3d_copy_paste_uv_editmode.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 
diff --git a/magic_uv/ui/view3d_copy_paste_uv_objectmode.py b/magic_uv/ui/view3d_copy_paste_uv_objectmode.py
index 1153bedd4..2219d9e8a 100644
--- a/magic_uv/ui/view3d_copy_paste_uv_objectmode.py
+++ b/magic_uv/ui/view3d_copy_paste_uv_objectmode.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 
diff --git a/magic_uv/ui/view3d_uv_manipulation.py b/magic_uv/ui/view3d_uv_manipulation.py
index 5e7ae28ab..106ba2bc1 100644
--- a/magic_uv/ui/view3d_uv_manipulation.py
+++ b/magic_uv/ui/view3d_uv_manipulation.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 
diff --git a/magic_uv/ui/view3d_uv_mapping.py b/magic_uv/ui/view3d_uv_mapping.py
index 1fd05dffa..5da7cb6c4 100644
--- a/magic_uv/ui/view3d_uv_mapping.py
+++ b/magic_uv/ui/view3d_uv_mapping.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 
diff --git a/magic_uv/utils/__init__.py b/magic_uv/utils/__init__.py
index 22ed284a3..2ce7f34a5 100644
--- a/magic_uv/utils/__init__.py
+++ b/magic_uv/utils/__init__.py
@@ -4,17 +4,19 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 if "bpy" in locals():
     import importlib
     importlib.reload(bl_class_registry)
     importlib.reload(compatibility)
+    importlib.reload(graph)
     importlib.reload(property_class_registry)
 else:
     from . import bl_class_registry
     from . import compatibility
+    from . import graph
     from . import property_class_registry
 
 import bpy
diff --git a/magic_uv/utils/bl_class_registry.py b/magic_uv/utils/bl_class_registry.py
index 56ee91eb0..10d63d246 100644
--- a/magic_uv/utils/bl_class_registry.py
+++ b/magic_uv/utils/bl_class_registry.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 
diff --git a/magic_uv/utils/compatibility.py b/magic_uv/utils/compatibility.py
index cc8813e58..50ce304f6 100644
--- a/magic_uv/utils/compatibility.py
+++ b/magic_uv/utils/compatibility.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 import bpy
 import bgl
diff --git a/magic_uv/utils/graph.py b/magic_uv/utils/graph.py
new file mode 100644
index 000000000..c86547739
--- /dev/null
+++ b/magic_uv/utils/graph.py
@@ -0,0 +1,152 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8-80 compliant>
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
+
+
+class Node:
+    def __init__(self, key, value=None):
+        self.key = key
+        self.value = value
+        self.edges = []
+
+    def degree(self):
+        return len(self.edges)
+
+    def connected_nodes(self):
+        return [e.other(self) for e in self.edges]
+
+
+class Edge:
+    def __init__(self, node_1, node_2):
+        self.node_1 = node_1
+        self.node_2 = node_2
+
+    def other(self, node):
+        if self.node_1 == node and self.node_2 == node:
+            raise RuntimeError("Loop edge in {} is not supported."
+                               .format(node.key))
+        if node not in (self.node_1, self.node_2):
+            raise RuntimeError("Node {} does not belog this edge."
+                               .format(node.key))
+        if self.node_1 == node:
+            return self.node_2
+        return self.node_1
+
+
+class Graph:
+    def __init__(self):
+        self.edges = []
+        self.nodes = {}
+
+    def add_node(self, node):
+        if node.key in self.nodes:
+            raise RuntimeError("Node '{}' is already registered."
+                               .format(node.key))
+        self.nodes[node.key] = node
+
+    def add_edge(self, node_1, node_2):
+        if node_1.key not in self.nodes:
+            raise RuntimeError("Node '{}' is not registered."
+                               .format(node_1.key))
+        if node_2.key not in self.nodes:
+            raise RuntimeError("Node '{}' is not registered."
+                               .format(node_2.key))
+
+        edge = Edge(node_1, node_2)
+        self.edges.append(edge)
+        node_1.edges.append(edge)
+        node_2.edges.append(edge)
+
+    def get_node(self, key):
+        return self.nodes[key]
+
+
+def dump_graph(graph):
+    print("=== Node ===")
+    for _, node in graph.nodes.items():
+        print("Key: {}, Value {}".format(node.key, node.value))
+
+    print("=== Edge ===")
+    for edge in graph.edges:
+        print("{} - {}".format(edge.node_1.key, edge.node_2.key))
+
+
+# VF2 algorithm
+#   Ref: https://stackoverflow.com/questions/8176298/
+#            vf2-algorithm-steps-with-example
+#   Ref: https://github.com/satemochi/saaaaah/blob/master/geometric_misc/
+#            isomorph/vf2/vf2.py
+def graph_is_isomorphic(graph_1, graph_2):
+    def is_iso(pairs, matching_node, new_node):
+        # Algorithm:
+        #   1. The degree is same (It's faster).
+        #   2. The connected node is same.
+        if matching_node.degree() != new_node.degree():
+            return False
+
+        matching_connected = [c.key for c in matching_node.connected_nodes()]
+        new_connected = [c.key for c in new_node.connected_nodes()]
+
+        for p in pairs:
+            n1 = p[0]
+            n2 = p[1]
+            if n1 in matching_connected and n2 not in new_connected:
+                return False
+            if n1 not in matching_connected and n2 in new_connected:
+                return False
+
+        return True
+
+    def dfs(graph_1, graph_2):
+        def generate_pair(g1, g2, pairs):
+            remove_1 = [p[0] for p in pairs]
+            remove_2 = [p[1] for p in pairs]
+
+            keys_1 = sorted(list(set(g1.nodes.keys()) - set(remove_1)))
+            keys_2 = sorted(list(set(g2.nodes.keys()) - set(remove_2)))
+            for k1 in keys_1:
+                for k2 in keys_2:
+                    yield (k1, k2)
+
+        pairs = []
+        stack = [generate_pair(graph_1, graph_2, pairs)]
+        while stack:
+            try:
+                k1, k2 = next(stack[-1])
+                n1 = graph_1.get_node(k1)
+                n2 = graph_2.get_node(k2)
+                if is_iso(pairs, n1, n2):
+                    pairs.append([k1, k2])
+                    stack.append(generate_pair(graph_1, graph_2, pairs))
+                    if len(pairs) == len(graph_1.nodes):
+                        return True, pairs
+            except StopIteration:
+                stack.pop()
+                diff = len(pairs) - len(stack)
+                for _ in range(diff):
+                    pairs.pop()
+
+        return False, []
+
+    # First, check simple condition.
+    if len(graph_1.nodes) != len(graph_2.nodes):
+        return False, {}
+    if len(graph_1.edges) != len(graph_2.edges):
+        return False, {}
+
+    is_isomorphic, pairs = dfs(graph_1, graph_2)
+
+    node_pairs = {}
+    for pair in pairs:
+        n1 = pair[0]
+        n2 = pair[1]
+        node_1 = graph_1.get_node(n1)
+        node_2 = graph_2.get_node(n2)
+        node_pairs[node_1] = node_2
+
+    return is_isomorphic, node_pairs
diff --git a/magic_uv/utils/property_class_registry.py b/magic_uv/utils/property_class_registry.py
index 62cba9032..e09ad3208 100644
--- a/magic_uv/utils/property_class_registry.py
+++ b/magic_uv/utils/property_class_registry.py
@@ -4,8 +4,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
 
 from .. import common
 
-- 
GitLab