diff --git a/ant_landscape/__init__.py b/ant_landscape/__init__.py
index fd758e3327d6c5b889460ffc72ed8422a658da91..49432e81f23a337c57dc88726733b16b66c90613 100644
--- a/ant_landscape/__init__.py
+++ b/ant_landscape/__init__.py
@@ -391,7 +391,7 @@ class AntDisplaceSettingsPanel(bpy.types.Panel):
         if not ant.sphere_mesh:
             col = box.column()
             col.prop(ant, "edge_falloff")
-            if ant.edge_falloff is not "0":
+            if ant.edge_falloff != "0":
                 col = box.column(align=True)
                 col.prop(ant, "edge_level")
                 if ant.edge_falloff in ["2", "3"]:
@@ -401,7 +401,7 @@ class AntDisplaceSettingsPanel(bpy.types.Panel):
 
         col = box.column()
         col.prop(ant, "strata_type")
-        if ant.strata_type is not "0":
+        if ant.strata_type != "0":
             col = box.column()
             col.prop(ant, "strata")
         col = box.column()
diff --git a/ant_landscape/ant_functions.py b/ant_landscape/ant_functions.py
index bdabb62c8ee6abfcabeb16576afc635f3c8091ce..ca81ce6d1fb45a155db83aeb7ad09092f67888c8 100644
--- a/ant_landscape/ant_functions.py
+++ b/ant_landscape/ant_functions.py
@@ -662,7 +662,7 @@ def draw_ant_displace(self, context, generate=True):
             if not self.sphere_mesh:
                 col = box.column()
                 col.prop(self, "edge_falloff")
-                if self.edge_falloff is not "0":
+                if self.edge_falloff != "0":
                     col = box.column(align=True)
                     col.prop(self, "edge_level")
                     if self.edge_falloff in ["2", "3"]:
@@ -672,7 +672,7 @@ def draw_ant_displace(self, context, generate=True):
 
         col = box.column()
         col.prop(self, "strata_type")
-        if self.strata_type is not "0":
+        if self.strata_type != "0":
             col = box.column()
             col.prop(self, "strata")
 
diff --git a/archipack/archipack_fence.py b/archipack/archipack_fence.py
index 376f90e686b0fa8e680a983428caf06164426dbe..5f987779c46c933bc5974d9cf7bc7bd7a2e3b905 100644
--- a/archipack/archipack_fence.py
+++ b/archipack/archipack_fence.py
@@ -1482,7 +1482,7 @@ class ARCHIPACK_PT_fence(Panel):
         row = box.row(align=True)
         row.operator("archipack.fence_curve_update", text="", icon='FILE_REFRESH')
         row.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
-        if prop.user_defined_path is not "":
+        if prop.user_defined_path != "":
             box.prop(prop, 'user_defined_spline')
             box.prop(prop, 'user_defined_resolution')
         box.prop(prop, 'angle_limit')
diff --git a/archipack/archipack_stair.py b/archipack/archipack_stair.py
index 578208f55abbac911ef97e83c3168f93363335a5..c0d75317fdd10ef4eff8c4744c2449c62166c996 100644
--- a/archipack/archipack_stair.py
+++ b/archipack/archipack_stair.py
@@ -2518,7 +2518,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
 
         self.setup_manipulators()
 
-        if self.presets is not 'STAIR_O':
+        if self.presets != 'STAIR_O':
             for i, part in enumerate(self.parts):
                 if i >= self.n_parts:
                     break
diff --git a/development_iskeyfree.py b/development_iskeyfree.py
index c2e3f34f6e95c2d3fb6619d4711d92df0c66c242..5d5600f4748fcd07b3aab6c5c245e25ff6037192 100644
--- a/development_iskeyfree.py
+++ b/development_iskeyfree.py
@@ -105,18 +105,18 @@ class MyChecker():
         cls.mylist.clear()
         for e in sortkeys:
             cmd = ""
-            if e[2] is not "":
+            if e[2] != "":
                 cmd += e[2] + "+"
-            if e[3] is not "":
+            if e[3] != "":
                 cmd += e[3] + "+"
-            if e[4] is not "":
+            if e[4] != "":
                 cmd += e[4] + "+"
-            if e[5] is not "":
+            if e[5] != "":
                 cmd += e[5] + "+"
 
             cmd += e[1]
 
-            if e[6] is not "":
+            if e[6] != "":
                 cmd += "  " + e[6]
             cls.mylist.append([e[0], cmd])
 
diff --git a/io_import_images_as_planes.py b/io_import_images_as_planes.py
index 2b57c6763af870e4dac188811a8b7b7e66452636..88cee32a9a4816238ffb2126e5d1da3f1b7d0629 100644
--- a/io_import_images_as_planes.py
+++ b/io_import_images_as_planes.py
@@ -1042,7 +1042,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
         bpy.ops.mesh.primitive_plane_add('INVOKE_REGION_WIN')
         plane = context.active_object
         # Why does mesh.primitive_plane_add leave the object in edit mode???
-        if plane.mode is not 'OBJECT':
+        if plane.mode != 'OBJECT':
             bpy.ops.object.mode_set(mode='OBJECT')
         plane.dimensions = width, height, 0.0
         plane.data.name = plane.name = name
diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py
index 8e4747b052ade52715abc8ad89b6274cd3b471b3..f35a638755c91c3de904a4610e56cb0bd2f51b62 100644
--- a/io_scene_fbx/import_fbx.py
+++ b/io_scene_fbx/import_fbx.py
@@ -1124,7 +1124,7 @@ def blen_read_geom_layer_normal(fbx_obj, mesh, xform=None):
         bdata = [None] * len(blen_data) if is_fake else blen_data
         if func(mesh, bdata, "normal",
                 fbx_layer_data, fbx_layer_index, fbx_layer_mapping, fbx_layer_ref, 3, 3, layer_id, xform, True):
-            if blen_data_type is "Polygons":
+            if blen_data_type == "Polygons":
                 for pidx, p in enumerate(mesh.polygons):
                     for lidx in range(p.loop_start, p.loop_start + p.loop_total):
                         mesh.loops[lidx].normal[:] = bdata[pidx]
diff --git a/measureit/measureit_geometry.py b/measureit/measureit_geometry.py
index 191f981b42a84824f77bbac549cf642301475ac4..6340a89054099c67a2ecbc33c5e57859ae896942 100644
--- a/measureit/measureit_geometry.py
+++ b/measureit/measureit_geometry.py
@@ -835,9 +835,9 @@ def draw_text(myobj, pos2d, display_text, rgba, fsize, align='L', text_rot=0.0):
     # -------------------
     for line in mylines:
         text_width, text_height = blf.dimensions(font_id, line)
-        if align is 'C':
+        if align == 'C':
             newx = x_pos - text_width / 2
-        elif align is 'R':
+        elif align == 'R':
             newx = x_pos - text_width - gap
         else:
             newx = x_pos
@@ -855,7 +855,7 @@ def draw_text(myobj, pos2d, display_text, rgba, fsize, align='L', text_rot=0.0):
         if maxwidth < text_width:
             maxwidth = text_width
 
-    if align is 'L':
+    if align == 'L':
         blf.disable(font_id, ROTATION)
 
     return maxwidth, maxheight
diff --git a/mesh_bsurfaces.py b/mesh_bsurfaces.py
index ffe16e041380fba6942e0f6bb94ff85753cb22ba..53b88d495d839142e01825abc772b2d65f6f7b6a 100644
--- a/mesh_bsurfaces.py
+++ b/mesh_bsurfaces.py
@@ -2928,7 +2928,7 @@ class GPENCIL_OT_SURFSK_add_surface(Operator):
                 for i in range(0, len(surface_splines_parsed[0])):
                     surface_splines_parsed[0][i] = self.main_object.matrix_world * verts_ordered_V2[i].co
 
-        # When "Automatic join" option is active (and the selection type is not "TWO_CONNECTED"),
+        # When "Automatic join" option is active (and the selection type != "TWO_CONNECTED"),
         # merge the verts of the tips of the loops when they are "near enough"
         if self.automatic_join and selection_type != "TWO_CONNECTED":
             # Join the tips of "Follow" loops that are near enough and must be "closed"
diff --git a/mesh_extra_tools/mesh_extrude_and_reshape.py b/mesh_extra_tools/mesh_extrude_and_reshape.py
index 696e775d6dac224b4659f405ee16facf9699e0fd..a7ca0fdd5ddf36248f47177ef05f176c8d63d4f3 100644
--- a/mesh_extra_tools/mesh_extrude_and_reshape.py
+++ b/mesh_extra_tools/mesh_extrude_and_reshape.py
@@ -245,7 +245,7 @@ class Extrude_and_Reshape(Operator):
 
     @classmethod
     def poll(cls, context):
-        return context.mode is not 'EDIT_MESH'
+        return context.mode != 'EDIT_MESH'
 
     def modal(self, context, event):
         if self.confirm:
diff --git a/node_wrangler.py b/node_wrangler.py
index b27e469da54a9784078737e55d7c103b4e9b6233..d3fd844867f417a792fc7c7701214b1ac90fccd5 100644
--- a/node_wrangler.py
+++ b/node_wrangler.py
@@ -2372,7 +2372,10 @@ class NWCopySettings(Operator, NWBase):
     def poll(cls, context):
         valid = False
         if nw_check(context):
-            if context.active_node is not None and context.active_node.type is not 'FRAME':
+            if (
+                    context.active_node is not None and
+                    context.active_node.type != 'FRAME'
+            ):
                 valid = True
         return valid
 
@@ -2955,7 +2958,7 @@ class NWAddReroutes(Operator, NWBase):
             reroutes_count = 0  # will be used when aligning reroutes added to hidden nodes
             for out_i, output in enumerate(node.outputs):
                 pass_used = False  # initial value to be analyzed if 'R_LAYERS'
-                # if node is not 'R_LAYERS' - "pass_used" not needed, so set it to True
+                # if node != 'R_LAYERS' - "pass_used" not needed, so set it to True
                 if node.type != 'R_LAYERS':
                     pass_used = True
                 else:  # if 'R_LAYERS' check if output represent used render pass
diff --git a/presets/operator/mesh.landscape_add/canion.py b/presets/operator/mesh.landscape_add/canyon.py
similarity index 100%
rename from presets/operator/mesh.landscape_add/canion.py
rename to presets/operator/mesh.landscape_add/canyon.py
diff --git a/presets/operator/mesh.landscape_add/canions.py b/presets/operator/mesh.landscape_add/canyons.py
similarity index 100%
rename from presets/operator/mesh.landscape_add/canions.py
rename to presets/operator/mesh.landscape_add/canyons.py
diff --git a/presets/operator/mesh.landscape_add/cristaline.py b/presets/operator/mesh.landscape_add/crystalline.py
similarity index 100%
rename from presets/operator/mesh.landscape_add/cristaline.py
rename to presets/operator/mesh.landscape_add/crystalline.py
diff --git a/presets/operator/mesh.landscape_add/vulcano.py b/presets/operator/mesh.landscape_add/volcano.py
similarity index 100%
rename from presets/operator/mesh.landscape_add/vulcano.py
rename to presets/operator/mesh.landscape_add/volcano.py
diff --git a/render_freestyle_svg.py b/render_freestyle_svg.py
index 6cc2e1b61f37942c1a32a2d37a11964afcc95e16..7e1ffc19dda62d8233c487897d7cdfb28353792f 100644
--- a/render_freestyle_svg.py
+++ b/render_freestyle_svg.py
@@ -241,7 +241,7 @@ class SVGExport(bpy.types.PropertyGroup):
     line_join_type = EnumProperty(
             name="Linejoin",
             items=(
-                ('MITTER', "Mitter", "Corners are sharp", 0),
+                ('MITER', "Miter", "Corners are sharp", 0),
                 ('ROUND', "Round", "Corners are smoothed", 1),
                 ('BEVEL', "Bevel", "Corners are bevelled", 2),
                 ),
diff --git a/space_view3d_pie_menus/pie_save_open_menu.py b/space_view3d_pie_menus/pie_save_open_menu.py
index a8307d079e9196c1a992beec89d8bf1b7dc04b74..e71f7924c62cf508e418ad396902b16744acfc98 100644
--- a/space_view3d_pie_menus/pie_save_open_menu.py
+++ b/space_view3d_pie_menus/pie_save_open_menu.py
@@ -130,7 +130,7 @@ class FileIncrementalSave(Operator):
 
     @classmethod
     def poll(cls, context):
-        return (bpy.data.filepath is not "")
+        return (bpy.data.filepath != "")
 
     def execute(self, context):
         f_path = bpy.data.filepath
diff --git a/uv_magic_uv/__init__.py b/uv_magic_uv/__init__.py
index 080d2414fbf5b2e27f81eef620c1cbd80890a1d5..63591526db6e2001725105393dd30f47ef8c0a96 100644
--- a/uv_magic_uv/__init__.py
+++ b/uv_magic_uv/__init__.py
@@ -20,16 +20,16 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 
 bl_info = {
     "name": "Magic UV",
     "author": "Nutti, Mifth, Jace Priester, kgeogeo, mem, imdjs"
               "Keith (Wahooney) Boshoff, McBuff, MaxRobinot, Alexander Milovsky",
-    "version": (5, 1, 0),
-    "blender": (2, 79, 0),
+    "version": (5, 3, 0),
+    "blender": (2, 80, 0),
     "location": "See Add-ons Preferences",
     "description": "UV Toolset. See Add-ons Preferences for details",
     "warning": "",
@@ -40,31 +40,80 @@ bl_info = {
     "category": "UV"
 }
 
+def check_version(major, minor, _):
+    """
+    Check blender version
+    """
+
+    if bpy.app.version[0] == major and bpy.app.version[1] == minor:
+        return 0
+    if bpy.app.version[0] > major:
+        return 1
+    if bpy.app.version[1] > minor:
+        return 1
+    return -1
+
+
 if "bpy" in locals():
     import importlib
-    importlib.reload(op)
-    importlib.reload(ui)
     importlib.reload(common)
-    importlib.reload(preferences)
-    importlib.reload(properites)
+    importlib.reload(utils)
+    utils.bl_class_registry.BlClassRegistry.cleanup()
+    if check_version(2, 80, 0) >= 0:
+        importlib.reload(op)
+        importlib.reload(ui)
+        importlib.reload(properites)
+        importlib.reload(preferences)
+        importlib.reload(addon_updater_ops)
+        importlib.reload(addon_updater)
+    else:
+        importlib.reload(legacy)
 else:
-    from . import op
-    from . import ui
+    import bpy
     from . import common
-    from . import preferences
-    from . import properites
+    from . import utils
+    if check_version(2, 80, 0) >= 0:
+        from . import op
+        from . import ui
+        from . import properites
+        from . import preferences
+        from . import addon_updater_ops
+        from . import addon_updater
+    else:
+        from . import legacy
+
 
 import bpy
 
 
 def register():
-    bpy.utils.register_module(__name__)
-    properites.init_props(bpy.types.Scene)
+    if common.check_version(2, 80, 0) >= 0:
+        utils.bl_class_registry.BlClassRegistry.register()
+        properites.init_props(bpy.types.Scene)
+        if preferences.Preferences.enable_builtin_menu:
+            preferences.add_builtin_menu()
+    else:
+        utils.bl_class_registry.BlClassRegistry.register()
+        legacy.properites.init_props(bpy.types.Scene)
+        if legacy.preferences.Preferences.enable_builtin_menu:
+            legacy.preferences.add_builtin_menu()
+        if not common.is_console_mode():
+            addon_updater_ops.register(bl_info)
 
 
 def unregister():
-    bpy.utils.unregister_module(__name__)
-    properites.clear_props(bpy.types.Scene)
+    if common.check_version(2, 80, 0) >= 0:
+        if preferences.Preferences.enable_builtin_menu:
+            preferences.remove_builtin_menu()
+        properites.clear_props(bpy.types.Scene)
+        utils.bl_class_registry.BlClassRegistry.unregister()
+    else:
+        if not common.is_console_mode():
+            addon_updater_ops.unregister()
+        if legacy.preferences.Preferences.enable_builtin_menu:
+            legacy.preferences.remove_builtin_menu()
+        legacy.properites.clear_props(bpy.types.Scene)
+        utils.bl_class_registry.BlClassRegistry.unregister()
 
 
 if __name__ == "__main__":
diff --git a/uv_magic_uv/addon_updater.py b/uv_magic_uv/addon_updater.py
new file mode 100644
index 0000000000000000000000000000000000000000..70b6a287fd48f62e3bb6b802a4511ba0f95ab599
--- /dev/null
+++ b/uv_magic_uv/addon_updater.py
@@ -0,0 +1,1501 @@
+# ##### 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 #####
+
+
+"""
+See documentation for usage
+https://github.com/CGCookie/blender-addon-updater
+
+"""
+
+import ssl
+import urllib.request
+import urllib
+import os
+import json
+import zipfile
+import shutil
+import asyncio
+import threading
+import time
+import fnmatch
+from datetime import datetime, timedelta
+
+# blender imports, used in limited cases
+import bpy
+import addon_utils
+
+# -----------------------------------------------------------------------------
+# Define error messages/notices & hard coded globals
+# -----------------------------------------------------------------------------
+
+# currently not used
+DEFAULT_TIMEOUT = 10
+DEFAULT_PER_PAGE = 30
+
+
+# -----------------------------------------------------------------------------
+# The main class
+# -----------------------------------------------------------------------------
+
+class Singleton_updater(object):
+	"""
+	This is the singleton class to reference a copy from,
+	it is the shared module level class
+	"""
+	def __init__(self):
+
+		self._engine = GithubEngine()
+		self._user = None
+		self._repo = None
+		self._website = None
+		self._current_version = None
+		self._subfolder_path = None
+		self._tags = []
+		self._tag_latest = None
+		self._tag_names = []
+		self._latest_release = None
+		self._use_releases = False
+		self._include_branches = False
+		self._include_branch_list = ['master']
+		self._include_branch_autocheck = False
+		self._manual_only = False
+		self._version_min_update = None
+		self._version_max_update = None
+
+		# by default, backup current addon if new is being loaded
+		self._backup_current = True
+		self._backup_ignore_patterns = None
+
+		# set patterns for what files to overwrite on update
+		self._overwrite_patterns = ["*.py","*.pyc"]
+		self._remove_pre_update_patterns = []
+
+		# by default, don't auto enable/disable the addon on update
+		# as it is slightly less stable/won't always fully reload module
+		self._auto_reload_post_update = False
+
+		# settings relating to frequency and whether to enable auto background check
+		self._check_interval_enable = False
+		self._check_interval_months = 0
+		self._check_interval_days = 7
+		self._check_interval_hours = 0
+		self._check_interval_minutes = 0
+
+		# runtime variables, initial conditions
+		self._verbose = False
+		self._fake_install = False
+		self._async_checking = False  # only true when async daemon started
+		self._update_ready = None
+		self._update_link = None
+		self._update_version = None
+		self._source_zip = None
+		self._check_thread = None
+		self.skip_tag = None
+		self.select_link = None
+
+		# get from module data
+		self._addon = __package__.lower()
+		self._addon_package = __package__  # must not change
+		self._updater_path = os.path.join(os.path.dirname(__file__),
+										self._addon+"_updater")
+		self._addon_root = os.path.dirname(__file__)
+		self._json = {}
+		self._error = None
+		self._error_msg = None
+		self._prefiltered_tag_count = 0
+
+		# UI code only, ie not used within this module but still useful
+		# properties to have
+
+		# to verify a valid import, in place of placeholder import
+		self.showpopups = True # used in UI to show or not show update popups
+		self.invalidupdater = False
+
+
+	# -------------------------------------------------------------------------
+	# Getters and setters
+	# -------------------------------------------------------------------------
+
+	@property
+	def engine(self):
+		return self._engine.name
+	@engine.setter
+	def engine(self, value):
+		if value.lower()=="github":
+			self._engine = GithubEngine()
+		elif value.lower()=="gitlab":
+			self._engine = GitlabEngine()
+		elif value.lower()=="bitbucket":
+			self._engine = BitbucketEngine()
+		else:
+			raise ValueError("Invalid engine selection")
+
+	@property
+	def private_token(self):
+		return self._engine.token
+	@private_token.setter
+	def private_token(self, value):
+		if value==None:
+			self._engine.token = None
+		else:
+			self._engine.token = str(value)
+
+	@property
+	def addon(self):
+		return self._addon
+	@addon.setter
+	def addon(self, value):
+		self._addon = str(value)
+
+	@property
+	def verbose(self):
+		return self._verbose
+	@verbose.setter
+	def verbose(self, value):
+		try:
+			self._verbose = bool(value)
+			if self._verbose == True:
+				print(self._addon+" updater verbose is enabled")
+		except:
+			raise ValueError("Verbose must be a boolean value")
+
+	@property
+	def include_branches(self):
+		return self._include_branches
+	@include_branches.setter
+	def include_branches(self, value):
+		try:
+			self._include_branches = bool(value)
+		except:
+			raise ValueError("include_branches must be a boolean value")
+
+	@property
+	def use_releases(self):
+		return self._use_releases
+	@use_releases.setter
+	def use_releases(self, value):
+		try:
+			self._use_releases = bool(value)
+		except:
+			raise ValueError("use_releases must be a boolean value")
+
+	@property
+	def include_branch_list(self):
+		return self._include_branch_list
+	@include_branch_list.setter
+	def include_branch_list(self, value):
+		try:
+			if value == None:
+				self._include_branch_list = ['master']
+			elif type(value) != type(['master']) or value==[]:
+				raise ValueError("include_branch_list should be a list of valid branches")
+			else:
+				self._include_branch_list = value
+		except:
+			raise ValueError("include_branch_list should be a list of valid branches")
+
+	@property
+	def overwrite_patterns(self):
+		return self._overwrite_patterns
+	@overwrite_patterns.setter
+	def overwrite_patterns(self, value):
+		if value == None:
+			self._overwrite_patterns = ["*.py","*.pyc"]
+		elif type(value) != type(['']):
+			raise ValueError("overwrite_patterns needs to be in a list format")
+		else:
+			self._overwrite_patterns = value
+
+	@property
+	def remove_pre_update_patterns(self):
+		return self._remove_pre_update_patterns
+	@remove_pre_update_patterns.setter
+	def remove_pre_update_patterns(self, value):
+		if value == None:
+			self._remove_pre_update_patterns = []
+		elif type(value) != type(['']):
+			raise ValueError("remove_pre_update_patterns needs to be in a list format")
+		else:
+			self._remove_pre_update_patterns = value
+
+	# not currently used
+	@property
+	def include_branch_autocheck(self):
+		return self._include_branch_autocheck
+	@include_branch_autocheck.setter
+	def include_branch_autocheck(self, value):
+		try:
+			self._include_branch_autocheck = bool(value)
+		except:
+			raise ValueError("include_branch_autocheck must be a boolean value")
+
+	@property
+	def manual_only(self):
+		return self._manual_only
+	@manual_only.setter
+	def manual_only(self, value):
+		try:
+			self._manual_only = bool(value)
+		except:
+			raise ValueError("manual_only must be a boolean value")
+
+	@property
+	def auto_reload_post_update(self):
+		return self._auto_reload_post_update
+	@auto_reload_post_update.setter
+	def auto_reload_post_update(self, value):
+		try:
+			self._auto_reload_post_update = bool(value)
+		except:
+			raise ValueError("Must be a boolean value")
+
+	@property
+	def fake_install(self):
+		return self._fake_install
+	@fake_install.setter
+	def fake_install(self, value):
+		if type(value) != type(False):
+			raise ValueError("fake_install must be a boolean value")
+		self._fake_install = bool(value)
+
+	@property
+	def user(self):
+		return self._user
+	@user.setter
+	def user(self, value):
+		try:
+			self._user = str(value)
+		except:
+			raise ValueError("User must be a string value")
+
+	@property
+	def json(self):
+		if self._json == {}:
+			self.set_updater_json()
+		return self._json
+
+	@property
+	def repo(self):
+		return self._repo
+	@repo.setter
+	def repo(self, value):
+		try:
+			self._repo = str(value)
+		except:
+			raise ValueError("User must be a string")
+
+	@property
+	def website(self):
+		return self._website
+	@website.setter
+	def website(self, value):
+		if self.check_is_url(value) == False:
+			raise ValueError("Not a valid URL: " + value)
+		self._website = value
+
+	@property
+	def async_checking(self):
+		return self._async_checking
+
+	@property
+	def api_url(self):
+		return self._engine.api_url
+	@api_url.setter
+	def api_url(self, value):
+		if self.check_is_url(value) == False:
+			raise ValueError("Not a valid URL: " + value)
+		self._engine.api_url = value
+
+	@property
+	def stage_path(self):
+		return self._updater_path
+	@stage_path.setter
+	def stage_path(self, value):
+		if value == None:
+			if self._verbose: print("Aborting assigning stage_path, it's null")
+			return
+		elif value != None and not os.path.exists(value):
+			try:
+				os.makedirs(value)
+			except:
+				if self._verbose: print("Error trying to staging path")
+				return
+		self._updater_path = value
+
+	@property
+	def tags(self):
+		if self._tags == []:
+			return []
+		tag_names = []
+		for tag in self._tags:
+			tag_names.append(tag["name"])
+		return tag_names
+
+	@property
+	def tag_latest(self):
+		if self._tag_latest == None:
+			return None
+		return self._tag_latest["name"]
+
+	@property
+	def latest_release(self):
+		if self._releases_latest == None:
+			return None
+		return self._latest_release
+
+	@property
+	def current_version(self):
+		return self._current_version
+
+	@property
+	def subfolder_path(self):
+		return self._subfolder_path
+
+	@subfolder_path.setter
+	def subfolder_path(self, value):
+		self._subfolder_path = value
+
+	@property
+	def update_ready(self):
+		return self._update_ready
+
+	@property
+	def update_version(self):
+		return self._update_version
+
+	@property
+	def update_link(self):
+		return self._update_link
+
+	@current_version.setter
+	def current_version(self, tuple_values):
+		if tuple_values==None:
+			self._current_version = None
+			return
+		elif type(tuple_values) is not tuple:
+			try:
+				tuple(tuple_values)
+			except:
+				raise ValueError(
+				"Not a tuple! current_version must be a tuple of integers")
+		for i in tuple_values:
+			if type(i) is not int:
+				raise ValueError(
+				"Not an integer! current_version must be a tuple of integers")
+		self._current_version = tuple(tuple_values)
+
+	def set_check_interval(self,enable=False,months=0,days=14,hours=0,minutes=0):
+		# enabled = False, default initially will not check against frequency
+		# if enabled, default is then 2 weeks
+
+		if type(enable) is not bool:
+			raise ValueError("Enable must be a boolean value")
+		if type(months) is not int:
+			raise ValueError("Months must be an integer value")
+		if type(days) is not int:
+			raise ValueError("Days must be an integer value")
+		if type(hours) is not int:
+			raise ValueError("Hours must be an integer value")
+		if type(minutes) is not int:
+			raise ValueError("Minutes must be an integer value")
+
+		if enable==False:
+			self._check_interval_enable = False
+		else:
+			self._check_interval_enable = True
+
+		self._check_interval_months = months
+		self._check_interval_days = days
+		self._check_interval_hours = hours
+		self._check_interval_minutes = minutes
+
+	@property
+	def check_interval(self):
+		return (self._check_interval_enable,
+				self._check_interval_months,
+				self._check_interval_days,
+				self._check_interval_hours,
+				self._check_interval_minutes)
+
+	@property
+	def error(self):
+		return self._error
+
+	@property
+	def error_msg(self):
+		return self._error_msg
+
+	@property
+	def version_min_update(self):
+		return self._version_min_update
+	@version_min_update.setter
+	def version_min_update(self, value):
+		if value == None:
+			self._version_min_update = None
+			return
+		if type(value) != type((1,2,3)):
+			raise ValueError("Version minimum must be a tuple")
+		else:
+			# potentially check entries are integers
+			self._version_min_update = value
+
+	@property
+	def version_max_update(self):
+		return self._version_max_update
+	@version_max_update.setter
+	def version_max_update(self, value):
+		if value == None:
+			self._version_max_update = None
+			return
+		if type(value) != type((1,2,3)):
+			raise ValueError("Version maximum must be a tuple")
+		else:
+			# potentially check entries are integers
+			self._version_max_update = value
+
+	@property
+	def backup_current(self):
+		return self._backup_current
+	@backup_current.setter
+	def backup_current(self, value):
+		if value == None:
+			self._backup_current = False
+			return
+		else:
+			self._backup_current = value
+
+	@property
+	def backup_ignore_patterns(self):
+		return self._backup_ignore_patterns
+	@backup_ignore_patterns.setter
+	def backup_ignore_patterns(self, value):
+		if value == None:
+			self._backup_ignore_patterns = None
+			return
+		elif type(value) != type(['list']):
+			raise ValueError("Backup pattern must be in list format")
+		else:
+			self._backup_ignore_patterns = value
+
+	# -------------------------------------------------------------------------
+	# Parameter validation related functions
+	# -------------------------------------------------------------------------
+
+
+	def check_is_url(self, url):
+		if not ("http://" in url or "https://" in url):
+			return False
+		if "." not in url:
+			return False
+		return True
+
+	def get_tag_names(self):
+		tag_names = []
+		self.get_tags(self)
+		for tag in self._tags:
+			tag_names.append(tag["name"])
+		return tag_names
+
+
+	# declare how the class gets printed
+
+	def __repr__(self):
+		return "<Module updater from {a}>".format(a=__file__)
+
+	def __str__(self):
+		return "Updater, with user: {a}, repository: {b}, url: {c}".format(
+						a=self._user,
+						b=self._repo, c=self.form_repo_url())
+
+
+	# -------------------------------------------------------------------------
+	# API-related functions
+	# -------------------------------------------------------------------------
+
+	def form_repo_url(self):
+		return self._engine.form_repo_url(self)
+
+	def form_tags_url(self):
+		return self._engine.form_tags_url(self)
+
+	def form_branch_url(self, branch):
+		return self._engine.form_branch_url(branch, self)
+
+	def get_tags(self):
+		request = self.form_tags_url()
+		if self._verbose: print("Getting tags from server")
+
+		# get all tags, internet call
+		all_tags = self._engine.parse_tags(self.get_api(request), self)
+		if all_tags is not None:
+			self._prefiltered_tag_count = len(all_tags)
+		else:
+			self._prefiltered_tag_count = 0
+			all_tags = []
+
+		# pre-process to skip tags
+		if self.skip_tag != None:
+			self._tags = [tg for tg in all_tags if self.skip_tag(self, tg)==False]
+		else:
+			self._tags = all_tags
+
+		# get additional branches too, if needed, and place in front
+		# Does NO checking here whether branch is valid
+		if self._include_branches == True:
+			temp_branches = self._include_branch_list.copy()
+			temp_branches.reverse()
+			for branch in temp_branches:
+				request = self.form_branch_url(branch)
+				include = {
+					"name":branch.title(),
+					"zipball_url":request
+				}
+				self._tags = [include] + self._tags  # append to front
+
+		if self._tags == None:
+			# some error occurred
+			self._tag_latest = None
+			self._tags = []
+			return
+		elif self._prefiltered_tag_count == 0 and self._include_branches == False:
+			self._tag_latest = None
+			if self._error == None: # if not None, could have had no internet
+				self._error = "No releases found"
+				self._error_msg = "No releases or tags found on this repository"
+			if self._verbose: print("No releases or tags found on this repository")
+		elif self._prefiltered_tag_count == 0 and self._include_branches == True:
+			if not self._error: self._tag_latest = self._tags[0]
+			if self._verbose:
+				branch = self._include_branch_list[0]
+				print("{} branch found, no releases".format(branch), self._tags[0])
+		elif (len(self._tags)-len(self._include_branch_list)==0 and self._include_branches==True) \
+				or (len(self._tags)==0 and self._include_branches==False) \
+				and self._prefiltered_tag_count > 0:
+			self._tag_latest = None
+			self._error = "No releases available"
+			self._error_msg = "No versions found within compatible version range"
+			if self._verbose: print("No versions found within compatible version range")
+		else:
+			if self._include_branches == False:
+				self._tag_latest = self._tags[0]
+				if self._verbose: print("Most recent tag found:",self._tags[0]['name'])
+			else:
+				# don't return branch if in list
+				n = len(self._include_branch_list)
+				self._tag_latest = self._tags[n]  # guaranteed at least len()=n+1
+				if self._verbose: print("Most recent tag found:",self._tags[n]['name'])
+
+
+	# all API calls to base url
+	def get_raw(self, url):
+		# print("Raw request:", url)
+		request = urllib.request.Request(url)
+		context = ssl._create_unverified_context()
+
+		# setup private request headers if appropriate
+		if self._engine.token != None:
+			if self._engine.name == "gitlab":
+				request.add_header('PRIVATE-TOKEN',self._engine.token)
+			else:
+				if self._verbose: print("Tokens not setup for engine yet")
+
+		# run the request
+		try:
+			result = urllib.request.urlopen(request,context=context)
+		except urllib.error.HTTPError as e:
+			self._error = "HTTP error"
+			self._error_msg = str(e.code)
+			self._update_ready = None
+		except urllib.error.URLError as e:
+			reason = str(e.reason)
+			if "TLSV1_ALERT" in reason or "SSL" in reason:
+				self._error = "Connection rejected, download manually"
+				self._error_msg = reason
+			else:
+				self._error = "URL error, check internet connection"
+				self._error_msg = reason
+			self._update_ready = None
+			return None
+		else:
+			result_string = result.read()
+			result.close()
+			return result_string.decode()
+
+
+	# result of all api calls, decoded into json format
+	def get_api(self, url):
+		# return the json version
+		get = None
+		get = self.get_raw(url)
+		if get != None:
+			try:
+				return json.JSONDecoder().decode(get)
+			except Exception as e:
+				self._error = "API response has invalid JSON format"
+				self._error_msg = str(e.reason)
+				self._update_ready = None
+				return None
+		else:
+			return None
+
+
+	# create a working directory and download the new files
+	def stage_repository(self, url):
+
+		local = os.path.join(self._updater_path,"update_staging")
+		error = None
+
+		# make/clear the staging folder
+		# ensure the folder is always "clean"
+		if self._verbose: print("Preparing staging folder for download:\n",local)
+		if os.path.isdir(local) == True:
+			try:
+				shutil.rmtree(local)
+				os.makedirs(local)
+			except:
+				error = "failed to remove existing staging directory"
+		else:
+			try:
+				os.makedirs(local)
+			except:
+				error = "failed to create staging directory"
+
+		if error != None:
+			if self._verbose: print("Error: Aborting update, "+error)
+			self._error = "Update aborted, staging path error"
+			self._error_msg = "Error: {}".format(error)
+			return False
+
+		if self._backup_current==True:
+			self.create_backup()
+		if self._verbose: print("Now retrieving the new source zip")
+
+		self._source_zip = os.path.join(local,"source.zip")
+
+		if self._verbose: print("Starting download update zip")
+		try:
+			request = urllib.request.Request(url)
+			context = ssl._create_unverified_context()
+
+			# setup private token if appropriate
+			if self._engine.token != None:
+				if self._engine.name == "gitlab":
+					request.add_header('PRIVATE-TOKEN',self._engine.token)
+				else:
+					if self._verbose: print("Tokens not setup for selected engine yet")
+			self.urlretrieve(urllib.request.urlopen(request,context=context), self._source_zip)
+			# add additional checks on file size being non-zero
+			if self._verbose: print("Successfully downloaded update zip")
+			return True
+		except Exception as e:
+			self._error = "Error retrieving download, bad link?"
+			self._error_msg = "Error: {}".format(e)
+			if self._verbose:
+				print("Error retrieving download, bad link?")
+				print("Error: {}".format(e))
+			return False
+
+
+	def create_backup(self):
+		if self._verbose: print("Backing up current addon folder")
+		local = os.path.join(self._updater_path,"backup")
+		tempdest = os.path.join(self._addon_root,
+						os.pardir,
+						self._addon+"_updater_backup_temp")
+
+		if self._verbose: print("Backup destination path: ",local)
+
+		if os.path.isdir(local):
+			try:
+				shutil.rmtree(local)
+			except:
+				if self._verbose:print("Failed to removed previous backup folder, contininuing")
+
+		# remove the temp folder; shouldn't exist but could if previously interrupted
+		if os.path.isdir(tempdest):
+			try:
+				shutil.rmtree(tempdest)
+			except:
+				if self._verbose:print("Failed to remove existing temp folder, contininuing")
+		# make the full addon copy, which temporarily places outside the addon folder
+		if self._backup_ignore_patterns != None:
+			shutil.copytree(
+				self._addon_root,tempdest,
+				ignore=shutil.ignore_patterns(*self._backup_ignore_patterns))
+		else:
+			shutil.copytree(self._addon_root,tempdest)
+		shutil.move(tempdest,local)
+
+		# save the date for future ref
+		now = datetime.now()
+		self._json["backup_date"] = "{m}-{d}-{yr}".format(
+				m=now.strftime("%B"),d=now.day,yr=now.year)
+		self.save_updater_json()
+
+	def restore_backup(self):
+		if self._verbose: print("Restoring backup")
+
+		if self._verbose: print("Backing up current addon folder")
+		backuploc = os.path.join(self._updater_path,"backup")
+		tempdest = os.path.join(self._addon_root,
+						os.pardir,
+						self._addon+"_updater_backup_temp")
+		tempdest = os.path.abspath(tempdest)
+
+		# make the copy
+		shutil.move(backuploc,tempdest)
+		shutil.rmtree(self._addon_root)
+		os.rename(tempdest,self._addon_root)
+
+		self._json["backup_date"] = ""
+		self._json["just_restored"] = True
+		self._json["just_updated"] = True
+		self.save_updater_json()
+
+		self.reload_addon()
+
+	def unpack_staged_zip(self,clean=False):
+
+		if os.path.isfile(self._source_zip) == False:
+			if self._verbose: print("Error, update zip not found")
+			return -1
+
+		# clear the existing source folder in case previous files remain
+		try:
+			shutil.rmtree(os.path.join(self._updater_path,"source"))
+			os.makedirs(os.path.join(self._updater_path,"source"))
+			if self._verbose: print("Source folder cleared and recreated")
+		except:
+			pass
+
+		if self._verbose: print("Begin extracting source")
+		if zipfile.is_zipfile(self._source_zip):
+			with zipfile.ZipFile(self._source_zip) as zf:
+				# extractall is no longer a security hazard, below is safe
+				zf.extractall(os.path.join(self._updater_path,"source"))
+		else:
+			if self._verbose:
+				print("Not a zip file, future add support for just .py files")
+			raise ValueError("Resulting file is not a zip")
+		if self._verbose: print("Extracted source")
+
+		# either directly in root of zip, or one folder level deep
+		unpath = os.path.join(self._updater_path,"source")
+		if os.path.isfile(os.path.join(unpath,"__init__.py")) == False:
+			dirlist = os.listdir(unpath)
+			if len(dirlist)>0:
+				if self._subfolder_path == "" or self._subfolder_path == None:
+					unpath = os.path.join(unpath,dirlist[0])
+				else:
+					unpath = os.path.join(unpath,dirlist[0],self._subfolder_path)
+
+			# smarter check for additional sub folders for a single folder
+			# containing __init__.py
+			if os.path.isfile(os.path.join(unpath,"__init__.py")) == False:
+				if self._verbose:
+					print("not a valid addon found")
+					print("Paths:")
+					print(dirlist)
+
+				raise ValueError("__init__ file not found in new source")
+
+		# now commence merging in the two locations:
+		# note this MAY not be accurate, as updater files could be placed elsewhere
+		origpath = os.path.dirname(__file__)
+
+		# merge code with running addon directory, using blender default behavior
+		# plus any modifiers indicated by user (e.g. force remove/keep)
+		self.deepMergeDirectory(origpath,unpath,clean)
+
+		# Now save the json state
+		#  Change to True, to trigger the handler on other side
+		#  if allowing reloading within same blender instance
+		self._json["just_updated"] = True
+		self.save_updater_json()
+		self.reload_addon()
+		self._update_ready = False
+
+
+	# merge folder 'merger' into folder 'base' without deleting existing
+	def deepMergeDirectory(self,base,merger,clean=False):
+		if not os.path.exists(base):
+			if self._verbose: print("Base path does not exist")
+			return -1
+		elif not os.path.exists(merger):
+			if self._verbose: print("Merger path does not exist")
+			return -1
+
+		# paths to be aware of and not overwrite/remove/etc
+		staging_path = os.path.join(self._updater_path,"update_staging")
+		backup_path = os.path.join(self._updater_path,"backup")
+		json_path = os.path.join(self._updater_path,"updater_status.json")
+
+		# If clean install is enabled, clear existing files ahead of time
+		# note: will not delete the update.json, update folder, staging, or staging
+		# but will delete all other folders/files in addon directory
+		error = None
+		if clean==True:
+			try:
+				# implement clearing of all folders/files, except the
+				# updater folder and updater json
+				# Careful, this deletes entire subdirectories recursively...
+				# make sure that base is not a high level shared folder, but
+				# is dedicated just to the addon itself
+				if self._verbose: print("clean=True, clearing addon folder to fresh install state")
+
+				# remove root files and folders (except update folder)
+				files = [f for f in os.listdir(base) if os.path.isfile(os.path.join(base,f))]
+				folders = [f for f in os.listdir(base) if os.path.isdir(os.path.join(base,f))]
+
+				for f in files:
+					os.remove(os.path.join(base,f))
+					print("Clean removing file {}".format(os.path.join(base,f)))
+				for f in folders:
+					if os.path.join(base,f)==self._updater_path: continue
+					shutil.rmtree(os.path.join(base,f))
+					print("Clean removing folder and contents {}".format(os.path.join(base,f)))
+
+			except error:
+				error = "failed to create clean existing addon folder"
+				print(error,str(e))
+
+		# Walk through the base addon folder for rules on pre-removing
+		# but avoid removing/altering backup and updater file
+		for path, dirs, files in os.walk(base):
+			# prune ie skip updater folder
+			dirs[:] = [d for d in dirs if os.path.join(path,d) not in [self._updater_path]]
+			for file in files:
+				for ptrn in self.remove_pre_update_patterns:
+					if fnmatch.filter([file],ptrn):
+						try:
+							fl = os.path.join(path,file)
+							os.remove(fl)
+							if self._verbose: print("Pre-removed file "+file)
+						except OSError:
+							print("Failed to pre-remove "+file)
+
+		# Walk through the temp addon sub folder for replacements
+		# this implements the overwrite rules, which apply after
+		# the above pre-removal rules. This also performs the
+		# actual file copying/replacements
+		for path, dirs, files in os.walk(merger):
+			# verify this structure works to prune updater sub folder overwriting
+			dirs[:] = [d for d in dirs if os.path.join(path,d) not in [self._updater_path]]
+			relPath = os.path.relpath(path, merger)
+			destPath = os.path.join(base, relPath)
+			if not os.path.exists(destPath):
+				os.makedirs(destPath)
+			for file in files:
+				# bring in additional logic around copying/replacing
+				# Blender default: overwrite .py's, don't overwrite the rest
+				destFile = os.path.join(destPath, file)
+				srcFile = os.path.join(path, file)
+
+				# decide whether to replace if file already exists, and copy new over
+				if os.path.isfile(destFile):
+					# otherwise, check each file to see if matches an overwrite pattern
+					replaced=False
+					for ptrn in self._overwrite_patterns:
+						if fnmatch.filter([destFile],ptrn):
+							replaced=True
+							break
+					if replaced:
+						os.remove(destFile)
+						os.rename(srcFile, destFile)
+						if self._verbose: print("Overwrote file "+os.path.basename(destFile))
+					else:
+						if self._verbose: print("Pattern not matched to "+os.path.basename(destFile)+", not overwritten")
+				else:
+					# file did not previously exist, simply move it over
+					os.rename(srcFile, destFile)
+					if self._verbose: print("New file "+os.path.basename(destFile))
+
+		# now remove the temp staging folder and downloaded zip
+		try:
+			shutil.rmtree(staging_path)
+		except:
+			error = "Error: Failed to remove existing staging directory, consider manually removing "+staging_path
+			if self._verbose: print(error)
+
+
+	def reload_addon(self):
+		# if post_update false, skip this function
+		# else, unload/reload addon & trigger popup
+		if self._auto_reload_post_update == False:
+			print("Restart blender to reload addon and complete update")
+			return
+
+		if self._verbose: print("Reloading addon...")
+		addon_utils.modules(refresh=True)
+		bpy.utils.refresh_script_paths()
+
+		# not allowed in restricted context, such as register module
+		# toggle to refresh
+		bpy.ops.wm.addon_disable(module=self._addon_package)
+		bpy.ops.wm.addon_refresh()
+		bpy.ops.wm.addon_enable(module=self._addon_package)
+
+
+	# -------------------------------------------------------------------------
+	# Other non-api functions and setups
+	# -------------------------------------------------------------------------
+
+	def clear_state(self):
+		self._update_ready = None
+		self._update_link = None
+		self._update_version = None
+		self._source_zip = None
+		self._error = None
+		self._error_msg = None
+
+	# custom urlretrieve implementation
+	def urlretrieve(self, urlfile, filepath):
+		chunk = 1024*8
+		f = open(filepath, "wb")
+		while 1:
+			data = urlfile.read(chunk)
+			if not data:
+				#print("done.")
+				break
+			f.write(data)
+			#print("Read %s bytes"%len(data))
+		f.close()
+
+
+	def version_tuple_from_text(self,text):
+		if text == None: return ()
+
+		# should go through string and remove all non-integers,
+		# and for any given break split into a different section
+		segments = []
+		tmp = ''
+		for l in str(text):
+			if l.isdigit()==False:
+				if len(tmp)>0:
+					segments.append(int(tmp))
+					tmp = ''
+			else:
+				tmp+=l
+		if len(tmp)>0:
+			segments.append(int(tmp))
+
+		if len(segments)==0:
+			if self._verbose: print("No version strings found text: ",text)
+			if self._include_branches == False:
+				return ()
+			else:
+				return (text)
+		return tuple(segments)
+
+	# called for running check in a background thread
+	def check_for_update_async(self, callback=None):
+
+		if self._json != None and "update_ready" in self._json and self._json["version_text"]!={}:
+			if self._json["update_ready"] == True:
+				self._update_ready = True
+				self._update_link = self._json["version_text"]["link"]
+				self._update_version = str(self._json["version_text"]["version"])
+				# cached update
+				callback(True)
+				return
+
+		# do the check
+		if self._check_interval_enable == False:
+			return
+		elif self._async_checking == True:
+			if self._verbose: print("Skipping async check, already started")
+			return  # already running the bg thread
+		elif self._update_ready == None:
+			self.start_async_check_update(False, callback)
+
+
+	def check_for_update_now(self, callback=None):
+
+		self._error = None
+		self._error_msg = None
+
+		if self._verbose:
+			print("Check update pressed, first getting current status")
+		if self._async_checking == True:
+			if self._verbose: print("Skipping async check, already started")
+			return  # already running the bg thread
+		elif self._update_ready == None:
+			self.start_async_check_update(True, callback)
+		else:
+			self._update_ready = None
+			self.start_async_check_update(True, callback)
+
+
+	# this function is not async, will always return in sequential fashion
+	# but should have a parent which calls it in another thread
+	def check_for_update(self, now=False):
+		if self._verbose: print("Checking for update function")
+
+		# clear the errors if any
+		self._error = None
+		self._error_msg = None
+
+		# avoid running again in, just return past result if found
+		# but if force now check, then still do it
+		if self._update_ready != None and now == False:
+			return (self._update_ready,self._update_version,self._update_link)
+
+		if self._current_version == None:
+			raise ValueError("current_version not yet defined")
+		if self._repo == None:
+			raise ValueError("repo not yet defined")
+		if self._user == None:
+			raise ValueError("username not yet defined")
+
+		self.set_updater_json()  # self._json
+
+		if now == False and self.past_interval_timestamp()==False:
+			if self._verbose:
+				print("Aborting check for updated, check interval not reached")
+			return (False, None, None)
+
+		# check if using tags or releases
+		# note that if called the first time, this will pull tags from online
+		if self._fake_install == True:
+			if self._verbose:
+				print("fake_install = True, setting fake version as ready")
+			self._update_ready = True
+			self._update_version = "(999,999,999)"
+			self._update_link = "http://127.0.0.1"
+
+			return (self._update_ready, self._update_version, self._update_link)
+
+		# primary internet call
+		self.get_tags()  # sets self._tags and self._tag_latest
+
+		self._json["last_check"] = str(datetime.now())
+		self.save_updater_json()
+
+		# can be () or ('master') in addition to branches, and version tag
+		new_version = self.version_tuple_from_text(self.tag_latest)
+
+		if len(self._tags)==0:
+			self._update_ready = False
+			self._update_version = None
+			self._update_link = None
+			return (False, None, None)
+		if self._include_branches == False:
+			link = self.select_link(self, self._tags[0])
+		else:
+			n = len(self._include_branch_list)
+			if len(self._tags)==n:
+				# effectively means no tags found on repo
+				# so provide the first one as default
+				link = self.select_link(self, self._tags[0])
+			else:
+				link = self.select_link(self, self._tags[n])
+
+		if new_version == ():
+			self._update_ready = False
+			self._update_version = None
+			self._update_link = None
+			return (False, None, None)
+		elif str(new_version).lower() in self._include_branch_list:
+			# handle situation where master/whichever branch is included
+			# however, this code effectively is not triggered now
+			# as new_version will only be tag names, not branch names
+			if self._include_branch_autocheck == False:
+				# don't offer update as ready,
+				# but set the link for the default
+				# branch for installing
+				self._update_ready = True
+				self._update_version = new_version
+				self._update_link = link
+				self.save_updater_json()
+				return (True, new_version, link)
+			else:
+				raise ValueError("include_branch_autocheck: NOT YET DEVELOPED")
+				# bypass releases and look at timestamp of last update
+				# from a branch compared to now, see if commit values
+				# match or not.
+
+		else:
+			# situation where branches not included
+
+			if new_version > self._current_version:
+
+				self._update_ready = True
+				self._update_version = new_version
+				self._update_link = link
+				self.save_updater_json()
+				return (True, new_version, link)
+
+		# elif new_version != self._current_version:
+		# 	self._update_ready = False
+		# 	self._update_version = new_version
+		# 	self._update_link = link
+		# 	self.save_updater_json()
+		# 	return (True, new_version, link)
+
+		# if no update, set ready to False from None
+		self._update_ready = False
+		self._update_version = None
+		self._update_link = None
+		return (False, None, None)
+
+
+	def set_tag(self,name):
+		tg = None
+		for tag in self._tags:
+			if name == tag["name"]:
+				tg = tag
+				break
+		if tg == None:
+			raise ValueError("Version tag not found: "+revert_tag)
+		new_version = self.version_tuple_from_text(self.tag_latest)
+		self._update_version = new_version
+		self._update_link = self.select_link(self, tg)
+
+
+	def run_update(self,force=False,revert_tag=None,clean=False,callback=None):
+		# revert_tag: could e.g. get from drop down list
+		# different versions of the addon to revert back to
+		# clean: not used, but in future could use to totally refresh addon
+		self._json["update_ready"] = False
+		self._json["ignore"] = False  # clear ignore flag
+		self._json["version_text"] = {}
+
+		if revert_tag != None:
+			self.set_tag(revert_tag)
+			self._update_ready = True
+
+		# clear the errors if any
+		self._error = None
+		self._error_msg = None
+
+		if self._verbose: print("Running update")
+
+		if self._fake_install == True:
+			# change to True, to trigger the reload/"update installed" handler
+			if self._verbose:
+				print("fake_install=True")
+				print("Just reloading and running any handler triggers")
+			self._json["just_updated"] = True
+			self.save_updater_json()
+			if self._backup_current == True:
+				self.create_backup()
+			self.reload_addon()
+			self._update_ready = False
+			res = True  # fake "success" zip download flag
+
+		elif force==False:
+			if self._update_ready != True:
+				if self._verbose: print("Update stopped, new version not ready")
+				return "Update stopped, new version not ready"
+			elif self._update_link == None:
+				# this shouldn't happen if update is ready
+				if self._verbose: print("Update stopped, update link unavailable")
+				return "Update stopped, update link unavailable"
+
+			if self._verbose and revert_tag==None:
+				print("Staging update")
+			elif self._verbose:
+				print("Staging install")
+
+			res = self.stage_repository(self._update_link)
+			if res !=True:
+				print("Error in staging repository: "+str(res))
+				if callback != None: callback(self._error_msg)
+				return self._error_msg
+			self.unpack_staged_zip(clean)
+
+		else:
+			if self._update_link == None:
+				if self._verbose: print("Update stopped, could not get link")
+				return "Update stopped, could not get link"
+			if self._verbose: print("Forcing update")
+
+			res = self.stage_repository(self._update_link)
+			if res !=True:
+				print("Error in staging repository: "+str(res))
+				if callback != None: callback(self._error_msg)
+				return self._error_msg
+			self.unpack_staged_zip(clean)
+			# would need to compare against other versions held in tags
+
+		# run the front-end's callback if provided
+		if callback != None: callback()
+
+		# return something meaningful, 0 means it worked
+		return 0
+
+
+	def past_interval_timestamp(self):
+		if self._check_interval_enable == False:
+			return True  # ie this exact feature is disabled
+
+		if "last_check" not in self._json or self._json["last_check"] == "":
+			return True
+		else:
+			now = datetime.now()
+			last_check = datetime.strptime(self._json["last_check"],
+										"%Y-%m-%d %H:%M:%S.%f")
+			next_check = last_check
+			offset = timedelta(
+				days=self._check_interval_days + 30*self._check_interval_months,
+				hours=self._check_interval_hours,
+				minutes=self._check_interval_minutes
+				)
+
+			delta = (now - offset) - last_check
+			if delta.total_seconds() > 0:
+				if self._verbose:
+					print("{} Updater: Time to check for updates!".format(self._addon))
+				return True
+			else:
+				if self._verbose:
+					print("{} Updater: Determined it's not yet time to check for updates".format(self._addon))
+				return False
+
+
+	def set_updater_json(self):
+		if self._updater_path == None:
+			raise ValueError("updater_path is not defined")
+		elif os.path.isdir(self._updater_path) == False:
+			os.makedirs(self._updater_path)
+
+		jpath = os.path.join(self._updater_path,"updater_status.json")
+		if os.path.isfile(jpath):
+			with open(jpath) as data_file:
+				self._json = json.load(data_file)
+				if self._verbose: print("{} Updater: Read in json settings from file".format(self._addon))
+		else:
+			# set data structure
+			self._json = {
+				"last_check":"",
+				"backup_date":"",
+				"update_ready":False,
+				"ignore":False,
+				"just_restored":False,
+				"just_updated":False,
+				"version_text":{}
+			}
+			self.save_updater_json()
+
+
+	def save_updater_json(self):
+		# first save the state
+		if self._update_ready == True:
+			if type(self._update_version) == type((0,0,0)):
+				self._json["update_ready"] = True
+				self._json["version_text"]["link"]=self._update_link
+				self._json["version_text"]["version"]=self._update_version
+			else:
+				self._json["update_ready"] = False
+				self._json["version_text"] = {}
+		else:
+			self._json["update_ready"] = False
+			self._json["version_text"] = {}
+
+		jpath = os.path.join(self._updater_path,"updater_status.json")
+		outf = open(jpath,'w')
+		data_out = json.dumps(self._json, indent=4)
+		outf.write(data_out)
+		outf.close()
+		if self._verbose:
+			print(self._addon+": Wrote out updater json settings to file, with the contents:")
+			print(self._json)
+
+	def json_reset_postupdate(self):
+		self._json["just_updated"] = False
+		self._json["update_ready"] = False
+		self._json["version_text"] = {}
+		self.save_updater_json()
+
+	def json_reset_restore(self):
+		self._json["just_restored"] = False
+		self._json["update_ready"] = False
+		self._json["version_text"] = {}
+		self.save_updater_json()
+		self._update_ready = None  # reset so you could check update again
+
+	def ignore_update(self):
+		self._json["ignore"] = True
+		self.save_updater_json()
+
+
+	# -------------------------------------------------------------------------
+	# ASYNC stuff
+	# -------------------------------------------------------------------------
+
+	def start_async_check_update(self, now=False, callback=None):
+		if self._async_checking == True:
+			return
+		if self._verbose: print("{} updater: Starting background checking thread".format(self._addon))
+		check_thread = threading.Thread(target=self.async_check_update,
+										args=(now,callback,))
+		check_thread.daemon = True
+		self._check_thread = check_thread
+		check_thread.start()
+
+		return True
+
+	def async_check_update(self, now, callback=None):
+		self._async_checking = True
+		if self._verbose: print("{} BG thread: Checking for update now in background".format(self._addon))
+		# time.sleep(3)  # to test background, in case internet too fast to tell
+		# try:
+		self.check_for_update(now=now)
+		# except Exception as exception:
+		# 	print("Checking for update error:")
+		# 	print(exception)
+		# 	self._update_ready = False
+		# 	self._update_version = None
+		# 	self._update_link = None
+		# 	self._error = "Error occurred"
+		# 	self._error_msg = "Encountered an error while checking for updates"
+
+		self._async_checking = False
+		self._check_thread = None
+
+		if self._verbose:
+			print("{} BG thread: Finished checking for update, doing callback".format(self._addon))
+		if callback != None: callback(self._update_ready)
+
+
+	def stop_async_check_update(self):
+		if self._check_thread != None:
+			try:
+				if self._verbose: print("Thread will end in normal course.")
+				# however, "There is no direct kill method on a thread object."
+				# better to let it run its course
+				#self._check_thread.stop()
+			except:
+				pass
+		self._async_checking = False
+		self._error = None
+		self._error_msg = None
+
+
+# -----------------------------------------------------------------------------
+# Updater Engines
+# -----------------------------------------------------------------------------
+
+
+class BitbucketEngine(object):
+
+	def __init__(self):
+		self.api_url = 'https://api.bitbucket.org'
+		self.token = None
+		self.name = "bitbucket"
+
+	def form_repo_url(self, updater):
+		return self.api_url+"/2.0/repositories/"+updater.user+"/"+updater.repo
+
+	def form_tags_url(self, updater):
+		return self.form_repo_url(updater) + "/refs/tags?sort=-name"
+
+	def form_branch_url(self, branch, updater):
+		return self.get_zip_url(branch, updater)
+
+	def get_zip_url(self, name, updater):
+		return "https://bitbucket.org/{user}/{repo}/get/{name}.zip".format(
+			user=updater.user,
+			repo=updater.repo,
+			name=name)
+
+	def parse_tags(self, response, updater):
+		if response == None:
+			return []
+		return [{"name": tag["name"], "zipball_url": self.get_zip_url(tag["name"], updater)} for tag in response["values"]]
+
+
+class GithubEngine(object):
+
+	def __init__(self):
+		self.api_url = 'https://api.github.com'
+		self.token = None
+		self.name = "github"
+
+	def form_repo_url(self, updater):
+		return "{}{}{}{}{}".format(self.api_url,"/repos/",updater.user,
+								"/",updater.repo)
+
+	def form_tags_url(self, updater):
+		if updater.use_releases:
+			return "{}{}".format(self.form_repo_url(updater),"/releases")
+		else:
+			return "{}{}".format(self.form_repo_url(updater),"/tags")
+
+	def form_branch_list_url(self, updater):
+		return "{}{}".format(self.form_repo_url(updater),"/branches")
+
+	def form_branch_url(self, branch, updater):
+		return "{}{}{}".format(self.form_repo_url(updater),
+							"/zipball/",branch)
+
+	def parse_tags(self, response, updater):
+		if response == None:
+			return []
+		return response
+
+
+class GitlabEngine(object):
+
+	def __init__(self):
+		self.api_url = 'https://gitlab.com'
+		self.token = None
+		self.name = "gitlab"
+
+	def form_repo_url(self, updater):
+		return "{}{}{}".format(self.api_url,"/api/v3/projects/",updater.repo)
+
+	def form_tags_url(self, updater):
+		return "{}{}".format(self.form_repo_url(updater),"/repository/tags")
+
+	def form_branch_list_url(self, updater):
+		# does not validate branch name.
+		return "{}{}".format(
+			self.form_repo_url(updater),
+			"/repository/branches")
+
+	def form_branch_url(self, branch, updater):
+		# Could clash with tag names and if it does, it will
+		# download TAG zip instead of branch zip to get
+		# direct path, would need.
+		return "{}{}{}".format(
+			self.form_repo_url(updater),
+			"/repository/archive.zip?sha=",
+			branch)
+
+	def get_zip_url(self, sha, updater):
+		return "{base}/repository/archive.zip?sha:{sha}".format(
+			base=self.form_repo_url(updater),
+			sha=sha)
+
+	# def get_commit_zip(self, id, updater):
+	# 	return self.form_repo_url(updater)+"/repository/archive.zip?sha:"+id
+
+	def parse_tags(self, response, updater):
+		if response == None:
+			return []
+		return [{"name": tag["name"], "zipball_url": self.get_zip_url(tag["commit"]["id"], updater)} for tag in response]
+
+
+# -----------------------------------------------------------------------------
+# The module-shared class instance,
+# should be what's imported to other files
+# -----------------------------------------------------------------------------
+
+Updater = Singleton_updater()
diff --git a/uv_magic_uv/addon_updater_ops.py b/uv_magic_uv/addon_updater_ops.py
new file mode 100644
index 0000000000000000000000000000000000000000..418334ad044c216b42993a8060c3a9c360c0072b
--- /dev/null
+++ b/uv_magic_uv/addon_updater_ops.py
@@ -0,0 +1,1357 @@
+# ##### 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 #####
+
+import bpy
+from bpy.app.handlers import persistent
+import os
+
+# updater import, import safely
+# Prevents popups for users with invalid python installs e.g. missing libraries
+try:
+	from .addon_updater import Updater as updater
+except Exception as e:
+	print("ERROR INITIALIZING UPDATER")
+	print(str(e))
+	class Singleton_updater_none(object):
+		def __init__(self):
+			self.addon = None
+			self.verbose = False
+			self.invalidupdater = True # used to distinguish bad install
+			self.error = None
+			self.error_msg = None
+			self.async_checking = None
+		def clear_state(self):
+			self.addon = None
+			self.verbose = False
+			self.invalidupdater = True
+			self.error = None
+			self.error_msg = None
+			self.async_checking = None
+		def run_update(self): pass
+		def check_for_update(self): pass
+	updater = Singleton_updater_none()
+	updater.error = "Error initializing updater module"
+	updater.error_msg = str(e)
+
+# Must declare this before classes are loaded
+# otherwise the bl_idname's will not match and have errors.
+# Must be all lowercase and no spaces
+updater.addon = "magic_uv"
+
+dispaly_addon_name = "Magic UV"
+
+# -----------------------------------------------------------------------------
+# Updater operators
+# -----------------------------------------------------------------------------
+
+
+# simple popup for prompting checking for update & allow to install if available
+class addon_updater_install_popup(bpy.types.Operator):
+	"""Check and install update if available"""
+	bl_label = "Update {x} addon".format(x=updater.addon)
+	bl_idname = updater.addon+".updater_install_popup"
+	bl_description = "Popup menu to check and display current updates available"
+	bl_options = {'REGISTER', 'INTERNAL'}
+
+	# if true, run clean install - ie remove all files before adding new
+	# equivalent to deleting the addon and reinstalling, except the
+	# updater folder/backup folder remains
+	clean_install = bpy.props.BoolProperty(
+		name="Clean install",
+		description="If enabled, completely clear the addon's folder before installing new update, creating a fresh install",
+		default=False,
+		options={'HIDDEN'}
+	)
+	ignore_enum = bpy.props.EnumProperty(
+		name="Process update",
+		description="Decide to install, ignore, or defer new addon update",
+		items=[
+			("install","Update Now","Install update now"),
+			("ignore","Ignore", "Ignore this update to prevent future popups"),
+			("defer","Defer","Defer choice till next blender session")
+		],
+		options={'HIDDEN'}
+	)
+
+	def check (self, context):
+		return True
+
+	def invoke(self, context, event):
+		return context.window_manager.invoke_props_dialog(self)
+
+	def draw(self, context):
+		layout = self.layout
+		if updater.invalidupdater == True:
+			layout.label("Updater module error")
+			return
+		elif updater.update_ready == True:
+			col = layout.column()
+			col.scale_y = 0.7
+			col.label("Update {} ready!".format(str(updater.update_version)),
+						icon="LOOP_FORWARDS")
+			col.label("Choose 'Update Now' & press OK to install, ",icon="BLANK1")
+			col.label("or click outside window to defer",icon="BLANK1")
+			row = col.row()
+			row.prop(self,"ignore_enum",expand=True)
+			col.split()
+		elif updater.update_ready == False:
+			col = layout.column()
+			col.scale_y = 0.7
+			col.label("No updates available")
+			col.label("Press okay to dismiss dialog")
+			# add option to force install
+		else:
+			# case: updater.update_ready = None
+			# we have not yet checked for the update
+			layout.label("Check for update now?")
+
+		# potentially in future, could have UI for 'check to select old version'
+		# to revert back to.
+
+	def execute(self,context):
+
+		# in case of error importing updater
+		if updater.invalidupdater == True:
+			return {'CANCELLED'}
+
+		if updater.manual_only==True:
+			bpy.ops.wm.url_open(url=updater.website)
+		elif updater.update_ready == True:
+
+			# action based on enum selection
+			if self.ignore_enum=='defer':
+				return {'FINISHED'}
+			elif self.ignore_enum=='ignore':
+				updater.ignore_update()
+				return {'FINISHED'}
+			#else: "install update now!"
+
+			res = updater.run_update(
+							force=False,
+							callback=post_update_callback,
+							clean=self.clean_install)
+			# should return 0, if not something happened
+			if updater.verbose:
+				if res==0: print("Updater returned successful")
+				else: print("Updater returned "+str(res)+", error occurred")
+		elif updater.update_ready == None:
+			(update_ready, version, link) = updater.check_for_update(now=True)
+
+			# re-launch this dialog
+			atr = addon_updater_install_popup.bl_idname.split(".")
+			getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT')
+		else:
+			if updater.verbose:print("Doing nothing, not ready for update")
+		return {'FINISHED'}
+
+
+# User preference check-now operator
+class addon_updater_check_now(bpy.types.Operator):
+	bl_label = "Check now for "+dispaly_addon_name+" update"
+	bl_idname = updater.addon+".updater_check_now"
+	bl_description = "Check now for an update to the {x} addon".format(
+														x=updater.addon)
+	bl_options = {'REGISTER', 'INTERNAL'}
+
+	def execute(self,context):
+
+		# in case of error importing updater
+		if updater.invalidupdater == True:
+			return {'CANCELLED'}
+
+		if updater.async_checking == True and updater.error == None:
+			# Check already happened
+			# Used here to just avoid constant applying settings below
+			# Ignoring if error, to prevent being stuck on the error screen
+			return {'CANCELLED'}
+
+		# apply the UI settings
+		settings = context.user_preferences.addons[__package__].preferences
+		updater.set_check_interval(enable=settings.auto_check_update,
+					months=settings.updater_intrval_months,
+					days=settings.updater_intrval_days,
+					hours=settings.updater_intrval_hours,
+					minutes=settings.updater_intrval_minutes
+					) # optional, if auto_check_update
+
+		# input is an optional callback function
+		# this function should take a bool input, if true: update ready
+		# if false, no update ready
+		updater.check_for_update_now(ui_refresh)
+
+		return {'FINISHED'}
+
+
+class addon_updater_update_now(bpy.types.Operator):
+	bl_label = "Update "+updater.addon+" addon now"
+	bl_idname = updater.addon+".updater_update_now"
+	bl_description = "Update to the latest version of the {x} addon".format(
+														x=updater.addon)
+	bl_options = {'REGISTER', 'INTERNAL'}
+
+	# if true, run clean install - ie remove all files before adding new
+	# equivalent to deleting the addon and reinstalling, except the
+	# updater folder/backup folder remains
+	clean_install = bpy.props.BoolProperty(
+		name="Clean install",
+		description="If enabled, completely clear the addon's folder before installing new update, creating a fresh install",
+		default=False,
+		options={'HIDDEN'}
+	)
+
+	def execute(self,context):
+
+		# in case of error importing updater
+		if updater.invalidupdater == True:
+			return {'CANCELLED'}
+
+		if updater.manual_only == True:
+			bpy.ops.wm.url_open(url=updater.website)
+		if updater.update_ready == True:
+			# if it fails, offer to open the website instead
+			try:
+				res = updater.run_update(
+								force=False,
+								callback=post_update_callback,
+								clean=self.clean_install)
+
+				# should return 0, if not something happened
+				if updater.verbose:
+					if res==0: print("Updater returned successful")
+					else: print("Updater returned "+str(res)+", error occurred")
+			except Exception as e:
+				updater._error = "Error trying to run update"
+				updater._error_msg = str(e)
+				atr = addon_updater_install_manually.bl_idname.split(".")
+				getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT')
+		elif updater.update_ready == None:
+			(update_ready, version, link) = updater.check_for_update(now=True)
+			# re-launch this dialog
+			atr = addon_updater_install_popup.bl_idname.split(".")
+			getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT')
+
+		elif updater.update_ready == False:
+			self.report({'INFO'}, "Nothing to update")
+		else:
+			self.report({'ERROR'}, "Encountered problem while trying to update")
+
+		return {'FINISHED'}
+
+
+class addon_updater_update_target(bpy.types.Operator):
+	bl_label = updater.addon+" addon version target"
+	bl_idname = updater.addon+".updater_update_target"
+	bl_description = "Install a targeted version of the {x} addon".format(
+														x=updater.addon)
+	bl_options = {'REGISTER', 'INTERNAL'}
+
+	def target_version(self, context):
+		# in case of error importing updater
+		if updater.invalidupdater == True:
+			ret = []
+
+		ret = []
+		i=0
+		for tag in updater.tags:
+			ret.append( (tag,tag,"Select to install "+tag) )
+			i+=1
+		return ret
+
+	target = bpy.props.EnumProperty(
+		name="Target version to install",
+		description="Select the version to install",
+		items=target_version
+		)
+
+	# if true, run clean install - ie remove all files before adding new
+	# equivalent to deleting the addon and reinstalling, except the
+	# updater folder/backup folder remains
+	clean_install = bpy.props.BoolProperty(
+		name="Clean install",
+		description="If enabled, completely clear the addon's folder before installing new update, creating a fresh install",
+		default=False,
+		options={'HIDDEN'}
+	)
+
+	@classmethod
+	def poll(cls, context):
+		if updater.invalidupdater == True: return False
+		return updater.update_ready != None and len(updater.tags)>0
+
+	def invoke(self, context, event):
+		return context.window_manager.invoke_props_dialog(self)
+
+	def draw(self, context):
+		layout = self.layout
+		if updater.invalidupdater == True:
+			layout.label("Updater error")
+			return
+		split = layout.split(percentage=0.66)
+		subcol = split.column()
+		subcol.label("Select install version")
+		subcol = split.column()
+		subcol.prop(self, "target", text="")
+
+
+	def execute(self,context):
+
+		# in case of error importing updater
+		if updater.invalidupdater == True:
+			return {'CANCELLED'}
+
+		res = updater.run_update(
+						force=False,
+						revert_tag=self.target,
+						callback=post_update_callback,
+						clean=self.clean_install)
+
+		# should return 0, if not something happened
+		if updater.verbose:
+			if res==0: print("Updater returned successful")
+			else: print("Updater returned "+str(res)+", error occurred")
+			return {'CANCELLED'}
+
+		return {'FINISHED'}
+
+
+class addon_updater_install_manually(bpy.types.Operator):
+	"""As a fallback, direct the user to download the addon manually"""
+	bl_label = "Install update manually"
+	bl_idname = updater.addon+".updater_install_manually"
+	bl_description = "Proceed to manually install update"
+	bl_options = {'REGISTER', 'INTERNAL'}
+
+	error = bpy.props.StringProperty(
+		name="Error Occurred",
+		default="",
+		options={'HIDDEN'}
+		)
+
+	def invoke(self, context, event):
+		return context.window_manager.invoke_popup(self)
+
+	def draw(self, context):
+		layout = self.layout
+
+		if updater.invalidupdater == True:
+			layout.label("Updater error")
+			return
+
+		# use a "failed flag"? it shows this label if the case failed.
+		if self.error!="":
+			col = layout.column()
+			col.scale_y = 0.7
+			col.label("There was an issue trying to auto-install",icon="ERROR")
+			col.label("Press the download button below and install",icon="BLANK1")
+			col.label("the zip file like a normal addon.",icon="BLANK1")
+		else:
+			col = layout.column()
+			col.scale_y = 0.7
+			col.label("Install the addon manually")
+			col.label("Press the download button below and install")
+			col.label("the zip file like a normal addon.")
+
+		# if check hasn't happened, i.e. accidentally called this menu
+		# allow to check here
+
+		row = layout.row()
+
+		if updater.update_link != None:
+			row.operator("wm.url_open",text="Direct download").url=\
+					updater.update_link
+		else:
+			row.operator("wm.url_open",text="(failed to retrieve direct download)")
+			row.enabled = False
+
+			if updater.website != None:
+				row = layout.row()
+				row.operator("wm.url_open",text="Open website").url=\
+						updater.website
+			else:
+				row = layout.row()
+				row.label("See source website to download the update")
+
+	def execute(self,context):
+
+		return {'FINISHED'}
+
+
+class addon_updater_updated_successful(bpy.types.Operator):
+	"""Addon in place, popup telling user it completed or what went wrong"""
+	bl_label = "Installation Report"
+	bl_idname = updater.addon+".updater_update_successful"
+	bl_description = "Update installation response"
+	bl_options = {'REGISTER', 'INTERNAL', 'UNDO'}
+
+	error = bpy.props.StringProperty(
+		name="Error Occurred",
+		default="",
+		options={'HIDDEN'}
+		)
+
+	def invoke(self, context, event):
+		return context.window_manager.invoke_props_popup(self, event)
+
+	def draw(self, context):
+		layout = self.layout
+
+		if updater.invalidupdater == True:
+			layout.label("Updater error")
+			return
+
+		saved = updater.json
+		if self.error != "":
+			col = layout.column()
+			col.scale_y = 0.7
+			col.label("Error occurred, did not install", icon="ERROR")
+			col.label(updater.error_msg, icon="BLANK1")
+			rw = col.row()
+			rw.scale_y = 2
+			rw.operator("wm.url_open",
+				text="Click for manual download.",
+				icon="BLANK1"
+				).url=updater.website
+			# manual download button here
+		elif updater.auto_reload_post_update == False:
+			# tell user to restart blender
+			if "just_restored" in saved and saved["just_restored"] == True:
+				col = layout.column()
+				col.scale_y = 0.7
+				col.label("Addon restored", icon="RECOVER_LAST")
+				col.label("Restart blender to reload.",icon="BLANK1")
+				updater.json_reset_restore()
+			else:
+				col = layout.column()
+				col.scale_y = 0.7
+				col.label("Addon successfully installed", icon="FILE_TICK")
+				col.label("Restart blender to reload.", icon="BLANK1")
+
+		else:
+			# reload addon, but still recommend they restart blender
+			if "just_restored" in saved and saved["just_restored"] == True:
+				col = layout.column()
+				col.scale_y = 0.7
+				col.label("Addon restored", icon="RECOVER_LAST")
+				col.label("Consider restarting blender to fully reload.",icon="BLANK1")
+				updater.json_reset_restore()
+			else:
+				col = layout.column()
+				col.scale_y = 0.7
+				col.label("Addon successfully installed", icon="FILE_TICK")
+				col.label("Consider restarting blender to fully reload.", icon="BLANK1")
+
+	def execut(self, context):
+		return {'FINISHED'}
+
+
+class addon_updater_restore_backup(bpy.types.Operator):
+	"""Restore addon from backup"""
+	bl_label = "Restore backup"
+	bl_idname = updater.addon+".updater_restore_backup"
+	bl_description = "Restore addon from backup"
+	bl_options = {'REGISTER', 'INTERNAL'}
+
+	@classmethod
+	def poll(cls, context):
+		try:
+			return os.path.isdir(os.path.join(updater.stage_path,"backup"))
+		except:
+			return False
+
+	def execute(self, context):
+		# in case of error importing updater
+		if updater.invalidupdater == True:
+			return {'CANCELLED'}
+		updater.restore_backup()
+		return {'FINISHED'}
+
+
+class addon_updater_ignore(bpy.types.Operator):
+	"""Prevent future update notice popups"""
+	bl_label = "Ignore update"
+	bl_idname = updater.addon+".updater_ignore"
+	bl_description = "Ignore update to prevent future popups"
+	bl_options = {'REGISTER', 'INTERNAL'}
+
+	@classmethod
+	def poll(cls, context):
+		if updater.invalidupdater == True:
+			return False
+		elif updater.update_ready == True:
+			return True
+		else:
+			return False
+
+	def execute(self, context):
+		# in case of error importing updater
+		if updater.invalidupdater == True:
+			return {'CANCELLED'}
+		updater.ignore_update()
+		self.report({"INFO"},"Open addon preferences for updater options")
+		return {'FINISHED'}
+
+
+class addon_updater_end_background(bpy.types.Operator):
+	"""Stop checking for update in the background"""
+	bl_label = "End background check"
+	bl_idname = updater.addon+".end_background_check"
+	bl_description = "Stop checking for update in the background"
+	bl_options = {'REGISTER', 'INTERNAL'}
+
+	# @classmethod
+	# def poll(cls, context):
+	# 	if updater.async_checking == True:
+	# 		return True
+	# 	else:
+	# 		return False
+
+	def execute(self, context):
+		# in case of error importing updater
+		if updater.invalidupdater == True:
+			return {'CANCELLED'}
+		updater.stop_async_check_update()
+		return {'FINISHED'}
+
+
+# -----------------------------------------------------------------------------
+# Handler related, to create popups
+# -----------------------------------------------------------------------------
+
+
+# global vars used to prevent duplicate popup handlers
+ran_autocheck_install_popup = False
+ran_update_sucess_popup = False
+
+# global var for preventing successive calls
+ran_background_check = False
+
+@persistent
+def updater_run_success_popup_handler(scene):
+	global ran_update_sucess_popup
+	ran_update_sucess_popup = True
+
+	# in case of error importing updater
+	if updater.invalidupdater == True:
+		return
+
+	try:
+		bpy.app.handlers.scene_update_post.remove(
+				updater_run_success_popup_handler)
+	except:
+		pass
+
+	atr = addon_updater_updated_successful.bl_idname.split(".")
+	getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT')
+
+
+@persistent
+def updater_run_install_popup_handler(scene):
+	global ran_autocheck_install_popup
+	ran_autocheck_install_popup = True
+
+	# in case of error importing updater
+	if updater.invalidupdater == True:
+		return
+
+	try:
+		bpy.app.handlers.scene_update_post.remove(
+				updater_run_install_popup_handler)
+	except:
+		pass
+
+	if "ignore" in updater.json and updater.json["ignore"] == True:
+		return # don't do popup if ignore pressed
+	# elif type(updater.update_version) != type((0,0,0)):
+	# 	# likely was from master or another branch, shouldn't trigger popup
+	# 	updater.json_reset_restore()
+	# 	return
+	elif "version_text" in updater.json and "version" in updater.json["version_text"]:
+		version = updater.json["version_text"]["version"]
+		ver_tuple = updater.version_tuple_from_text(version)
+
+		if ver_tuple < updater.current_version:
+			# user probably manually installed to get the up to date addon
+			# in here. Clear out the update flag using this function
+			if updater.verbose:
+				print("{} updater: appears user updated, clearing flag".format(\
+						updater.addon))
+			updater.json_reset_restore()
+			return
+	atr = addon_updater_install_popup.bl_idname.split(".")
+	getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT')
+
+
+# passed into the updater, background thread updater
+def background_update_callback(update_ready):
+	global ran_autocheck_install_popup
+
+	# in case of error importing updater
+	if updater.invalidupdater == True:
+		return
+
+	if updater.showpopups == False:
+		return
+
+	if update_ready != True:
+		return
+
+	if updater_run_install_popup_handler not in \
+				bpy.app.handlers.scene_update_post and \
+				ran_autocheck_install_popup==False:
+		bpy.app.handlers.scene_update_post.append(
+				updater_run_install_popup_handler)
+
+		ran_autocheck_install_popup = True
+
+
+# a callback for once the updater has completed
+# Only makes sense to use this if "auto_reload_post_update" == False,
+# i.e. don't auto-restart the addon
+def post_update_callback(res=None):
+
+	# in case of error importing updater
+	if updater.invalidupdater == True:
+		return
+
+	if res==None:
+		# this is the same code as in conditional at the end of the register function
+		# ie if "auto_reload_post_update" == True, comment out this code
+		if updater.verbose: print("{} updater: Running post update callback".format(updater.addon))
+		#bpy.app.handlers.scene_update_post.append(updater_run_success_popup_handler)
+
+		atr = addon_updater_updated_successful.bl_idname.split(".")
+		getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT')
+		global ran_update_sucess_popup
+		ran_update_sucess_popup = True
+	else:
+		# some kind of error occured and it was unable to install,
+		# offer manual download instead
+		atr = addon_updater_updated_successful.bl_idname.split(".")
+		getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT',error=res)
+	return
+
+def ui_refresh(update_status):
+	# find a way to just re-draw self?
+	# callback intended for trigger by async thread
+	for windowManager in bpy.data.window_managers:
+		for window in windowManager.windows:
+			for area in window.screen.areas:
+				area.tag_redraw()
+
+# function for asynchronous background check, which *could* be called on register
+def check_for_update_background():
+
+	# in case of error importing updater
+	if updater.invalidupdater == True:
+		return
+
+	global ran_background_check
+	if ran_background_check == True:
+		# Global var ensures check only happens once
+		return
+	elif updater.update_ready != None or updater.async_checking == True:
+		# Check already happened
+		# Used here to just avoid constant applying settings below
+		return
+
+	# apply the UI settings
+	addon_prefs = bpy.context.user_preferences.addons.get(__package__, None)
+	if not addon_prefs:
+		return
+	settings = addon_prefs.preferences
+	updater.set_check_interval(enable=settings.auto_check_update,
+				months=settings.updater_intrval_months,
+				days=settings.updater_intrval_days,
+				hours=settings.updater_intrval_hours,
+				minutes=settings.updater_intrval_minutes
+				) # optional, if auto_check_update
+
+	# input is an optional callback function
+	# this function should take a bool input, if true: update ready
+	# if false, no update ready
+	if updater.verbose:
+		print("{} updater: Running background check for update".format(\
+				updater.addon))
+	updater.check_for_update_async(background_update_callback)
+	ran_background_check = True
+
+
+# can be placed in front of other operators to launch when pressed
+def check_for_update_nonthreaded(self, context):
+
+	# in case of error importing updater
+	if updater.invalidupdater == True:
+		return
+
+	# only check if it's ready, ie after the time interval specified
+	# should be the async wrapper call here
+
+	settings = context.user_preferences.addons[__package__].preferences
+	updater.set_check_interval(enable=settings.auto_check_update,
+				months=settings.updater_intrval_months,
+				days=settings.updater_intrval_days,
+				hours=settings.updater_intrval_hours,
+				minutes=settings.updater_intrval_minutes
+				) # optional, if auto_check_update
+
+	(update_ready, version, link) = updater.check_for_update(now=False)
+	if update_ready == True:
+		atr = addon_updater_install_popup.bl_idname.split(".")
+		getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT')
+	else:
+		if updater.verbose: print("No update ready")
+		self.report({'INFO'}, "No update ready")
+
+# for use in register only, to show popup after re-enabling the addon
+# must be enabled by developer
+def showReloadPopup():
+
+	# in case of error importing updater
+	if updater.invalidupdater == True:
+		return
+
+	saved_state = updater.json
+	global ran_update_sucess_popup
+
+	a = saved_state != None
+	b = "just_updated" in saved_state
+	c = saved_state["just_updated"]
+
+	if a and b and c:
+		updater.json_reset_postupdate() # so this only runs once
+
+		# no handlers in this case
+		if updater.auto_reload_post_update == False: return
+
+		if updater_run_success_popup_handler not in \
+					bpy.app.handlers.scene_update_post \
+					and ran_update_sucess_popup==False:
+			bpy.app.handlers.scene_update_post.append(
+					updater_run_success_popup_handler)
+			ran_update_sucess_popup = True
+
+
+# -----------------------------------------------------------------------------
+# Example UI integrations
+# -----------------------------------------------------------------------------
+
+
+# Panel - Update Available for placement at end/beginning of panel
+# After a check for update has occurred, this function will draw a box
+# saying an update is ready, and give a button for: update now, open website,
+# or ignore popup. Ideal to be placed at the end / beginning of a panel
+def update_notice_box_ui(self, context):
+
+	# in case of error importing updater
+	if updater.invalidupdater == True:
+		return
+
+	saved_state = updater.json
+	if updater.auto_reload_post_update == False:
+		if "just_updated" in saved_state and saved_state["just_updated"] == True:
+			layout = self.layout
+			box = layout.box()
+			col = box.column()
+			col.scale_y = 0.7
+			col.label("Restart blender", icon="ERROR")
+			col.label("to complete update")
+			return
+
+	# if user pressed ignore, don't draw the box
+	if "ignore" in updater.json and updater.json["ignore"] == True:
+		return
+
+	if updater.update_ready != True: return
+
+	settings = context.user_preferences.addons[__package__].preferences
+	layout = self.layout
+	box = layout.box()
+	col = box.column(align=True)
+	col.label("Update ready!",icon="ERROR")
+	col.separator()
+	row = col.row(align=True)
+	split = row.split(align=True)
+	colL = split.column(align=True)
+	colL.scale_y = 1.5
+	colL.operator(addon_updater_ignore.bl_idname,icon="X",text="Ignore")
+	colR = split.column(align=True)
+	colR.scale_y = 1.5
+	if updater.manual_only==False:
+		colR.operator(addon_updater_update_now.bl_idname,
+						"Update", icon="LOOP_FORWARDS")
+		col.operator("wm.url_open", text="Open website").url = updater.website
+		#col.operator("wm.url_open",text="Direct download").url=updater.update_link
+		col.operator(addon_updater_install_manually.bl_idname, "Install manually")
+	else:
+		#col.operator("wm.url_open",text="Direct download").url=updater.update_link
+		col.operator("wm.url_open", text="Get it now").url = \
+				updater.website
+
+
+# Preferences - for drawing with full width inside user preferences
+# Create a function that can be run inside user preferences panel for prefs UI
+# Place inside UI draw using: addon_updater_ops.updaterSettingsUI(self, context)
+# or by: addon_updater_ops.updaterSettingsUI(context)
+def update_settings_ui(self, context, element=None):
+	# element is a UI element, such as layout, a row, column, or box
+	if element==None: element = self.layout
+	box = element.box()
+
+	# in case of error importing updater
+	if updater.invalidupdater == True:
+		box.label("Error initializing updater code:")
+		box.label(updater.error_msg)
+		return
+
+	settings = context.user_preferences.addons[__package__].preferences
+
+	# auto-update settings
+	box.label("Updater Settings")
+	row = box.row()
+
+	# special case to tell user to restart blender, if set that way
+	if updater.auto_reload_post_update == False:
+		saved_state = updater.json
+		if "just_updated" in saved_state and saved_state["just_updated"] == True:
+			row.label("Restart blender to complete update", icon="ERROR")
+			return
+
+	split = row.split(percentage=0.3)
+	subcol = split.column()
+	subcol.prop(settings, "auto_check_update")
+	subcol = split.column()
+
+	if settings.auto_check_update==False: subcol.enabled = False
+	subrow = subcol.row()
+	subrow.label("Interval between checks")
+	subrow = subcol.row(align=True)
+	checkcol = subrow.column(align=True)
+	checkcol.prop(settings,"updater_intrval_months")
+	checkcol = subrow.column(align=True)
+	checkcol.prop(settings,"updater_intrval_days")
+	checkcol = subrow.column(align=True)
+	checkcol.prop(settings,"updater_intrval_hours")
+	checkcol = subrow.column(align=True)
+	checkcol.prop(settings,"updater_intrval_minutes")
+
+	# checking / managing updates
+	row = box.row()
+	col = row.column()
+	if updater.error != None:
+		subcol = col.row(align=True)
+		subcol.scale_y = 1
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		if "ssl" in updater.error_msg.lower():
+			split.enabled = True
+			split.operator(addon_updater_install_manually.bl_idname,
+						updater.error)
+		else:
+			split.enabled = False
+			split.operator(addon_updater_check_now.bl_idname,
+						updater.error)
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						text = "", icon="FILE_REFRESH")
+
+	elif updater.update_ready == None and updater.async_checking == False:
+		col.scale_y = 2
+		col.operator(addon_updater_check_now.bl_idname)
+	elif updater.update_ready == None: # async is running
+		subcol = col.row(align=True)
+		subcol.scale_y = 1
+		split = subcol.split(align=True)
+		split.enabled = False
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						"Checking...")
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_end_background.bl_idname,
+						text = "", icon="X")
+
+	elif updater.include_branches==True and \
+			len(updater.tags)==len(updater.include_branch_list) and \
+			updater.manual_only==False:
+		# no releases found, but still show the appropriate branch
+		subcol = col.row(align=True)
+		subcol.scale_y = 1
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_update_now.bl_idname,
+					"Update directly to "+str(updater.include_branch_list[0]))
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						text = "", icon="FILE_REFRESH")
+
+	elif updater.update_ready==True and updater.manual_only==False:
+		subcol = col.row(align=True)
+		subcol.scale_y = 1
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_update_now.bl_idname,
+					"Update now to "+str(updater.update_version))
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						text = "", icon="FILE_REFRESH")
+
+	elif updater.update_ready==True and updater.manual_only==True:
+		col.scale_y = 2
+		col.operator("wm.url_open",
+				"Download "+str(updater.update_version)).url=updater.website
+	else: # i.e. that updater.update_ready == False
+		subcol = col.row(align=True)
+		subcol.scale_y = 1
+		split = subcol.split(align=True)
+		split.enabled = False
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						"Addon is up to date")
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						text = "", icon="FILE_REFRESH")
+
+	if updater.manual_only == False:
+		col = row.column(align=True)
+		#col.operator(addon_updater_update_target.bl_idname,
+		if updater.include_branches == True and len(updater.include_branch_list)>0:
+			branch = updater.include_branch_list[0]
+			col.operator(addon_updater_update_target.bl_idname,
+					"Install latest {} / old version".format(branch))
+		else:
+			col.operator(addon_updater_update_target.bl_idname,
+					"Reinstall / install old version")
+		lastdate = "none found"
+		backuppath = os.path.join(updater.stage_path,"backup")
+		if "backup_date" in updater.json and os.path.isdir(backuppath):
+			if updater.json["backup_date"] == "":
+				lastdate = "Date not found"
+			else:
+				lastdate = updater.json["backup_date"]
+		backuptext = "Restore addon backup ({})".format(lastdate)
+		col.operator(addon_updater_restore_backup.bl_idname, backuptext)
+
+	row = box.row()
+	row.scale_y = 0.7
+	lastcheck = updater.json["last_check"]
+	if updater.error != None and updater.error_msg != None:
+		row.label(updater.error_msg)
+	elif lastcheck != "" and lastcheck != None:
+		lastcheck = lastcheck[0: lastcheck.index(".") ]
+		row.label("Last update check: " + lastcheck)
+	else:
+		row.label("Last update check: Never")
+
+
+# Preferences - Condensed drawing within preferences
+# alternate draw for user preferences or other places, does not draw a box
+def update_settings_ui_condensed(self, context, element=None):
+	# element is a UI element, such as layout, a row, column, or box
+	if element==None: element = self.layout
+	row = element.row()
+
+	# in case of error importing updater
+	if updater.invalidupdater == True:
+		row.label("Error initializing updater code:")
+		row.label(updater.error_msg)
+		return
+
+	settings = context.user_preferences.addons[__package__].preferences
+
+	# special case to tell user to restart blender, if set that way
+	if updater.auto_reload_post_update == False:
+		saved_state = updater.json
+		if "just_updated" in saved_state and saved_state["just_updated"] == True:
+			row.label("Restart blender to complete update", icon="ERROR")
+			return
+
+	col = row.column()
+	if updater.error != None:
+		subcol = col.row(align=True)
+		subcol.scale_y = 1
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		if "ssl" in updater.error_msg.lower():
+			split.enabled = True
+			split.operator(addon_updater_install_manually.bl_idname,
+						updater.error)
+		else:
+			split.enabled = False
+			split.operator(addon_updater_check_now.bl_idname,
+						updater.error)
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						text = "", icon="FILE_REFRESH")
+
+	elif updater.update_ready == None and updater.async_checking == False:
+		col.scale_y = 2
+		col.operator(addon_updater_check_now.bl_idname)
+	elif updater.update_ready == None: # async is running
+		subcol = col.row(align=True)
+		subcol.scale_y = 1
+		split = subcol.split(align=True)
+		split.enabled = False
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						"Checking...")
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_end_background.bl_idname,
+						text = "", icon="X")
+
+	elif updater.include_branches==True and \
+			len(updater.tags)==len(updater.include_branch_list) and \
+			updater.manual_only==False:
+		# no releases found, but still show the appropriate branch
+		subcol = col.row(align=True)
+		subcol.scale_y = 1
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_update_now.bl_idname,
+					"Update directly to "+str(updater.include_branch_list[0]))
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						text = "", icon="FILE_REFRESH")
+
+	elif updater.update_ready==True and updater.manual_only==False:
+		subcol = col.row(align=True)
+		subcol.scale_y = 1
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_update_now.bl_idname,
+					"Update now to "+str(updater.update_version))
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						text = "", icon="FILE_REFRESH")
+
+	elif updater.update_ready==True and updater.manual_only==True:
+		col.scale_y = 2
+		col.operator("wm.url_open",
+				"Download "+str(updater.update_version)).url=updater.website
+	else: # i.e. that updater.update_ready == False
+		subcol = col.row(align=True)
+		subcol.scale_y = 1
+		split = subcol.split(align=True)
+		split.enabled = False
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						"Addon is up to date")
+		split = subcol.split(align=True)
+		split.scale_y = 2
+		split.operator(addon_updater_check_now.bl_idname,
+						text = "", icon="FILE_REFRESH")
+
+	row = element.row()
+	row.prop(settings, "auto_check_update")
+
+	row = element.row()
+	row.scale_y = 0.7
+	lastcheck = updater.json["last_check"]
+	if updater.error != None and updater.error_msg != None:
+		row.label(updater.error_msg)
+	elif lastcheck != "" and lastcheck != None:
+		lastcheck = lastcheck[0: lastcheck.index(".") ]
+		row.label("Last check: " + lastcheck)
+	else:
+		row.label("Last check: Never")
+
+
+# a global function for tag skipping
+# a way to filter which tags are displayed,
+# e.g. to limit downgrading too far
+# input is a tag text, e.g. "v1.2.3"
+# output is True for skipping this tag number,
+# False if the tag is allowed (default for all)
+# Note: here, "self" is the acting updater shared class instance
+def skip_tag_function(self, tag):
+
+	# in case of error importing updater
+	if self.invalidupdater == True:
+		return False
+
+	# ---- write any custom code here, return true to disallow version ---- #
+	#
+	# # Filter out e.g. if 'beta' is in name of release
+	# if 'beta' in tag.lower():
+	#	return True
+	# ---- write any custom code above, return true to disallow version --- #
+
+	if self.include_branches == True:
+		for branch in self.include_branch_list:
+			if tag["name"].lower() == branch: return False
+
+	# function converting string to tuple, ignoring e.g. leading 'v'
+	tupled = self.version_tuple_from_text(tag["name"])
+	if type(tupled) != type( (1,2,3) ): return True
+
+	# select the min tag version - change tuple accordingly
+	if self.version_min_update != None:
+		if tupled < self.version_min_update:
+			return True # skip if current version below this
+
+	# select the max tag version
+	if self.version_max_update != None:
+		if tupled >= self.version_max_update:
+			return True # skip if current version at or above this
+
+	# in all other cases, allow showing the tag for updating/reverting
+	return False
+
+# Only customize if trying to leverage "attachments" in *GitHub* releases
+# A way to select from one or multiple attached donwloadable files from the
+# server, instead of downloading the default release/tag source code
+def select_link_function(self, tag):
+	link = ""
+
+	# -- Default, universal case (and is the only option for GitLab/Bitbucket)
+	link = tag["zipball_url"]
+
+	# -- Example: select the first (or only) asset instead source code --
+	#if "assets" in tag and "browser_download_url" in tag["assets"][0]:
+	#	link = tag["assets"][0]["browser_download_url"]
+
+	# -- Example: select asset based on OS, where multiple builds exist --
+	# # not tested/no error checking, modify to fit your own needs!
+	# # assume each release has three attached builds:
+	# #		release_windows.zip, release_OSX.zip, release_linux.zip
+	# # This also would logically not be used with "branches" enabled
+	# if platform.system() == "Darwin": # ie OSX
+	#	link = [asset for asset in tag["assets"] if 'OSX' in asset][0]
+	# elif platform.system() == "Windows":
+	#	link = [asset for asset in tag["assets"] if 'windows' in asset][0]
+	# elif platform.system() == "Linux":
+	#	link = [asset for asset in tag["assets"] if 'linux' in asset][0]
+
+	return link
+
+
+# -----------------------------------------------------------------------------
+# Register, should be run in the register module itself
+# -----------------------------------------------------------------------------
+
+
+# registering the operators in this module
+def register(bl_info):
+
+	# See output to verify this register function is working properly
+	# print("Running updater reg")
+
+	# safer failure in case of issue loading module
+	if updater.error != None:
+		print("Exiting updater registration, error return")
+		return
+
+	# confirm your updater "engine" (Github is default if not specified)
+	updater.engine = "Github"
+	# updater.engine = "GitLab"
+	# updater.engine = "Bitbucket"
+
+	# If using private repository, indicate the token here
+	# Must be set after assigning the engine.
+	# **WARNING** Depending on the engine, this token can act like a password!!
+	# Only provide a token if the project is *non-public*, see readme for
+	# other considerations and suggestions from a security standpoint
+	updater.private_token = None # "tokenstring"
+
+	# choose your own username, must match website (not needed for GitLab)
+	updater.user = "nutti"
+
+	# choose your own repository, must match git name
+	updater.repo = "Magic-UV"
+
+	#updater.addon = # define at top of module, MUST be done first
+
+	# Website for manual addon download, optional but recommended to set
+	updater.website = "https://github.com/nutti/Magic-UV"
+
+	# Addon subfolder path
+	# "sample/path/to/addon"
+	# default is "" or None, meaning root
+	updater.subfolder_path = "uv_magic_uv"
+
+	# used to check/compare versions
+	updater.current_version = bl_info["version"]
+
+	# Optional, to hard-set update frequency, use this here - however,
+	# this demo has this set via UI properties.
+	# updater.set_check_interval(
+	# 		enable=False,months=0,days=0,hours=0,minutes=2)
+
+	# Optional, consider turning off for production or allow as an option
+	# This will print out additional debugging info to the console
+	updater.verbose = False # make False for production default
+
+	# Optional, customize where the addon updater processing subfolder is,
+	# essentially a staging folder used by the updater on its own
+	# Needs to be within the same folder as the addon itself
+	# Need to supply a full, absolute path to folder
+	# updater.updater_path = # set path of updater folder, by default:
+	#			/addons/{__package__}/{__package__}_updater
+
+	# auto create a backup of the addon when installing other versions
+	updater.backup_current = True # True by default
+
+	# Sample ignore patterns for when creating backup of current during update
+	updater.backup_ignore_patterns = ["__pycache__"]
+	# Alternate example patterns
+	# updater.backup_ignore_patterns = [".git", "__pycache__", "*.bat", ".gitignore", "*.exe"]
+
+	# Patterns for files to actively overwrite if found in new update
+	# file and are also found in the currently installed addon. Note that
+
+	# by default (ie if set to []), updates are installed in the same way as blender:
+	# .py files are replaced, but other file types (e.g. json, txt, blend)
+	# will NOT be overwritten if already present in current install. Thus
+	# if you want to automatically update resources/non py files, add them
+	# as a part of the pattern list below so they will always be overwritten by an
+	# update. If a pattern file is not found in new update, no action is taken
+	# This does NOT detele anything, only defines what is allowed to be overwritten
+	updater.overwrite_patterns = ["*.png","*.jpg","README.md","LICENSE.txt"]
+	# updater.overwrite_patterns = []
+	# other examples:
+	# ["*"] means ALL files/folders will be overwritten by update, was the behavior pre updater v1.0.4
+	# [] or ["*.py","*.pyc"] matches default blender behavior, ie same effect if user installs update manually without deleting the existing addon first
+	#    e.g. if existing install and update both have a resource.blend file, the existing installed one will remain
+	# ["some.py"] means if some.py is found in addon update, it will overwrite any existing some.py in current addon install, if any
+	# ["*.json"] means all json files found in addon update will overwrite those of same name in current install
+	# ["*.png","README.md","LICENSE.txt"] means the readme, license, and all pngs will be overwritten by update
+
+	# Patterns for files to actively remove prior to running update
+	# Useful if wanting to remove old code due to changes in filenames
+	# that otherwise would accumulate. Note: this runs after taking
+	# a backup (if enabled) but before placing in new update. If the same
+	# file name removed exists in the update, then it acts as if pattern
+	# is placed in the overwrite_patterns property. Note this is effectively
+	# ignored if clean=True in the run_update method
+	updater.remove_pre_update_patterns = ["*.py", "*.pyc"]
+	# Note setting ["*"] here is equivalent to always running updates with
+	# clean = True in the run_update method, ie the equivalent of a fresh,
+	# new install. This would also delete any resources or user-made/modified
+	# files setting ["__pycache__"] ensures the pycache folder is always removed
+	# The configuration of ["*.py","*.pyc"] is a safe option as this
+	# will ensure no old python files/caches remain in event different addon
+	# versions have different filenames or structures
+
+	# Allow branches like 'master' as an option to update to, regardless
+	# of release or version.
+	# Default behavior: releases will still be used for auto check (popup),
+	# but the user has the option from user preferences to directly
+	# update to the master branch or any other branches specified using
+	# the "install {branch}/older version" operator.
+	updater.include_branches = True
+
+	# (GitHub only) This options allows the user to use releases over tags for data,
+	# which enables pulling down release logs/notes, as well as specify installs from
+	# release-attached zips (instead of just the auto-packaged code generated with
+	# a release/tag). Setting has no impact on BitBucket or GitLab repos
+	updater.use_releases = False
+	# note: Releases always have a tag, but a tag may not always be a release
+	# Therefore, setting True above will filter out any non-annoted tags
+	# note 2: Using this option will also display the release name instead of
+	# just the tag name, bear this in mind given the skip_tag_function filtering above
+
+	# if using "include_branches",
+	# updater.include_branch_list defaults to ['master'] branch if set to none
+	# example targeting another multiple branches allowed to pull from
+	# updater.include_branch_list = ['master', 'dev'] # example with two branches
+	updater.include_branch_list = ['master', 'develop']  # None is the equivalent to setting ['master']
+
+	# Only allow manual install, thus prompting the user to open
+	# the addon's web page to download, specifically: updater.website
+	# Useful if only wanting to get notification of updates but not
+	# directly install.
+	updater.manual_only = False
+
+	# Used for development only, "pretend" to install an update to test
+	# reloading conditions
+	updater.fake_install = False # Set to true to test callback/reloading
+
+	# Show popups, ie if auto-check for update is enabled or a previous
+	# check for update in user preferences found a new version, show a popup
+	# (at most once per blender session, and it provides an option to ignore
+	# for future sessions); default behavior is set to True
+	updater.showpopups = True
+	# note: if set to false, there will still be an "update ready" box drawn
+	# using the `update_notice_box_ui` panel function.
+
+	# Override with a custom function on what tags
+	# to skip showing for updater; see code for function above.
+	# Set the min and max versions allowed to install.
+	# Optional, default None
+	# min install (>=) will install this and higher
+	updater.version_min_update = (5,2,0)
+	# updater.version_min_update = None  # if not wanting to define a min
+
+	# max install (<) will install strictly anything lower
+	# updater.version_max_update = (9,9,9)
+	updater.version_max_update = None  # if not wanting to define a max
+
+	# Function defined above, customize as appropriate per repository
+	updater.skip_tag = skip_tag_function # min and max used in this function
+
+	# Function defined above, customize as appropriate per repository; not required
+	updater.select_link = select_link_function
+
+	# The register line items for all operators/panels
+	# If using bpy.utils.register_module(__name__) to register elsewhere
+	# in the addon, delete these lines (also from unregister)
+	bpy.utils.register_class(addon_updater_install_popup)
+	bpy.utils.register_class(addon_updater_check_now)
+	bpy.utils.register_class(addon_updater_update_now)
+	bpy.utils.register_class(addon_updater_update_target)
+	bpy.utils.register_class(addon_updater_install_manually)
+	bpy.utils.register_class(addon_updater_updated_successful)
+	bpy.utils.register_class(addon_updater_restore_backup)
+	bpy.utils.register_class(addon_updater_ignore)
+	bpy.utils.register_class(addon_updater_end_background)
+
+	# special situation: we just updated the addon, show a popup
+	# to tell the user it worked
+	# should be enclosed in try/catch in case other issues arise
+	showReloadPopup()
+
+
+def unregister():
+	bpy.utils.unregister_class(addon_updater_install_popup)
+	bpy.utils.unregister_class(addon_updater_check_now)
+	bpy.utils.unregister_class(addon_updater_update_now)
+	bpy.utils.unregister_class(addon_updater_update_target)
+	bpy.utils.unregister_class(addon_updater_install_manually)
+	bpy.utils.unregister_class(addon_updater_updated_successful)
+	bpy.utils.unregister_class(addon_updater_restore_backup)
+	bpy.utils.unregister_class(addon_updater_ignore)
+	bpy.utils.unregister_class(addon_updater_end_background)
+
+	# clear global vars since they may persist if not restarting blender
+	updater.clear_state() # clear internal vars, avoids reloading oddities
+
+	global ran_autocheck_install_popup
+	ran_autocheck_install_popup = False
+
+	global ran_update_sucess_popup
+	ran_update_sucess_popup = False
+
+	global ran_background_check
+	ran_background_check = False
diff --git a/uv_magic_uv/common.py b/uv_magic_uv/common.py
index 475efd59b7859ec51ce08351457517f984bae849..bad88167743bedafa944cff34b2c94ffdfaa808c 100644
--- a/uv_magic_uv/common.py
+++ b/uv_magic_uv/common.py
@@ -20,19 +20,71 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 from collections import defaultdict
 from pprint import pprint
 from math import fabs, sqrt
+import os
 
 import bpy
 from mathutils import Vector
 import bmesh
 
 
-DEBUG = False
+__all__ = [
+    'is_console_mode',
+    'is_debug_mode',
+    'enable_debugg_mode',
+    'disable_debug_mode',
+    'debug_print',
+    'check_version',
+    'redraw_all_areas',
+    'get_space',
+    'mouse_on_region',
+    'mouse_on_area',
+    'mouse_on_regions',
+    'create_bmesh',
+    'create_new_uv_map',
+    'get_island_info',
+    'get_island_info_from_bmesh',
+    'get_island_info_from_faces',
+    'get_uvimg_editor_board_size',
+    'calc_polygon_2d_area',
+    'calc_polygon_3d_area',
+    'measure_mesh_area',
+    'measure_uv_area',
+    'diff_point_to_segment',
+    'get_loop_sequences',
+    'get_overlapped_uv_info',
+    'get_flipped_uv_info',
+]
+
+
+__DEBUG_MODE = True
+
+
+def is_console_mode():
+    if "MUV_CONSOLE_MODE" not in os.environ:
+        return False
+    return os.environ["MUV_CONSOLE_MODE"] == "True"
+
+
+def is_debug_mode():
+    return __DEBUG_MODE
+
+
+def enable_debugg_mode():
+    # pylint: disable=W0603
+    global __DEBUG_MODE
+    __DEBUG_MODE = True
+
+
+def disable_debug_mode():
+    # pylint: disable=W0603
+    global __DEBUG_MODE
+    __DEBUG_MODE = False
 
 
 def debug_print(*s):
@@ -40,7 +92,7 @@ def debug_print(*s):
     Print message to console in debugging mode
     """
 
-    if DEBUG:
+    if is_debug_mode():
         pprint(s)
 
 
@@ -91,6 +143,71 @@ def get_space(area_type, region_type, space_type):
     return (area, region, space)
 
 
+def mouse_on_region(event, area_type, region_type):
+    pos = Vector((event.mouse_x, event.mouse_y))
+
+    _, region, _ = get_space(area_type, region_type, "")
+    if region is None:
+        return False
+
+    if (pos.x > region.x) and (pos.x < region.x + region.width) and \
+       (pos.y > region.y) and (pos.y < region.y + region.height):
+        return True
+
+    return False
+
+
+def mouse_on_area(event, area_type):
+    pos = Vector((event.mouse_x, event.mouse_y))
+
+    area, _, _ = get_space(area_type, "", "")
+    if area is None:
+        return False
+
+    if (pos.x > area.x) and (pos.x < area.x + area.width) and \
+       (pos.y > area.y) and (pos.y < area.y + area.height):
+        return True
+
+    return False
+
+
+def mouse_on_regions(event, area_type, regions):
+    if not mouse_on_area(event, area_type):
+        return False
+
+    for region in regions:
+        result = mouse_on_region(event, area_type, region)
+        if result:
+            return True
+
+    return False
+
+
+def create_bmesh(obj):
+    bm = bmesh.from_edit_mesh(obj.data)
+    if check_version(2, 73, 0) >= 0:
+        bm.faces.ensure_lookup_table()
+
+    return bm
+
+
+def create_new_uv_map(obj, name=None):
+    uv_maps_old = {l.name for l in obj.data.uv_layers}
+    bpy.ops.mesh.uv_texture_add()
+    uv_maps_new = {l.name for l in obj.data.uv_layers}
+    diff = uv_maps_new - uv_maps_old
+
+    if not list(diff):
+        return None     # no more UV maps can not be created
+
+    # rename UV map
+    new = obj.data.uv_layers[list(diff)[0]]
+    if name:
+        new.name = name
+
+    return new
+
+
 def __get_island_info(uv_layer, islands):
     """
     get information about each island
@@ -273,7 +390,7 @@ def measure_mesh_area(obj):
     return mesh_area
 
 
-def measure_uv_area(obj):
+def measure_uv_area(obj, tex_size=None):
     bm = bmesh.from_edit_mesh(obj.data)
     if check_version(2, 73, 0) >= 0:
         bm.verts.ensure_lookup_table()
@@ -296,22 +413,38 @@ def measure_uv_area(obj):
         uvs = [l[uv_layer].uv for l in f.loops]
         f_uv_area = calc_polygon_2d_area(uvs)
 
-        if not tex_layer:
-            return None
-        img = f[tex_layer].image
-        # not found, try to search from node
+        # user specified
+        if tex_size:
+            uv_area = uv_area + f_uv_area * tex_size[0] * tex_size[1]
+            continue
+
+        # try to find from texture_layer
+        img = None
+        if tex_layer:
+            img = f[tex_layer].image
+
+        # not found, then try to search from node
         if not img:
             for mat in obj.material_slots:
+                if not mat.material.node_tree:
+                    continue
                 for node in mat.material.node_tree.nodes:
                     tex_node_types = [
                         'TEX_ENVIRONMENT',
                         'TEX_IMAGE',
                     ]
-                    if (node.type in tex_node_types) and node.image:
-                        img = node.image
+                    if node.type not in tex_node_types:
+                        continue
+                    if not node.image:
+                        continue
+                    img = node.image
+
+        # can not find from node, so we can not get texture size
         if not img:
             return None
-        uv_area = uv_area + f_uv_area * img.size[0] * img.size[1]
+
+        img_size = img.size
+        uv_area = uv_area + f_uv_area * img_size[0] * img_size[1]
 
     return uv_area
 
@@ -602,3 +735,380 @@ def get_loop_sequences(bm, uv_layer, closed=False):
         return None, err
 
     return loop_seqs, ""
+
+
+def __is_segment_intersect(start1, end1, start2, end2):
+    seg1 = end1 - start1
+    seg2 = end2 - start2
+
+    a1 = -seg1.y
+    b1 = seg1.x
+    d1 = -(a1 * start1.x + b1 * start1.y)
+
+    a2 = -seg2.y
+    b2 = seg2.x
+    d2 = -(a2 * start2.x + b2 * start2.y)
+
+    seg1_line2_start = a2 * start1.x + b2 * start1.y + d2
+    seg1_line2_end = a2 * end1.x + b2 * end1.y + d2
+
+    seg2_line1_start = a1 * start2.x + b1 * start2.y + d1
+    seg2_line1_end = a1 * end2.x + b1 * end2.y + d1
+
+    if (seg1_line2_start * seg1_line2_end >= 0) or \
+            (seg2_line1_start * seg2_line1_end >= 0):
+        return False, None
+
+    u = seg1_line2_start / (seg1_line2_start - seg1_line2_end)
+    out = start1 + u * seg1
+
+    return True, out
+
+
+class RingBuffer:
+    def __init__(self, arr):
+        self.__buffer = arr.copy()
+        self.__pointer = 0
+
+    def __repr__(self):
+        return repr(self.__buffer)
+
+    def __len__(self):
+        return len(self.__buffer)
+
+    def insert(self, val, offset=0):
+        self.__buffer.insert(self.__pointer + offset, val)
+
+    def head(self):
+        return self.__buffer[0]
+
+    def tail(self):
+        return self.__buffer[-1]
+
+    def get(self, offset=0):
+        size = len(self.__buffer)
+        val = self.__buffer[(self.__pointer + offset) % size]
+        return val
+
+    def next(self):
+        size = len(self.__buffer)
+        self.__pointer = (self.__pointer + 1) % size
+
+    def reset(self):
+        self.__pointer = 0
+
+    def find(self, obj):
+        try:
+            idx = self.__buffer.index(obj)
+        except ValueError:
+            return None
+        return self.__buffer[idx]
+
+    def find_and_next(self, obj):
+        size = len(self.__buffer)
+        idx = self.__buffer.index(obj)
+        self.__pointer = (idx + 1) % size
+
+    def find_and_set(self, obj):
+        idx = self.__buffer.index(obj)
+        self.__pointer = idx
+
+    def as_list(self):
+        return self.__buffer.copy()
+
+    def reverse(self):
+        self.__buffer.reverse()
+        self.reset()
+
+
+# clip: reference polygon
+# subject: tested polygon
+def __do_weiler_atherton_cliping(clip, subject, uv_layer, mode):
+
+    clip_uvs = RingBuffer([l[uv_layer].uv.copy() for l in clip.loops])
+    if __is_polygon_flipped(clip_uvs):
+        clip_uvs.reverse()
+    subject_uvs = RingBuffer([l[uv_layer].uv.copy() for l in subject.loops])
+    if __is_polygon_flipped(subject_uvs):
+        subject_uvs.reverse()
+
+    debug_print("===== Clip UV List =====")
+    debug_print(clip_uvs)
+    debug_print("===== Subject UV List =====")
+    debug_print(subject_uvs)
+
+    # check if clip and subject is overlapped completely
+    if __is_polygon_same(clip_uvs, subject_uvs):
+        polygons = [subject_uvs.as_list()]
+        debug_print("===== Polygons Overlapped Completely =====")
+        debug_print(polygons)
+        return True, polygons
+
+    # check if subject is in clip
+    if __is_points_in_polygon(subject_uvs, clip_uvs):
+        polygons = [subject_uvs.as_list()]
+        return True, polygons
+
+    # check if clip is in subject
+    if __is_points_in_polygon(clip_uvs, subject_uvs):
+        polygons = [subject_uvs.as_list()]
+        return True, polygons
+
+    # check if clip and subject is overlapped partially
+    intersections = []
+    while True:
+        subject_uvs.reset()
+        while True:
+            uv_start1 = clip_uvs.get()
+            uv_end1 = clip_uvs.get(1)
+            uv_start2 = subject_uvs.get()
+            uv_end2 = subject_uvs.get(1)
+            intersected, point = __is_segment_intersect(uv_start1, uv_end1,
+                                                        uv_start2, uv_end2)
+            if intersected:
+                clip_uvs.insert(point, 1)
+                subject_uvs.insert(point, 1)
+                intersections.append([point,
+                                      [clip_uvs.get(), clip_uvs.get(1)]])
+            subject_uvs.next()
+            if subject_uvs.get() == subject_uvs.head():
+                break
+        clip_uvs.next()
+        if clip_uvs.get() == clip_uvs.head():
+            break
+
+    debug_print("===== Intersection List =====")
+    debug_print(intersections)
+
+    # no intersection, so subject and clip is not overlapped
+    if not intersections:
+        return False, None
+
+    def get_intersection_pair(intersects, key):
+        for sect in intersects:
+            if sect[0] == key:
+                return sect[1]
+
+        return None
+
+    # make enter/exit pair
+    subject_uvs.reset()
+    subject_entering = []
+    subject_exiting = []
+    clip_entering = []
+    clip_exiting = []
+    intersect_uv_list = []
+    while True:
+        pair = get_intersection_pair(intersections, subject_uvs.get())
+        if pair:
+            sub = subject_uvs.get(1) - subject_uvs.get(-1)
+            inter = pair[1] - pair[0]
+            cross = sub.x * inter.y - inter.x * sub.y
+            if cross < 0:
+                subject_entering.append(subject_uvs.get())
+                clip_exiting.append(subject_uvs.get())
+            else:
+                subject_exiting.append(subject_uvs.get())
+                clip_entering.append(subject_uvs.get())
+            intersect_uv_list.append(subject_uvs.get())
+
+        subject_uvs.next()
+        if subject_uvs.get() == subject_uvs.head():
+            break
+
+    debug_print("===== Enter List =====")
+    debug_print(clip_entering)
+    debug_print(subject_entering)
+    debug_print("===== Exit List =====")
+    debug_print(clip_exiting)
+    debug_print(subject_exiting)
+
+    # for now, can't handle the situation when fulfill all below conditions
+    #        * two faces have common edge
+    #        * each face is intersected
+    #        * Show Mode is "Part"
+    #       so for now, ignore this situation
+    if len(subject_entering) != len(subject_exiting):
+        if mode == 'FACE':
+            polygons = [subject_uvs.as_list()]
+            return True, polygons
+        return False, None
+
+    def traverse(current_list, entering, exiting, p, current, other_list):
+        result = current_list.find(current)
+        if not result:
+            return None
+        if result != current:
+            print("Internal Error")
+            return None
+
+        # enter
+        if entering.count(current) >= 1:
+            entering.remove(current)
+
+        current_list.find_and_next(current)
+        current = current_list.get()
+
+        while exiting.count(current) == 0:
+            p.append(current.copy())
+            current_list.find_and_next(current)
+            current = current_list.get()
+
+        # exit
+        p.append(current.copy())
+        exiting.remove(current)
+
+        other_list.find_and_set(current)
+        return other_list.get()
+
+    # Traverse
+    polygons = []
+    current_uv_list = subject_uvs
+    other_uv_list = clip_uvs
+    current_entering = subject_entering
+    current_exiting = subject_exiting
+
+    poly = []
+    current_uv = current_entering[0]
+
+    while True:
+        current_uv = traverse(current_uv_list, current_entering,
+                              current_exiting, poly, current_uv, other_uv_list)
+
+        if current_uv_list == subject_uvs:
+            current_uv_list = clip_uvs
+            other_uv_list = subject_uvs
+            current_entering = clip_entering
+            current_exiting = clip_exiting
+            debug_print("-- Next: Clip --")
+        else:
+            current_uv_list = subject_uvs
+            other_uv_list = clip_uvs
+            current_entering = subject_entering
+            current_exiting = subject_exiting
+            debug_print("-- Next: Subject --")
+
+        debug_print(clip_entering)
+        debug_print(clip_exiting)
+        debug_print(subject_entering)
+        debug_print(subject_exiting)
+
+        if not clip_entering and not clip_exiting \
+                and not subject_entering and not subject_exiting:
+            break
+
+    polygons.append(poly)
+
+    debug_print("===== Polygons Overlapped Partially =====")
+    debug_print(polygons)
+
+    return True, polygons
+
+
+def __is_polygon_flipped(points):
+    area = 0.0
+    for i in range(len(points)):
+        uv1 = points.get(i)
+        uv2 = points.get(i + 1)
+        a = uv1.x * uv2.y - uv1.y * uv2.x
+        area = area + a
+    if area < 0:
+        # clock-wise
+        return True
+    return False
+
+
+def __is_point_in_polygon(point, subject_points):
+    count = 0
+    for i in range(len(subject_points)):
+        uv_start1 = subject_points.get(i)
+        uv_end1 = subject_points.get(i + 1)
+        uv_start2 = point
+        uv_end2 = Vector((1000000.0, point.y))
+        intersected, _ = __is_segment_intersect(uv_start1, uv_end1,
+                                                uv_start2, uv_end2)
+        if intersected:
+            count = count + 1
+
+    return count % 2
+
+
+def __is_points_in_polygon(points, subject_points):
+    for i in range(len(points)):
+        internal = __is_point_in_polygon(points.get(i), subject_points)
+        if not internal:
+            return False
+
+    return True
+
+
+def get_overlapped_uv_info(bm, faces, uv_layer, mode):
+    # at first, check island overlapped
+    isl = get_island_info_from_faces(bm, faces, uv_layer)
+    overlapped_isl_pairs = []
+    for i, i1 in enumerate(isl):
+        for i2 in isl[i + 1:]:
+            if (i1["max"].x < i2["min"].x) or (i2["max"].x < i1["min"].x) or \
+               (i1["max"].y < i2["min"].y) or (i2["max"].y < i1["min"].y):
+                continue
+            overlapped_isl_pairs.append([i1, i2])
+
+    # next, check polygon overlapped
+    overlapped_uvs = []
+    for oip in overlapped_isl_pairs:
+        for clip in oip[0]["faces"]:
+            f_clip = clip["face"]
+            for subject in oip[1]["faces"]:
+                f_subject = subject["face"]
+
+                # fast operation, apply bounding box algorithm
+                if (clip["max_uv"].x < subject["min_uv"].x) or \
+                   (subject["max_uv"].x < clip["min_uv"].x) or \
+                   (clip["max_uv"].y < subject["min_uv"].y) or \
+                   (subject["max_uv"].y < clip["min_uv"].y):
+                    continue
+
+                # slow operation, apply Weiler-Atherton cliping algorithm
+                result, polygons = __do_weiler_atherton_cliping(f_clip,
+                                                                f_subject,
+                                                                uv_layer, mode)
+                if result:
+                    subject_uvs = [l[uv_layer].uv.copy()
+                                   for l in f_subject.loops]
+                    overlapped_uvs.append({"clip_face": f_clip,
+                                           "subject_face": f_subject,
+                                           "subject_uvs": subject_uvs,
+                                           "polygons": polygons})
+
+    return overlapped_uvs
+
+
+def get_flipped_uv_info(faces, uv_layer):
+    flipped_uvs = []
+    for f in faces:
+        polygon = RingBuffer([l[uv_layer].uv.copy() for l in f.loops])
+        if __is_polygon_flipped(polygon):
+            uvs = [l[uv_layer].uv.copy() for l in f.loops]
+            flipped_uvs.append({"face": f, "uvs": uvs,
+                                "polygons": [polygon.as_list()]})
+
+    return flipped_uvs
+
+
+def __is_polygon_same(points1, points2):
+    if len(points1) != len(points2):
+        return False
+
+    pts1 = points1.as_list()
+    pts2 = points2.as_list()
+
+    for p1 in pts1:
+        for p2 in pts2:
+            diff = p2 - p1
+            if diff.length < 0.0000001:
+                pts2.remove(p2)
+                break
+        else:
+            return False
+
+    return True
diff --git a/uv_magic_uv/impl/__init__.py b/uv_magic_uv/impl/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
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 0000000000000000000000000000000000000000..ed44637b31b1b054010754a477e0f98bf5416662
--- /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 0000000000000000000000000000000000000000..f14a70d63c9c81fedc297ee0d107c24b9d708548
--- /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 0000000000000000000000000000000000000000..f74bc256a6ac39c2958fc3ff50df3ae3299ce9ec
--- /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 0000000000000000000000000000000000000000..e79fbc2ce2d7c97ab5c65532e96133e19536bee6
--- /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 0000000000000000000000000000000000000000..ce507fbab165c8ef75fc80e5a554a7344e62650c
--- /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 0000000000000000000000000000000000000000..adc973527f9d6dcd1a49c8a81150e8206917cc18
--- /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 0000000000000000000000000000000000000000..e815f54fcc95ef89959d2a66aaa5e40ade1f81fa
--- /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))
diff --git a/uv_magic_uv/legacy/__init__.py b/uv_magic_uv/legacy/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..794d02bc0c0c9f380584bc1f290d13395ed65e1b
--- /dev/null
+++ b/uv_magic_uv/legacy/__init__.py
@@ -0,0 +1,38 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+if "bpy" in locals():
+    import importlib
+    importlib.reload(op)
+    importlib.reload(ui)
+    importlib.reload(properites)
+    importlib.reload(preferences)
+else:
+    from . import op
+    from . import ui
+    from . import properites
+    from . import preferences
+
+import bpy
diff --git a/uv_magic_uv/legacy/op/__init__.py b/uv_magic_uv/legacy/op/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9535b76d094b148741f5563ad34f356f810cad5a
--- /dev/null
+++ b/uv_magic_uv/legacy/op/__init__.py
@@ -0,0 +1,74 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+if "bpy" in locals():
+    import importlib
+    importlib.reload(align_uv)
+    importlib.reload(align_uv_cursor)
+    importlib.reload(copy_paste_uv)
+    importlib.reload(copy_paste_uv_object)
+    importlib.reload(copy_paste_uv_uvedit)
+    importlib.reload(flip_rotate_uv)
+    importlib.reload(mirror_uv)
+    importlib.reload(move_uv)
+    importlib.reload(pack_uv)
+    importlib.reload(preserve_uv_aspect)
+    importlib.reload(select_uv)
+    importlib.reload(smooth_uv)
+    importlib.reload(texture_lock)
+    importlib.reload(texture_projection)
+    importlib.reload(texture_wrap)
+    importlib.reload(transfer_uv)
+    importlib.reload(unwrap_constraint)
+    importlib.reload(uv_bounding_box)
+    importlib.reload(uv_inspection)
+    importlib.reload(uv_sculpt)
+    importlib.reload(uvw)
+    importlib.reload(world_scale_uv)
+else:
+    from . import align_uv
+    from . import align_uv_cursor
+    from . import copy_paste_uv
+    from . import copy_paste_uv_object
+    from . import copy_paste_uv_uvedit
+    from . import flip_rotate_uv
+    from . import mirror_uv
+    from . import move_uv
+    from . import pack_uv
+    from . import preserve_uv_aspect
+    from . import select_uv
+    from . import smooth_uv
+    from . import texture_lock
+    from . import texture_projection
+    from . import texture_wrap
+    from . import transfer_uv
+    from . import unwrap_constraint
+    from . import uv_bounding_box
+    from . import uv_inspection
+    from . import uv_sculpt
+    from . import uvw
+    from . import world_scale_uv
+
+import bpy
diff --git a/uv_magic_uv/op/align_uv.py b/uv_magic_uv/legacy/op/align_uv.py
similarity index 69%
rename from uv_magic_uv/op/align_uv.py
rename to uv_magic_uv/legacy/op/align_uv.py
index dcfb57c3fb4f82703709267d63c6def9373a75f9..9d0ff5f40b9764e389594801abeb6b69c457bd8f 100644
--- a/uv_magic_uv/op/align_uv.py
+++ b/uv_magic_uv/legacy/op/align_uv.py
@@ -20,8 +20,8 @@
 
 __author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import math
 from math import atan2, tan, sin, cos
@@ -29,9 +29,104 @@ from math import atan2, tan, sin, cos
 import bpy
 import bmesh
 from mathutils import Vector
-from bpy.props import EnumProperty, BoolProperty
+from bpy.props import EnumProperty, BoolProperty, FloatProperty
 
-from .. import common
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_AlignUV_Circle',
+    'MUV_OT_AlignUV_Straighten',
+    'MUV_OT_AlignUV_Axis',
+]
+
+
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "align_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_align_uv_enabled = BoolProperty(
+            name="Align UV Enabled",
+            description="Align UV is enabled",
+            default=False
+        )
+        scene.muv_align_uv_transmission = BoolProperty(
+            name="Transmission",
+            description="Align linked UVs",
+            default=False
+        )
+        scene.muv_align_uv_select = BoolProperty(
+            name="Select",
+            description="Select UVs which are aligned",
+            default=False
+        )
+        scene.muv_align_uv_vertical = BoolProperty(
+            name="Vert-Infl (Vertical)",
+            description="Align vertical direction influenced "
+                        "by mesh vertex proportion",
+            default=False
+        )
+        scene.muv_align_uv_horizontal = BoolProperty(
+            name="Vert-Infl (Horizontal)",
+            description="Align horizontal direction influenced "
+                        "by mesh vertex proportion",
+            default=False
+        )
+        scene.muv_align_uv_mesh_infl = FloatProperty(
+            name="Mesh Influence",
+            description="Influence rate of mesh vertex",
+            min=0.0,
+            max=1.0,
+            default=0.0
+        )
+        scene.muv_align_uv_location = EnumProperty(
+            name="Location",
+            description="Align location",
+            items=[
+                ('LEFT_TOP', "Left/Top", "Align to Left or Top"),
+                ('MIDDLE', "Middle", "Align to middle"),
+                ('RIGHT_BOTTOM', "Right/Bottom", "Align to Right or Bottom")
+            ],
+            default='MIDDLE'
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_align_uv_enabled
+        del scene.muv_align_uv_transmission
+        del scene.muv_align_uv_select
+        del scene.muv_align_uv_vertical
+        del scene.muv_align_uv_horizontal
+        del scene.muv_align_uv_mesh_infl
+        del scene.muv_align_uv_location
 
 
 # get sum vertex length of loop sequences
@@ -86,10 +181,11 @@ def calc_v_on_circle(v, center, radius):
     return new_v
 
 
-class MUV_AUVCircle(bpy.types.Operator):
+@BlClassRegistry(legacy=True)
+class MUV_OT_AlignUV_Circle(bpy.types.Operator):
 
-    bl_idname = "uv.muv_auv_circle"
-    bl_label = "Circle"
+    bl_idname = "uv.muv_align_uv_operator_circle"
+    bl_label = "Align UV (Circle)"
     bl_description = "Align UV coordinates to Circle"
     bl_options = {'REGISTER', 'UNDO'}
 
@@ -106,7 +202,10 @@ class MUV_AUVCircle(bpy.types.Operator):
 
     @classmethod
     def poll(cls, context):
-        return context.mode == 'EDIT_MESH'
+        # 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):
         obj = context.active_object
@@ -167,66 +266,164 @@ class MUV_AUVCircle(bpy.types.Operator):
         return {'FINISHED'}
 
 
+# get accumulate vertex lengths of loop sequences
+def get_loop_vert_accum_len(loops):
+    accum_lengths = [0.0]
+    length = 0
+    for l1, l2 in zip(loops[:-1], loops[1:]):
+        diff = l2.vert.co - l1.vert.co
+        length = length + abs(diff.length)
+        accum_lengths.extend([length])
+
+    return accum_lengths
+
+
+# get sum uv length of loop sequences
+def get_loop_uv_accum_len(loops, uv_layer):
+    accum_lengths = [0.0]
+    length = 0
+    for l1, l2 in zip(loops[:-1], loops[1:]):
+        diff = l2[uv_layer].uv - l1[uv_layer].uv
+        length = length + abs(diff.length)
+        accum_lengths.extend([length])
+
+    return accum_lengths
+
+
 # get horizontal differential of UV influenced by mesh vertex
-def get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pair_idx):
+def get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pidx, infl):
     common.debug_print(
-        "vidx={0}, hidx={1}, pair_idx={2}".format(vidx, hidx, pair_idx))
+        "loop_seqs[hidx={0}][vidx={1}][pidx={2}]".format(hidx, vidx, pidx))
 
-    # get total vertex length
-    hloops = []
-    for s in loop_seqs:
-        hloops.extend([s[vidx][0], s[vidx][1]])
-    vert_total_hlen = get_loop_vert_len(hloops)
-    common.debug_print(vert_total_hlen)
+    base_uv = loop_seqs[0][vidx][0][uv_layer].uv.copy()
 
-    # target vertex length
+    # calculate original length
     hloops = []
-    for s in loop_seqs[:hidx]:
+    for s in loop_seqs:
         hloops.extend([s[vidx][0], s[vidx][1]])
-    for pidx, l in enumerate(loop_seqs[hidx][vidx]):
-        if pidx > pair_idx:
+    total_vlen = get_loop_vert_len(hloops)
+    accum_vlens = get_loop_vert_accum_len(hloops)
+    total_uvlen = get_loop_uv_len(hloops, uv_layer)
+    accum_uvlens = get_loop_uv_accum_len(hloops, uv_layer)
+    orig_uvs = [l[uv_layer].uv.copy() for l in hloops]
+
+    # calculate target length
+    tgt_noinfl = total_uvlen * (hidx + pidx) / len(loop_seqs)
+    tgt_infl = total_uvlen * accum_vlens[hidx * 2 + pidx] / total_vlen
+    target_length = tgt_noinfl * (1 - infl) + tgt_infl * infl
+    common.debug_print(target_length)
+    common.debug_print(accum_uvlens)
+
+    # calculate target UV
+    for i in range(len(accum_uvlens[:-1])):
+        # get line segment which UV will be placed
+        if ((accum_uvlens[i] <= target_length) and
+                (accum_uvlens[i + 1] > target_length)):
+            tgt_seg_len = target_length - accum_uvlens[i]
+            seg_len = accum_uvlens[i + 1] - accum_uvlens[i]
+            uv1 = orig_uvs[i]
+            uv2 = orig_uvs[i + 1]
+            target_uv = (uv1 - base_uv) + (uv2 - uv1) * tgt_seg_len / seg_len
             break
-        hloops.append(l)
-    vert_hlen = get_loop_vert_len(hloops)
-    common.debug_print(vert_hlen)
+        elif i == (len(accum_uvlens[:-1]) - 1):
+            if accum_uvlens[i + 1] != target_length:
+                raise Exception(
+                    "Internal Error: horizontal_target_length={}"
+                    " is not equal to {}"
+                    .format(target_length, accum_uvlens[-1]))
+            tgt_seg_len = target_length - accum_uvlens[i]
+            seg_len = accum_uvlens[i + 1] - accum_uvlens[i]
+            uv1 = orig_uvs[i]
+            uv2 = orig_uvs[i + 1]
+            target_uv = (uv1 - base_uv) + (uv2 - uv1) * tgt_seg_len / seg_len
+            break
+    else:
+        raise Exception("Internal Error: horizontal_target_length={}"
+                        " is not in range {} to {}"
+                        .format(target_length, accum_uvlens[0],
+                                accum_uvlens[-1]))
+
+    return target_uv
 
-    # get total UV length
-    # uv_all_hdiff = loop_seqs[-1][0][-1][uv_layer].uv -
-    # loop_seqs[0][0][0][uv_layer].uv
-    uv_total_hlen = loop_seqs[-1][vidx][-1][uv_layer].uv -\
-        loop_seqs[0][vidx][0][uv_layer].uv
-    common.debug_print(uv_total_hlen)
 
-    return uv_total_hlen * vert_hlen / vert_total_hlen
+# --------------------- LOOP STRUCTURE ----------------------
+#
+#  loops[hidx][vidx][pidx]
+#     hidx: horizontal index
+#     vidx: vertical index
+#     pidx: pair index
+#
+#              <----- horizontal ----->
+#
+#              (hidx, vidx, pidx) = (0, 3, 0)
+#              |      (hidx, vidx, pidx) = (1, 3, 0)
+#              v      v
+#          ^   o --- oo --- o
+#          |   |     ||     |
+# vertical |   o --- oo --- o  <- (hidx, vidx, pidx)
+#          |   o --- oo --- o          = (1, 2, 1)
+#          |   |     ||     |
+#          v   o --- oo --- o
+#              ^            ^
+#              |            (hidx, vidx, pidx) = (1, 0, 1)
+#              (hidx, vidx, pidx) = (0, 0, 0)
+#
+# -----------------------------------------------------------
 
 
 # get vertical differential of UV influenced by mesh vertex
-def get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pair_idx):
+def get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pidx, infl):
     common.debug_print(
-        "vidx={0}, hidx={1}, pair_idx={2}".format(vidx, hidx, pair_idx))
-
-    # get total vertex length
-    hloops = []
-    for s in loop_seqs[hidx]:
-        hloops.append(s[pair_idx])
-    vert_total_hlen = get_loop_vert_len(hloops)
-    common.debug_print(vert_total_hlen)
+        "loop_seqs[hidx={0}][vidx={1}][pidx={2}]".format(hidx, vidx, pidx))
 
-    # target vertex length
-    hloops = []
-    for s in loop_seqs[hidx][:vidx + 1]:
-        hloops.append(s[pair_idx])
-    vert_hlen = get_loop_vert_len(hloops)
-    common.debug_print(vert_hlen)
+    base_uv = loop_seqs[hidx][0][pidx][uv_layer].uv.copy()
 
-    # get total UV length
-    # uv_all_hdiff = loop_seqs[0][-1][pair_idx][uv_layer].uv - \
-    #                loop_seqs[0][0][pair_idx][uv_layer].uv
-    uv_total_hlen = loop_seqs[hidx][-1][pair_idx][uv_layer].uv -\
-        loop_seqs[hidx][0][pair_idx][uv_layer].uv
-    common.debug_print(uv_total_hlen)
+    # calculate original length
+    vloops = []
+    for s in loop_seqs[hidx]:
+        vloops.append(s[pidx])
+    total_vlen = get_loop_vert_len(vloops)
+    accum_vlens = get_loop_vert_accum_len(vloops)
+    total_uvlen = get_loop_uv_len(vloops, uv_layer)
+    accum_uvlens = get_loop_uv_accum_len(vloops, uv_layer)
+    orig_uvs = [l[uv_layer].uv.copy() for l in vloops]
+
+    # calculate target length
+    tgt_noinfl = total_uvlen * int((vidx + 1) / 2) / len(loop_seqs)
+    tgt_infl = total_uvlen * accum_vlens[vidx] / total_vlen
+    target_length = tgt_noinfl * (1 - infl) + tgt_infl * infl
+    common.debug_print(target_length)
+    common.debug_print(accum_uvlens)
+
+    # calculate target UV
+    for i in range(len(accum_uvlens[:-1])):
+        # get line segment which UV will be placed
+        if ((accum_uvlens[i] <= target_length) and
+                (accum_uvlens[i + 1] > target_length)):
+            tgt_seg_len = target_length - accum_uvlens[i]
+            seg_len = accum_uvlens[i + 1] - accum_uvlens[i]
+            uv1 = orig_uvs[i]
+            uv2 = orig_uvs[i + 1]
+            target_uv = (uv1 - base_uv) + (uv2 - uv1) * tgt_seg_len / seg_len
+            break
+        elif i == (len(accum_uvlens[:-1]) - 1):
+            if accum_uvlens[i + 1] != target_length:
+                raise Exception("Internal Error: horizontal_target_length={}"
+                                " is not equal to {}"
+                                .format(target_length, accum_uvlens[-1]))
+            tgt_seg_len = target_length - accum_uvlens[i]
+            seg_len = accum_uvlens[i + 1] - accum_uvlens[i]
+            uv1 = orig_uvs[i]
+            uv2 = orig_uvs[i + 1]
+            target_uv = (uv1 - base_uv) + (uv2 - uv1) * tgt_seg_len / seg_len
+            break
+    else:
+        raise Exception("Internal Error: horizontal_target_length={}"
+                        " is not in range {} to {}"
+                        .format(target_length, accum_uvlens[0],
+                                accum_uvlens[-1]))
 
-    return uv_total_hlen * vert_hlen / vert_total_hlen
+    return target_uv
 
 
 # get horizontal differential of UV no influenced
@@ -246,10 +443,11 @@ def get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx):
     return int((vidx + 1) / 2) * v_uv / (len(hseq) / 2)
 
 
-class MUV_AUVStraighten(bpy.types.Operator):
+@BlClassRegistry(legacy=True)
+class MUV_OT_AlignUV_Straighten(bpy.types.Operator):
 
-    bl_idname = "uv.muv_auv_straighten"
-    bl_label = "Straighten"
+    bl_idname = "uv.muv_align_uv_operator_straighten"
+    bl_label = "Align UV (Straighten)"
     bl_description = "Straighten UV coordinates"
     bl_options = {'REGISTER', 'UNDO'}
 
@@ -275,10 +473,20 @@ class MUV_AUVStraighten(bpy.types.Operator):
                     "by mesh vertex proportion",
         default=False
     )
+    mesh_infl = FloatProperty(
+        name="Mesh Influence",
+        description="Influence rate of mesh vertex",
+        min=0.0,
+        max=1.0,
+        default=0.0
+    )
 
     @classmethod
     def poll(cls, context):
-        return context.mode == 'EDIT_MESH'
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return is_valid_context(context)
 
     # selected and paralleled UV loop sequence will be aligned
     def __align_w_transmission(self, loop_seqs, uv_layer):
@@ -293,12 +501,14 @@ class MUV_AUVStraighten(bpy.types.Operator):
             for vidx in range(0, len(hseq), 2):
                 if self.horizontal:
                     hdiff_uvs = [
-                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
-                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+                                           self.mesh_infl),
+                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+                                           self.mesh_infl),
                         get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 0),
+                                           hidx, 0, self.mesh_infl),
                         get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 1),
+                                           hidx, 1, self.mesh_infl),
                     ]
                 else:
                     hdiff_uvs = [
@@ -309,12 +519,14 @@ class MUV_AUVStraighten(bpy.types.Operator):
                     ]
                 if self.vertical:
                     vdiff_uvs = [
-                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
-                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+                                           self.mesh_infl),
+                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+                                           self.mesh_infl),
                         get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 0),
+                                           hidx, 0, self.mesh_infl),
                         get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 1),
+                                           hidx, 1, self.mesh_infl),
                     ]
                 else:
                     vdiff_uvs = [
@@ -382,10 +594,11 @@ class MUV_AUVStraighten(bpy.types.Operator):
         return {'FINISHED'}
 
 
-class MUV_AUVAxis(bpy.types.Operator):
+@BlClassRegistry(legacy=True)
+class MUV_OT_AlignUV_Axis(bpy.types.Operator):
 
-    bl_idname = "uv.muv_auv_axis"
-    bl_label = "XY-Axis"
+    bl_idname = "uv.muv_align_uv_operator_axis"
+    bl_label = "Align UV (XY-Axis)"
     bl_description = "Align UV to XY-axis"
     bl_options = {'REGISTER', 'UNDO'}
 
@@ -421,10 +634,20 @@ class MUV_AUVAxis(bpy.types.Operator):
         ],
         default='MIDDLE'
     )
+    mesh_infl = FloatProperty(
+        name="Mesh Influence",
+        description="Influence rate of mesh vertex",
+        min=0.0,
+        max=1.0,
+        default=0.0
+    )
 
     @classmethod
     def poll(cls, context):
-        return context.mode == 'EDIT_MESH'
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return is_valid_context(context)
 
     # get min/max of UV
     def __get_uv_max_min(self, loop_seqs, uv_layer):
@@ -579,12 +802,14 @@ class MUV_AUVAxis(bpy.types.Operator):
             for vidx in range(0, len(hseq), 2):
                 if self.horizontal:
                     hdiff_uvs = [
-                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
-                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+                                           self.mesh_infl),
+                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+                                           self.mesh_infl),
                         get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 0),
+                                           hidx, 0, self.mesh_infl),
                         get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 1),
+                                           hidx, 1, self.mesh_infl),
                     ]
                     hdiff_uvs[0].y = hdiff_uvs[0].y + offset_uvs[hidx][0].y
                     hdiff_uvs[1].y = hdiff_uvs[1].y + offset_uvs[hidx][1].y
@@ -599,12 +824,14 @@ class MUV_AUVAxis(bpy.types.Operator):
                     ]
                 if self.vertical:
                     vdiff_uvs = [
-                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
-                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+                                           self.mesh_infl),
+                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+                                           self.mesh_infl),
                         get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 0),
+                                           hidx, 0, self.mesh_infl),
                         get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 1),
+                                           hidx, 1, self.mesh_infl),
                     ]
                 else:
                     vdiff_uvs = [
@@ -664,12 +891,14 @@ class MUV_AUVAxis(bpy.types.Operator):
             for vidx in range(0, len(hseq), 2):
                 if self.horizontal:
                     hdiff_uvs = [
-                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
-                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+                                           self.mesh_infl),
+                        get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+                                           self.mesh_infl),
                         get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 0),
+                                           hidx, 0, self.mesh_infl),
                         get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 1),
+                                           hidx, 1, self.mesh_infl),
                     ]
                     hdiff_uvs[0].x = hdiff_uvs[0].x + offset_uvs[hidx][0].x
                     hdiff_uvs[1].x = hdiff_uvs[1].x + offset_uvs[hidx][1].x
@@ -684,12 +913,14 @@ class MUV_AUVAxis(bpy.types.Operator):
                     ]
                 if self.vertical:
                     vdiff_uvs = [
-                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
-                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+                                           self.mesh_infl),
+                        get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+                                           self.mesh_infl),
                         get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 0),
+                                           hidx, 0, self.mesh_infl),
                         get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
-                                           hidx, 1),
+                                           hidx, 1, self.mesh_infl),
                     ]
                 else:
                     vdiff_uvs = [
diff --git a/uv_magic_uv/op/align_uv_cursor.py b/uv_magic_uv/legacy/op/align_uv_cursor.py
similarity index 59%
rename from uv_magic_uv/op/align_uv_cursor.py
rename to uv_magic_uv/legacy/op/align_uv_cursor.py
index cae1c89a55fd693b7debd2defa27a3e4d6e1d39d..ec3e703609ba9ab2180dcb15a2676ae97ebcd471 100644
--- a/uv_magic_uv/op/align_uv_cursor.py
+++ b/uv_magic_uv/legacy/op/align_uv_cursor.py
@@ -20,21 +20,117 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 from mathutils import Vector
-from bpy.props import EnumProperty
+from bpy.props import EnumProperty, BoolProperty, FloatVectorProperty
 import bmesh
 
-from .. import common
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
 
 
-class MUV_AUVCAlignOps(bpy.types.Operator):
+__all__ = [
+    'Properties',
+    'MUV_OT_AlignUVCursor',
+]
 
-    bl_idname = "uv.muv_auvc_align"
-    bl_label = "Align"
+
+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
+    for space in context.area.spaces:
+        if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
+            break
+    else:
+        return False
+
+    return True
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "align_uv_cursor"
+
+    @classmethod
+    def init_props(cls, scene):
+        def auvc_get_cursor_loc(self):
+            area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
+                                              'IMAGE_EDITOR')
+            bd_size = common.get_uvimg_editor_board_size(area)
+            loc = space.cursor_location
+            if bd_size[0] < 0.000001:
+                cx = 0.0
+            else:
+                cx = loc[0] / bd_size[0]
+            if bd_size[1] < 0.000001:
+                cy = 0.0
+            else:
+                cy = loc[1] / bd_size[1]
+            self['muv_align_uv_cursor_cursor_loc'] = Vector((cx, cy))
+            return self.get('muv_align_uv_cursor_cursor_loc', (0.0, 0.0))
+
+        def auvc_set_cursor_loc(self, value):
+            self['muv_align_uv_cursor_cursor_loc'] = value
+            area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
+                                              'IMAGE_EDITOR')
+            bd_size = common.get_uvimg_editor_board_size(area)
+            cx = bd_size[0] * value[0]
+            cy = bd_size[1] * value[1]
+            space.cursor_location = Vector((cx, cy))
+
+        scene.muv_align_uv_cursor_enabled = BoolProperty(
+            name="Align UV Cursor Enabled",
+            description="Align UV Cursor is enabled",
+            default=False
+        )
+
+        scene.muv_align_uv_cursor_cursor_loc = FloatVectorProperty(
+            name="UV Cursor Location",
+            size=2,
+            precision=4,
+            soft_min=-1.0,
+            soft_max=1.0,
+            step=1,
+            default=(0.000, 0.000),
+            get=auvc_get_cursor_loc,
+            set=auvc_set_cursor_loc
+        )
+        scene.muv_align_uv_cursor_align_method = EnumProperty(
+            name="Align Method",
+            description="Align Method",
+            default='TEXTURE',
+            items=[
+                ('TEXTURE', "Texture", "Align to texture"),
+                ('UV', "UV", "Align to UV"),
+                ('UV_SEL', "UV (Selected)", "Align to Selected UV")
+            ]
+        )
+
+        scene.muv_uv_cursor_location_enabled = BoolProperty(
+            name="UV Cursor Location Enabled",
+            description="UV Cursor Location is enabled",
+            default=False
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_align_uv_cursor_enabled
+        del scene.muv_align_uv_cursor_cursor_loc
+        del scene.muv_align_uv_cursor_align_method
+
+        del scene.muv_uv_cursor_location_enabled
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_AlignUVCursor(bpy.types.Operator):
+
+    bl_idname = "uv.muv_align_uv_cursor_operator"
+    bl_label = "Align UV Cursor"
     bl_description = "Align cursor to the center of UV island"
     bl_options = {'REGISTER', 'UNDO'}
 
@@ -65,6 +161,13 @@ class MUV_AUVCAlignOps(bpy.types.Operator):
         default='TEXTURE'
     )
 
+    @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):
         area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
                                           'IMAGE_EDITOR')
diff --git a/uv_magic_uv/legacy/op/copy_paste_uv.py b/uv_magic_uv/legacy/op/copy_paste_uv.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8aef017fb2e3c587be00cba29ea169b5775ddff
--- /dev/null
+++ b/uv_magic_uv/legacy/op/copy_paste_uv.py
@@ -0,0 +1,531 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+
+import bmesh
+import bpy.utils
+from bpy.props import (
+    StringProperty,
+    BoolProperty,
+    IntProperty,
+    EnumProperty,
+)
+
+from ...impl import copy_paste_uv_impl as impl
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+__all__ = [
+    'Properties',
+    'MUV_OT_CopyPasteUV_CopyUV',
+    'MUV_MT_CopyPasteUV_CopyUV',
+    'MUV_OT_CopyPasteUV_PasteUV',
+    'MUV_MT_CopyPasteUV_PasteUV',
+    'MUV_OT_CopyPasteUV_SelSeqCopyUV',
+    'MUV_MT_CopyPasteUV_SelSeqCopyUV',
+    'MUV_OT_CopyPasteUV_SelSeqPasteUV',
+    'MUV_MT_CopyPasteUV_SelSeqPasteUV',
+]
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "copy_paste_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            src_info = None
+
+        scene.muv_props.copy_paste_uv = Props()
+        scene.muv_props.copy_paste_uv_selseq = Props()
+
+        scene.muv_copy_paste_uv_enabled = BoolProperty(
+            name="Copy/Paste UV Enabled",
+            description="Copy/Paste UV is enabled",
+            default=False
+        )
+        scene.muv_copy_paste_uv_copy_seams = BoolProperty(
+            name="Seams",
+            description="Copy Seams",
+            default=True
+        )
+        scene.muv_copy_paste_uv_mode = EnumProperty(
+            items=[
+                ('DEFAULT', "Default", "Default Mode"),
+                ('SEL_SEQ', "Selection Sequence", "Selection Sequence Mode")
+            ],
+            name="Copy/Paste UV Mode",
+            description="Copy/Paste UV Mode",
+            default='DEFAULT'
+        )
+        scene.muv_copy_paste_uv_strategy = EnumProperty(
+            name="Strategy",
+            description="Paste Strategy",
+            items=[
+                ('N_N', 'N:N', 'Number of faces must be equal to source'),
+                ('N_M', 'N:M', 'Number of faces must not be equal to source')
+            ],
+            default='N_M'
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_props.copy_paste_uv
+        del scene.muv_props.copy_paste_uv_selseq
+        del scene.muv_copy_paste_uv_enabled
+        del scene.muv_copy_paste_uv_copy_seams
+        del scene.muv_copy_paste_uv_mode
+        del scene.muv_copy_paste_uv_strategy
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_CopyPasteUV_CopyUV(bpy.types.Operator):
+    """
+    Operation class: Copy UV coordinate
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_operator_copy_uv"
+    bl_label = "Copy UV"
+    bl_description = "Copy UV coordinate"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    uv_map = StringProperty(default="__default", options={'HIDDEN'})
+
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return impl.is_valid_context(context)
+
+    def execute(self, context):
+        props = context.scene.muv_props.copy_paste_uv
+        obj = context.active_object
+        bm = common.create_bmesh(obj)
+
+        # get UV layer
+        uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map)
+        if not uv_layers:
+            return {'CANCELLED'}
+
+        # get selected face
+        src_info = impl.get_src_face_info(self, bm, uv_layers, True)
+        if src_info is None:
+            return {'CANCELLED'}
+        props.src_info = src_info
+
+        face_count = len(props.src_info[list(props.src_info.keys())[0]])
+        self.report({'INFO'}, "{} face(s) are copied".format(face_count))
+
+        return {'FINISHED'}
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_CopyPasteUV_CopyUV(bpy.types.Menu):
+    """
+    Menu class: Copy UV coordinate
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_menu_copy_uv"
+    bl_label = "Copy UV (Menu)"
+    bl_description = "Menu of Copy UV coordinate"
+
+    @classmethod
+    def poll(cls, context):
+        return impl.is_valid_context(context)
+
+    def draw(self, context):
+        layout = self.layout
+        # create sub menu
+        obj = context.active_object
+        bm = common.create_bmesh(obj)
+        uv_maps = bm.loops.layers.uv.keys()
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname,
+                              text="[Default]")
+        ops.uv_map = "__default"
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+
+        for m in uv_maps:
+            ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname, text=m)
+            ops.uv_map = m
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_CopyPasteUV_PasteUV(bpy.types.Operator):
+    """
+    Operation class: Paste UV coordinate
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_operator_paste_uv"
+    bl_label = "Paste UV"
+    bl_description = "Paste UV coordinate"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    uv_map = StringProperty(default="__default", options={'HIDDEN'})
+    strategy = EnumProperty(
+        name="Strategy",
+        description="Paste Strategy",
+        items=[
+            ('N_N', 'N:N', 'Number of faces must be equal to source'),
+            ('N_M', 'N:M', 'Number of faces must not be equal to source')
+        ],
+        default="N_M"
+    )
+    flip_copied_uv = BoolProperty(
+        name="Flip Copied UV",
+        description="Flip Copied UV...",
+        default=False
+    )
+    rotate_copied_uv = IntProperty(
+        default=0,
+        name="Rotate Copied UV",
+        min=0,
+        max=30
+    )
+    copy_seams = BoolProperty(
+        name="Seams",
+        description="Copy Seams",
+        default=True
+    )
+
+    @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
+        if not props.src_info:
+            return False
+        return impl.is_valid_context(context)
+
+    def execute(self, context):
+        props = context.scene.muv_props.copy_paste_uv
+        if not props.src_info:
+            self.report({'WARNING'}, "Need copy UV at first")
+            return {'CANCELLED'}
+        obj = context.active_object
+        bm = common.create_bmesh(obj)
+
+        # get UV layer
+        uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info,
+                                             self.uv_map)
+        if not uv_layers:
+            return {'CANCELLED'}
+
+        # get selected face
+        dest_info = impl.get_dest_face_info(self, bm, uv_layers,
+                                            props.src_info, self.strategy,
+                                            True)
+        if dest_info is None:
+            return {'CANCELLED'}
+
+        # paste
+        ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers,
+                            self.strategy, self.flip_copied_uv,
+                            self.rotate_copied_uv, self.copy_seams)
+        if ret:
+            return {'CANCELLED'}
+
+        face_count = len(props.src_info[list(dest_info.keys())[0]])
+        self.report({'INFO'}, "{} face(s) are pasted".format(face_count))
+
+        bmesh.update_edit_mesh(obj.data)
+        if self.copy_seams is True:
+            obj.data.show_edge_seams = True
+
+        return {'FINISHED'}
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_CopyPasteUV_PasteUV(bpy.types.Menu):
+    """
+    Menu class: Paste UV coordinate
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_menu_paste_uv"
+    bl_label = "Paste UV (Menu)"
+    bl_description = "Menu of Paste UV coordinate"
+
+    @classmethod
+    def poll(cls, context):
+        sc = context.scene
+        props = sc.muv_props.copy_paste_uv
+        if not props.src_info:
+            return False
+        return impl.is_valid_context(context)
+
+    def draw(self, context):
+        sc = context.scene
+        layout = self.layout
+        # create sub menu
+        obj = context.active_object
+        bm = common.create_bmesh(obj)
+        uv_maps = bm.loops.layers.uv.keys()
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname,
+                              text="[Default]")
+        ops.uv_map = "__default"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname,
+                              text="[New]")
+        ops.uv_map = "__new"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
+        for m in uv_maps:
+            ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname, text=m)
+            ops.uv_map = m
+            ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+            ops.strategy = sc.muv_copy_paste_uv_strategy
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_CopyPasteUV_SelSeqCopyUV(bpy.types.Operator):
+    """
+    Operation class: Copy UV coordinate by selection sequence
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_operator_selseq_copy_uv"
+    bl_label = "Copy UV (Selection Sequence)"
+    bl_description = "Copy UV data by selection sequence"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    uv_map = StringProperty(default="__default", options={'HIDDEN'})
+
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return impl.is_valid_context(context)
+
+    def execute(self, context):
+        props = context.scene.muv_props.copy_paste_uv_selseq
+        obj = context.active_object
+        bm = common.create_bmesh(obj)
+
+        # get UV layer
+        uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map)
+        if not uv_layers:
+            return {'CANCELLED'}
+
+        # get selected face
+        src_info = impl.get_select_history_src_face_info(self, bm, uv_layers)
+        if src_info is None:
+            return {'CANCELLED'}
+        props.src_info = src_info
+
+        face_count = len(props.src_info[list(props.src_info.keys())[0]])
+        self.report({'INFO'}, "{} face(s) are selected".format(face_count))
+
+        return {'FINISHED'}
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_CopyPasteUV_SelSeqCopyUV(bpy.types.Menu):
+    """
+    Menu class: Copy UV coordinate by selection sequence
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_menu_selseq_copy_uv"
+    bl_label = "Copy UV (Selection Sequence) (Menu)"
+    bl_description = "Menu of Copy UV coordinate by selection sequence"
+
+    @classmethod
+    def poll(cls, context):
+        return impl.is_valid_context(context)
+
+    def draw(self, context):
+        layout = self.layout
+        obj = context.active_object
+        bm = common.create_bmesh(obj)
+        uv_maps = bm.loops.layers.uv.keys()
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname,
+                              text="[Default]")
+        ops.uv_map = "__default"
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+
+        for m in uv_maps:
+            ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname,
+                                  text=m)
+            ops.uv_map = m
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_CopyPasteUV_SelSeqPasteUV(bpy.types.Operator):
+    """
+    Operation class: Paste UV coordinate by selection sequence
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_operator_selseq_paste_uv"
+    bl_label = "Paste UV (Selection Sequence)"
+    bl_description = "Paste UV coordinate by selection sequence"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    uv_map = StringProperty(default="__default", options={'HIDDEN'})
+    strategy = EnumProperty(
+        name="Strategy",
+        description="Paste Strategy",
+        items=[
+            ('N_N', 'N:N', 'Number of faces must be equal to source'),
+            ('N_M', 'N:M', 'Number of faces must not be equal to source')
+        ],
+        default="N_M"
+    )
+    flip_copied_uv = BoolProperty(
+        name="Flip Copied UV",
+        description="Flip Copied UV...",
+        default=False
+    )
+    rotate_copied_uv = IntProperty(
+        default=0,
+        name="Rotate Copied UV",
+        min=0,
+        max=30
+    )
+    copy_seams = BoolProperty(
+        name="Seams",
+        description="Copy Seams",
+        default=True
+    )
+
+    @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_selseq
+        if not props.src_info:
+            return False
+        return impl.is_valid_context(context)
+
+    def execute(self, context):
+        props = context.scene.muv_props.copy_paste_uv_selseq
+        if not props.src_info:
+            self.report({'WARNING'}, "Need copy UV at first")
+            return {'CANCELLED'}
+        obj = context.active_object
+        bm = common.create_bmesh(obj)
+
+        # get UV layer
+        uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info,
+                                             self.uv_map)
+        if not uv_layers:
+            return {'CANCELLED'}
+
+        # get selected face
+        dest_info = impl.get_select_history_dest_face_info(self, bm, uv_layers,
+                                                           props.src_info,
+                                                           self.strategy)
+        if dest_info is None:
+            return {'CANCELLED'}
+
+        # paste
+        ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers,
+                            self.strategy, self.flip_copied_uv,
+                            self.rotate_copied_uv, self.copy_seams)
+        if ret:
+            return {'CANCELLED'}
+
+        face_count = len(props.src_info[list(dest_info.keys())[0]])
+        self.report({'INFO'}, "{} face(s) are pasted".format(face_count))
+
+        bmesh.update_edit_mesh(obj.data)
+        if self.copy_seams is True:
+            obj.data.show_edge_seams = True
+
+        return {'FINISHED'}
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_CopyPasteUV_SelSeqPasteUV(bpy.types.Menu):
+    """
+    Menu class: Paste UV coordinate by selection sequence
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_menu_selseq_paste_uv"
+    bl_label = "Paste UV (Selection Sequence) (Menu)"
+    bl_description = "Menu of Paste UV coordinate by selection sequence"
+
+    @classmethod
+    def poll(cls, context):
+        sc = context.scene
+        props = sc.muv_props.copy_paste_uv_selseq
+        if not props.src_uvs or not props.src_pin_uvs:
+            return False
+        return impl.is_valid_context(context)
+
+    def draw(self, context):
+        sc = context.scene
+        layout = self.layout
+        # create sub menu
+        obj = context.active_object
+        bm = common.create_bmesh(obj)
+        uv_maps = bm.loops.layers.uv.keys()
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname,
+                              text="[Default]")
+        ops.uv_map = "__default"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname,
+                              text="[New]")
+        ops.uv_map = "__new"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
+        for m in uv_maps:
+            ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname,
+                                  text=m)
+            ops.uv_map = m
+            ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+            ops.strategy = sc.muv_copy_paste_uv_strategy
diff --git a/uv_magic_uv/legacy/op/copy_paste_uv_object.py b/uv_magic_uv/legacy/op/copy_paste_uv_object.py
new file mode 100644
index 0000000000000000000000000000000000000000..e09b003b91a96f5ed5fdf5bdcf7499f7f79d8ee2
--- /dev/null
+++ b/uv_magic_uv/legacy/op/copy_paste_uv_object.py
@@ -0,0 +1,298 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bmesh
+import bpy
+from bpy.props import (
+    StringProperty,
+    BoolProperty,
+)
+
+from ...impl import copy_paste_uv_impl as impl
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+__all__ = [
+    'Properties',
+    'MUV_OT_CopyPasteUVObject_CopyUV',
+    'MUV_MT_CopyPasteUVObject_CopyUV',
+    'MUV_OT_CopyPasteUVObject_PasteUV',
+    'MUV_MT_CopyPasteUVObject_PasteUV',
+]
+
+
+def is_valid_context(context):
+    obj = context.object
+
+    # only object mode is allowed to execute
+    if obj is None:
+        return False
+    if obj.type != 'MESH':
+        return False
+    if context.object.mode != 'OBJECT':
+        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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "copy_paste_uv_object"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            src_info = None
+
+        scene.muv_props.copy_paste_uv_object = Props()
+
+        scene.muv_copy_paste_uv_object_copy_seams = BoolProperty(
+            name="Seams",
+            description="Copy Seams",
+            default=True
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_props.copy_paste_uv_object
+        del scene.muv_copy_paste_uv_object_copy_seams
+
+
+def memorize_view_3d_mode(fn):
+    def __memorize_view_3d_mode(self, context):
+        mode_orig = bpy.context.object.mode
+        result = fn(self, context)
+        bpy.ops.object.mode_set(mode=mode_orig)
+        return result
+    return __memorize_view_3d_mode
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_CopyPasteUVObject_CopyUV(bpy.types.Operator):
+    """
+    Operation class: Copy UV coordinate among objects
+    """
+
+    bl_idname = "object.muv_copy_paste_uv_object_operator_copy_uv"
+    bl_label = "Copy UV (Among Objects)"
+    bl_description = "Copy UV coordinate (Among Objects)"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    uv_map = StringProperty(default="__default", options={'HIDDEN'})
+
+    @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)
+
+    @memorize_view_3d_mode
+    def execute(self, context):
+        props = context.scene.muv_props.copy_paste_uv_object
+        bpy.ops.object.mode_set(mode='EDIT')
+        obj = context.active_object
+        bm = common.create_bmesh(obj)
+
+        # get UV layer
+        uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map)
+        if not uv_layers:
+            return {'CANCELLED'}
+
+        # get selected face
+        src_info = impl.get_src_face_info(self, bm, uv_layers)
+        if src_info is None:
+            return {'CANCELLED'}
+        props.src_info = src_info
+
+        self.report({'INFO'},
+                    "{}'s UV coordinates are copied".format(obj.name))
+
+        return {'FINISHED'}
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_CopyPasteUVObject_CopyUV(bpy.types.Menu):
+    """
+    Menu class: Copy UV coordinate among objects
+    """
+
+    bl_idname = "object.muv_copy_paste_uv_object_menu_copy_uv"
+    bl_label = "Copy UV (Among Objects) (Menu)"
+    bl_description = "Menu of Copy UV coordinate (Among Objects)"
+
+    @classmethod
+    def poll(cls, context):
+        return is_valid_context(context)
+
+    def draw(self, _):
+        layout = self.layout
+        # create sub menu
+        uv_maps = bpy.context.active_object.data.uv_textures.keys()
+
+        ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname,
+                              text="[Default]")
+        ops.uv_map = "__default"
+
+        ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+
+        for m in uv_maps:
+            ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname,
+                                  text=m)
+            ops.uv_map = m
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_CopyPasteUVObject_PasteUV(bpy.types.Operator):
+    """
+    Operation class: Paste UV coordinate among objects
+    """
+
+    bl_idname = "object.muv_copy_paste_uv_object_operator_paste_uv"
+    bl_label = "Paste UV (Among Objects)"
+    bl_description = "Paste UV coordinate (Among Objects)"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    uv_map = StringProperty(default="__default", options={'HIDDEN'})
+    copy_seams = BoolProperty(
+        name="Seams",
+        description="Copy Seams",
+        default=True
+    )
+
+    @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_object
+        if not props.src_info:
+            return False
+        return is_valid_context(context)
+
+    @memorize_view_3d_mode
+    def execute(self, context):
+        props = context.scene.muv_props.copy_paste_uv_object
+        if not props.src_info:
+            self.report({'WARNING'}, "Need copy UV at first")
+            return {'CANCELLED'}
+
+        for o in bpy.data.objects:
+            if not hasattr(o.data, "uv_textures") or not o.select:
+                continue
+
+            bpy.ops.object.mode_set(mode='OBJECT')
+            bpy.context.scene.objects.active = o
+            bpy.ops.object.mode_set(mode='EDIT')
+
+            obj = context.active_object
+            bm = common.create_bmesh(obj)
+
+            # get UV layer
+            uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info,
+                                                 self.uv_map)
+            if not uv_layers:
+                return {'CANCELLED'}
+
+            # get selected face
+            dest_info = impl.get_dest_face_info(self, bm, uv_layers,
+                                                props.src_info, 'N_N')
+            if dest_info is None:
+                return {'CANCELLED'}
+
+            # paste
+            ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers,
+                                'N_N', 0, 0, self.copy_seams)
+            if ret:
+                return {'CANCELLED'}
+
+            bmesh.update_edit_mesh(obj.data)
+            if self.copy_seams is True:
+                obj.data.show_edge_seams = True
+
+            self.report(
+                {'INFO'}, "{}'s UV coordinates are pasted".format(obj.name))
+
+        return {'FINISHED'}
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_CopyPasteUVObject_PasteUV(bpy.types.Menu):
+    """
+    Menu class: Paste UV coordinate among objects
+    """
+
+    bl_idname = "object.muv_copy_paste_uv_object_menu_paste_uv"
+    bl_label = "Paste UV (Among Objects) (Menu)"
+    bl_description = "Menu of Paste UV coordinate (Among Objects)"
+
+    @classmethod
+    def poll(cls, context):
+        sc = context.scene
+        props = sc.muv_props.copy_paste_uv_object
+        if not props.src_info:
+            return False
+        return is_valid_context(context)
+
+    def draw(self, context):
+        sc = context.scene
+        layout = self.layout
+        # create sub menu
+        uv_maps = []
+        for obj in bpy.data.objects:
+            if hasattr(obj.data, "uv_textures") and obj.select:
+                uv_maps.extend(obj.data.uv_textures.keys())
+
+        ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname,
+                              text="[Default]")
+        ops.uv_map = "__default"
+        ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
+
+        ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname,
+                              text="[New]")
+        ops.uv_map = "__new"
+        ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
+
+        ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+        ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
+
+        for m in uv_maps:
+            ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname,
+                                  text=m)
+            ops.uv_map = m
+            ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
diff --git a/uv_magic_uv/legacy/op/copy_paste_uv_uvedit.py b/uv_magic_uv/legacy/op/copy_paste_uv_uvedit.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb72d42a1b83708e2608e2db810a4c89bca103c5
--- /dev/null
+++ b/uv_magic_uv/legacy/op/copy_paste_uv_uvedit.py
@@ -0,0 +1,97 @@
+# <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 bpy
+
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+from ...impl import copy_paste_uv_uvedit_impl as impl
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_CopyPasteUVUVEdit_CopyUV',
+    'MUV_OT_CopyPasteUVUVEdit_PasteUV',
+]
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "copy_paste_uv_uvedit"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            src_uvs = None
+
+        scene.muv_props.copy_paste_uv_uvedit = Props()
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_props.copy_paste_uv_uvedit
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_CopyPasteUVUVEdit_CopyUV(bpy.types.Operator):
+    """
+    Operation class: Copy UV coordinate on UV/Image Editor
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_uvedit_operator_copy_uv"
+    bl_label = "Copy UV (UV/Image Editor)"
+    bl_description = "Copy UV coordinate (only selected in UV/Image Editor)"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    def __init__(self):
+        self.__impl = impl.CopyUVImpl()
+
+    @classmethod
+    def poll(cls, context):
+        return impl.CopyUVImpl.poll(context)
+
+    def execute(self, context):
+        return self.__impl.execute(self, context)
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_CopyPasteUVUVEdit_PasteUV(bpy.types.Operator):
+    """
+    Operation class: Paste UV coordinate on UV/Image Editor
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_uvedit_operator_paste_uv"
+    bl_label = "Paste UV (UV/Image Editor)"
+    bl_description = "Paste UV coordinate (only selected in UV/Image Editor)"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    def __init__(self):
+        self.__impl = impl.PasteUVImpl()
+
+    @classmethod
+    def poll(cls, context):
+        return impl.PasteUVImpl.poll(context)
+
+    def execute(self, context):
+        return self.__impl.execute(self, context)
diff --git a/uv_magic_uv/legacy/op/flip_rotate_uv.py b/uv_magic_uv/legacy/op/flip_rotate_uv.py
new file mode 100644
index 0000000000000000000000000000000000000000..d94e4808c269ce75460fa4686a93ce93099ff143
--- /dev/null
+++ b/uv_magic_uv/legacy/op/flip_rotate_uv.py
@@ -0,0 +1,132 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+import bmesh
+from bpy.props import (
+    BoolProperty,
+    IntProperty,
+)
+
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+from ...impl import flip_rotate_impl as impl
+
+__all__ = [
+    'Properties',
+    'MUV_OT_FlipRotate',
+]
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "flip_rotate_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_flip_rotate_uv_enabled = BoolProperty(
+            name="Flip/Rotate UV Enabled",
+            description="Flip/Rotate UV is enabled",
+            default=False
+        )
+        scene.muv_flip_rotate_uv_seams = BoolProperty(
+            name="Seams",
+            description="Seams",
+            default=True
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_flip_rotate_uv_enabled
+        del scene.muv_flip_rotate_uv_seams
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_FlipRotate(bpy.types.Operator):
+    """
+    Operation class: Flip and Rotate UV coordinate
+    """
+
+    bl_idname = "uv.muv_flip_rotate_uv_operator"
+    bl_label = "Flip/Rotate UV"
+    bl_description = "Flip/Rotate UV coordinate"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    flip = BoolProperty(
+        name="Flip UV",
+        description="Flip UV...",
+        default=False
+    )
+    rotate = IntProperty(
+        default=0,
+        name="Rotate UV",
+        min=0,
+        max=30
+    )
+    seams = BoolProperty(
+        name="Seams",
+        description="Seams",
+        default=True
+    )
+
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return impl.is_valid_context(context)
+
+    def execute(self, context):
+        self.report({'INFO'}, "Flip/Rotate UV")
+        obj = context.active_object
+        bm = bmesh.from_edit_mesh(obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.faces.ensure_lookup_table()
+
+        # get UV layer
+        uv_layer = impl.get_uv_layer(self, bm)
+        if not uv_layer:
+            return {'CANCELLED'}
+
+        # get selected face
+        src_info = impl.get_src_face_info(self, bm, [uv_layer], True)
+        if not src_info:
+            return {'CANCELLED'}
+
+        face_count = len(src_info[list(src_info.keys())[0]])
+        self.report({'INFO'}, "{} face(s) are selected".format(face_count))
+
+        # paste
+        ret = impl.paste_uv(self, bm, src_info, src_info, [uv_layer], 'N_N',
+                            self.flip, self.rotate, self.seams)
+        if ret:
+            return {'CANCELLED'}
+
+        bmesh.update_edit_mesh(obj.data)
+        if self.seams is True:
+            obj.data.show_edge_seams = True
+
+        return {'FINISHED'}
diff --git a/uv_magic_uv/legacy/op/mirror_uv.py b/uv_magic_uv/legacy/op/mirror_uv.py
new file mode 100644
index 0000000000000000000000000000000000000000..e869e5e8ab964f5ef9c52347643398f0315713a2
--- /dev/null
+++ b/uv_magic_uv/legacy/op/mirror_uv.py
@@ -0,0 +1,110 @@
+# <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 bpy
+from bpy.props import (
+    EnumProperty,
+    FloatProperty,
+    BoolProperty,
+)
+
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+from ...impl import mirror_uv_impl as impl
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_MirrorUV',
+]
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "mirror_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_mirror_uv_enabled = BoolProperty(
+            name="Mirror UV Enabled",
+            description="Mirror UV is enabled",
+            default=False
+        )
+        scene.muv_mirror_uv_axis = EnumProperty(
+            items=[
+                ('X', "X", "Mirror Along X axis"),
+                ('Y', "Y", "Mirror Along Y axis"),
+                ('Z', "Z", "Mirror Along Z axis")
+            ],
+            name="Axis",
+            description="Mirror Axis",
+            default='X'
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_mirror_uv_enabled
+        del scene.muv_mirror_uv_axis
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_MirrorUV(bpy.types.Operator):
+    """
+    Operation class: Mirror UV
+    """
+
+    bl_idname = "uv.muv_mirror_uv_operator"
+    bl_label = "Mirror UV"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    axis = EnumProperty(
+        items=(
+            ('X', "X", "Mirror Along X axis"),
+            ('Y', "Y", "Mirror Along Y axis"),
+            ('Z', "Z", "Mirror Along Z axis")
+        ),
+        name="Axis",
+        description="Mirror Axis",
+        default='X'
+    )
+    error = FloatProperty(
+        name="Error",
+        description="Error threshold",
+        default=0.001,
+        min=0.0,
+        max=100.0,
+        soft_min=0.0,
+        soft_max=1.0
+    )
+
+    def __init__(self):
+        self.__impl = impl.MirrorUVImpl()
+
+    @classmethod
+    def poll(cls, context):
+        return impl.MirrorUVImpl.poll(context)
+
+    def execute(self, context):
+        return self.__impl.execute(self, context)
diff --git a/uv_magic_uv/legacy/op/move_uv.py b/uv_magic_uv/legacy/op/move_uv.py
new file mode 100644
index 0000000000000000000000000000000000000000..2988c2ce6cf699e551d05ef71f8cd35d0e67a544
--- /dev/null
+++ b/uv_magic_uv/legacy/op/move_uv.py
@@ -0,0 +1,82 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+from bpy.props import BoolProperty
+
+from ...impl import move_uv_impl as impl
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_MoveUV',
+]
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "move_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_move_uv_enabled = BoolProperty(
+            name="Move UV Enabled",
+            description="Move UV is enabled",
+            default=False
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_move_uv_enabled
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_MoveUV(bpy.types.Operator):
+    """
+    Operator class: Move UV
+    """
+
+    bl_idname = "uv.muv_move_uv_operator"
+    bl_label = "Move UV"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    def __init__(self):
+        self.__impl = impl.MoveUVImpl()
+
+    @classmethod
+    def poll(cls, context):
+        return impl.MoveUVImpl.poll(context)
+
+    @classmethod
+    def is_running(cls, _):
+        return impl.MoveUVImpl.is_running(_)
+
+    def modal(self, context, event):
+        return self.__impl.modal(self, context, event)
+
+    def execute(self, context):
+        return self.__impl.execute(self, context)
diff --git a/uv_magic_uv/op/pack_uv.py b/uv_magic_uv/legacy/op/pack_uv.py
similarity index 76%
rename from uv_magic_uv/op/pack_uv.py
rename to uv_magic_uv/legacy/op/pack_uv.py
index a780af3e765e6f3ab3108c7327d49bf0aabe5c40..f8d5884324628e28b27b5152645c31c9b8df50a0 100644
--- a/uv_magic_uv/op/pack_uv.py
+++ b/uv_magic_uv/legacy/op/pack_uv.py
@@ -20,8 +20,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 from math import fabs
 
@@ -35,10 +35,77 @@ from bpy.props import (
 )
 from mathutils import Vector
 
-from .. import common
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
 
 
-class MUV_PackUV(bpy.types.Operator):
+__all__ = [
+    'Properties',
+    'MUV_OT_PackUV',
+]
+
+
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "pack_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_pack_uv_enabled = BoolProperty(
+            name="Pack UV Enabled",
+            description="Pack UV is enabled",
+            default=False
+        )
+        scene.muv_pack_uv_allowable_center_deviation = FloatVectorProperty(
+            name="Allowable Center Deviation",
+            description="Allowable center deviation to judge same UV island",
+            min=0.000001,
+            max=0.1,
+            default=(0.001, 0.001),
+            size=2
+        )
+        scene.muv_pack_uv_allowable_size_deviation = FloatVectorProperty(
+            name="Allowable Size Deviation",
+            description="Allowable sizse deviation to judge same UV island",
+            min=0.000001,
+            max=0.1,
+            default=(0.001, 0.001),
+            size=2
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_pack_uv_enabled
+        del scene.muv_pack_uv_allowable_center_deviation
+        del scene.muv_pack_uv_allowable_size_deviation
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_PackUV(bpy.types.Operator):
     """
     Operation class: Pack UV with same UV islands are integrated
     Island matching algorithm
@@ -47,7 +114,7 @@ class MUV_PackUV(bpy.types.Operator):
      - Same number of UV
     """
 
-    bl_idname = "uv.muv_packuv"
+    bl_idname = "uv.muv_pack_uv_operator"
     bl_label = "Pack UV"
     bl_description = "Pack UV (Same UV Islands are integrated)"
     bl_options = {'REGISTER', 'UNDO'}
@@ -79,6 +146,13 @@ class MUV_PackUV(bpy.types.Operator):
         size=2
     )
 
+    @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):
         obj = context.active_object
         bm = bmesh.from_edit_mesh(obj.data)
diff --git a/uv_magic_uv/op/preserve_uv_aspect.py b/uv_magic_uv/legacy/op/preserve_uv_aspect.py
similarity index 72%
rename from uv_magic_uv/op/preserve_uv_aspect.py
rename to uv_magic_uv/legacy/op/preserve_uv_aspect.py
index bc2f1b81ee7b633f43ca63b4ce7908285ffd63da..cf9349bcc4d412ec1e10e1d5d648fa93cf0ce916 100644
--- a/uv_magic_uv/op/preserve_uv_aspect.py
+++ b/uv_magic_uv/legacy/op/preserve_uv_aspect.py
@@ -20,23 +20,99 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 import bmesh
-from bpy.props import StringProperty, EnumProperty
+from bpy.props import StringProperty, EnumProperty, BoolProperty
 from mathutils import Vector
 
-from .. import common
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
 
 
-class MUV_PreserveUVAspect(bpy.types.Operator):
+__all__ = [
+    'Properties',
+    'MUV_OT_PreserveUVAspect',
+]
+
+
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "preserve_uv_aspect"
+
+    @classmethod
+    def init_props(cls, scene):
+        def get_loaded_texture_name(_, __):
+            items = [(key, key, "") for key in bpy.data.images.keys()]
+            items.append(("None", "None", ""))
+            return items
+
+        scene.muv_preserve_uv_aspect_enabled = BoolProperty(
+            name="Preserve UV Aspect Enabled",
+            description="Preserve UV Aspect is enabled",
+            default=False
+        )
+        scene.muv_preserve_uv_aspect_tex_image = EnumProperty(
+            name="Image",
+            description="Texture Image",
+            items=get_loaded_texture_name
+        )
+        scene.muv_preserve_uv_aspect_origin = EnumProperty(
+            name="Origin",
+            description="Aspect Origin",
+            items=[
+                ('CENTER', 'Center', 'Center'),
+                ('LEFT_TOP', 'Left Top', 'Left Bottom'),
+                ('LEFT_CENTER', 'Left Center', 'Left Center'),
+                ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'),
+                ('CENTER_TOP', 'Center Top', 'Center Top'),
+                ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'),
+                ('RIGHT_TOP', 'Right Top', 'Right Top'),
+                ('RIGHT_CENTER', 'Right Center', 'Right Center'),
+                ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom')
+
+            ],
+            default="CENTER"
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_preserve_uv_aspect_enabled
+        del scene.muv_preserve_uv_aspect_tex_image
+        del scene.muv_preserve_uv_aspect_origin
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_PreserveUVAspect(bpy.types.Operator):
     """
     Operation class: Preserve UV Aspect
     """
 
-    bl_idname = "uv.muv_preserve_uv_aspect"
+    bl_idname = "uv.muv_preserve_uv_aspect_operator"
     bl_label = "Preserve UV Aspect"
     bl_description = "Choose Image"
     bl_options = {'REGISTER', 'UNDO'}
@@ -62,8 +138,10 @@ class MUV_PreserveUVAspect(bpy.types.Operator):
 
     @classmethod
     def poll(cls, context):
-        obj = context.active_object
-        return obj and obj.type == 'MESH'
+        # 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):
         # Note: the current system only works if the
diff --git a/uv_magic_uv/legacy/op/select_uv.py b/uv_magic_uv/legacy/op/select_uv.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdc182d5cc46b81bf064c194d15fc1aa320837df
--- /dev/null
+++ b/uv_magic_uv/legacy/op/select_uv.py
@@ -0,0 +1,168 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+import bmesh
+from bpy.props import BoolProperty
+
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_SelectUV_SelectFlipped',
+    'MUV_OT_SelectUV_SelectOverlapped',
+]
+
+
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "select_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_select_uv_enabled = BoolProperty(
+            name="Select UV Enabled",
+            description="Select UV is enabled",
+            default=False
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_select_uv_enabled
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_SelectUV_SelectOverlapped(bpy.types.Operator):
+    """
+    Operation class: Select faces which have overlapped UVs
+    """
+
+    bl_idname = "uv.muv_select_uv_operator_select_overlapped"
+    bl_label = "Overlapped"
+    bl_description = "Select faces which have overlapped UVs"
+    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):
+        obj = context.active_object
+        bm = bmesh.from_edit_mesh(obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.faces.ensure_lookup_table()
+        uv_layer = bm.loops.layers.uv.verify()
+
+        if context.tool_settings.use_uv_select_sync:
+            sel_faces = [f for f in bm.faces]
+        else:
+            sel_faces = [f for f in bm.faces if f.select]
+
+        overlapped_info = common.get_overlapped_uv_info(bm, sel_faces,
+                                                        uv_layer, 'FACE')
+
+        for info in overlapped_info:
+            if context.tool_settings.use_uv_select_sync:
+                info["subject_face"].select = True
+            else:
+                for l in info["subject_face"].loops:
+                    l[uv_layer].select = True
+
+        bmesh.update_edit_mesh(obj.data)
+
+        return {'FINISHED'}
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_SelectUV_SelectFlipped(bpy.types.Operator):
+    """
+    Operation class: Select faces which have flipped UVs
+    """
+
+    bl_idname = "uv.muv_select_uv_operator_select_flipped"
+    bl_label = "Flipped"
+    bl_description = "Select faces which have flipped UVs"
+    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):
+        obj = context.active_object
+        bm = bmesh.from_edit_mesh(obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.faces.ensure_lookup_table()
+        uv_layer = bm.loops.layers.uv.verify()
+
+        if context.tool_settings.use_uv_select_sync:
+            sel_faces = [f for f in bm.faces]
+        else:
+            sel_faces = [f for f in bm.faces if f.select]
+
+        flipped_info = common.get_flipped_uv_info(sel_faces, uv_layer)
+
+        for info in flipped_info:
+            if context.tool_settings.use_uv_select_sync:
+                info["face"].select = True
+            else:
+                for l in info["face"].loops:
+                    l[uv_layer].select = True
+
+        bmesh.update_edit_mesh(obj.data)
+
+        return {'FINISHED'}
diff --git a/uv_magic_uv/op/smooth_uv.py b/uv_magic_uv/legacy/op/smooth_uv.py
similarity index 77%
rename from uv_magic_uv/op/smooth_uv.py
rename to uv_magic_uv/legacy/op/smooth_uv.py
index aa9b22c09c0c4518809264d4368255df92a9b53a..630625547eb31f2a75226a7fefac9b6fc077b3e9 100644
--- a/uv_magic_uv/op/smooth_uv.py
+++ b/uv_magic_uv/legacy/op/smooth_uv.py
@@ -20,19 +20,88 @@
 
 __author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 import bmesh
 from bpy.props import BoolProperty, FloatProperty
 
-from .. import common
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
 
 
-class MUV_AUVSmooth(bpy.types.Operator):
+__all__ = [
+    'Properties',
+    'MUV_OT_SmoothUV',
+]
 
-    bl_idname = "uv.muv_auv_smooth"
+
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "smooth_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_smooth_uv_enabled = BoolProperty(
+            name="Smooth UV Enabled",
+            description="Smooth UV is enabled",
+            default=False
+        )
+        scene.muv_smooth_uv_transmission = BoolProperty(
+            name="Transmission",
+            description="Smooth linked UVs",
+            default=False
+        )
+        scene.muv_smooth_uv_mesh_infl = FloatProperty(
+            name="Mesh Influence",
+            description="Influence rate of mesh vertex",
+            min=0.0,
+            max=1.0,
+            default=0.0
+        )
+        scene.muv_smooth_uv_select = BoolProperty(
+            name="Select",
+            description="Select UVs which are smoothed",
+            default=False
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_smooth_uv_enabled
+        del scene.muv_smooth_uv_transmission
+        del scene.muv_smooth_uv_mesh_infl
+        del scene.muv_smooth_uv_select
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_SmoothUV(bpy.types.Operator):
+
+    bl_idname = "uv.muv_smooth_uv_operator"
     bl_label = "Smooth"
     bl_description = "Smooth UV coordinates"
     bl_options = {'REGISTER', 'UNDO'}
@@ -57,7 +126,10 @@ class MUV_AUVSmooth(bpy.types.Operator):
 
     @classmethod
     def poll(cls, context):
-        return context.mode == 'EDIT_MESH'
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return is_valid_context(context)
 
     def __smooth_wo_transmission(self, loop_seqs, uv_layer):
         # calculate path length
diff --git a/uv_magic_uv/op/texture_lock.py b/uv_magic_uv/legacy/op/texture_lock.py
similarity index 62%
rename from uv_magic_uv/op/texture_lock.py
rename to uv_magic_uv/legacy/op/texture_lock.py
index d6c56f5afc4be2e584f8c9105f1dca5e43c5c795..65873106e20ae4ab2bedccc6a676afc0aaac8faa 100644
--- a/uv_magic_uv/op/texture_lock.py
+++ b/uv_magic_uv/legacy/op/texture_lock.py
@@ -20,8 +20,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import math
 from math import atan2, cos, sqrt, sin, fabs
@@ -31,7 +31,17 @@ import bmesh
 from mathutils import Vector
 from bpy.props import BoolProperty
 
-from .. import common
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_TextureLock_Lock',
+    'MUV_OT_TextureLock_Unlock',
+    'MUV_OT_TextureLock_Intr',
+]
 
 
 def get_vco(verts_orig, loop):
@@ -169,8 +179,13 @@ def calc_tri_vert(v0, v1, angle0, angle1):
         xd = 0
         yd = 0
     else:
-        xd = (b * b - a * a + d * d) / (2 * d)
-        yd = 2 * sqrt(s * (s - a) * (s - b) * (s - d)) / d
+        r = s * (s - a) * (s - b) * (s - d)
+        if r < 0:
+            xd = 0
+            yd = 0
+        else:
+            xd = (b * b - a * a + d * d) / (2 * d)
+            yd = 2 * sqrt(r) / d
     x1 = xd * cos(alpha) - yd * sin(alpha) + v0.x
     y1 = xd * sin(alpha) + yd * cos(alpha) + v0.y
     x2 = xd * cos(alpha) + yd * sin(alpha) + v0.x
@@ -179,18 +194,101 @@ def calc_tri_vert(v0, v1, angle0, angle1):
     return Vector((x1, y1)), Vector((x2, y2))
 
 
-class MUV_TexLockStart(bpy.types.Operator):
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "texture_lock"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            verts_orig = None
+
+        scene.muv_props.texture_lock = Props()
+
+        def get_func(_):
+            return MUV_OT_TextureLock_Intr.is_running(bpy.context)
+
+        def set_func(_, __):
+            pass
+
+        def update_func(_, __):
+            bpy.ops.uv.muv_texture_lock_operator_intr('INVOKE_REGION_WIN')
+
+        scene.muv_texture_lock_enabled = BoolProperty(
+            name="Texture Lock Enabled",
+            description="Texture Lock is enabled",
+            default=False
+        )
+        scene.muv_texture_lock_lock = BoolProperty(
+            name="Texture Lock Locked",
+            description="Texture Lock is locked",
+            default=False,
+            get=get_func,
+            set=set_func,
+            update=update_func
+        )
+        scene.muv_texture_lock_connect = BoolProperty(
+            name="Connect UV",
+            default=True
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_props.texture_lock
+        del scene.muv_texture_lock_enabled
+        del scene.muv_texture_lock_lock
+        del scene.muv_texture_lock_connect
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_TextureLock_Lock(bpy.types.Operator):
     """
-    Operation class: Start Texture Lock
+    Operation class: Lock Texture
     """
 
-    bl_idname = "uv.muv_texlock_start"
-    bl_label = "Start"
-    bl_description = "Start Texture Lock"
+    bl_idname = "uv.muv_texture_lock_operator_lock"
+    bl_label = "Lock Texture"
+    bl_description = "Lock Texture"
     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)
+
+    @classmethod
+    def is_ready(cls, context):
+        sc = context.scene
+        props = sc.muv_props.texture_lock
+        if props.verts_orig:
+            return True
+        return False
+
     def execute(self, context):
-        props = context.scene.muv_props.texlock
+        props = context.scene.muv_props.texture_lock
         obj = bpy.context.active_object
         bm = bmesh.from_edit_mesh(obj.data)
         if common.check_version(2, 73, 0) >= 0:
@@ -210,14 +308,15 @@ class MUV_TexLockStart(bpy.types.Operator):
         return {'FINISHED'}
 
 
-class MUV_TexLockStop(bpy.types.Operator):
+@BlClassRegistry(legacy=True)
+class MUV_OT_TextureLock_Unlock(bpy.types.Operator):
     """
-    Operation class: Stop Texture Lock
+    Operation class: Unlock Texture
     """
 
-    bl_idname = "uv.muv_texlock_stop"
-    bl_label = "Stop"
-    bl_description = "Stop Texture Lock"
+    bl_idname = "uv.muv_texture_lock_operator_unlock"
+    bl_label = "Unlock Texture"
+    bl_description = "Unlock Texture"
     bl_options = {'REGISTER', 'UNDO'}
 
     connect = BoolProperty(
@@ -225,9 +324,24 @@ class MUV_TexLockStop(bpy.types.Operator):
         default=True
     )
 
+    @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.texture_lock
+        if not props.verts_orig:
+            return False
+        if not MUV_OT_TextureLock_Lock.is_ready(context):
+            return False
+        if not is_valid_context(context):
+            return False
+        return True
+
     def execute(self, context):
         sc = context.scene
-        props = sc.muv_props.texlock
+        props = sc.muv_props.texture_lock
         obj = bpy.context.active_object
         bm = bmesh.from_edit_mesh(obj.data)
         if common.check_version(2, 73, 0) >= 0:
@@ -275,27 +389,82 @@ class MUV_TexLockStop(bpy.types.Operator):
             v_orig["moved"] = True
             bmesh.update_edit_mesh(obj.data)
 
+        props.verts_orig = None
+
         return {'FINISHED'}
 
 
-class MUV_TexLockUpdater(bpy.types.Operator):
+@BlClassRegistry(legacy=True)
+class MUV_OT_TextureLock_Intr(bpy.types.Operator):
     """
-    Operation class: Texture locking updater
+    Operation class: Texture Lock (Interactive mode)
     """
 
-    bl_idname = "uv.muv_texlock_updater"
-    bl_label = "Texture Lock Updater"
-    bl_description = "Texture Lock Updater"
+    bl_idname = "uv.muv_texture_lock_operator_intr"
+    bl_label = "Texture Lock (Interactive mode)"
+    bl_description = "Internal operation for Texture Lock (Interactive mode)"
+
+    __timer = None
+
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return False
+        return is_valid_context(context)
+
+    @classmethod
+    def is_running(cls, _):
+        return 1 if cls.__timer else 0
+
+    @classmethod
+    def handle_add(cls, self_, context):
+        if cls.__timer is None:
+            cls.__timer = context.window_manager.event_timer_add(
+                0.10, context.window)
+            context.window_manager.modal_handler_add(self_)
+
+    @classmethod
+    def handle_remove(cls, context):
+        if cls.__timer is not None:
+            context.window_manager.event_timer_remove(cls.__timer)
+            cls.__timer = None
 
     def __init__(self):
-        self.__timer = None
+        self.__intr_verts_orig = []
+        self.__intr_verts = []
+
+    def __sel_verts_changed(self, context):
+        obj = context.active_object
+        bm = bmesh.from_edit_mesh(obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.verts.ensure_lookup_table()
+            bm.edges.ensure_lookup_table()
+            bm.faces.ensure_lookup_table()
+
+        prev = set(self.__intr_verts)
+        now = set([v.index for v in bm.verts if v.select])
+
+        return prev != now
+
+    def __reinit_verts(self, context):
+        obj = context.active_object
+        bm = bmesh.from_edit_mesh(obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.verts.ensure_lookup_table()
+            bm.edges.ensure_lookup_table()
+            bm.faces.ensure_lookup_table()
+
+        self.__intr_verts_orig = [
+            {"vidx": v.index, "vco": v.co.copy(), "moved": False}
+            for v in bm.verts if v.select]
+        self.__intr_verts = [v.index for v in bm.verts if v.select]
 
     def __update_uv(self, context):
         """
         Update UV when vertex coordinates are changed
         """
-        props = context.scene.muv_props.texlock
-        obj = bpy.context.active_object
+        obj = context.active_object
         bm = bmesh.from_edit_mesh(obj.data)
         if common.check_version(2, 73, 0) >= 0:
             bm.verts.ensure_lookup_table()
@@ -308,7 +477,7 @@ class MUV_TexLockUpdater(bpy.types.Operator):
         uv_layer = bm.loops.layers.uv.verify()
 
         verts = [v.index for v in bm.verts if v.select]
-        verts_orig = props.intr_verts_orig
+        verts_orig = self.__intr_verts_orig
 
         for vidx, v_orig in zip(verts, verts_orig):
             if vidx != v_orig["vidx"]:
@@ -337,98 +506,40 @@ class MUV_TexLockUpdater(bpy.types.Operator):
             bmesh.update_edit_mesh(obj.data)
 
         common.redraw_all_areas()
-        props.intr_verts_orig = [
+        self.__intr_verts_orig = [
             {"vidx": v.index, "vco": v.co.copy(), "moved": False}
             for v in bm.verts if v.select]
 
     def modal(self, context, event):
-        props = context.scene.muv_props.texlock
-        if context.area:
-            context.area.tag_redraw()
-        if props.intr_running is False:
-            self.__handle_remove(context)
+        if not is_valid_context(context):
+            MUV_OT_TextureLock_Intr.handle_remove(context)
             return {'FINISHED'}
-        if event.type == 'TIMER':
-            self.__update_uv(context)
-
-        return {'PASS_THROUGH'}
-
-    def __handle_add(self, context):
-        if self.__timer is None:
-            self.__timer = context.window_manager.event_timer_add(
-                0.10, context.window)
-            context.window_manager.modal_handler_add(self)
 
-    def __handle_remove(self, context):
-        if self.__timer is not None:
-            context.window_manager.event_timer_remove(self.__timer)
-            self.__timer = None
+        if not MUV_OT_TextureLock_Intr.is_running(context):
+            return {'FINISHED'}
 
-    def execute(self, context):
-        props = context.scene.muv_props.texlock
-        if props.intr_running is False:
-            self.__handle_add(context)
-            props.intr_running = True
-            return {'RUNNING_MODAL'}
-        else:
-            props.intr_running = False
         if context.area:
             context.area.tag_redraw()
 
-        return {'FINISHED'}
-
-
-class MUV_TexLockIntrStart(bpy.types.Operator):
-    """
-    Operation class: Start texture locking (Interactive mode)
-    """
-
-    bl_idname = "uv.muv_texlock_intr_start"
-    bl_label = "Texture Lock Start (Interactive mode)"
-    bl_description = "Texture Lock Start (Realtime UV update)"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    def execute(self, context):
-        props = context.scene.muv_props.texlock
-        if props.intr_running is True:
-            return {'CANCELLED'}
+        if event.type == 'TIMER':
+            if self.__sel_verts_changed(context):
+                self.__reinit_verts(context)
+            else:
+                self.__update_uv(context)
 
-        obj = bpy.context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.verts.ensure_lookup_table()
-            bm.edges.ensure_lookup_table()
-            bm.faces.ensure_lookup_table()
+        return {'PASS_THROUGH'}
 
-        if not bm.loops.layers.uv:
-            self.report({'WARNING'}, "Object must have more than one UV map")
+    def invoke(self, context, _):
+        if not is_valid_context(context):
             return {'CANCELLED'}
 
-        props.intr_verts_orig = [
-            {"vidx": v.index, "vco": v.co.copy(), "moved": False}
-            for v in bm.verts if v.select]
-
-        bpy.ops.uv.muv_texlock_updater()
-
-        return {'FINISHED'}
-
-
-# Texture lock (Stop, Interactive mode)
-class MUV_TexLockIntrStop(bpy.types.Operator):
-    """
-    Operation class: Stop texture locking (interactive mode)
-    """
-
-    bl_idname = "uv.muv_texlock_intr_stop"
-    bl_label = "Texture Lock Stop (Interactive mode)"
-    bl_description = "Texture Lock Stop (Realtime UV update)"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    def execute(self, context):
-        props = context.scene.muv_props.texlock
-        if props.intr_running is False:
-            return {'CANCELLED'}
+        if not MUV_OT_TextureLock_Intr.is_running(context):
+            MUV_OT_TextureLock_Intr.handle_add(self, context)
+            return {'RUNNING_MODAL'}
+        else:
+            MUV_OT_TextureLock_Intr.handle_remove(context)
 
-        bpy.ops.uv.muv_texlock_updater()
+        if context.area:
+            context.area.tag_redraw()
 
         return {'FINISHED'}
diff --git a/uv_magic_uv/legacy/op/texture_projection.py b/uv_magic_uv/legacy/op/texture_projection.py
new file mode 100644
index 0000000000000000000000000000000000000000..58f69c9d8733143eb5ad17d975b61565ddff68e4
--- /dev/null
+++ b/uv_magic_uv/legacy/op/texture_projection.py
@@ -0,0 +1,402 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+from collections import namedtuple
+
+import bpy
+import bgl
+import bmesh
+import mathutils
+from bpy_extras import view3d_utils
+from bpy.props import (
+    BoolProperty,
+    EnumProperty,
+    FloatProperty,
+)
+
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_TextureProjection',
+    'MUV_OT_TextureProjection_Project',
+]
+
+
+Rect = namedtuple('Rect', 'x0 y0 x1 y1')
+Rect2 = namedtuple('Rect2', 'x y width height')
+
+
+def get_loaded_texture_name(_, __):
+    items = [(key, key, "") for key in bpy.data.images.keys()]
+    items.append(("None", "None", ""))
+    return items
+
+
+def get_canvas(context, magnitude):
+    """
+    Get canvas to be renderred texture
+    """
+    sc = context.scene
+    prefs = context.user_preferences.addons["uv_magic_uv"].preferences
+
+    region_w = context.region.width
+    region_h = context.region.height
+    canvas_w = region_w - prefs.texture_projection_canvas_padding[0] * 2.0
+    canvas_h = region_h - prefs.texture_projection_canvas_padding[1] * 2.0
+
+    img = bpy.data.images[sc.muv_texture_projection_tex_image]
+    tex_w = img.size[0]
+    tex_h = img.size[1]
+
+    center_x = region_w * 0.5
+    center_y = region_h * 0.5
+
+    if sc.muv_texture_projection_adjust_window:
+        ratio_x = canvas_w / tex_w
+        ratio_y = canvas_h / tex_h
+        if sc.muv_texture_projection_apply_tex_aspect:
+            ratio = ratio_y if ratio_x > ratio_y else ratio_x
+            len_x = ratio * tex_w
+            len_y = ratio * tex_h
+        else:
+            len_x = canvas_w
+            len_y = canvas_h
+    else:
+        if sc.muv_texture_projection_apply_tex_aspect:
+            len_x = tex_w * magnitude
+            len_y = tex_h * magnitude
+        else:
+            len_x = region_w * magnitude
+            len_y = region_h * magnitude
+
+    x0 = int(center_x - len_x * 0.5)
+    y0 = int(center_y - len_y * 0.5)
+    x1 = int(center_x + len_x * 0.5)
+    y1 = int(center_y + len_y * 0.5)
+
+    return Rect(x0, y0, x1, y1)
+
+
+def rect_to_rect2(rect):
+    """
+    Convert Rect1 to Rect2
+    """
+
+    return Rect2(rect.x0, rect.y0, rect.x1 - rect.x0, rect.y1 - rect.y0)
+
+
+def region_to_canvas(rg_vec, canvas):
+    """
+    Convert screen region to canvas
+    """
+
+    cv_rect = rect_to_rect2(canvas)
+    cv_vec = mathutils.Vector()
+    cv_vec.x = (rg_vec.x - cv_rect.x) / cv_rect.width
+    cv_vec.y = (rg_vec.y - cv_rect.y) / cv_rect.height
+
+    return cv_vec
+
+
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "texture_projection"
+
+    @classmethod
+    def init_props(cls, scene):
+        def get_func(_):
+            return MUV_OT_TextureProjection.is_running(bpy.context)
+
+        def set_func(_, __):
+            pass
+
+        def update_func(_, __):
+            bpy.ops.uv.muv_texture_projection_operator('INVOKE_REGION_WIN')
+
+        scene.muv_texture_projection_enabled = BoolProperty(
+            name="Texture Projection Enabled",
+            description="Texture Projection is enabled",
+            default=False
+        )
+        scene.muv_texture_projection_enable = BoolProperty(
+            name="Texture Projection Enabled",
+            description="Texture Projection is enabled",
+            default=False,
+            get=get_func,
+            set=set_func,
+            update=update_func
+        )
+        scene.muv_texture_projection_tex_magnitude = FloatProperty(
+            name="Magnitude",
+            description="Texture Magnitude",
+            default=0.5,
+            min=0.0,
+            max=100.0
+        )
+        scene.muv_texture_projection_tex_image = EnumProperty(
+            name="Image",
+            description="Texture Image",
+            items=get_loaded_texture_name
+        )
+        scene.muv_texture_projection_tex_transparency = FloatProperty(
+            name="Transparency",
+            description="Texture Transparency",
+            default=0.2,
+            min=0.0,
+            max=1.0
+        )
+        scene.muv_texture_projection_adjust_window = BoolProperty(
+            name="Adjust Window",
+            description="Size of renderered texture is fitted to window",
+            default=True
+        )
+        scene.muv_texture_projection_apply_tex_aspect = BoolProperty(
+            name="Texture Aspect Ratio",
+            description="Apply Texture Aspect ratio to displayed texture",
+            default=True
+        )
+        scene.muv_texture_projection_assign_uvmap = BoolProperty(
+            name="Assign UVMap",
+            description="Assign UVMap when no UVmaps are available",
+            default=True
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_texture_projection_enabled
+        del scene.muv_texture_projection_tex_magnitude
+        del scene.muv_texture_projection_tex_image
+        del scene.muv_texture_projection_tex_transparency
+        del scene.muv_texture_projection_adjust_window
+        del scene.muv_texture_projection_apply_tex_aspect
+        del scene.muv_texture_projection_assign_uvmap
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_TextureProjection(bpy.types.Operator):
+    """
+    Operation class: Texture Projection
+    Render texture
+    """
+
+    bl_idname = "uv.muv_texture_projection_operator"
+    bl_description = "Render selected texture"
+    bl_label = "Texture renderer"
+
+    __handle = None
+
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return False
+        return is_valid_context(context)
+
+    @classmethod
+    def is_running(cls, _):
+        return 1 if cls.__handle else 0
+
+    @classmethod
+    def handle_add(cls, obj, context):
+        cls.__handle = bpy.types.SpaceView3D.draw_handler_add(
+            MUV_OT_TextureProjection.draw_texture,
+            (obj, context), 'WINDOW', 'POST_PIXEL')
+
+    @classmethod
+    def handle_remove(cls):
+        if cls.__handle is not None:
+            bpy.types.SpaceView3D.draw_handler_remove(cls.__handle, 'WINDOW')
+            cls.__handle = None
+
+    @classmethod
+    def draw_texture(cls, _, context):
+        sc = context.scene
+
+        if not cls.is_running(context):
+            return
+
+        # no textures are selected
+        if sc.muv_texture_projection_tex_image == "None":
+            return
+
+        # get texture to be renderred
+        img = bpy.data.images[sc.muv_texture_projection_tex_image]
+
+        # setup rendering region
+        rect = get_canvas(context, sc.muv_texture_projection_tex_magnitude)
+        positions = [
+            [rect.x0, rect.y0],
+            [rect.x0, rect.y1],
+            [rect.x1, rect.y1],
+            [rect.x1, rect.y0]
+        ]
+        tex_coords = [
+            [0.0, 0.0],
+            [0.0, 1.0],
+            [1.0, 1.0],
+            [1.0, 0.0]
+        ]
+
+        # OpenGL configuration
+        bgl.glEnable(bgl.GL_BLEND)
+        bgl.glEnable(bgl.GL_TEXTURE_2D)
+        if img.bindcode:
+            bind = img.bindcode[0]
+            bgl.glBindTexture(bgl.GL_TEXTURE_2D, bind)
+            bgl.glTexParameteri(
+                bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR)
+            bgl.glTexParameteri(
+                bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR)
+            bgl.glTexEnvi(
+                bgl.GL_TEXTURE_ENV, bgl.GL_TEXTURE_ENV_MODE, bgl.GL_MODULATE)
+
+        # render texture
+        bgl.glBegin(bgl.GL_QUADS)
+        bgl.glColor4f(1.0, 1.0, 1.0,
+                      sc.muv_texture_projection_tex_transparency)
+        for (v1, v2), (u, v) in zip(positions, tex_coords):
+            bgl.glTexCoord2f(u, v)
+            bgl.glVertex2f(v1, v2)
+        bgl.glEnd()
+
+    def invoke(self, context, _):
+        if not MUV_OT_TextureProjection.is_running(context):
+            MUV_OT_TextureProjection.handle_add(self, context)
+        else:
+            MUV_OT_TextureProjection.handle_remove()
+
+        if context.area:
+            context.area.tag_redraw()
+
+        return {'FINISHED'}
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_TextureProjection_Project(bpy.types.Operator):
+    """
+    Operation class: Project texture
+    """
+
+    bl_idname = "uv.muv_texture_projection_operator_project"
+    bl_label = "Project Texture"
+    bl_description = "Project Texture"
+    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
+        if not MUV_OT_TextureProjection.is_running(context):
+            return False
+        return is_valid_context(context)
+
+    def execute(self, context):
+        sc = context.scene
+
+        if sc.muv_texture_projection_tex_image == "None":
+            self.report({'WARNING'}, "No textures are selected")
+            return {'CANCELLED'}
+
+        _, region, space = common.get_space(
+            'VIEW_3D', 'WINDOW', 'VIEW_3D')
+
+        # get faces to be texture projected
+        obj = context.active_object
+        world_mat = obj.matrix_world
+        bm = bmesh.from_edit_mesh(obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.faces.ensure_lookup_table()
+
+        # get UV and texture layer
+        if not bm.loops.layers.uv:
+            if sc.muv_texture_projection_assign_uvmap:
+                bm.loops.layers.uv.new()
+            else:
+                self.report({'WARNING'},
+                            "Object must have more than one UV map")
+                return {'CANCELLED'}
+
+        uv_layer = bm.loops.layers.uv.verify()
+        tex_layer = bm.faces.layers.tex.verify()
+
+        sel_faces = [f for f in bm.faces if f.select]
+
+        # transform 3d space to screen region
+        v_screen = [
+            view3d_utils.location_3d_to_region_2d(
+                region,
+                space.region_3d,
+                world_mat * l.vert.co)
+            for f in sel_faces for l in f.loops
+        ]
+
+        # transform screen region to canvas
+        v_canvas = [
+            region_to_canvas(
+                v,
+                get_canvas(bpy.context,
+                           sc.muv_texture_projection_tex_magnitude)
+            ) for v in v_screen
+        ]
+
+        # project texture to object
+        i = 0
+        for f in sel_faces:
+            f[tex_layer].image = \
+                bpy.data.images[sc.muv_texture_projection_tex_image]
+            for l in f.loops:
+                l[uv_layer].uv = v_canvas[i].to_2d()
+                i = i + 1
+
+        common.redraw_all_areas()
+        bmesh.update_edit_mesh(obj.data)
+
+        return {'FINISHED'}
diff --git a/uv_magic_uv/op/texture_wrap.py b/uv_magic_uv/legacy/op/texture_wrap.py
similarity index 71%
rename from uv_magic_uv/op/texture_wrap.py
rename to uv_magic_uv/legacy/op/texture_wrap.py
index 046692148b02df9718ccb3e5fec5671cbe517679..cb4cc78c9f04ed48736997e506988dd43f763cd3 100644
--- a/uv_magic_uv/op/texture_wrap.py
+++ b/uv_magic_uv/legacy/op/texture_wrap.py
@@ -20,27 +20,104 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 import bmesh
-
-from .. import common
-
-
-class MUV_TexWrapRefer(bpy.types.Operator):
+from bpy.props import (
+    BoolProperty,
+)
+
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_TextureWrap_Refer',
+    'MUV_OT_TextureWrap_Set',
+]
+
+
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "texture_wrap"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            ref_face_index = -1
+            ref_obj = None
+
+        scene.muv_props.texture_wrap = Props()
+
+        scene.muv_texture_wrap_enabled = BoolProperty(
+            name="Texture Wrap",
+            description="Texture Wrap is enabled",
+            default=False
+        )
+        scene.muv_texture_wrap_set_and_refer = BoolProperty(
+            name="Set and Refer",
+            description="Refer and set UV",
+            default=True
+        )
+        scene.muv_texture_wrap_selseq = BoolProperty(
+            name="Selection Sequence",
+            description="Set UV sequentially",
+            default=False
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_props.texture_wrap
+        del scene.muv_texture_wrap_enabled
+        del scene.muv_texture_wrap_set_and_refer
+        del scene.muv_texture_wrap_selseq
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_TextureWrap_Refer(bpy.types.Operator):
     """
     Operation class: Refer UV
     """
 
-    bl_idname = "uv.muv_texwrap_refer"
+    bl_idname = "uv.muv_texture_wrap_operator_refer"
     bl_label = "Refer"
     bl_description = "Refer UV"
     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):
-        props = context.scene.muv_props.texwrap
+        props = context.scene.muv_props.texture_wrap
         obj = context.active_object
         bm = bmesh.from_edit_mesh(obj.data)
         if common.check_version(2, 73, 0) >= 0:
@@ -61,19 +138,31 @@ class MUV_TexWrapRefer(bpy.types.Operator):
         return {'FINISHED'}
 
 
-class MUV_TexWrapSet(bpy.types.Operator):
+@BlClassRegistry(legacy=True)
+class MUV_OT_TextureWrap_Set(bpy.types.Operator):
     """
     Operation class: Set UV
     """
 
-    bl_idname = "uv.muv_texwrap_set"
+    bl_idname = "uv.muv_texture_wrap_operator_set"
     bl_label = "Set"
     bl_description = "Set UV"
     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
+        sc = context.scene
+        props = sc.muv_props.texture_wrap
+        if not props.ref_obj:
+            return False
+        return is_valid_context(context)
+
     def execute(self, context):
         sc = context.scene
-        props = sc.muv_props.texwrap
+        props = sc.muv_props.texture_wrap
         obj = context.active_object
         bm = bmesh.from_edit_mesh(obj.data)
         if common.check_version(2, 73, 0) >= 0:
@@ -84,7 +173,7 @@ class MUV_TexWrapSet(bpy.types.Operator):
             return {'CANCELLED'}
         uv_layer = bm.loops.layers.uv.verify()
 
-        if sc.muv_texwrap_selseq:
+        if sc.muv_texture_wrap_selseq:
             sel_faces = []
             for hist in bm.select_history:
                 if isinstance(hist, bmesh.types.BMFace) and hist.select:
@@ -206,7 +295,7 @@ class MUV_TexWrapSet(bpy.types.Operator):
 
             ref_face_index = tgt_face_index
 
-        if sc.muv_texwrap_set_and_refer:
+        if sc.muv_texture_wrap_set_and_refer:
             props.ref_face_index = tgt_face_index
 
         return {'FINISHED'}
diff --git a/uv_magic_uv/legacy/op/transfer_uv.py b/uv_magic_uv/legacy/op/transfer_uv.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd0e4dd9feffde6d3d95a6e8484bcdc38e24ce48
--- /dev/null
+++ b/uv_magic_uv/legacy/op/transfer_uv.py
@@ -0,0 +1,172 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>, Mifth, MaxRobinot"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+import bmesh
+from bpy.props import BoolProperty
+
+from ... import common
+from ...impl import transfer_uv_impl as impl
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_TransferUV_CopyUV',
+    'MUV_OT_TransferUV_PasteUV',
+]
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "transfer_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            topology_copied = None
+
+        scene.muv_props.transfer_uv = Props()
+
+        scene.muv_transfer_uv_enabled = BoolProperty(
+            name="Transfer UV Enabled",
+            description="Transfer UV is enabled",
+            default=False
+        )
+        scene.muv_transfer_uv_invert_normals = BoolProperty(
+            name="Invert Normals",
+            description="Invert Normals",
+            default=False
+        )
+        scene.muv_transfer_uv_copy_seams = BoolProperty(
+            name="Copy Seams",
+            description="Copy Seams",
+            default=True
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_transfer_uv_enabled
+        del scene.muv_transfer_uv_invert_normals
+        del scene.muv_transfer_uv_copy_seams
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_TransferUV_CopyUV(bpy.types.Operator):
+    """
+        Operation class: Transfer UV copy
+        Topological based copy
+    """
+
+    bl_idname = "uv.muv_transfer_uv_operator_copy_uv"
+    bl_label = "Transfer UV Copy UV"
+    bl_description = "Transfer UV Copy UV (Topological based copy)"
+    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 impl.is_valid_context(context)
+
+    def execute(self, context):
+        props = context.scene.muv_props.transfer_uv
+        active_obj = context.scene.objects.active
+        bm = bmesh.from_edit_mesh(active_obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.faces.ensure_lookup_table()
+
+        uv_layer = impl.get_uv_layer(self, bm)
+        if uv_layer is None:
+            return {'CANCELLED'}
+
+        faces = impl.get_selected_src_faces(self, bm, uv_layer)
+        if faces is None:
+            return {'CANCELLED'}
+        props.topology_copied = faces
+
+        bmesh.update_edit_mesh(active_obj.data)
+
+        return {'FINISHED'}
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_TransferUV_PasteUV(bpy.types.Operator):
+    """
+        Operation class: Transfer UV paste
+        Topological based paste
+    """
+
+    bl_idname = "uv.muv_transfer_uv_operator_paste_uv"
+    bl_label = "Transfer UV Paste UV"
+    bl_description = "Transfer UV Paste UV (Topological based paste)"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    invert_normals = BoolProperty(
+        name="Invert Normals",
+        description="Invert Normals",
+        default=False
+    )
+    copy_seams = BoolProperty(
+        name="Copy Seams",
+        description="Copy Seams",
+        default=True
+    )
+
+    @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.transfer_uv
+        if not props.topology_copied:
+            return False
+        return impl.is_valid_context(context)
+
+    def execute(self, context):
+        props = context.scene.muv_props.transfer_uv
+        active_obj = context.scene.objects.active
+        bm = bmesh.from_edit_mesh(active_obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.faces.ensure_lookup_table()
+
+        # get UV layer
+        uv_layer = impl.get_uv_layer(self, bm)
+        if uv_layer is None:
+            return {'CANCELLED'}
+
+        ret = impl.paste_uv(self, bm, uv_layer, props.topology_copied,
+                            self.invert_normals, self.copy_seams)
+        if ret:
+            return {'CANCELLED'}
+
+        bmesh.update_edit_mesh(active_obj.data)
+        if self.copy_seams:
+            active_obj.data.show_edge_seams = True
+
+        return {'FINISHED'}
diff --git a/uv_magic_uv/op/unwrap_constraint.py b/uv_magic_uv/legacy/op/unwrap_constraint.py
similarity index 66%
rename from uv_magic_uv/op/unwrap_constraint.py
rename to uv_magic_uv/legacy/op/unwrap_constraint.py
index e98879b762c51328077bff12ce68f7eea0b24621..f06efce18b2228702d9c9550c72e580fb35d5f46 100644
--- a/uv_magic_uv/op/unwrap_constraint.py
+++ b/uv_magic_uv/legacy/op/unwrap_constraint.py
@@ -18,8 +18,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 import bmesh
@@ -29,15 +29,74 @@ from bpy.props import (
     FloatProperty,
 )
 
-from .. import common
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
 
 
-class MUV_UnwrapConstraint(bpy.types.Operator):
+__all__ = [
+    'Properties',
+    'MUV_OT_UnwrapConstraint',
+]
+
+
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "unwrap_constraint"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_unwrap_constraint_enabled = BoolProperty(
+            name="Unwrap Constraint Enabled",
+            description="Unwrap Constraint is enabled",
+            default=False
+        )
+        scene.muv_unwrap_constraint_u_const = BoolProperty(
+            name="U-Constraint",
+            description="Keep UV U-axis coordinate",
+            default=False
+        )
+        scene.muv_unwrap_constraint_v_const = BoolProperty(
+            name="V-Constraint",
+            description="Keep UV V-axis coordinate",
+            default=False
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_unwrap_constraint_enabled
+        del scene.muv_unwrap_constraint_u_const
+        del scene.muv_unwrap_constraint_v_const
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_UnwrapConstraint(bpy.types.Operator):
     """
     Operation class: Unwrap with constrain UV coordinate
     """
 
-    bl_idname = "uv.muv_unwrap_constraint"
+    bl_idname = "uv.muv_unwrap_constraint_operator"
     bl_label = "Unwrap Constraint"
     bl_description = "Unwrap while keeping uv coordinate"
     bl_options = {'REGISTER', 'UNDO'}
@@ -83,6 +142,13 @@ class MUV_UnwrapConstraint(bpy.types.Operator):
         default=False
     )
 
+    @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, _):
         obj = bpy.context.active_object
         bm = bmesh.from_edit_mesh(obj.data)
diff --git a/uv_magic_uv/op/uv_bounding_box.py b/uv_magic_uv/legacy/op/uv_bounding_box.py
similarity index 71%
rename from uv_magic_uv/op/uv_bounding_box.py
rename to uv_magic_uv/legacy/op/uv_bounding_box.py
index 9ebc76c47e6f4c97b56534936f89bf7e8cfd068f..47c27e41c752f8fef122d8cd727f9dfa4b8ee496 100644
--- a/uv_magic_uv/op/uv_bounding_box.py
+++ b/uv_magic_uv/legacy/op/uv_bounding_box.py
@@ -20,8 +20,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 from enum import IntEnum
 import math
@@ -30,14 +30,106 @@ import bpy
 import bgl
 import mathutils
 import bmesh
+from bpy.props import BoolProperty, EnumProperty
 
-from .. import common
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_UVBoundingBox',
+]
 
 
 MAX_VALUE = 100000.0
 
 
-class MUV_UVBBCmd():
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "uv_bounding_box"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            uv_info_ini = []
+            ctrl_points_ini = []
+            ctrl_points = []
+
+        scene.muv_props.uv_bounding_box = Props()
+
+        def get_func(_):
+            return MUV_OT_UVBoundingBox.is_running(bpy.context)
+
+        def set_func(_, __):
+            pass
+
+        def update_func(_, __):
+            bpy.ops.uv.muv_uv_bounding_box_operator('INVOKE_REGION_WIN')
+
+        scene.muv_uv_bounding_box_enabled = BoolProperty(
+            name="UV Bounding Box Enabled",
+            description="UV Bounding Box is enabled",
+            default=False
+        )
+        scene.muv_uv_bounding_box_show = BoolProperty(
+            name="UV Bounding Box Showed",
+            description="UV Bounding Box is showed",
+            default=False,
+            get=get_func,
+            set=set_func,
+            update=update_func
+        )
+        scene.muv_uv_bounding_box_uniform_scaling = BoolProperty(
+            name="Uniform Scaling",
+            description="Enable Uniform Scaling",
+            default=False
+        )
+        scene.muv_uv_bounding_box_boundary = EnumProperty(
+            name="Boundary",
+            description="Boundary",
+            default='UV_SEL',
+            items=[
+                ('UV', "UV", "Boundary is decided by UV"),
+                ('UV_SEL', "UV (Selected)",
+                 "Boundary is decided by Selected UV")
+            ]
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_props.uv_bounding_box
+        del scene.muv_uv_bounding_box_enabled
+        del scene.muv_uv_bounding_box_show
+        del scene.muv_uv_bounding_box_uniform_scaling
+        del scene.muv_uv_bounding_box_boundary
+
+
+class CommandBase():
     """
     Custom class: Base class of command
     """
@@ -52,7 +144,7 @@ class MUV_UVBBCmd():
         return mat
 
 
-class MUV_UVBBTranslationCmd(MUV_UVBBCmd):
+class TranslationCommand(CommandBase):
     """
     Custom class: Translation operation
     """
@@ -76,7 +168,7 @@ class MUV_UVBBTranslationCmd(MUV_UVBBCmd):
         self.__y = y
 
 
-class MUV_UVBBRotationCmd(MUV_UVBBCmd):
+class RotationCommand(CommandBase):
     """
     Custom class: Rotation operation
     """
@@ -107,7 +199,7 @@ class MUV_UVBBRotationCmd(MUV_UVBBCmd):
         self.__y = y
 
 
-class MUV_UVBBScalingCmd(MUV_UVBBCmd):
+class ScalingCommand(CommandBase):
     """
     Custom class: Scaling operation
     """
@@ -158,7 +250,7 @@ class MUV_UVBBScalingCmd(MUV_UVBBCmd):
         self.__y = y
 
 
-class MUV_UVBBUniformScalingCmd(MUV_UVBBCmd):
+class UniformScalingCommand(CommandBase):
     """
     Custom class: Uniform Scaling operation
     """
@@ -222,7 +314,7 @@ class MUV_UVBBUniformScalingCmd(MUV_UVBBCmd):
         self.__y = y
 
 
-class MUV_UVBBCmdExecuter():
+class CommandExecuter():
     """
     Custom class: manage command history and execute command
     """
@@ -288,67 +380,7 @@ class MUV_UVBBCmdExecuter():
         self.__cmd_list.append(cmd)
 
 
-class MUV_UVBBRenderer(bpy.types.Operator):
-    """
-    Operation class: Render UV bounding box
-    """
-
-    bl_idname = "uv.muv_uvbb_renderer"
-    bl_label = "UV Bounding Box Renderer"
-    bl_description = "Bounding Box Renderer about UV in Image Editor"
-
-    __handle = None
-
-    @staticmethod
-    def handle_add(obj, context):
-        if MUV_UVBBRenderer.__handle is None:
-            sie = bpy.types.SpaceImageEditor
-            MUV_UVBBRenderer.__handle = sie.draw_handler_add(
-                MUV_UVBBRenderer.draw_bb,
-                (obj, context), "WINDOW", "POST_PIXEL")
-
-    @staticmethod
-    def handle_remove():
-        if MUV_UVBBRenderer.__handle is not None:
-            sie = bpy.types.SpaceImageEditor
-            sie.draw_handler_remove(
-                MUV_UVBBRenderer.__handle, "WINDOW")
-            MUV_UVBBRenderer.__handle = None
-
-    @staticmethod
-    def __draw_ctrl_point(context, pos):
-        """
-        Draw control point
-        """
-        prefs = context.user_preferences.addons["uv_magic_uv"].preferences
-        cp_size = prefs.uvbb_cp_size
-        offset = cp_size / 2
-        verts = [
-            [pos.x - offset, pos.y - offset],
-            [pos.x - offset, pos.y + offset],
-            [pos.x + offset, pos.y + offset],
-            [pos.x + offset, pos.y - offset]
-        ]
-        bgl.glEnable(bgl.GL_BLEND)
-        bgl.glBegin(bgl.GL_QUADS)
-        bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
-        for (x, y) in verts:
-            bgl.glVertex2f(x, y)
-        bgl.glEnd()
-
-    @staticmethod
-    def draw_bb(_, context):
-        """
-        Draw bounding box
-        """
-        props = context.scene.muv_props.uvbb
-        for cp in props.ctrl_points:
-            MUV_UVBBRenderer.__draw_ctrl_point(
-                context, mathutils.Vector(
-                    context.region.view2d.view_to_region(cp.x, cp.y)))
-
-
-class MUV_UVBBState(IntEnum):
+class State(IntEnum):
     """
     Enum: State definition used by MUV_UVBBStateMgr
     """
@@ -369,7 +401,7 @@ class MUV_UVBBState(IntEnum):
     UNIFORM_SCALING_4 = 14
 
 
-class MUV_UVBBStateBase():
+class StateBase():
     """
     Custom class: Base class of state
     """
@@ -381,7 +413,7 @@ class MUV_UVBBStateBase():
         raise NotImplementedError
 
 
-class MUV_UVBBStateNone(MUV_UVBBStateBase):
+class StateNone(StateBase):
     """
     Custom class:
     No state
@@ -397,8 +429,8 @@ class MUV_UVBBStateNone(MUV_UVBBStateBase):
         Update state
         """
         prefs = context.user_preferences.addons["uv_magic_uv"].preferences
-        cp_react_size = prefs.uvbb_cp_react_size
-        is_uscaling = context.scene.muv_uvbb_uniform_scaling
+        cp_react_size = prefs.uv_bounding_box_cp_react_size
+        is_uscaling = context.scene.muv_uv_bounding_box_uniform_scaling
         if (event.type == 'LEFTMOUSE') and (event.value == 'PRESS'):
             x, y = context.region.view2d.view_to_region(
                 mouse_view.x, mouse_view.y)
@@ -413,16 +445,16 @@ class MUV_UVBBStateNone(MUV_UVBBStateBase):
                         arr = [1, 3, 6, 8]
                         if i in arr:
                             return (
-                                MUV_UVBBState.UNIFORM_SCALING_1 +
+                                State.UNIFORM_SCALING_1 +
                                 arr.index(i)
                             )
                     else:
-                        return MUV_UVBBState.TRANSLATING + i
+                        return State.TRANSLATING + i
 
-        return MUV_UVBBState.NONE
+        return State.NONE
 
 
-class MUV_UVBBStateTranslating(MUV_UVBBStateBase):
+class StateTranslating(StateBase):
     """
     Custom class: Translating state
     """
@@ -431,19 +463,19 @@ class MUV_UVBBStateTranslating(MUV_UVBBStateBase):
         super().__init__()
         self.__cmd_exec = cmd_exec
         ix, iy = ctrl_points[0].x, ctrl_points[0].y
-        self.__cmd_exec.append(MUV_UVBBTranslationCmd(ix, iy))
+        self.__cmd_exec.append(TranslationCommand(ix, iy))
 
     def update(self, context, event, ctrl_points, mouse_view):
         if event.type == 'LEFTMOUSE':
             if event.value == 'RELEASE':
-                return MUV_UVBBState.NONE
+                return State.NONE
         if event.type == 'MOUSEMOVE':
             x, y = mouse_view.x, mouse_view.y
             self.__cmd_exec.top().set(x, y)
-        return MUV_UVBBState.TRANSLATING
+        return State.TRANSLATING
 
 
-class MUV_UVBBStateScaling(MUV_UVBBStateBase):
+class StateScaling(StateBase):
     """
     Custom class: Scaling state
     """
@@ -460,19 +492,19 @@ class MUV_UVBBStateScaling(MUV_UVBBStateBase):
         dir_x, dir_y = dir_x_list[idx], dir_y_list[idx]
         mat = self.__cmd_exec.execute(end=self.__cmd_exec.undo_size())
         self.__cmd_exec.append(
-            MUV_UVBBScalingCmd(ix, iy, ox, oy, dir_x, dir_y, mat.inverted()))
+            ScalingCommand(ix, iy, ox, oy, dir_x, dir_y, mat.inverted()))
 
     def update(self, context, event, ctrl_points, mouse_view):
         if event.type == 'LEFTMOUSE':
             if event.value == 'RELEASE':
-                return MUV_UVBBState.NONE
+                return State.NONE
         if event.type == 'MOUSEMOVE':
             x, y = mouse_view.x, mouse_view.y
             self.__cmd_exec.top().set(x, y)
         return self.__state
 
 
-class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase):
+class StateUniformScaling(StateBase):
     """
     Custom class: Uniform Scaling state
     """
@@ -483,17 +515,17 @@ class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase):
         self.__cmd_exec = cmd_exec
         icp_idx = [1, 3, 6, 8]
         ocp_idx = [8, 6, 3, 1]
-        idx = state - MUV_UVBBState.UNIFORM_SCALING_1
+        idx = state - State.UNIFORM_SCALING_1
         ix, iy = ctrl_points[icp_idx[idx]].x, ctrl_points[icp_idx[idx]].y
         ox, oy = ctrl_points[ocp_idx[idx]].x, ctrl_points[ocp_idx[idx]].y
         mat = self.__cmd_exec.execute(end=self.__cmd_exec.undo_size())
-        self.__cmd_exec.append(MUV_UVBBUniformScalingCmd(
+        self.__cmd_exec.append(UniformScalingCommand(
             ix, iy, ox, oy, mat.inverted()))
 
     def update(self, context, event, ctrl_points, mouse_view):
         if event.type == 'LEFTMOUSE':
             if event.value == 'RELEASE':
-                return MUV_UVBBState.NONE
+                return State.NONE
         if event.type == 'MOUSEMOVE':
             x, y = mouse_view.x, mouse_view.y
             self.__cmd_exec.top().set(x, y)
@@ -501,7 +533,7 @@ class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase):
         return self.__state
 
 
-class MUV_UVBBStateRotating(MUV_UVBBStateBase):
+class StateRotating(StateBase):
     """
     Custom class: Rotating state
     """
@@ -511,27 +543,27 @@ class MUV_UVBBStateRotating(MUV_UVBBStateBase):
         self.__cmd_exec = cmd_exec
         ix, iy = ctrl_points[9].x, ctrl_points[9].y
         ox, oy = ctrl_points[0].x, ctrl_points[0].y
-        self.__cmd_exec.append(MUV_UVBBRotationCmd(ix, iy, ox, oy))
+        self.__cmd_exec.append(RotationCommand(ix, iy, ox, oy))
 
     def update(self, context, event, ctrl_points, mouse_view):
         if event.type == 'LEFTMOUSE':
             if event.value == 'RELEASE':
-                return MUV_UVBBState.NONE
+                return State.NONE
         if event.type == 'MOUSEMOVE':
             x, y = mouse_view.x, mouse_view.y
             self.__cmd_exec.top().set(x, y)
-        return MUV_UVBBState.ROTATING
+        return State.ROTATING
 
 
-class MUV_UVBBStateMgr():
+class StateManager():
     """
     Custom class: Manage state about this feature
     """
 
     def __init__(self, cmd_exec):
         self.__cmd_exec = cmd_exec          # command executer
-        self.__state = MUV_UVBBState.NONE   # current state
-        self.__state_obj = MUV_UVBBStateNone(self.__cmd_exec)
+        self.__state = State.NONE   # current state
+        self.__state_obj = StateNone(self.__cmd_exec)
 
     def __update_state(self, next_state, ctrl_points):
         """
@@ -541,18 +573,18 @@ class MUV_UVBBStateMgr():
         if next_state == self.__state:
             return
         obj = None
-        if next_state == MUV_UVBBState.TRANSLATING:
-            obj = MUV_UVBBStateTranslating(self.__cmd_exec, ctrl_points)
-        elif MUV_UVBBState.SCALING_1 <= next_state <= MUV_UVBBState.SCALING_8:
-            obj = MUV_UVBBStateScaling(
+        if next_state == State.TRANSLATING:
+            obj = StateTranslating(self.__cmd_exec, ctrl_points)
+        elif State.SCALING_1 <= next_state <= State.SCALING_8:
+            obj = StateScaling(
                 self.__cmd_exec, next_state, ctrl_points)
-        elif next_state == MUV_UVBBState.ROTATING:
-            obj = MUV_UVBBStateRotating(self.__cmd_exec, ctrl_points)
-        elif next_state == MUV_UVBBState.NONE:
-            obj = MUV_UVBBStateNone(self.__cmd_exec)
-        elif (MUV_UVBBState.UNIFORM_SCALING_1 <= next_state <=
-              MUV_UVBBState.UNIFORM_SCALING_4):
-            obj = MUV_UVBBStateUniformScaling(
+        elif next_state == State.ROTATING:
+            obj = StateRotating(self.__cmd_exec, ctrl_points)
+        elif next_state == State.NONE:
+            obj = StateNone(self.__cmd_exec)
+        elif (State.UNIFORM_SCALING_1 <= next_state <=
+              State.UNIFORM_SCALING_4):
+            obj = StateUniformScaling(
                 self.__cmd_exec, next_state, ctrl_points)
 
         if obj is not None:
@@ -569,34 +601,98 @@ class MUV_UVBBStateMgr():
             context, event, ctrl_points, mouse_view)
         self.__update_state(next_state, ctrl_points)
 
+        return self.__state
+
 
-class MUV_UVBBUpdater(bpy.types.Operator):
+@BlClassRegistry(legacy=True)
+class MUV_OT_UVBoundingBox(bpy.types.Operator):
     """
-    Operation class: Update state and handle event by modal function
+    Operation class: UV Bounding Box
     """
 
-    bl_idname = "uv.muv_uvbb_updater"
-    bl_label = "UV Bounding Box Updater"
-    bl_description = "Update UV Bounding Box"
+    bl_idname = "uv.muv_uv_bounding_box_operator"
+    bl_label = "UV Bounding Box"
+    bl_description = "Internal operation for UV Bounding Box"
     bl_options = {'REGISTER', 'UNDO'}
 
     def __init__(self):
         self.__timer = None
-        self.__cmd_exec = MUV_UVBBCmdExecuter()         # Command executer
-        self.__state_mgr = MUV_UVBBStateMgr(self.__cmd_exec)    # State Manager
+        self.__cmd_exec = CommandExecuter()         # Command executor
+        self.__state_mgr = StateManager(self.__cmd_exec)    # State Manager
 
-    def __handle_add(self, context):
-        if self.__timer is None:
-            self.__timer = context.window_manager.event_timer_add(
+    __handle = None
+    __timer = None
+
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return False
+        return is_valid_context(context)
+
+    @classmethod
+    def is_running(cls, _):
+        return 1 if cls.__handle else 0
+
+    @classmethod
+    def handle_add(cls, obj, context):
+        if cls.__handle is None:
+            sie = bpy.types.SpaceImageEditor
+            cls.__handle = sie.draw_handler_add(
+                cls.draw_bb, (obj, context), "WINDOW", "POST_PIXEL")
+        if cls.__timer is None:
+            cls.__timer = context.window_manager.event_timer_add(
                 0.1, context.window)
-            context.window_manager.modal_handler_add(self)
-        MUV_UVBBRenderer.handle_add(self, context)
+            context.window_manager.modal_handler_add(obj)
 
-    def __handle_remove(self, context):
-        MUV_UVBBRenderer.handle_remove()
-        if self.__timer is not None:
-            context.window_manager.event_timer_remove(self.__timer)
-            self.__timer = None
+    @classmethod
+    def handle_remove(cls, context):
+        if cls.__handle is not None:
+            sie = bpy.types.SpaceImageEditor
+            sie.draw_handler_remove(cls.__handle, "WINDOW")
+            cls.__handle = None
+        if cls.__timer is not None:
+            context.window_manager.event_timer_remove(cls.__timer)
+            cls.__timer = None
+
+    @classmethod
+    def __draw_ctrl_point(cls, context, pos):
+        """
+        Draw control point
+        """
+        prefs = context.user_preferences.addons["uv_magic_uv"].preferences
+        cp_size = prefs.uv_bounding_box_cp_size
+        offset = cp_size / 2
+        verts = [
+            [pos.x - offset, pos.y - offset],
+            [pos.x - offset, pos.y + offset],
+            [pos.x + offset, pos.y + offset],
+            [pos.x + offset, pos.y - offset]
+        ]
+        bgl.glEnable(bgl.GL_BLEND)
+        bgl.glBegin(bgl.GL_QUADS)
+        bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
+        for (x, y) in verts:
+            bgl.glVertex2f(x, y)
+        bgl.glEnd()
+
+    @classmethod
+    def draw_bb(cls, _, context):
+        """
+        Draw bounding box
+        """
+        props = context.scene.muv_props.uv_bounding_box
+
+        if not MUV_OT_UVBoundingBox.is_running(context):
+            return
+
+        if not is_valid_context(context):
+            return
+
+        for cp in props.ctrl_points:
+            cls.__draw_ctrl_point(
+                context, mathutils.Vector(
+                    context.region.view2d.view_to_region(cp.x, cp.y)))
 
     def __get_uv_info(self, context):
         """
@@ -615,10 +711,10 @@ class MUV_UVBBUpdater(bpy.types.Operator):
             if not f.select:
                 continue
             for i, l in enumerate(f.loops):
-                if sc.muv_uvbb_boundary == 'UV_SEL':
+                if sc.muv_uv_bounding_box_boundary == 'UV_SEL':
                     if l[uv_layer].select:
                         uv_info.append((f.index, i, l[uv_layer].uv.copy()))
-                elif sc.muv_uvbb_boundary == 'UV':
+                elif sc.muv_uv_bounding_box_boundary == 'UV':
                     uv_info.append((f.index, i, l[uv_layer].uv.copy()))
         if not uv_info:
             return None
@@ -688,16 +784,23 @@ class MUV_UVBBUpdater(bpy.types.Operator):
         return [trans_mat * cp for cp in ctrl_points_ini]
 
     def modal(self, context, event):
-        props = context.scene.muv_props.uvbb
+        props = context.scene.muv_props.uv_bounding_box
         common.redraw_all_areas()
-        if props.running is False:
-            self.__handle_remove(context)
+
+        if not MUV_OT_UVBoundingBox.is_running(context):
             return {'FINISHED'}
 
-        area, _, _ = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
+        if not is_valid_context(context):
+            MUV_OT_UVBoundingBox.handle_remove(context)
+            return {'FINISHED'}
 
-        if event.mouse_region_x < 0 or event.mouse_region_x > area.width or \
-           event.mouse_region_y < 0 or event.mouse_region_y > area.height:
+        region_types = [
+            'HEADER',
+            'UI',
+            'TOOLS',
+        ]
+        if not common.mouse_on_area(event, 'IMAGE_EDITOR') or \
+           common.mouse_on_regions(event, 'IMAGE_EDITOR', region_types):
             return {'PASS_THROUGH'}
 
         if event.type == 'TIMER':
@@ -706,27 +809,30 @@ class MUV_UVBBUpdater(bpy.types.Operator):
             props.ctrl_points = self.__update_ctrl_point(
                 props.ctrl_points_ini, trans_mat)
 
-        self.__state_mgr.update(context, props.ctrl_points, event)
+        state = self.__state_mgr.update(context, props.ctrl_points, event)
+        if state == State.NONE:
+            return {'PASS_THROUGH'}
 
         return {'RUNNING_MODAL'}
 
-    def execute(self, context):
-        props = context.scene.muv_props.uvbb
+    def invoke(self, context, _):
+        props = context.scene.muv_props.uv_bounding_box
 
-        if props.running is True:
-            props.running = False
+        if MUV_OT_UVBoundingBox.is_running(context):
+            MUV_OT_UVBoundingBox.handle_remove(context)
             return {'FINISHED'}
 
         props.uv_info_ini = self.__get_uv_info(context)
         if props.uv_info_ini is None:
             return {'CANCELLED'}
+
+        MUV_OT_UVBoundingBox.handle_add(self, context)
+
         props.ctrl_points_ini = self.__get_ctrl_point(props.uv_info_ini)
         trans_mat = self.__cmd_exec.execute()
         # Update is needed in order to display control point
         self.__update_uvs(context, props.uv_info_ini, trans_mat)
         props.ctrl_points = self.__update_ctrl_point(
             props.ctrl_points_ini, trans_mat)
-        self.__handle_add(context)
-        props.running = True
 
         return {'RUNNING_MODAL'}
diff --git a/uv_magic_uv/legacy/op/uv_inspection.py b/uv_magic_uv/legacy/op/uv_inspection.py
new file mode 100644
index 0000000000000000000000000000000000000000..57d424685ec3910b0fdb38d4750dd2d506946572
--- /dev/null
+++ b/uv_magic_uv/legacy/op/uv_inspection.py
@@ -0,0 +1,280 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+import bmesh
+import bgl
+from bpy.props import BoolProperty, EnumProperty
+
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_UVInspection_Render',
+    'MUV_OT_UVInspection_Update',
+]
+
+
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "uv_inspection"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            overlapped_info = []
+            flipped_info = []
+
+        scene.muv_props.uv_inspection = Props()
+
+        def get_func(_):
+            return MUV_OT_UVInspection_Render.is_running(bpy.context)
+
+        def set_func(_, __):
+            pass
+
+        def update_func(_, __):
+            bpy.ops.uv.muv_uv_inspection_operator_render('INVOKE_REGION_WIN')
+
+        scene.muv_uv_inspection_enabled = BoolProperty(
+            name="UV Inspection Enabled",
+            description="UV Inspection is enabled",
+            default=False
+        )
+        scene.muv_uv_inspection_show = BoolProperty(
+            name="UV Inspection Showed",
+            description="UV Inspection is showed",
+            default=False,
+            get=get_func,
+            set=set_func,
+            update=update_func
+        )
+        scene.muv_uv_inspection_show_overlapped = BoolProperty(
+            name="Overlapped",
+            description="Show overlapped UVs",
+            default=False
+        )
+        scene.muv_uv_inspection_show_flipped = BoolProperty(
+            name="Flipped",
+            description="Show flipped UVs",
+            default=False
+        )
+        scene.muv_uv_inspection_show_mode = EnumProperty(
+            name="Mode",
+            description="Show mode",
+            items=[
+                ('PART', "Part", "Show only overlapped/flipped part"),
+                ('FACE', "Face", "Show overlapped/flipped face")
+            ],
+            default='PART'
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_props.uv_inspection
+        del scene.muv_uv_inspection_enabled
+        del scene.muv_uv_inspection_show
+        del scene.muv_uv_inspection_show_overlapped
+        del scene.muv_uv_inspection_show_flipped
+        del scene.muv_uv_inspection_show_mode
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_UVInspection_Render(bpy.types.Operator):
+    """
+    Operation class: Render UV Inspection
+    No operation (only rendering)
+    """
+
+    bl_idname = "uv.muv_uv_inspection_operator_render"
+    bl_description = "Render overlapped/flipped UVs"
+    bl_label = "Overlapped/Flipped UV renderer"
+
+    __handle = None
+
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return False
+        return is_valid_context(context)
+
+    @classmethod
+    def is_running(cls, _):
+        return 1 if cls.__handle else 0
+
+    @classmethod
+    def handle_add(cls, obj, context):
+        sie = bpy.types.SpaceImageEditor
+        cls.__handle = sie.draw_handler_add(
+            MUV_OT_UVInspection_Render.draw, (obj, context),
+            'WINDOW', 'POST_PIXEL')
+
+    @classmethod
+    def handle_remove(cls):
+        if cls.__handle is not None:
+            bpy.types.SpaceImageEditor.draw_handler_remove(
+                cls.__handle, 'WINDOW')
+            cls.__handle = None
+
+    @staticmethod
+    def draw(_, context):
+        sc = context.scene
+        props = sc.muv_props.uv_inspection
+        prefs = context.user_preferences.addons["uv_magic_uv"].preferences
+
+        if not MUV_OT_UVInspection_Render.is_running(context):
+            return
+
+        # OpenGL configuration
+        bgl.glEnable(bgl.GL_BLEND)
+
+        # render overlapped UV
+        if sc.muv_uv_inspection_show_overlapped:
+            color = prefs.uv_inspection_overlapped_color
+            for info in props.overlapped_info:
+                if sc.muv_uv_inspection_show_mode == 'PART':
+                    for poly in info["polygons"]:
+                        bgl.glBegin(bgl.GL_TRIANGLE_FAN)
+                        bgl.glColor4f(color[0], color[1], color[2], color[3])
+                        for uv in poly:
+                            x, y = context.region.view2d.view_to_region(
+                                uv.x, uv.y)
+                            bgl.glVertex2f(x, y)
+                        bgl.glEnd()
+                elif sc.muv_uv_inspection_show_mode == 'FACE':
+                    bgl.glBegin(bgl.GL_TRIANGLE_FAN)
+                    bgl.glColor4f(color[0], color[1], color[2], color[3])
+                    for uv in info["subject_uvs"]:
+                        x, y = context.region.view2d.view_to_region(uv.x, uv.y)
+                        bgl.glVertex2f(x, y)
+                    bgl.glEnd()
+
+        # render flipped UV
+        if sc.muv_uv_inspection_show_flipped:
+            color = prefs.uv_inspection_flipped_color
+            for info in props.flipped_info:
+                if sc.muv_uv_inspection_show_mode == 'PART':
+                    for poly in info["polygons"]:
+                        bgl.glBegin(bgl.GL_TRIANGLE_FAN)
+                        bgl.glColor4f(color[0], color[1], color[2], color[3])
+                        for uv in poly:
+                            x, y = context.region.view2d.view_to_region(
+                                uv.x, uv.y)
+                            bgl.glVertex2f(x, y)
+                        bgl.glEnd()
+                elif sc.muv_uv_inspection_show_mode == 'FACE':
+                    bgl.glBegin(bgl.GL_TRIANGLE_FAN)
+                    bgl.glColor4f(color[0], color[1], color[2], color[3])
+                    for uv in info["uvs"]:
+                        x, y = context.region.view2d.view_to_region(uv.x, uv.y)
+                        bgl.glVertex2f(x, y)
+                    bgl.glEnd()
+
+    def invoke(self, context, _):
+        if not MUV_OT_UVInspection_Render.is_running(context):
+            update_uvinsp_info(context)
+            MUV_OT_UVInspection_Render.handle_add(self, context)
+        else:
+            MUV_OT_UVInspection_Render.handle_remove()
+
+        if context.area:
+            context.area.tag_redraw()
+
+        return {'FINISHED'}
+
+
+def update_uvinsp_info(context):
+    sc = context.scene
+    props = sc.muv_props.uv_inspection
+
+    obj = context.active_object
+    bm = bmesh.from_edit_mesh(obj.data)
+    if common.check_version(2, 73, 0) >= 0:
+        bm.faces.ensure_lookup_table()
+    uv_layer = bm.loops.layers.uv.verify()
+
+    if context.tool_settings.use_uv_select_sync:
+        sel_faces = [f for f in bm.faces]
+    else:
+        sel_faces = [f for f in bm.faces if f.select]
+    props.overlapped_info = common.get_overlapped_uv_info(
+        bm, sel_faces, uv_layer, sc.muv_uv_inspection_show_mode)
+    props.flipped_info = common.get_flipped_uv_info(sel_faces, uv_layer)
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_UVInspection_Update(bpy.types.Operator):
+    """
+    Operation class: Update
+    """
+
+    bl_idname = "uv.muv_uv_inspection_operator_update"
+    bl_label = "Update UV Inspection"
+    bl_description = "Update UV Inspection"
+    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
+        if not MUV_OT_UVInspection_Render.is_running(context):
+            return False
+        return is_valid_context(context)
+
+    def execute(self, context):
+        update_uvinsp_info(context)
+
+        if context.area:
+            context.area.tag_redraw()
+
+        return {'FINISHED'}
diff --git a/uv_magic_uv/op/uv_sculpt.py b/uv_magic_uv/legacy/op/uv_sculpt.py
similarity index 62%
rename from uv_magic_uv/op/uv_sculpt.py
rename to uv_magic_uv/legacy/op/uv_sculpt.py
index 2bf76abd5a6fea4b7cbede13d1a7a024e195f9d9..3754a759c06dcfed49c1f5ad6644b139301d834b 100644
--- a/uv_magic_uv/op/uv_sculpt.py
+++ b/uv_magic_uv/legacy/op/uv_sculpt.py
@@ -20,8 +20,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 from math import pi, cos, tan, sin
 
@@ -32,39 +32,178 @@ from mathutils import Vector
 from bpy_extras import view3d_utils
 from mathutils.bvhtree import BVHTree
 from mathutils.geometry import barycentric_transform
-
-from .. import common
-
-
-class MUV_UVSculptRenderer(bpy.types.Operator):
+from bpy.props import (
+    BoolProperty,
+    IntProperty,
+    EnumProperty,
+    FloatProperty,
+)
+
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_UVSculpt',
+]
+
+
+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
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "uv_sculpt"
+
+    @classmethod
+    def init_props(cls, scene):
+        def get_func(_):
+            return MUV_OT_UVSculpt.is_running(bpy.context)
+
+        def set_func(_, __):
+            pass
+
+        def update_func(_, __):
+            bpy.ops.uv.muv_uv_sculpt_operator('INVOKE_REGION_WIN')
+
+        scene.muv_uv_sculpt_enabled = BoolProperty(
+            name="UV Sculpt",
+            description="UV Sculpt is enabled",
+            default=False
+        )
+        scene.muv_uv_sculpt_enable = BoolProperty(
+            name="UV Sculpt Showed",
+            description="UV Sculpt is enabled",
+            default=False,
+            get=get_func,
+            set=set_func,
+            update=update_func
+        )
+        scene.muv_uv_sculpt_radius = IntProperty(
+            name="Radius",
+            description="Radius of the brush",
+            min=1,
+            max=500,
+            default=30
+        )
+        scene.muv_uv_sculpt_strength = FloatProperty(
+            name="Strength",
+            description="How powerful the effect of the brush when applied",
+            min=0.0,
+            max=1.0,
+            default=0.03,
+        )
+        scene.muv_uv_sculpt_tools = EnumProperty(
+            name="Tools",
+            description="Select Tools for the UV sculpt brushes",
+            items=[
+                ('GRAB', "Grab", "Grab UVs"),
+                ('RELAX', "Relax", "Relax UVs"),
+                ('PINCH', "Pinch", "Pinch UVs")
+            ],
+            default='GRAB'
+        )
+        scene.muv_uv_sculpt_show_brush = BoolProperty(
+            name="Show Brush",
+            description="Show Brush",
+            default=True
+        )
+        scene.muv_uv_sculpt_pinch_invert = BoolProperty(
+            name="Invert",
+            description="Pinch UV to invert direction",
+            default=False
+        )
+        scene.muv_uv_sculpt_relax_method = EnumProperty(
+            name="Method",
+            description="Algorithm used for relaxation",
+            items=[
+                ('HC', "HC", "Use HC method for relaxation"),
+                ('LAPLACIAN', "Laplacian",
+                 "Use laplacian method for relaxation")
+            ],
+            default='HC'
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_uv_sculpt_enabled
+        del scene.muv_uv_sculpt_enable
+        del scene.muv_uv_sculpt_radius
+        del scene.muv_uv_sculpt_strength
+        del scene.muv_uv_sculpt_tools
+        del scene.muv_uv_sculpt_show_brush
+        del scene.muv_uv_sculpt_pinch_invert
+        del scene.muv_uv_sculpt_relax_method
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_UVSculpt(bpy.types.Operator):
     """
-    Operation class: Render Brush
+    Operation class: UV Sculpt in View3D
     """
 
-    bl_idname = "uv.muv_uvsculpt_renderer"
-    bl_label = "Brush Renderer"
-    bl_description = "Brush Renderer in View3D"
+    bl_idname = "uv.muv_uv_sculpt_operator"
+    bl_label = "UV Sculpt"
+    bl_description = "UV Sculpt in View3D"
+    bl_options = {'REGISTER'}
 
     __handle = None
-
-    @staticmethod
-    def handle_add(obj, context):
-        if MUV_UVSculptRenderer.__handle is None:
+    __timer = None
+
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return False
+        return is_valid_context(context)
+
+    @classmethod
+    def is_running(cls, _):
+        return 1 if cls.__handle else 0
+
+    @classmethod
+    def handle_add(cls, obj, context):
+        if not cls.__handle:
             sv = bpy.types.SpaceView3D
-            MUV_UVSculptRenderer.__handle = sv.draw_handler_add(
-                MUV_UVSculptRenderer.draw_brush,
-                (obj, context), "WINDOW", "POST_PIXEL")
+            cls.__handle = sv.draw_handler_add(cls.draw_brush, (obj, context),
+                                               "WINDOW", "POST_PIXEL")
+        if not cls.__timer:
+            cls.__timer = context.window_manager.event_timer_add(
+                0.1, context.window)
+            context.window_manager.modal_handler_add(obj)
 
-    @staticmethod
-    def handle_remove():
-        if MUV_UVSculptRenderer.__handle is not None:
+    @classmethod
+    def handle_remove(cls, context):
+        if cls.__handle:
             sv = bpy.types.SpaceView3D
-            sv.draw_handler_remove(
-                MUV_UVSculptRenderer.__handle, "WINDOW")
-            MUV_UVSculptRenderer.__handle = None
-
-    @staticmethod
-    def draw_brush(obj, context):
+            sv.draw_handler_remove(cls.__handle, "WINDOW")
+            cls.__handle = None
+        if cls.__timer:
+            context.window_manager.event_timer_remove(cls.__timer)
+            cls.__timer = None
+
+    @classmethod
+    def draw_brush(cls, obj, context):
         sc = context.scene
         prefs = context.user_preferences.addons["uv_magic_uv"].preferences
 
@@ -72,12 +211,12 @@ class MUV_UVSculptRenderer(bpy.types.Operator):
         theta = 2 * pi / num_segment
         fact_t = tan(theta)
         fact_r = cos(theta)
-        color = prefs.uvsculpt_brush_color
+        color = prefs.uv_sculpt_brush_color
 
         bgl.glBegin(bgl.GL_LINE_STRIP)
         bgl.glColor4f(color[0], color[1], color[2], color[3])
-        x = sc.muv_uvsculpt_radius * cos(0.0)
-        y = sc.muv_uvsculpt_radius * sin(0.0)
+        x = sc.muv_uv_sculpt_radius * cos(0.0)
+        y = sc.muv_uv_sculpt_radius * sin(0.0)
         for _ in range(num_segment):
             bgl.glVertex2f(x + obj.current_mco.x, y + obj.current_mco.y)
             tx = -y
@@ -88,19 +227,7 @@ class MUV_UVSculptRenderer(bpy.types.Operator):
             y = y * fact_r
         bgl.glEnd()
 
-
-class MUV_UVSculptOps(bpy.types.Operator):
-    """
-    Operation class: UV Sculpt in View3D
-    """
-
-    bl_idname = "uv.muv_uvsculpt_ops"
-    bl_label = "UV Sculpt"
-    bl_description = "UV Sculpt in View3D"
-    bl_options = {'REGISTER'}
-
     def __init__(self):
-        self.__timer = None
         self.__loop_info = []
         self.__stroking = False
         self.current_mco = Vector((0.0, 0.0))
@@ -137,7 +264,7 @@ class MUV_UVSculptOps(bpy.types.Operator):
                 loc_2d = view3d_utils.location_3d_to_region_2d(
                     region, space.region_3d, world_mat * l.vert.co)
                 diff = loc_2d - self.__initial_mco
-                if diff.length < sc.muv_uvsculpt_radius:
+                if diff.length < sc.muv_uv_sculpt_radius:
                     info = {
                         "face_idx": f.index,
                         "loop_idx": i,
@@ -145,8 +272,8 @@ class MUV_UVSculptOps(bpy.types.Operator):
                         "initial_vco_2d": loc_2d,
                         "initial_uv": l[uv_layer].uv.copy(),
                         "strength": self.__get_strength(
-                            diff.length, sc.muv_uvsculpt_radius,
-                            sc.muv_uvsculpt_strength)
+                            diff.length, sc.muv_uv_sculpt_radius,
+                            sc.muv_uv_sculpt_strength)
                     }
                     self.__loop_info.append(info)
 
@@ -158,13 +285,13 @@ class MUV_UVSculptOps(bpy.types.Operator):
         uv_layer = bm.loops.layers.uv.verify()
         mco = self.current_mco
 
-        if sc.muv_uvsculpt_tools == 'GRAB':
+        if sc.muv_uv_sculpt_tools == 'GRAB':
             for info in self.__loop_info:
                 diff_uv = (mco - self.__initial_mco) * info["strength"]
                 l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
                 l[uv_layer].uv = info["initial_uv"] + diff_uv / 100.0
 
-        elif sc.muv_uvsculpt_tools == 'PINCH':
+        elif sc.muv_uv_sculpt_tools == 'PINCH':
             _, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
             loop_info = []
             for f in bm.faces:
@@ -174,7 +301,7 @@ class MUV_UVSculptOps(bpy.types.Operator):
                     loc_2d = view3d_utils.location_3d_to_region_2d(
                         region, space.region_3d, world_mat * l.vert.co)
                     diff = loc_2d - self.__initial_mco
-                    if diff.length < sc.muv_uvsculpt_radius:
+                    if diff.length < sc.muv_uv_sculpt_radius:
                         info = {
                             "face_idx": f.index,
                             "loop_idx": i,
@@ -182,8 +309,8 @@ class MUV_UVSculptOps(bpy.types.Operator):
                             "initial_vco_2d": loc_2d,
                             "initial_uv": l[uv_layer].uv.copy(),
                             "strength": self.__get_strength(
-                                diff.length, sc.muv_uvsculpt_radius,
-                                sc.muv_uvsculpt_strength)
+                                diff.length, sc.muv_uv_sculpt_radius,
+                                sc.muv_uv_sculpt_strength)
                         }
                         loop_info.append(info)
 
@@ -215,13 +342,13 @@ class MUV_UVSculptOps(bpy.types.Operator):
             # move to target UV coordinate
             for info in loop_info:
                 l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
-                if sc.muv_uvsculpt_pinch_invert:
+                if sc.muv_uv_sculpt_pinch_invert:
                     diff_uv = (l[uv_layer].uv - target_uv) * info["strength"]
                 else:
                     diff_uv = (target_uv - l[uv_layer].uv) * info["strength"]
                 l[uv_layer].uv = l[uv_layer].uv + diff_uv / 10.0
 
-        elif sc.muv_uvsculpt_tools == 'RELAX':
+        elif sc.muv_uv_sculpt_tools == 'RELAX':
             _, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
 
             # get vertex and loop relation
@@ -265,19 +392,19 @@ class MUV_UVSculptOps(bpy.types.Operator):
                     loc_2d = view3d_utils.location_3d_to_region_2d(
                         region, space.region_3d, world_mat * l.vert.co)
                     diff = loc_2d - self.__initial_mco
-                    if diff.length >= sc.muv_uvsculpt_radius:
+                    if diff.length >= sc.muv_uv_sculpt_radius:
                         continue
                     db = vert_db[l.vert]
                     strength = self.__get_strength(diff.length,
-                                                   sc.muv_uvsculpt_radius,
-                                                   sc.muv_uvsculpt_strength)
+                                                   sc.muv_uv_sculpt_radius,
+                                                   sc.muv_uv_sculpt_strength)
 
                     base = (1.0 - strength) * l[uv_layer].uv
-                    if sc.muv_uvsculpt_relax_method == 'HC':
+                    if sc.muv_uv_sculpt_relax_method == 'HC':
                         t = 0.5 * (db["uv_b"] + db["uv_sum_b"] / d["uv_count"])
                         diff = strength * (db["uv_p"] - t)
                         target_uv = base + diff
-                    elif sc.muv_uvsculpt_relax_method == 'LAPLACIAN':
+                    elif sc.muv_uv_sculpt_relax_method == 'LAPLACIAN':
                         diff = strength * db["uv_p"]
                         target_uv = base + diff
                     else:
@@ -294,7 +421,7 @@ class MUV_UVSculptOps(bpy.types.Operator):
         uv_layer = bm.loops.layers.uv.verify()
         mco = self.current_mco
 
-        if sc.muv_uvsculpt_tools == 'GRAB':
+        if sc.muv_uv_sculpt_tools == 'GRAB':
             for info in self.__loop_info:
                 diff_uv = (mco - self.__initial_mco) * info["strength"]
                 l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
@@ -303,23 +430,24 @@ class MUV_UVSculptOps(bpy.types.Operator):
         bmesh.update_edit_mesh(obj.data)
 
     def modal(self, context, event):
-        props = context.scene.muv_props.uvsculpt
-
         if context.area:
             context.area.tag_redraw()
 
-        if not props.running:
-            if self.__timer is not None:
-                MUV_UVSculptRenderer.handle_remove()
-                context.window_manager.event_timer_remove(self.__timer)
-                self.__timer = None
+        if not MUV_OT_UVSculpt.is_running(context):
+            MUV_OT_UVSculpt.handle_remove(context)
+
             return {'FINISHED'}
 
         self.current_mco = Vector((event.mouse_region_x, event.mouse_region_y))
-        area, _, _ = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
 
-        if self.current_mco.x < 0 or self.current_mco.x > area.width or \
-           self.current_mco.y < 0 or self.current_mco.y > area.height:
+        region_types = [
+            'HEADER',
+            'UI',
+            'TOOLS',
+            'TOOL_PROPS',
+        ]
+        if not common.mouse_on_area(event, 'VIEW_3D') or \
+           common.mouse_on_regions(event, 'VIEW_3D', region_types):
             return {'PASS_THROUGH'}
 
         if event.type == 'LEFTMOUSE':
@@ -331,30 +459,25 @@ class MUV_UVSculptOps(bpy.types.Operator):
                 if self.__stroking:
                     self.__stroke_exit(context, event)
                 self.__stroking = False
+            return {'RUNNING_MODAL'}
         elif event.type == 'MOUSEMOVE':
             if self.__stroking:
                 self.__stroke_apply(context, event)
+            return {'RUNNING_MODAL'}
         elif event.type == 'TIMER':
             if self.__stroking:
                 self.__stroke_apply(context, event)
+            return {'RUNNING_MODAL'}
 
-        return {'RUNNING_MODAL'}
+        return {'PASS_THROUGH'}
 
     def invoke(self, context, _):
-        props = context.scene.muv_props.uvsculpt
-
         if context.area:
             context.area.tag_redraw()
 
-        if props.running:
-            props.running = False
-            return {'FINISHED'}
-
-        props.running = True
-        if self.__timer is None:
-            self.__timer = context.window_manager.event_timer_add(
-                0.1, context.window)
-            context.window_manager.modal_handler_add(self)
-            MUV_UVSculptRenderer.handle_add(self, context)
+        if MUV_OT_UVSculpt.is_running(context):
+            MUV_OT_UVSculpt.handle_remove(context)
+        else:
+            MUV_OT_UVSculpt.handle_add(self, context)
 
         return {'RUNNING_MODAL'}
diff --git a/uv_magic_uv/legacy/op/uvw.py b/uv_magic_uv/legacy/op/uvw.py
new file mode 100644
index 0000000000000000000000000000000000000000..56777b18aa18f1a451a0bcbafb42a31ead56012d
--- /dev/null
+++ b/uv_magic_uv/legacy/op/uvw.py
@@ -0,0 +1,181 @@
+# <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__ = "Alexander Milovsky, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+import bmesh
+from bpy.props import (
+    FloatProperty,
+    FloatVectorProperty,
+    BoolProperty
+)
+
+from ... import common
+from ...impl import uvw_impl as impl
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_UVW_BoxMap',
+    'MUV_OT_UVW_BestPlanerMap',
+]
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "uvw"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_uvw_enabled = BoolProperty(
+            name="UVW Enabled",
+            description="UVW is enabled",
+            default=False
+        )
+        scene.muv_uvw_assign_uvmap = BoolProperty(
+            name="Assign UVMap",
+            description="Assign UVMap when no UVmaps are available",
+            default=True
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_uvw_enabled
+        del scene.muv_uvw_assign_uvmap
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_UVW_BoxMap(bpy.types.Operator):
+    bl_idname = "uv.muv_uvw_operator_box_map"
+    bl_label = "Box Map"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    size = FloatProperty(
+        name="Size",
+        default=1.0,
+        precision=4
+    )
+    rotation = FloatVectorProperty(
+        name="XYZ Rotation",
+        size=3,
+        default=(0.0, 0.0, 0.0)
+    )
+    offset = FloatVectorProperty(
+        name="XYZ Offset",
+        size=3,
+        default=(0.0, 0.0, 0.0)
+    )
+    tex_aspect = FloatProperty(
+        name="Texture Aspect",
+        default=1.0,
+        precision=4
+    )
+    assign_uvmap = BoolProperty(
+        name="Assign UVMap",
+        description="Assign UVMap when no UVmaps are available",
+        default=True
+    )
+
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return impl.is_valid_context(context)
+
+    def execute(self, context):
+        obj = context.active_object
+        bm = bmesh.from_edit_mesh(obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.faces.ensure_lookup_table()
+
+        # get UV layer
+        uv_layer = impl.get_uv_layer(self, bm, self.assign_uvmap)
+        if not uv_layer:
+            return {'CANCELLED'}
+
+        impl.apply_box_map(bm, uv_layer, self.size, self.offset,
+                           self.rotation, self.tex_aspect)
+        bmesh.update_edit_mesh(obj.data)
+
+        return {'FINISHED'}
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_UVW_BestPlanerMap(bpy.types.Operator):
+    bl_idname = "uv.muv_uvw_operator_best_planer_map"
+    bl_label = "Best Planer Map"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    size = FloatProperty(
+        name="Size",
+        default=1.0,
+        precision=4
+    )
+    rotation = FloatProperty(
+        name="XY Rotation",
+        default=0.0
+    )
+    offset = FloatVectorProperty(
+        name="XY Offset",
+        size=2,
+        default=(0.0, 0.0)
+    )
+    tex_aspect = FloatProperty(
+        name="Texture Aspect",
+        default=1.0,
+        precision=4
+    )
+    assign_uvmap = BoolProperty(
+        name="Assign UVMap",
+        description="Assign UVMap when no UVmaps are available",
+        default=True
+    )
+
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return impl.is_valid_context(context)
+
+    def execute(self, context):
+        obj = context.active_object
+        bm = bmesh.from_edit_mesh(obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.faces.ensure_lookup_table()
+
+        # get UV layer
+        uv_layer = impl.get_uv_layer(self, bm, self.assign_uvmap)
+        if not uv_layer:
+            return {'CANCELLED'}
+
+        impl.apply_planer_map(bm, uv_layer, self.size, self.offset,
+                              self.rotation, self.tex_aspect)
+
+        bmesh.update_edit_mesh(obj.data)
+
+        return {'FINISHED'}
diff --git a/uv_magic_uv/legacy/op/world_scale_uv.py b/uv_magic_uv/legacy/op/world_scale_uv.py
new file mode 100644
index 0000000000000000000000000000000000000000..e56b6bfae3c4ca2c4f6cc8b31cce48103ceb556e
--- /dev/null
+++ b/uv_magic_uv/legacy/op/world_scale_uv.py
@@ -0,0 +1,655 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "McBuff, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+from math import sqrt
+
+import bpy
+import bmesh
+from mathutils import Vector
+from bpy.props import (
+    EnumProperty,
+    FloatProperty,
+    IntVectorProperty,
+    BoolProperty,
+)
+
+from ... import common
+from ...utils.bl_class_registry import BlClassRegistry
+from ...utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_WorldScaleUV_Measure',
+    'MUV_OT_WorldScaleUV_ApplyManual',
+    'MUV_OT_WorldScaleUV_ApplyScalingDensity',
+    'MUV_OT_WorldScaleUV_ApplyProportionalToMesh',
+]
+
+
+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 measure_wsuv_info(obj, tex_size=None):
+    mesh_area = common.measure_mesh_area(obj)
+    uv_area = common.measure_uv_area(obj, tex_size)
+
+    if not uv_area:
+        return None, mesh_area, None
+
+    if mesh_area == 0.0:
+        density = 0.0
+    else:
+        density = sqrt(uv_area) / sqrt(mesh_area)
+
+    return uv_area, mesh_area, density
+
+
+@PropertyClassRegistry(legacy=True)
+class Properties:
+    idname = "world_scale_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_world_scale_uv_enabled = BoolProperty(
+            name="World Scale UV Enabled",
+            description="World Scale UV is enabled",
+            default=False
+        )
+        scene.muv_world_scale_uv_src_mesh_area = FloatProperty(
+            name="Mesh Area",
+            description="Source Mesh Area",
+            default=0.0,
+            min=0.0
+        )
+        scene.muv_world_scale_uv_src_uv_area = FloatProperty(
+            name="UV Area",
+            description="Source UV Area",
+            default=0.0,
+            min=0.0
+        )
+        scene.muv_world_scale_uv_src_density = FloatProperty(
+            name="Density",
+            description="Source Texel Density",
+            default=0.0,
+            min=0.0
+        )
+        scene.muv_world_scale_uv_tgt_density = FloatProperty(
+            name="Density",
+            description="Target Texel Density",
+            default=0.0,
+            min=0.0
+        )
+        scene.muv_world_scale_uv_tgt_scaling_factor = FloatProperty(
+            name="Scaling Factor",
+            default=1.0,
+            max=1000.0,
+            min=0.00001
+        )
+        scene.muv_world_scale_uv_tgt_texture_size = IntVectorProperty(
+            name="Texture Size",
+            size=2,
+            min=1,
+            soft_max=10240,
+            default=(1024, 1024),
+        )
+        scene.muv_world_scale_uv_mode = EnumProperty(
+            name="Mode",
+            description="Density calculation mode",
+            items=[
+                ('PROPORTIONAL_TO_MESH', "Proportional to Mesh",
+                 "Apply density proportionaled by mesh size"),
+                ('SCALING_DENSITY', "Scaling Density",
+                 "Apply scaled density from source"),
+                ('SAME_DENSITY', "Same Density",
+                 "Apply same density of source"),
+                ('MANUAL', "Manual", "Specify density and size by manual"),
+            ],
+            default='MANUAL'
+        )
+        scene.muv_world_scale_uv_origin = EnumProperty(
+            name="Origin",
+            description="Aspect Origin",
+            items=[
+                ('CENTER', "Center", "Center"),
+                ('LEFT_TOP', "Left Top", "Left Bottom"),
+                ('LEFT_CENTER', "Left Center", "Left Center"),
+                ('LEFT_BOTTOM', "Left Bottom", "Left Bottom"),
+                ('CENTER_TOP', "Center Top", "Center Top"),
+                ('CENTER_BOTTOM', "Center Bottom", "Center Bottom"),
+                ('RIGHT_TOP', "Right Top", "Right Top"),
+                ('RIGHT_CENTER', "Right Center", "Right Center"),
+                ('RIGHT_BOTTOM', "Right Bottom", "Right Bottom")
+
+            ],
+            default='CENTER'
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_world_scale_uv_enabled
+        del scene.muv_world_scale_uv_src_mesh_area
+        del scene.muv_world_scale_uv_src_uv_area
+        del scene.muv_world_scale_uv_src_density
+        del scene.muv_world_scale_uv_tgt_density
+        del scene.muv_world_scale_uv_tgt_scaling_factor
+        del scene.muv_world_scale_uv_mode
+        del scene.muv_world_scale_uv_origin
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_WorldScaleUV_Measure(bpy.types.Operator):
+    """
+    Operation class: Measure face size
+    """
+
+    bl_idname = "uv.muv_world_scale_uv_operator_measure"
+    bl_label = "Measure World Scale UV"
+    bl_description = "Measure face size for scale calculation"
+    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
+        obj = context.active_object
+
+        uv_area, mesh_area, density = measure_wsuv_info(obj)
+        if not uv_area:
+            self.report({'WARNING'},
+                        "Object must have more than one UV map and texture")
+            return {'CANCELLED'}
+
+        sc.muv_world_scale_uv_src_uv_area = uv_area
+        sc.muv_world_scale_uv_src_mesh_area = mesh_area
+        sc.muv_world_scale_uv_src_density = density
+
+        self.report({'INFO'},
+                    "UV Area: {0}, Mesh Area: {1}, Texel Density: {2}"
+                    .format(uv_area, mesh_area, density))
+
+        return {'FINISHED'}
+
+
+def apply(obj, origin, factor):
+    bm = bmesh.from_edit_mesh(obj.data)
+    if common.check_version(2, 73, 0) >= 0:
+        bm.verts.ensure_lookup_table()
+        bm.edges.ensure_lookup_table()
+        bm.faces.ensure_lookup_table()
+
+    sel_faces = [f for f in bm.faces if f.select]
+
+    uv_layer = bm.loops.layers.uv.verify()
+
+    # calculate origin
+    if origin == 'CENTER':
+        origin = Vector((0.0, 0.0))
+        num = 0
+        for f in sel_faces:
+            for l in f.loops:
+                uv = l[uv_layer].uv
+                origin = origin + uv
+                num = num + 1
+        origin = origin / num
+    elif origin == 'LEFT_TOP':
+        origin = Vector((100000.0, -100000.0))
+        for f in sel_faces:
+            for l in f.loops:
+                uv = l[uv_layer].uv
+                origin.x = min(origin.x, uv.x)
+                origin.y = max(origin.y, uv.y)
+    elif origin == 'LEFT_CENTER':
+        origin = Vector((100000.0, 0.0))
+        num = 0
+        for f in sel_faces:
+            for l in f.loops:
+                uv = l[uv_layer].uv
+                origin.x = min(origin.x, uv.x)
+                origin.y = origin.y + uv.y
+                num = num + 1
+        origin.y = origin.y / num
+    elif origin == 'LEFT_BOTTOM':
+        origin = Vector((100000.0, 100000.0))
+        for f in sel_faces:
+            for l in f.loops:
+                uv = l[uv_layer].uv
+                origin.x = min(origin.x, uv.x)
+                origin.y = min(origin.y, uv.y)
+    elif origin == 'CENTER_TOP':
+        origin = Vector((0.0, -100000.0))
+        num = 0
+        for f in sel_faces:
+            for l in f.loops:
+                uv = l[uv_layer].uv
+                origin.x = origin.x + uv.x
+                origin.y = max(origin.y, uv.y)
+                num = num + 1
+        origin.x = origin.x / num
+    elif origin == 'CENTER_BOTTOM':
+        origin = Vector((0.0, 100000.0))
+        num = 0
+        for f in sel_faces:
+            for l in f.loops:
+                uv = l[uv_layer].uv
+                origin.x = origin.x + uv.x
+                origin.y = min(origin.y, uv.y)
+                num = num + 1
+        origin.x = origin.x / num
+    elif origin == 'RIGHT_TOP':
+        origin = Vector((-100000.0, -100000.0))
+        for f in sel_faces:
+            for l in f.loops:
+                uv = l[uv_layer].uv
+                origin.x = max(origin.x, uv.x)
+                origin.y = max(origin.y, uv.y)
+    elif origin == 'RIGHT_CENTER':
+        origin = Vector((-100000.0, 0.0))
+        num = 0
+        for f in sel_faces:
+            for l in f.loops:
+                uv = l[uv_layer].uv
+                origin.x = max(origin.x, uv.x)
+                origin.y = origin.y + uv.y
+                num = num + 1
+        origin.y = origin.y / num
+    elif origin == 'RIGHT_BOTTOM':
+        origin = Vector((-100000.0, 100000.0))
+        for f in sel_faces:
+            for l in f.loops:
+                uv = l[uv_layer].uv
+                origin.x = max(origin.x, uv.x)
+                origin.y = min(origin.y, uv.y)
+
+    # update UV coordinate
+    for f in sel_faces:
+        for l in f.loops:
+            uv = l[uv_layer].uv
+            diff = uv - origin
+            l[uv_layer].uv = origin + diff * factor
+
+    bmesh.update_edit_mesh(obj.data)
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_WorldScaleUV_ApplyManual(bpy.types.Operator):
+    """
+    Operation class: Apply scaled UV (Manual)
+    """
+
+    bl_idname = "uv.muv_world_scale_uv_operator_apply_manual"
+    bl_label = "Apply World Scale UV (Manual)"
+    bl_description = "Apply scaled UV based on user specification"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    tgt_density = FloatProperty(
+        name="Density",
+        description="Target Texel Density",
+        default=1.0,
+        min=0.0
+    )
+    tgt_texture_size = IntVectorProperty(
+        name="Texture Size",
+        size=2,
+        min=1,
+        soft_max=10240,
+        default=(1024, 1024),
+    )
+    origin = EnumProperty(
+        name="Origin",
+        description="Aspect Origin",
+        items=[
+            ('CENTER', "Center", "Center"),
+            ('LEFT_TOP', "Left Top", "Left Bottom"),
+            ('LEFT_CENTER', "Left Center", "Left Center"),
+            ('LEFT_BOTTOM', "Left Bottom", "Left Bottom"),
+            ('CENTER_TOP', "Center Top", "Center Top"),
+            ('CENTER_BOTTOM', "Center Bottom", "Center Bottom"),
+            ('RIGHT_TOP', "Right Top", "Right Top"),
+            ('RIGHT_CENTER', "Right Center", "Right Center"),
+            ('RIGHT_BOTTOM', "Right Bottom", "Right Bottom")
+
+        ],
+        default='CENTER'
+    )
+    show_dialog = BoolProperty(
+        name="Show Diaglog Menu",
+        description="Show dialog menu if true",
+        default=True,
+        options={'HIDDEN', 'SKIP_SAVE'}
+    )
+
+    @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 __apply_manual(self, context):
+        obj = context.active_object
+        bm = bmesh.from_edit_mesh(obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.verts.ensure_lookup_table()
+            bm.edges.ensure_lookup_table()
+            bm.faces.ensure_lookup_table()
+
+        tex_size = self.tgt_texture_size
+        uv_area, _, density = measure_wsuv_info(obj, tex_size)
+        if not uv_area:
+            self.report({'WARNING'},
+                        "Object must have more than one UV map")
+            return {'CANCELLED'}
+
+        tgt_density = self.tgt_density
+        factor = tgt_density / density
+
+        apply(context.active_object, self.origin, factor)
+        self.report({'INFO'}, "Scaling factor: {0}".format(factor))
+
+        return {'FINISHED'}
+
+    def draw(self, _):
+        layout = self.layout
+
+        layout.prop(self, "tgt_density")
+        layout.prop(self, "tgt_texture_size")
+        layout.prop(self, "origin")
+
+        layout.separator()
+
+    def invoke(self, context, _):
+        if self.show_dialog:
+            wm = context.window_manager
+            return wm.invoke_props_dialog(self)
+
+        return self.execute(context)
+
+    def execute(self, context):
+        return self.__apply_manual(context)
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_WorldScaleUV_ApplyScalingDensity(bpy.types.Operator):
+    """
+    Operation class: Apply scaled UV (Scaling Density)
+    """
+
+    bl_idname = "uv.muv_world_scale_uv_operator_apply_scaling_density"
+    bl_label = "Apply World Scale UV (Scaling Density)"
+    bl_description = "Apply scaled UV with scaling density"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    tgt_scaling_factor = FloatProperty(
+        name="Scaling Factor",
+        default=1.0,
+        max=1000.0,
+        min=0.00001
+    )
+    origin = EnumProperty(
+        name="Origin",
+        description="Aspect Origin",
+        items=[
+            ('CENTER', "Center", "Center"),
+            ('LEFT_TOP', "Left Top", "Left Bottom"),
+            ('LEFT_CENTER', "Left Center", "Left Center"),
+            ('LEFT_BOTTOM', "Left Bottom", "Left Bottom"),
+            ('CENTER_TOP', "Center Top", "Center Top"),
+            ('CENTER_BOTTOM', "Center Bottom", "Center Bottom"),
+            ('RIGHT_TOP', "Right Top", "Right Top"),
+            ('RIGHT_CENTER', "Right Center", "Right Center"),
+            ('RIGHT_BOTTOM', "Right Bottom", "Right Bottom")
+
+        ],
+        default='CENTER'
+    )
+    src_density = FloatProperty(
+        name="Density",
+        description="Source Texel Density",
+        default=0.0,
+        min=0.0,
+        options={'HIDDEN'}
+    )
+    same_density = BoolProperty(
+        name="Same Density",
+        description="Apply same density",
+        default=False,
+        options={'HIDDEN'}
+    )
+    show_dialog = BoolProperty(
+        name="Show Diaglog Menu",
+        description="Show dialog menu if true",
+        default=True,
+        options={'HIDDEN', 'SKIP_SAVE'}
+    )
+
+    @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 __apply_scaling_density(self, context):
+        obj = context.active_object
+        bm = bmesh.from_edit_mesh(obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.verts.ensure_lookup_table()
+            bm.edges.ensure_lookup_table()
+            bm.faces.ensure_lookup_table()
+
+        uv_area, _, density = measure_wsuv_info(obj)
+        if not uv_area:
+            self.report({'WARNING'},
+                        "Object must have more than one UV map and texture")
+            return {'CANCELLED'}
+
+        tgt_density = self.src_density * self.tgt_scaling_factor
+        factor = tgt_density / density
+
+        apply(context.active_object, self.origin, factor)
+        self.report({'INFO'}, "Scaling factor: {0}".format(factor))
+
+        return {'FINISHED'}
+
+    def draw(self, _):
+        layout = self.layout
+
+        layout.label("Source:")
+        col = layout.column()
+        col.prop(self, "src_density")
+        col.enabled = False
+
+        layout.separator()
+
+        if not self.same_density:
+            layout.prop(self, "tgt_scaling_factor")
+        layout.prop(self, "origin")
+
+        layout.separator()
+
+    def invoke(self, context, _):
+        sc = context.scene
+
+        if self.show_dialog:
+            wm = context.window_manager
+
+            if self.same_density:
+                self.tgt_scaling_factor = 1.0
+            else:
+                self.tgt_scaling_factor = \
+                    sc.muv_world_scale_uv_tgt_scaling_factor
+            self.src_density = sc.muv_world_scale_uv_src_density
+
+            return wm.invoke_props_dialog(self)
+
+        return self.execute(context)
+
+    def execute(self, context):
+        if self.same_density:
+            self.tgt_scaling_factor = 1.0
+
+        return self.__apply_scaling_density(context)
+
+
+@BlClassRegistry(legacy=True)
+class MUV_OT_WorldScaleUV_ApplyProportionalToMesh(bpy.types.Operator):
+    """
+    Operation class: Apply scaled UV (Proportional to mesh)
+    """
+
+    bl_idname = "uv.muv_world_scale_uv_operator_apply_proportional_to_mesh"
+    bl_label = "Apply World Scale UV (Proportional to mesh)"
+    bl_description = "Apply scaled UV proportionaled to mesh"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    origin = EnumProperty(
+        name="Origin",
+        description="Aspect Origin",
+        items=[
+            ('CENTER', "Center", "Center"),
+            ('LEFT_TOP', "Left Top", "Left Bottom"),
+            ('LEFT_CENTER', "Left Center", "Left Center"),
+            ('LEFT_BOTTOM', "Left Bottom", "Left Bottom"),
+            ('CENTER_TOP', "Center Top", "Center Top"),
+            ('CENTER_BOTTOM', "Center Bottom", "Center Bottom"),
+            ('RIGHT_TOP', "Right Top", "Right Top"),
+            ('RIGHT_CENTER', "Right Center", "Right Center"),
+            ('RIGHT_BOTTOM', "Right Bottom", "Right Bottom")
+
+        ],
+        default='CENTER'
+    )
+    src_density = FloatProperty(
+        name="Source Density",
+        description="Source Texel Density",
+        default=0.0,
+        min=0.0,
+        options={'HIDDEN'}
+    )
+    src_uv_area = FloatProperty(
+        name="Source UV Area",
+        description="Source UV Area",
+        default=0.0,
+        min=0.0,
+        options={'HIDDEN'}
+    )
+    src_mesh_area = FloatProperty(
+        name="Source Mesh Area",
+        description="Source Mesh Area",
+        default=0.0,
+        min=0.0,
+        options={'HIDDEN'}
+    )
+    show_dialog = BoolProperty(
+        name="Show Diaglog Menu",
+        description="Show dialog menu if true",
+        default=True,
+        options={'HIDDEN', 'SKIP_SAVE'}
+    )
+
+    @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 __apply_proportional_to_mesh(self, context):
+        obj = context.active_object
+        bm = bmesh.from_edit_mesh(obj.data)
+        if common.check_version(2, 73, 0) >= 0:
+            bm.verts.ensure_lookup_table()
+            bm.edges.ensure_lookup_table()
+            bm.faces.ensure_lookup_table()
+
+        uv_area, mesh_area, density = measure_wsuv_info(obj)
+        if not uv_area:
+            self.report({'WARNING'},
+                        "Object must have more than one UV map and texture")
+            return {'CANCELLED'}
+
+        tgt_density = self.src_density * sqrt(mesh_area) / sqrt(
+            self.src_mesh_area)
+
+        factor = tgt_density / density
+
+        apply(context.active_object, self.origin, factor)
+        self.report({'INFO'}, "Scaling factor: {0}".format(factor))
+
+        return {'FINISHED'}
+
+    def draw(self, _):
+        layout = self.layout
+
+        layout.label("Source:")
+        col = layout.column(align=True)
+        col.prop(self, "src_density")
+        col.prop(self, "src_uv_area")
+        col.prop(self, "src_mesh_area")
+        col.enabled = False
+
+        layout.separator()
+        layout.prop(self, "origin")
+
+        layout.separator()
+
+    def invoke(self, context, _):
+        if self.show_dialog:
+            wm = context.window_manager
+            sc = context.scene
+
+            self.src_density = sc.muv_world_scale_uv_src_density
+            self.src_mesh_area = sc.muv_world_scale_uv_src_mesh_area
+
+            return wm.invoke_props_dialog(self)
+
+        return self.execute(context)
+
+    def execute(self, context):
+        return self.__apply_proportional_to_mesh(context)
diff --git a/uv_magic_uv/legacy/preferences.py b/uv_magic_uv/legacy/preferences.py
new file mode 100644
index 0000000000000000000000000000000000000000..931cc1d49152e0eb2af3f06ba0591f1eb0f18f4a
--- /dev/null
+++ b/uv_magic_uv/legacy/preferences.py
@@ -0,0 +1,468 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+from bpy.props import (
+    FloatProperty,
+    FloatVectorProperty,
+    BoolProperty,
+    EnumProperty,
+    IntProperty,
+)
+from bpy.types import AddonPreferences
+
+from . import op
+from . import ui
+from .. import addon_updater_ops
+from ..utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'add_builtin_menu',
+    'remove_builtin_menu',
+    'Preferences'
+]
+
+
+def view3d_uvmap_menu_fn(self, context):
+    layout = self.layout
+    sc = context.scene
+
+    layout.separator()
+    layout.label(text="Copy/Paste UV", icon='IMAGE_COL')
+    # Copy/Paste UV
+    layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_CopyPasteUV.bl_idname,
+                text="Copy/Paste UV")
+    # Transfer UV
+    layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_TransferUV.bl_idname,
+                text="Transfer UV")
+
+    layout.separator()
+    layout.label("UV Manipulation", icon='IMAGE_COL')
+    # Flip/Rotate UV
+    ops = layout.operator(op.flip_rotate_uv.MUV_OT_FlipRotate.bl_idname,
+                          text="Flip/Rotate UV")
+    ops.seams = sc.muv_flip_rotate_uv_seams
+    # Mirror UV
+    ops = layout.operator(op.mirror_uv.MUV_OT_MirrorUV.bl_idname,
+                          text="Mirror UV")
+    ops.axis = sc.muv_mirror_uv_axis
+    # Move UV
+    layout.operator(op.move_uv.MUV_OT_MoveUV.bl_idname, text="Move UV")
+    # World Scale UV
+    layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_WorldScaleUV.bl_idname,
+                text="World Scale UV")
+    # Preserve UV
+    layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_PreserveUVAspect.bl_idname,
+                text="Preserve UV")
+    # Texture Lock
+    layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_TextureLock.bl_idname,
+                text="Texture Lock")
+    # Texture Wrap
+    layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_TextureWrap.bl_idname,
+                text="Texture Wrap")
+    # UV Sculpt
+    layout.prop(sc, "muv_uv_sculpt_enable", text="UV Sculpt")
+
+    layout.separator()
+    layout.label("UV Mapping", icon='IMAGE_COL')
+    # Unwrap Constraint
+    ops = layout.operator(
+        op.unwrap_constraint.MUV_OT_UnwrapConstraint.bl_idname,
+        text="Unwrap Constraint")
+    ops.u_const = sc.muv_unwrap_constraint_u_const
+    ops.v_const = sc.muv_unwrap_constraint_v_const
+    # Texture Projection
+    layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_TextureProjection.bl_idname,
+                text="Texture Projection")
+    # UVW
+    layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_UVW.bl_idname, text="UVW")
+
+
+def view3d_object_menu_fn(self, _):
+    layout = self.layout
+
+    layout.separator()
+    # Copy/Paste UV (Among Objecct)
+    layout.menu(ui.VIEW3D_MT_object.MUV_MT_CopyPasteUV_Object.bl_idname,
+                text="Copy/Paste UV")
+    layout.label("Copy/Paste UV", icon='IMAGE_COL')
+
+
+def image_uvs_menu_fn(self, context):
+    layout = self.layout
+    sc = context.scene
+
+    layout.separator()
+    # Align UV Cursor
+    layout.menu(ui.IMAGE_MT_uvs.MUV_MT_AlignUVCursor.bl_idname,
+                text="Align UV Cursor")
+    # UV Bounding Box
+    layout.prop(sc, "muv_uv_bounding_box_show", text="UV Bounding Box")
+    # UV Inspection
+    layout.menu(ui.IMAGE_MT_uvs.MUV_MT_UVInspection.bl_idname,
+                text="UV Inspection")
+    layout.label("Editor Enhancement", icon='IMAGE_COL')
+
+    layout.separator()
+    # Align UV
+    layout.menu(ui.IMAGE_MT_uvs.MUV_MT_AlignUV.bl_idname, text="Align UV")
+    # Smooth UV
+    ops = layout.operator(op.smooth_uv.MUV_OT_SmoothUV.bl_idname,
+                          text="Smooth")
+    ops.transmission = sc.muv_smooth_uv_transmission
+    ops.select = sc.muv_smooth_uv_select
+    ops.mesh_infl = sc.muv_smooth_uv_mesh_infl
+    # Select UV
+    layout.menu(ui.IMAGE_MT_uvs.MUV_MT_SelectUV.bl_idname, text="Select UV")
+    # Pack UV
+    ops = layout.operator(op.pack_uv.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
+    layout.label("UV Manipulation", icon='IMAGE_COL')
+
+    layout.separator()
+    # Copy/Paste UV (on UV/Image Editor)
+    layout.menu(ui.IMAGE_MT_uvs.MUV_MT_CopyPasteUV_UVEdit.bl_idname,
+                text="Copy/Paste UV")
+    layout.label("Copy/Paste UV", icon='IMAGE_COL')
+
+
+def add_builtin_menu():
+    bpy.types.VIEW3D_MT_uv_map.append(view3d_uvmap_menu_fn)
+    bpy.types.VIEW3D_MT_object.append(view3d_object_menu_fn)
+    bpy.types.IMAGE_MT_uvs.append(image_uvs_menu_fn)
+
+
+def remove_builtin_menu():
+    bpy.types.IMAGE_MT_uvs.remove(image_uvs_menu_fn)
+    bpy.types.VIEW3D_MT_uv_map.remove(view3d_uvmap_menu_fn)
+    bpy.types.VIEW3D_MT_object.remove(view3d_object_menu_fn)
+
+
+@BlClassRegistry(legacy=True)
+class Preferences(AddonPreferences):
+    """Preferences class: Preferences for this add-on"""
+
+    bl_idname = "uv_magic_uv"
+
+    def update_enable_builtin_menu(self, _):
+        if self['enable_builtin_menu']:
+            add_builtin_menu()
+        else:
+            remove_builtin_menu()
+
+    # enable to add features to built-in menu
+    enable_builtin_menu = BoolProperty(
+        name="Built-in Menu",
+        description="Enable built-in menu",
+        default=True,
+        update=update_enable_builtin_menu
+    )
+
+    # for UV Sculpt
+    uv_sculpt_brush_color = FloatVectorProperty(
+        name="Color",
+        description="Color",
+        default=(1.0, 0.4, 0.4, 1.0),
+        min=0.0,
+        max=1.0,
+        size=4,
+        subtype='COLOR'
+    )
+
+    # for Overlapped UV
+    uv_inspection_overlapped_color = FloatVectorProperty(
+        name="Color",
+        description="Color",
+        default=(0.0, 0.0, 1.0, 0.3),
+        min=0.0,
+        max=1.0,
+        size=4,
+        subtype='COLOR'
+    )
+
+    # for Flipped UV
+    uv_inspection_flipped_color = FloatVectorProperty(
+        name="Color",
+        description="Color",
+        default=(1.0, 0.0, 0.0, 0.3),
+        min=0.0,
+        max=1.0,
+        size=4,
+        subtype='COLOR'
+    )
+
+    # for Texture Projection
+    texture_projection_canvas_padding = FloatVectorProperty(
+        name="Canvas Padding",
+        description="Canvas Padding",
+        size=2,
+        max=50.0,
+        min=0.0,
+        default=(20.0, 20.0))
+
+    # for UV Bounding Box
+    uv_bounding_box_cp_size = FloatProperty(
+        name="Size",
+        description="Control Point Size",
+        default=6.0,
+        min=3.0,
+        max=100.0)
+    uv_bounding_box_cp_react_size = FloatProperty(
+        name="React Size",
+        description="Size event fired",
+        default=10.0,
+        min=3.0,
+        max=100.0)
+
+    # for UI
+    category = EnumProperty(
+        name="Category",
+        description="Preferences Category",
+        items=[
+            ('INFO', "Information", "Information about this add-on"),
+            ('CONFIG', "Configuration", "Configuration about this add-on"),
+            ('UPDATE', "Update", "Update this add-on"),
+        ],
+        default='INFO'
+    )
+    info_desc_expanded = BoolProperty(
+        name="Description",
+        description="Description",
+        default=False
+    )
+    info_loc_expanded = BoolProperty(
+        name="Location",
+        description="Location",
+        default=False
+    )
+    conf_uv_sculpt_expanded = BoolProperty(
+        name="UV Sculpt",
+        description="UV Sculpt",
+        default=False
+    )
+    conf_uv_inspection_expanded = BoolProperty(
+        name="UV Inspection",
+        description="UV Inspection",
+        default=False
+    )
+    conf_texture_projection_expanded = BoolProperty(
+        name="Texture Projection",
+        description="Texture Projection",
+        default=False
+    )
+    conf_uv_bounding_box_expanded = BoolProperty(
+        name="UV Bounding Box",
+        description="UV Bounding Box",
+        default=False
+    )
+
+    # for add-on updater
+    auto_check_update = BoolProperty(
+        name="Auto-check for Update",
+        description="If enabled, auto-check for updates using an interval",
+        default=False
+    )
+    updater_intrval_months = IntProperty(
+        name='Months',
+        description="Number of months between checking for updates",
+        default=0,
+        min=0
+    )
+    updater_intrval_days = IntProperty(
+        name='Days',
+        description="Number of days between checking for updates",
+        default=7,
+        min=0
+    )
+    updater_intrval_hours = IntProperty(
+        name='Hours',
+        description="Number of hours between checking for updates",
+        default=0,
+        min=0,
+        max=23
+    )
+    updater_intrval_minutes = IntProperty(
+        name='Minutes',
+        description="Number of minutes between checking for updates",
+        default=0,
+        min=0,
+        max=59
+    )
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.row().prop(self, "category", expand=True)
+
+        if self.category == 'INFO':
+            layout.prop(
+                self, "info_desc_expanded", text="Description",
+                icon='DISCLOSURE_TRI_DOWN' if self.info_desc_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.info_desc_expanded:
+                column = layout.column(align=True)
+                column.label("Magic UV is composed of many UV editing" +
+                             " features.")
+                column.label("See tutorial page if you are new to this" +
+                             " add-on.")
+                column.label("https://github.com/nutti/Magic-UV/wiki/Tutorial")
+
+            layout.prop(
+                self, "info_loc_expanded", text="Location",
+                icon='DISCLOSURE_TRI_DOWN' if self.info_loc_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.info_loc_expanded:
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("3D View > Tool shelf > Copy/Paste UV (Object mode)")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Copy/Paste UV (Among objects)")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("3D View > Tool shelf > Copy/Paste UV (Edit mode)")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Copy/Paste UV (Among faces in 3D View)")
+                col.label("Transfer UV")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Flip/Rotate UV")
+                col.label("Mirror UV")
+                col.label("Move UV")
+                col.label("World Scale UV")
+                col.label("Preserve UV Aspect")
+                col.label("Texture Lock")
+                col.label("Texture Wrap")
+                col.label("UV Sculpt")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Unwrap Constraint")
+                col.label("Texture Projection")
+                col.label("UVW")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("UV/Image Editor > Tool shelf > Copy/Paste UV")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Copy/Paste UV (Among faces in UV/Image Editor)")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("UV/Image Editor > Tool shelf > UV Manipulation")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Align UV")
+                col.label("Smooth UV")
+                col.label("Select UV")
+                col.label("Pack UV (Extension)")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("UV/Image Editor > Tool shelf > Editor Enhancement")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Align UV Cursor")
+                col.label("UV Cursor Location")
+                col.label("UV Bounding Box")
+                col.label("UV Inspection")
+
+        elif self.category == 'CONFIG':
+            layout.prop(self, "enable_builtin_menu", text="Built-in Menu")
+
+            layout.separator()
+
+            layout.prop(
+                self, "conf_uv_sculpt_expanded", text="UV Sculpt",
+                icon='DISCLOSURE_TRI_DOWN' if self.conf_uv_sculpt_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.conf_uv_sculpt_expanded:
+                sp = layout.split(percentage=0.05)
+                col = sp.column()  # spacer
+                sp = sp.split(percentage=0.3)
+                col = sp.column()
+                col.label("Brush Color:")
+                col.prop(self, "uv_sculpt_brush_color", text="")
+                layout.separator()
+
+            layout.prop(
+                self, "conf_uv_inspection_expanded", text="UV Inspection",
+                icon='DISCLOSURE_TRI_DOWN' if self.conf_uv_inspection_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.conf_uv_inspection_expanded:
+                sp = layout.split(percentage=0.05)
+                col = sp.column()  # spacer
+                sp = sp.split(percentage=0.3)
+                col = sp.column()
+                col.label("Overlapped UV Color:")
+                col.prop(self, "uv_inspection_overlapped_color", text="")
+                sp = sp.split(percentage=0.45)
+                col = sp.column()
+                col.label("Flipped UV Color:")
+                col.prop(self, "uv_inspection_flipped_color", text="")
+                layout.separator()
+
+            layout.prop(
+                self, "conf_texture_projection_expanded",
+                text="Texture Projection",
+                icon='DISCLOSURE_TRI_DOWN'
+                if self.conf_texture_projection_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.conf_texture_projection_expanded:
+                sp = layout.split(percentage=0.05)
+                col = sp.column()       # spacer
+                sp = sp.split(percentage=0.3)
+                col = sp.column()
+                col.prop(self, "texture_projection_canvas_padding")
+                layout.separator()
+
+            layout.prop(
+                self, "conf_uv_bounding_box_expanded", text="UV Bounding Box",
+                icon='DISCLOSURE_TRI_DOWN'
+                if self.conf_uv_bounding_box_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.conf_uv_bounding_box_expanded:
+                sp = layout.split(percentage=0.05)
+                col = sp.column()       # spacer
+                sp = sp.split(percentage=0.3)
+                col = sp.column()
+                col.label("Control Point:")
+                col.prop(self, "uv_bounding_box_cp_size")
+                col.prop(self, "uv_bounding_box_cp_react_size")
+                layout.separator()
+
+        elif self.category == 'UPDATE':
+            addon_updater_ops.update_settings_ui(self, context)
diff --git a/uv_magic_uv/legacy/properites.py b/uv_magic_uv/legacy/properites.py
new file mode 100644
index 0000000000000000000000000000000000000000..b64a48f39010fdfa5357146a0bcbbaac08dea18e
--- /dev/null
+++ b/uv_magic_uv/legacy/properites.py
@@ -0,0 +1,61 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+
+from ..utils.property_class_registry import PropertyClassRegistry
+
+__all__ = [
+    'MUV_Properties',
+    'init_props',
+    'clear_props',
+]
+
+
+# Properties used in this add-on.
+# pylint: disable=W0612
+class MUV_Properties():
+    def __init__(self):
+        self.prefs = MUV_Prefs()
+
+
+class MUV_Prefs():
+    expanded = {
+        "info_desc": False,
+        "info_loc": False,
+        "conf_uvsculpt": False,
+        "conf_uvinsp": False,
+        "conf_texproj": False,
+        "conf_uvbb": False
+    }
+
+
+def init_props(scene):
+    scene.muv_props = MUV_Properties()
+    PropertyClassRegistry.init_props(scene)
+
+
+def clear_props(scene):
+    PropertyClassRegistry.del_props(scene)
+    del scene.muv_props
diff --git a/uv_magic_uv/legacy/ui/IMAGE_MT_uvs.py b/uv_magic_uv/legacy/ui/IMAGE_MT_uvs.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf071bf5f07ea56d291e8fa1f6bc3640ea8d11dc
--- /dev/null
+++ b/uv_magic_uv/legacy/ui/IMAGE_MT_uvs.py
@@ -0,0 +1,197 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from ..op import (
+    align_uv_cursor,
+    copy_paste_uv_uvedit,
+    align_uv,
+    select_uv,
+    uv_inspection
+)
+from ...utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_MT_CopyPasteUV_UVEdit',
+    'MUV_MT_AlignUV',
+    'MUV_MT_SelectUV',
+    'MUV_MT_AlignUVCursor',
+    'MUV_MT_UVInspection',
+]
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_CopyPasteUV_UVEdit(bpy.types.Menu):
+    """
+    Menu class: Master menu of Copy/Paste UV coordinate on UV/ImageEditor
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_uvedit_menu"
+    bl_label = "Copy/Paste UV"
+    bl_description = "Copy and Paste UV coordinate among object"
+
+    def draw(self, _):
+        layout = self.layout
+
+        layout.operator(
+            copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_CopyUV.bl_idname,
+            text="Copy")
+        layout.operator(
+            copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_PasteUV.bl_idname,
+            text="Paste")
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_AlignUV(bpy.types.Menu):
+    """
+    Menu class: Master menu of Align UV
+    """
+
+    bl_idname = "uv.muv_align_uv_menu"
+    bl_label = "Align UV"
+    bl_description = "Align UV"
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        ops = layout.operator(align_uv.MUV_OT_AlignUV_Circle.bl_idname,
+                              text="Circle")
+        ops.transmission = sc.muv_align_uv_transmission
+        ops.select = sc.muv_align_uv_select
+
+        ops = layout.operator(align_uv.MUV_OT_AlignUV_Straighten.bl_idname,
+                              text="Straighten")
+        ops.transmission = sc.muv_align_uv_transmission
+        ops.select = sc.muv_align_uv_select
+        ops.vertical = sc.muv_align_uv_vertical
+        ops.horizontal = sc.muv_align_uv_horizontal
+
+        ops = layout.operator(align_uv.MUV_OT_AlignUV_Axis.bl_idname,
+                              text="XY-axis")
+        ops.transmission = sc.muv_align_uv_transmission
+        ops.select = sc.muv_align_uv_select
+        ops.vertical = sc.muv_align_uv_vertical
+        ops.horizontal = sc.muv_align_uv_horizontal
+        ops.location = sc.muv_align_uv_location
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_SelectUV(bpy.types.Menu):
+    """
+    Menu class: Master menu of Select UV
+    """
+
+    bl_idname = "uv.muv_select_uv_menu"
+    bl_label = "Select UV"
+    bl_description = "Select UV"
+
+    def draw(self, _):
+        layout = self.layout
+
+        layout.operator(select_uv.MUV_OT_SelectUV_SelectOverlapped.bl_idname,
+                        text="Overlapped")
+        layout.operator(select_uv.MUV_OT_SelectUV_SelectFlipped.bl_idname,
+                        text="Flipped")
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_AlignUVCursor(bpy.types.Menu):
+    """
+    Menu class: Master menu of Align UV Cursor
+    """
+
+    bl_idname = "uv.muv_align_uv_cursor_menu"
+    bl_label = "Align UV Cursor"
+    bl_description = "Align UV cursor"
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                              text="Left Top")
+        ops.position = 'LEFT_TOP'
+        ops.base = sc.muv_align_uv_cursor_align_method
+
+        ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                              text="Middle Top")
+        ops.position = 'MIDDLE_TOP'
+        ops.base = sc.muv_align_uv_cursor_align_method
+
+        ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                              text="Right Top")
+        ops.position = 'RIGHT_TOP'
+        ops.base = sc.muv_align_uv_cursor_align_method
+
+        ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                              text="Left Middle")
+        ops.position = 'LEFT_MIDDLE'
+        ops.base = sc.muv_align_uv_cursor_align_method
+
+        ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                              text="Center")
+        ops.position = 'CENTER'
+        ops.base = sc.muv_align_uv_cursor_align_method
+
+        ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                              text="Right Middle")
+        ops.position = 'RIGHT_MIDDLE'
+        ops.base = sc.muv_align_uv_cursor_align_method
+
+        ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                              text="Left Bottom")
+        ops.position = 'LEFT_BOTTOM'
+        ops.base = sc.muv_align_uv_cursor_align_method
+
+        ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                              text="Middle Bottom")
+        ops.position = 'MIDDLE_BOTTOM'
+        ops.base = sc.muv_align_uv_cursor_align_method
+
+        ops = layout.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                              text="Right Bottom")
+        ops.position = 'RIGHT_BOTTOM'
+        ops.base = sc.muv_align_uv_cursor_align_method
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_UVInspection(bpy.types.Menu):
+    """
+    Menu class: Master menu of UV Inspection
+    """
+
+    bl_idname = "uv.muv_uv_inspection_menu"
+    bl_label = "UV Inspection"
+    bl_description = "UV Inspection"
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        layout.prop(sc, "muv_uv_inspection_show", text="UV Inspection")
+        layout.operator(uv_inspection.MUV_OT_UVInspection_Update.bl_idname,
+                        text="Update")
diff --git a/uv_magic_uv/legacy/ui/VIEW3D_MT_object.py b/uv_magic_uv/legacy/ui/VIEW3D_MT_object.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1c751ae798dfc1793bae3dfc08b362dd62e2a17
--- /dev/null
+++ b/uv_magic_uv/legacy/ui/VIEW3D_MT_object.py
@@ -0,0 +1,54 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from ..op import copy_paste_uv_object
+from ...utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_MT_CopyPasteUV_Object',
+]
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_CopyPasteUV_Object(bpy.types.Menu):
+    """
+    Menu class: Master menu of Copy/Paste UV coordinate among object
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_object_menu"
+    bl_label = "Copy/Paste UV"
+    bl_description = "Copy and Paste UV coordinate among object"
+
+    def draw(self, _):
+        layout = self.layout
+
+        layout.menu(
+            copy_paste_uv_object.MUV_MT_CopyPasteUVObject_CopyUV.bl_idname,
+            text="Copy")
+        layout.menu(
+            copy_paste_uv_object.MUV_MT_CopyPasteUVObject_PasteUV.bl_idname,
+            text="Paste")
diff --git a/uv_magic_uv/legacy/ui/VIEW3D_MT_uv_map.py b/uv_magic_uv/legacy/ui/VIEW3D_MT_uv_map.py
new file mode 100644
index 0000000000000000000000000000000000000000..d229322a034aa382920c3b0ab782bb1d1f891d20
--- /dev/null
+++ b/uv_magic_uv/legacy/ui/VIEW3D_MT_uv_map.py
@@ -0,0 +1,257 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+import bpy.utils
+
+from ..op import (
+    texture_lock,
+    copy_paste_uv,
+    preserve_uv_aspect,
+    texture_projection,
+    texture_wrap,
+    transfer_uv,
+    uvw,
+    world_scale_uv
+)
+from ..op.world_scale_uv import MUV_OT_WorldScaleUV_ApplyProportionalToMesh
+from ...utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_MT_CopyPasteUV',
+    'MUV_MT_TransferUV',
+    'MUV_MT_TextureLock',
+    'MUV_MT_WorldScaleUV',
+    'MUV_MT_TextureWrap',
+    'MUV_MT_UVW',
+    'MUV_MT_TextureProjection',
+    'MUV_MT_PreserveUVAspect',
+]
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_CopyPasteUV(bpy.types.Menu):
+    """
+    Menu class: Master menu of Copy/Paste UV coordinate
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_menu"
+    bl_label = "Copy/Paste UV"
+    bl_description = "Copy and Paste UV coordinate"
+
+    def draw(self, _):
+        layout = self.layout
+
+        layout.label(text="Default")
+        layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_CopyUV.bl_idname,
+                    text="Copy")
+        layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_PasteUV.bl_idname,
+                    text="Paste")
+
+        layout.separator()
+
+        layout.label(text="Selection Sequence")
+        layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqCopyUV.bl_idname,
+                    text="Copy")
+        layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqPasteUV.bl_idname,
+                    text="Paste")
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_TransferUV(bpy.types.Menu):
+    """
+    Menu class: Master menu of Transfer UV coordinate
+    """
+
+    bl_idname = "uv.muv_transfer_uv_menu"
+    bl_label = "Transfer UV"
+    bl_description = "Transfer UV coordinate"
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        layout.operator(transfer_uv.MUV_OT_TransferUV_CopyUV.bl_idname,
+                        text="Copy")
+        ops = layout.operator(transfer_uv.MUV_OT_TransferUV_PasteUV.bl_idname,
+                              text="Paste")
+        ops.invert_normals = sc.muv_transfer_uv_invert_normals
+        ops.copy_seams = sc.muv_transfer_uv_copy_seams
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_TextureLock(bpy.types.Menu):
+    """
+    Menu class: Master menu of Texture Lock
+    """
+
+    bl_idname = "uv.muv_texture_lock_menu"
+    bl_label = "Texture Lock"
+    bl_description = "Lock texture when vertices of mesh (Preserve UV)"
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        layout.label("Normal Mode")
+        layout.operator(
+            texture_lock.MUV_OT_TextureLock_Lock.bl_idname,
+            text="Lock"
+            if not texture_lock.MUV_OT_TextureLock_Lock.is_ready(context)
+            else "ReLock")
+        ops = layout.operator(texture_lock.MUV_OT_TextureLock_Unlock.bl_idname,
+                              text="Unlock")
+        ops.connect = sc.muv_texture_lock_connect
+
+        layout.separator()
+
+        layout.label("Interactive Mode")
+        layout.prop(sc, "muv_texture_lock_lock", text="Lock")
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_WorldScaleUV(bpy.types.Menu):
+    """
+    Menu class: Master menu of world scale UV
+    """
+
+    bl_idname = "uv.muv_world_scale_uv_menu"
+    bl_label = "World Scale UV"
+    bl_description = ""
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        layout.operator(world_scale_uv.MUV_OT_WorldScaleUV_Measure.bl_idname,
+                        text="Measure")
+
+        layout.operator(
+            world_scale_uv.MUV_OT_WorldScaleUV_ApplyManual.bl_idname,
+            text="Apply (Manual)")
+
+        ops = layout.operator(
+            world_scale_uv.MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname,
+            text="Apply (Same Desity)")
+        ops.src_density = sc.muv_world_scale_uv_src_density
+        ops.same_density = True
+
+        ops = layout.operator(
+            world_scale_uv.MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname,
+            text="Apply (Scaling Desity)")
+        ops.src_density = sc.muv_world_scale_uv_src_density
+        ops.same_density = False
+        ops.tgt_scaling_factor = sc.muv_world_scale_uv_tgt_scaling_factor
+
+        ops = layout.operator(
+            MUV_OT_WorldScaleUV_ApplyProportionalToMesh.bl_idname,
+            text="Apply (Proportional to Mesh)")
+        ops.src_density = sc.muv_world_scale_uv_src_density
+        ops.src_uv_area = sc.muv_world_scale_uv_src_uv_area
+        ops.src_mesh_area = sc.muv_world_scale_uv_src_mesh_area
+        ops.origin = sc.muv_world_scale_uv_origin
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_TextureWrap(bpy.types.Menu):
+    """
+    Menu class: Master menu of Texture Wrap
+    """
+
+    bl_idname = "uv.muv_texture_wrap_menu"
+    bl_label = "Texture Wrap"
+    bl_description = ""
+
+    def draw(self, _):
+        layout = self.layout
+
+        layout.operator(texture_wrap.MUV_OT_TextureWrap_Refer.bl_idname,
+                        text="Refer")
+        layout.operator(texture_wrap.MUV_OT_TextureWrap_Set.bl_idname,
+                        text="Set")
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_UVW(bpy.types.Menu):
+    """
+    Menu class: Master menu of UVW
+    """
+
+    bl_idname = "uv.muv_uvw_menu"
+    bl_label = "UVW"
+    bl_description = ""
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        ops = layout.operator(uvw.MUV_OT_UVW_BoxMap.bl_idname, text="Box")
+        ops.assign_uvmap = sc.muv_uvw_assign_uvmap
+
+        ops = layout.operator(uvw.MUV_OT_UVW_BestPlanerMap.bl_idname,
+                              text="Best Planner")
+        ops.assign_uvmap = sc.muv_uvw_assign_uvmap
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_TextureProjection(bpy.types.Menu):
+    """
+    Menu class: Master menu of Texture Projection
+    """
+
+    bl_idname = "uv.muv_texture_projection_menu"
+    bl_label = "Texture Projection"
+    bl_description = ""
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        layout.prop(sc, "muv_texture_projection_enable",
+                    text="Texture Projection")
+        layout.operator(
+            texture_projection.MUV_OT_TextureProjection_Project.bl_idname,
+            text="Project")
+
+
+@BlClassRegistry(legacy=True)
+class MUV_MT_PreserveUVAspect(bpy.types.Menu):
+    """
+    Menu class: Master menu of Preserve UV Aspect
+    """
+
+    bl_idname = "uv.muv_preserve_uv_aspect_menu"
+    bl_label = "Preserve UV Aspect"
+    bl_description = ""
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        for key in bpy.data.images.keys():
+            ops = layout.operator(
+                preserve_uv_aspect.MUV_OT_PreserveUVAspect.bl_idname, text=key)
+            ops.dest_img_name = key
+            ops.origin = sc.muv_preserve_uv_aspect_origin
diff --git a/uv_magic_uv/legacy/ui/__init__.py b/uv_magic_uv/legacy/ui/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf790a8e4203c5d0e6593d695283e020c3368d49
--- /dev/null
+++ b/uv_magic_uv/legacy/ui/__init__.py
@@ -0,0 +1,50 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+if "bpy" in locals():
+    import importlib
+    importlib.reload(view3d_copy_paste_uv_objectmode)
+    importlib.reload(view3d_copy_paste_uv_editmode)
+    importlib.reload(view3d_uv_manipulation)
+    importlib.reload(view3d_uv_mapping)
+    importlib.reload(uvedit_copy_paste_uv)
+    importlib.reload(uvedit_uv_manipulation)
+    importlib.reload(uvedit_editor_enhancement)
+    importlib.reload(VIEW3D_MT_uv_map)
+    importlib.reload(VIEW3D_MT_object)
+    importlib.reload(IMAGE_MT_uvs)
+else:
+    from . import view3d_copy_paste_uv_objectmode
+    from . import view3d_copy_paste_uv_editmode
+    from . import view3d_uv_manipulation
+    from . import view3d_uv_mapping
+    from . import uvedit_copy_paste_uv
+    from . import uvedit_uv_manipulation
+    from . import uvedit_editor_enhancement
+    from . import VIEW3D_MT_uv_map
+    from . import VIEW3D_MT_object
+    from . import IMAGE_MT_uvs
+
+import bpy
\ No newline at end of file
diff --git a/uv_magic_uv/legacy/ui/uvedit_copy_paste_uv.py b/uv_magic_uv/legacy/ui/uvedit_copy_paste_uv.py
new file mode 100644
index 0000000000000000000000000000000000000000..9848f03b78951ca083828a90d32912459a8ecaeb
--- /dev/null
+++ b/uv_magic_uv/legacy/ui/uvedit_copy_paste_uv.py
@@ -0,0 +1,62 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from ..op import copy_paste_uv_uvedit
+from ...utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_PT_UVEdit_CopyPasteUV',
+]
+
+
+@BlClassRegistry(legacy=True)
+class MUV_PT_UVEdit_CopyPasteUV(bpy.types.Panel):
+    """
+    Panel class: Copy/Paste UV on Property Panel on UV/ImageEditor
+    """
+
+    bl_space_type = 'IMAGE_EDITOR'
+    bl_region_type = 'TOOLS'
+    bl_label = "Copy/Paste UV"
+    bl_category = "Magic UV"
+    bl_context = 'mesh_edit'
+    bl_options = {'DEFAULT_CLOSED'}
+
+    def draw_header(self, _):
+        layout = self.layout
+        layout.label(text="", icon='IMAGE_COL')
+
+    def draw(self, _):
+        layout = self.layout
+
+        row = layout.row(align=True)
+        row.operator(
+            copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_CopyUV.bl_idname,
+            text="Copy")
+        row.operator(
+            copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_PasteUV.bl_idname,
+            text="Paste")
diff --git a/uv_magic_uv/legacy/ui/uvedit_editor_enhancement.py b/uv_magic_uv/legacy/ui/uvedit_editor_enhancement.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f750febfbefbc51fd5f062b8916ad4572ea0b72
--- /dev/null
+++ b/uv_magic_uv/legacy/ui/uvedit_editor_enhancement.py
@@ -0,0 +1,149 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from ..op import (
+    align_uv_cursor,
+    uv_bounding_box,
+    uv_inspection,
+)
+from ...utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_PT_UVEdit_EditorEnhancement',
+]
+
+
+@BlClassRegistry(legacy=True)
+class MUV_PT_UVEdit_EditorEnhancement(bpy.types.Panel):
+    """
+    Panel class: UV/Image Editor Enhancement
+    """
+
+    bl_space_type = 'IMAGE_EDITOR'
+    bl_region_type = 'TOOLS'
+    bl_label = "Editor Enhancement"
+    bl_category = "Magic UV"
+    bl_context = 'mesh_edit'
+    bl_options = {'DEFAULT_CLOSED'}
+
+    def draw_header(self, _):
+        layout = self.layout
+        layout.label(text="", icon='IMAGE_COL')
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        box = layout.box()
+        box.prop(sc, "muv_align_uv_cursor_enabled", text="Align UV Cursor")
+        if sc.muv_align_uv_cursor_enabled:
+            box.prop(sc, "muv_align_uv_cursor_align_method", expand=True)
+
+            col = box.column(align=True)
+
+            row = col.row(align=True)
+            ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                               text="Left Top")
+            ops.position = 'LEFT_TOP'
+            ops.base = sc.muv_align_uv_cursor_align_method
+            ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                               text="Middle Top")
+            ops.position = 'MIDDLE_TOP'
+            ops.base = sc.muv_align_uv_cursor_align_method
+            ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                               text="Right Top")
+            ops.position = 'RIGHT_TOP'
+            ops.base = sc.muv_align_uv_cursor_align_method
+
+            row = col.row(align=True)
+            ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                               text="Left Middle")
+            ops.position = 'LEFT_MIDDLE'
+            ops.base = sc.muv_align_uv_cursor_align_method
+            ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                               text="Center")
+            ops.position = 'CENTER'
+            ops.base = sc.muv_align_uv_cursor_align_method
+            ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                               text="Right Middle")
+            ops.position = 'RIGHT_MIDDLE'
+            ops.base = sc.muv_align_uv_cursor_align_method
+
+            row = col.row(align=True)
+            ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                               text="Left Bottom")
+            ops.position = 'LEFT_BOTTOM'
+            ops.base = sc.muv_align_uv_cursor_align_method
+            ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                               text="Middle Bottom")
+            ops.position = 'MIDDLE_BOTTOM'
+            ops.base = sc.muv_align_uv_cursor_align_method
+            ops = row.operator(align_uv_cursor.MUV_OT_AlignUVCursor.bl_idname,
+                               text="Right Bottom")
+            ops.position = 'RIGHT_BOTTOM'
+            ops.base = sc.muv_align_uv_cursor_align_method
+
+        box = layout.box()
+        box.prop(sc, "muv_uv_cursor_location_enabled",
+                 text="UV Cursor Location")
+        if sc.muv_uv_cursor_location_enabled:
+            box.prop(sc, "muv_align_uv_cursor_cursor_loc", text="")
+
+        box = layout.box()
+        box.prop(sc, "muv_uv_bounding_box_enabled", text="UV Bounding Box")
+        if sc.muv_uv_bounding_box_enabled:
+            box.prop(
+                sc, "muv_uv_bounding_box_show",
+                text="Hide"
+                if uv_bounding_box.MUV_OT_UVBoundingBox.is_running(context)
+                else "Show",
+                icon='RESTRICT_VIEW_OFF'
+                if uv_bounding_box.MUV_OT_UVBoundingBox.is_running(context)
+                else 'RESTRICT_VIEW_ON')
+            box.prop(sc, "muv_uv_bounding_box_uniform_scaling",
+                     text="Uniform Scaling")
+            box.prop(sc, "muv_uv_bounding_box_boundary", text="Boundary")
+
+        box = layout.box()
+        box.prop(sc, "muv_uv_inspection_enabled", text="UV Inspection")
+        if sc.muv_uv_inspection_enabled:
+            row = box.row()
+            row.prop(
+                sc, "muv_uv_inspection_show",
+                text="Hide"
+                if uv_inspection.MUV_OT_UVInspection_Render.is_running(context)
+                else "Show",
+                icon='RESTRICT_VIEW_OFF'
+                if uv_inspection.MUV_OT_UVInspection_Render.is_running(context)
+                else 'RESTRICT_VIEW_ON')
+            row.operator(uv_inspection.MUV_OT_UVInspection_Update.bl_idname,
+                         text="Update")
+            row = box.row()
+            row.prop(sc, "muv_uv_inspection_show_overlapped")
+            row.prop(sc, "muv_uv_inspection_show_flipped")
+            row = box.row()
+            row.prop(sc, "muv_uv_inspection_show_mode")
diff --git a/uv_magic_uv/legacy/ui/uvedit_uv_manipulation.py b/uv_magic_uv/legacy/ui/uvedit_uv_manipulation.py
new file mode 100644
index 0000000000000000000000000000000000000000..96cf17d324c17eeba2b45cf478559702a237ff90
--- /dev/null
+++ b/uv_magic_uv/legacy/ui/uvedit_uv_manipulation.py
@@ -0,0 +1,130 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from ..op import (
+    align_uv,
+    smooth_uv,
+    pack_uv,
+    select_uv,
+)
+from ...utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_PT_UVEdit_UVManipulation',
+]
+
+
+@BlClassRegistry(legacy=True)
+class MUV_PT_UVEdit_UVManipulation(bpy.types.Panel):
+    """
+    Panel class: UV Manipulation on Property Panel on UV/ImageEditor
+    """
+
+    bl_space_type = 'IMAGE_EDITOR'
+    bl_region_type = 'TOOLS'
+    bl_label = "UV Manipulation"
+    bl_category = "Magic UV"
+    bl_context = 'mesh_edit'
+    bl_options = {'DEFAULT_CLOSED'}
+
+    def draw_header(self, _):
+        layout = self.layout
+        layout.label(text="", icon='IMAGE_COL')
+
+    def draw(self, context):
+        sc = context.scene
+        layout = self.layout
+
+        box = layout.box()
+        box.prop(sc, "muv_align_uv_enabled", text="Align UV")
+        if sc.muv_align_uv_enabled:
+            col = box.column()
+            row = col.row(align=True)
+            ops = row.operator(align_uv.MUV_OT_AlignUV_Circle.bl_idname,
+                               text="Circle")
+            ops.transmission = sc.muv_align_uv_transmission
+            ops.select = sc.muv_align_uv_select
+            ops = row.operator(align_uv.MUV_OT_AlignUV_Straighten.bl_idname,
+                               text="Straighten")
+            ops.transmission = sc.muv_align_uv_transmission
+            ops.select = sc.muv_align_uv_select
+            ops.vertical = sc.muv_align_uv_vertical
+            ops.horizontal = sc.muv_align_uv_horizontal
+            ops.mesh_infl = sc.muv_align_uv_mesh_infl
+            row = col.row()
+            ops = row.operator(align_uv.MUV_OT_AlignUV_Axis.bl_idname,
+                               text="XY-axis")
+            ops.transmission = sc.muv_align_uv_transmission
+            ops.select = sc.muv_align_uv_select
+            ops.vertical = sc.muv_align_uv_vertical
+            ops.horizontal = sc.muv_align_uv_horizontal
+            ops.location = sc.muv_align_uv_location
+            ops.mesh_infl = sc.muv_align_uv_mesh_infl
+            row.prop(sc, "muv_align_uv_location", text="")
+
+            col = box.column(align=True)
+            row = col.row(align=True)
+            row.prop(sc, "muv_align_uv_transmission", text="Transmission")
+            row.prop(sc, "muv_align_uv_select", text="Select")
+            row = col.row(align=True)
+            row.prop(sc, "muv_align_uv_vertical", text="Vertical")
+            row.prop(sc, "muv_align_uv_horizontal", text="Horizontal")
+            col.prop(sc, "muv_align_uv_mesh_infl", text="Mesh Influence")
+
+        box = layout.box()
+        box.prop(sc, "muv_smooth_uv_enabled", text="Smooth UV")
+        if sc.muv_smooth_uv_enabled:
+            ops = box.operator(smooth_uv.MUV_OT_SmoothUV.bl_idname,
+                               text="Smooth")
+            ops.transmission = sc.muv_smooth_uv_transmission
+            ops.select = sc.muv_smooth_uv_select
+            ops.mesh_infl = sc.muv_smooth_uv_mesh_infl
+            col = box.column(align=True)
+            row = col.row(align=True)
+            row.prop(sc, "muv_smooth_uv_transmission", text="Transmission")
+            row.prop(sc, "muv_smooth_uv_select", text="Select")
+            col.prop(sc, "muv_smooth_uv_mesh_infl", text="Mesh Influence")
+
+        box = layout.box()
+        box.prop(sc, "muv_select_uv_enabled", text="Select UV")
+        if sc.muv_select_uv_enabled:
+            row = box.row(align=True)
+            row.operator(select_uv.MUV_OT_SelectUV_SelectOverlapped.bl_idname)
+            row.operator(select_uv.MUV_OT_SelectUV_SelectFlipped.bl_idname)
+
+        box = layout.box()
+        box.prop(sc, "muv_pack_uv_enabled", text="Pack UV (Extension)")
+        if sc.muv_pack_uv_enabled:
+            ops = box.operator(pack_uv.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
+            box.label("Allowable Center Deviation:")
+            box.prop(sc, "muv_pack_uv_allowable_center_deviation", text="")
+            box.label("Allowable Size Deviation:")
+            box.prop(sc, "muv_pack_uv_allowable_size_deviation", text="")
diff --git a/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_editmode.py b/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_editmode.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee2713b22d19cfd193dd1ddd430e679c41aa0c1a
--- /dev/null
+++ b/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_editmode.py
@@ -0,0 +1,93 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from ..op import (
+    transfer_uv,
+    copy_paste_uv,
+)
+from ...utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_PT_View3D_Edit_CopyPasteUV',
+]
+
+
+@BlClassRegistry(legacy=True)
+class MUV_PT_View3D_Edit_CopyPasteUV(bpy.types.Panel):
+    """
+    Panel class: Copy/Paste UV on Property Panel on View3D
+    """
+
+    bl_space_type = 'VIEW_3D'
+    bl_region_type = 'TOOLS'
+    bl_label = "Copy/Paste UV"
+    bl_category = "Magic UV"
+    bl_context = 'mesh_edit'
+    bl_options = {'DEFAULT_CLOSED'}
+
+    def draw_header(self, _):
+        layout = self.layout
+        layout.label(text="", icon='IMAGE_COL')
+
+    def draw(self, context):
+        sc = context.scene
+        layout = self.layout
+
+        box = layout.box()
+        box.prop(sc, "muv_copy_paste_uv_enabled", text="Copy/Paste UV")
+        if sc.muv_copy_paste_uv_enabled:
+            row = box.row(align=True)
+            if sc.muv_copy_paste_uv_mode == 'DEFAULT':
+                row.menu(copy_paste_uv.MUV_MT_CopyPasteUV_CopyUV.bl_idname,
+                         text="Copy")
+                row.menu(copy_paste_uv.MUV_MT_CopyPasteUV_PasteUV.bl_idname,
+                         text="Paste")
+            elif sc.muv_copy_paste_uv_mode == 'SEL_SEQ':
+                row.menu(
+                    copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqCopyUV.bl_idname,
+                    text="Copy")
+                row.menu(
+                    copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqPasteUV.bl_idname,
+                    text="Paste")
+            box.prop(sc, "muv_copy_paste_uv_mode", expand=True)
+            box.prop(sc, "muv_copy_paste_uv_copy_seams", text="Seams")
+            box.prop(sc, "muv_copy_paste_uv_strategy", text="Strategy")
+
+        box = layout.box()
+        box.prop(sc, "muv_transfer_uv_enabled", text="Transfer UV")
+        if sc.muv_transfer_uv_enabled:
+            row = box.row(align=True)
+            row.operator(transfer_uv.MUV_OT_TransferUV_CopyUV.bl_idname,
+                         text="Copy")
+            ops = row.operator(transfer_uv.MUV_OT_TransferUV_PasteUV.bl_idname,
+                               text="Paste")
+            ops.invert_normals = sc.muv_transfer_uv_invert_normals
+            ops.copy_seams = sc.muv_transfer_uv_copy_seams
+            row = box.row()
+            row.prop(sc, "muv_transfer_uv_invert_normals",
+                     text="Invert Normals")
+            row.prop(sc, "muv_transfer_uv_copy_seams", text="Seams")
diff --git a/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_objectmode.py b/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_objectmode.py
new file mode 100644
index 0000000000000000000000000000000000000000..58031b0f3158a2f78689741713bd55ac3d899bb7
--- /dev/null
+++ b/uv_magic_uv/legacy/ui/view3d_copy_paste_uv_objectmode.py
@@ -0,0 +1,65 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from ..op import copy_paste_uv_object
+from ...utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_PT_View3D_Object_CopyPasteUV',
+]
+
+
+@BlClassRegistry(legacy=True)
+class MUV_PT_View3D_Object_CopyPasteUV(bpy.types.Panel):
+    """
+    Panel class: Copy/Paste UV on Property Panel on View3D
+    """
+
+    bl_space_type = 'VIEW_3D'
+    bl_region_type = 'TOOLS'
+    bl_label = "Copy/Paste UV"
+    bl_category = "Magic UV"
+    bl_context = 'objectmode'
+    bl_options = {'DEFAULT_CLOSED'}
+
+    def draw_header(self, _):
+        layout = self.layout
+        layout.label(text="", icon='IMAGE_COL')
+
+    def draw(self, context):
+        sc = context.scene
+        layout = self.layout
+
+        row = layout.row(align=True)
+        row.menu(
+            copy_paste_uv_object.MUV_MT_CopyPasteUVObject_CopyUV.bl_idname,
+            text="Copy")
+        row.menu(
+            copy_paste_uv_object.MUV_MT_CopyPasteUVObject_PasteUV.bl_idname,
+            text="Paste")
+        layout.prop(sc, "muv_copy_paste_uv_object_copy_seams",
+                    text="Seams")
diff --git a/uv_magic_uv/legacy/ui/view3d_uv_manipulation.py b/uv_magic_uv/legacy/ui/view3d_uv_manipulation.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d828a38fb1bf5aef2356f0a955badb4b915c17e
--- /dev/null
+++ b/uv_magic_uv/legacy/ui/view3d_uv_manipulation.py
@@ -0,0 +1,289 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from ..op import (
+    move_uv,
+    flip_rotate_uv,
+    mirror_uv,
+    preserve_uv_aspect,
+    texture_lock,
+    texture_wrap,
+    uv_sculpt,
+    world_scale_uv,
+)
+from ..op.world_scale_uv import (
+    MUV_OT_WorldScaleUV_ApplyProportionalToMesh,
+    MUV_OT_WorldScaleUV_ApplyScalingDensity
+)
+from ...utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_PT_View3D_UVManipulation',
+]
+
+
+@BlClassRegistry(legacy=True)
+class MUV_PT_View3D_UVManipulation(bpy.types.Panel):
+    """
+    Panel class: UV Manipulation on Property Panel on View3D
+    """
+
+    bl_space_type = 'VIEW_3D'
+    bl_region_type = 'TOOLS'
+    bl_label = "UV Manipulation"
+    bl_category = "Magic UV"
+    bl_context = 'mesh_edit'
+    bl_options = {'DEFAULT_CLOSED'}
+
+    def draw_header(self, _):
+        layout = self.layout
+        layout.label(text="", icon='IMAGE_COL')
+
+    def draw(self, context):
+        sc = context.scene
+        layout = self.layout
+
+        box = layout.box()
+        box.prop(sc, "muv_flip_rotate_uv_enabled", text="Flip/Rotate UV")
+        if sc.muv_flip_rotate_uv_enabled:
+            row = box.row()
+            ops = row.operator(flip_rotate_uv.MUV_OT_FlipRotate.bl_idname,
+                               text="Flip/Rotate")
+            ops.seams = sc.muv_flip_rotate_uv_seams
+            row.prop(sc, "muv_flip_rotate_uv_seams", text="Seams")
+
+        box = layout.box()
+        box.prop(sc, "muv_mirror_uv_enabled", text="Mirror UV")
+        if sc.muv_mirror_uv_enabled:
+            row = box.row()
+            ops = row.operator(mirror_uv.MUV_OT_MirrorUV.bl_idname,
+                               text="Mirror")
+            ops.axis = sc.muv_mirror_uv_axis
+            row.prop(sc, "muv_mirror_uv_axis", text="")
+
+        box = layout.box()
+        box.prop(sc, "muv_move_uv_enabled", text="Move UV")
+        if sc.muv_move_uv_enabled:
+            col = box.column()
+            if not move_uv.MUV_OT_MoveUV.is_running(context):
+                col.operator(move_uv.MUV_OT_MoveUV.bl_idname, icon='PLAY',
+                             text="Start")
+            else:
+                col.operator(move_uv.MUV_OT_MoveUV.bl_idname, icon='PAUSE',
+                             text="Stop")
+
+        box = layout.box()
+        box.prop(sc, "muv_world_scale_uv_enabled", text="World Scale UV")
+        if sc.muv_world_scale_uv_enabled:
+            box.prop(sc, "muv_world_scale_uv_mode", text="")
+
+            if sc.muv_world_scale_uv_mode == 'MANUAL':
+                sp = box.split(percentage=0.5)
+                col = sp.column()
+                col.prop(sc, "muv_world_scale_uv_tgt_texture_size",
+                         text="Texture Size")
+                sp = sp.split(percentage=1.0)
+                col = sp.column()
+                col.label("Density:")
+                col.prop(sc, "muv_world_scale_uv_tgt_density", text="")
+                box.prop(sc, "muv_world_scale_uv_origin", text="Origin")
+                ops = box.operator(
+                    world_scale_uv.MUV_OT_WorldScaleUV_ApplyManual.bl_idname,
+                    text="Apply")
+                ops.tgt_density = sc.muv_world_scale_uv_tgt_density
+                ops.tgt_texture_size = sc.muv_world_scale_uv_tgt_texture_size
+                ops.origin = sc.muv_world_scale_uv_origin
+                ops.show_dialog = False
+
+            elif sc.muv_world_scale_uv_mode == 'SAME_DENSITY':
+                sp = box.split(percentage=0.4)
+                col = sp.column(align=True)
+                col.label("Source:")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.operator(
+                    world_scale_uv.MUV_OT_WorldScaleUV_Measure.bl_idname,
+                    text="Measure")
+
+                sp = box.split(percentage=0.7)
+                col = sp.column(align=True)
+                col.prop(sc, "muv_world_scale_uv_src_density", text="Density")
+                col.enabled = False
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("px2/cm2")
+
+                box.separator()
+                box.prop(sc, "muv_world_scale_uv_origin", text="Origin")
+                ops = box.operator(
+                    MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname,
+                    text="Apply")
+                ops.src_density = sc.muv_world_scale_uv_src_density
+                ops.origin = sc.muv_world_scale_uv_origin
+                ops.same_density = True
+                ops.show_dialog = False
+
+            elif sc.muv_world_scale_uv_mode == 'SCALING_DENSITY':
+                sp = box.split(percentage=0.4)
+                col = sp.column(align=True)
+                col.label("Source:")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.operator(
+                    world_scale_uv.MUV_OT_WorldScaleUV_Measure.bl_idname,
+                    text="Measure")
+
+                sp = box.split(percentage=0.7)
+                col = sp.column(align=True)
+                col.prop(sc, "muv_world_scale_uv_src_density", text="Density")
+                col.enabled = False
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("px2/cm2")
+
+                box.separator()
+                box.prop(sc, "muv_world_scale_uv_tgt_scaling_factor",
+                         text="Scaling Factor")
+                box.prop(sc, "muv_world_scale_uv_origin", text="Origin")
+                ops = box.operator(
+                    MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname,
+                    text="Apply")
+                ops.src_density = sc.muv_world_scale_uv_src_density
+                ops.origin = sc.muv_world_scale_uv_origin
+                ops.same_density = False
+                ops.show_dialog = False
+                ops.tgt_scaling_factor = \
+                    sc.muv_world_scale_uv_tgt_scaling_factor
+
+            elif sc.muv_world_scale_uv_mode == 'PROPORTIONAL_TO_MESH':
+                sp = box.split(percentage=0.4)
+                col = sp.column(align=True)
+                col.label("Source:")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.operator(
+                    world_scale_uv.MUV_OT_WorldScaleUV_Measure.bl_idname,
+                    text="Measure")
+
+                sp = box.split(percentage=0.7)
+                col = sp.column(align=True)
+                col.prop(sc, "muv_world_scale_uv_src_mesh_area",
+                         text="Mesh Area")
+                col.prop(sc, "muv_world_scale_uv_src_uv_area", text="UV Area")
+                col.prop(sc, "muv_world_scale_uv_src_density", text="Density")
+                col.enabled = False
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("cm2")
+                col.label("px2")
+                col.label("px2/cm2")
+                col.enabled = False
+
+                box.separator()
+                box.prop(sc, "muv_world_scale_uv_origin", text="Origin")
+                ops = box.operator(
+                    MUV_OT_WorldScaleUV_ApplyProportionalToMesh.bl_idname,
+                    text="Apply")
+                ops.src_density = sc.muv_world_scale_uv_src_density
+                ops.src_uv_area = sc.muv_world_scale_uv_src_uv_area
+                ops.src_mesh_area = sc.muv_world_scale_uv_src_mesh_area
+                ops.origin = sc.muv_world_scale_uv_origin
+                ops.show_dialog = False
+
+        box = layout.box()
+        box.prop(sc, "muv_preserve_uv_aspect_enabled",
+                 text="Preserve UV Aspect")
+        if sc.muv_preserve_uv_aspect_enabled:
+            row = box.row()
+            ops = row.operator(
+                preserve_uv_aspect.MUV_OT_PreserveUVAspect.bl_idname,
+                text="Change Image")
+            ops.dest_img_name = sc.muv_preserve_uv_aspect_tex_image
+            ops.origin = sc.muv_preserve_uv_aspect_origin
+            row.prop(sc, "muv_preserve_uv_aspect_tex_image", text="")
+            box.prop(sc, "muv_preserve_uv_aspect_origin", text="Origin")
+
+        box = layout.box()
+        box.prop(sc, "muv_texture_lock_enabled", text="Texture Lock")
+        if sc.muv_texture_lock_enabled:
+            row = box.row(align=True)
+            col = row.column(align=True)
+            col.label("Normal Mode:")
+            col = row.column(align=True)
+            col.operator(
+                texture_lock.MUV_OT_TextureLock_Lock.bl_idname,
+                text="Lock"
+                if not texture_lock.MUV_OT_TextureLock_Lock.is_ready(context)
+                else "ReLock")
+            ops = col.operator(
+                texture_lock.MUV_OT_TextureLock_Unlock.bl_idname,
+                text="Unlock")
+            ops.connect = sc.muv_texture_lock_connect
+            col.prop(sc, "muv_texture_lock_connect", text="Connect")
+
+            row = box.row(align=True)
+            row.label("Interactive Mode:")
+            box.prop(
+                sc, "muv_texture_lock_lock",
+                text="Unlock"
+                if texture_lock.MUV_OT_TextureLock_Intr.is_running(context)
+                else "Lock",
+                icon='RESTRICT_VIEW_OFF'
+                if texture_lock.MUV_OT_TextureLock_Intr.is_running(context)
+                else 'RESTRICT_VIEW_ON')
+
+        box = layout.box()
+        box.prop(sc, "muv_texture_wrap_enabled", text="Texture Wrap")
+        if sc.muv_texture_wrap_enabled:
+            row = box.row(align=True)
+            row.operator(texture_wrap.MUV_OT_TextureWrap_Refer.bl_idname,
+                         text="Refer")
+            row.operator(texture_wrap.MUV_OT_TextureWrap_Set.bl_idname,
+                         text="Set")
+            box.prop(sc, "muv_texture_wrap_set_and_refer")
+            box.prop(sc, "muv_texture_wrap_selseq")
+
+        box = layout.box()
+        box.prop(sc, "muv_uv_sculpt_enabled", text="UV Sculpt")
+        if sc.muv_uv_sculpt_enabled:
+            box.prop(
+                sc, "muv_uv_sculpt_enable",
+                text="Disable"if uv_sculpt.MUV_OT_UVSculpt.is_running(context)
+                else "Enable",
+                icon='RESTRICT_VIEW_OFF'
+                if uv_sculpt.MUV_OT_UVSculpt.is_running(context)
+                else 'RESTRICT_VIEW_ON')
+            col = box.column()
+            col.label("Brush:")
+            col.prop(sc, "muv_uv_sculpt_radius")
+            col.prop(sc, "muv_uv_sculpt_strength")
+            box.prop(sc, "muv_uv_sculpt_tools")
+            if sc.muv_uv_sculpt_tools == 'PINCH':
+                box.prop(sc, "muv_uv_sculpt_pinch_invert")
+            elif sc.muv_uv_sculpt_tools == 'RELAX':
+                box.prop(sc, "muv_uv_sculpt_relax_method")
+            box.prop(sc, "muv_uv_sculpt_show_brush")
diff --git a/uv_magic_uv/legacy/ui/view3d_uv_mapping.py b/uv_magic_uv/legacy/ui/view3d_uv_mapping.py
new file mode 100644
index 0000000000000000000000000000000000000000..3de86d0de00a0f698dd86a002e39cca95ba8c2a0
--- /dev/null
+++ b/uv_magic_uv/legacy/ui/view3d_uv_mapping.py
@@ -0,0 +1,116 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from ..op import (
+    uvw,
+    texture_projection,
+    unwrap_constraint,
+)
+from ..op.texture_projection import (
+    MUV_OT_TextureProjection
+)
+from ...utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_PT_View3D_UVMapping',
+]
+
+
+@BlClassRegistry(legacy=True)
+class MUV_PT_View3D_UVMapping(bpy.types.Panel):
+    """
+    Panel class: UV Mapping on Property Panel on View3D
+    """
+
+    bl_space_type = 'VIEW_3D'
+    bl_region_type = 'TOOLS'
+    bl_label = "UV Mapping"
+    bl_category = "Magic UV"
+    bl_context = 'mesh_edit'
+    bl_options = {'DEFAULT_CLOSED'}
+
+    def draw_header(self, _):
+        layout = self.layout
+        layout.label(text="", icon='IMAGE_COL')
+
+    def draw(self, context):
+        sc = context.scene
+        layout = self.layout
+
+        box = layout.box()
+        box.prop(sc, "muv_unwrap_constraint_enabled", text="Unwrap Constraint")
+        if sc.muv_unwrap_constraint_enabled:
+            ops = box.operator(
+                unwrap_constraint.MUV_OT_UnwrapConstraint.bl_idname,
+                text="Unwrap")
+            ops.u_const = sc.muv_unwrap_constraint_u_const
+            ops.v_const = sc.muv_unwrap_constraint_v_const
+            row = box.row(align=True)
+            row.prop(sc, "muv_unwrap_constraint_u_const", text="U-Constraint")
+            row.prop(sc, "muv_unwrap_constraint_v_const", text="V-Constraint")
+
+        box = layout.box()
+        box.prop(sc, "muv_texture_projection_enabled",
+                 text="Texture Projection")
+        if sc.muv_texture_projection_enabled:
+            row = box.row()
+            row.prop(
+                sc, "muv_texture_projection_enable",
+                text="Disable"
+                if MUV_OT_TextureProjection.is_running(context)
+                else "Enable",
+                icon='RESTRICT_VIEW_OFF'
+                if MUV_OT_TextureProjection.is_running(context)
+                else 'RESTRICT_VIEW_ON')
+            row.prop(sc, "muv_texture_projection_tex_image", text="")
+            box.prop(sc, "muv_texture_projection_tex_transparency",
+                     text="Transparency")
+            col = box.column(align=True)
+            row = col.row()
+            row.prop(sc, "muv_texture_projection_adjust_window",
+                     text="Adjust Window")
+            if not sc.muv_texture_projection_adjust_window:
+                row.prop(sc, "muv_texture_projection_tex_magnitude",
+                         text="Magnitude")
+            col.prop(sc, "muv_texture_projection_apply_tex_aspect",
+                     text="Texture Aspect Ratio")
+            col.prop(sc, "muv_texture_projection_assign_uvmap",
+                     text="Assign UVMap")
+            box.operator(
+                texture_projection.MUV_OT_TextureProjection_Project.bl_idname,
+                text="Project")
+
+        box = layout.box()
+        box.prop(sc, "muv_uvw_enabled", text="UVW")
+        if sc.muv_uvw_enabled:
+            row = box.row(align=True)
+            ops = row.operator(uvw.MUV_OT_UVW_BoxMap.bl_idname, text="Box")
+            ops.assign_uvmap = sc.muv_uvw_assign_uvmap
+            ops = row.operator(uvw.MUV_OT_UVW_BestPlanerMap.bl_idname,
+                               text="Best Planner")
+            ops.assign_uvmap = sc.muv_uvw_assign_uvmap
+            box.prop(sc, "muv_uvw_assign_uvmap", text="Assign UVMap")
diff --git a/uv_magic_uv/op/__init__.py b/uv_magic_uv/op/__init__.py
index 75885ef6ae1bf48791c55e535d7cd19775d56974..2142c15771a51e7f94d76d039c4aef41d62a4832 100644
--- a/uv_magic_uv/op/__init__.py
+++ b/uv_magic_uv/op/__init__.py
@@ -20,53 +20,27 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 if "bpy" in locals():
     import importlib
-    importlib.reload(align_uv)
-    importlib.reload(align_uv_cursor)
     importlib.reload(copy_paste_uv)
     importlib.reload(copy_paste_uv_object)
     importlib.reload(copy_paste_uv_uvedit)
     importlib.reload(flip_rotate_uv)
     importlib.reload(mirror_uv)
     importlib.reload(move_uv)
-    importlib.reload(pack_uv)
-    importlib.reload(preserve_uv_aspect)
-    importlib.reload(smooth_uv)
-    importlib.reload(texture_lock)
-    importlib.reload(texture_projection)
-    importlib.reload(texture_wrap)
     importlib.reload(transfer_uv)
-    importlib.reload(unwrap_constraint)
-    importlib.reload(uv_bounding_box)
-    importlib.reload(uv_inspection)
-    importlib.reload(uv_sculpt)
     importlib.reload(uvw)
-    importlib.reload(world_scale_uv)
 else:
-    from . import align_uv
-    from . import align_uv_cursor
     from . import copy_paste_uv
     from . import copy_paste_uv_object
     from . import copy_paste_uv_uvedit
     from . import flip_rotate_uv
     from . import mirror_uv
     from . import move_uv
-    from . import pack_uv
-    from . import preserve_uv_aspect
-    from . import smooth_uv
-    from . import texture_lock
-    from . import texture_projection
-    from . import texture_wrap
     from . import transfer_uv
-    from . import unwrap_constraint
-    from . import uv_bounding_box
-    from . import uv_inspection
-    from . import uv_sculpt
     from . import uvw
-    from . import world_scale_uv
 
 import bpy
diff --git a/uv_magic_uv/op/copy_paste_uv.py b/uv_magic_uv/op/copy_paste_uv.py
index ee89b5e965afc996d94be49c37fb60db042e2e65..23bc8343853fa275a4d414e19a7ab68ea628de82 100644
--- a/uv_magic_uv/op/copy_paste_uv.py
+++ b/uv_magic_uv/op/copy_paste_uv.py
@@ -18,121 +18,179 @@
 #
 # ##### END GPL LICENSE BLOCK #####
 
-__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
+__author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
-import math
-from math import atan2, sin, cos
 
-import bpy
 import bmesh
+import bpy.utils
 from bpy.props import (
     StringProperty,
     BoolProperty,
     IntProperty,
     EnumProperty,
 )
-from mathutils import Vector
 
+from ..impl import copy_paste_uv_impl as impl
 from .. import common
+from ..utils.bl_class_registry import BlClassRegistry
+from ..utils.property_class_registry import PropertyClassRegistry
+
+__all__ = [
+    'Properties',
+    'MUV_OT_CopyPasteUV_CopyUV',
+    'MUV_MT_CopyPasteUV_CopyUV',
+    'MUV_OT_CopyPasteUV_PasteUV',
+    'MUV_MT_CopyPasteUV_PasteUV',
+    'MUV_OT_CopyPasteUV_SelSeqCopyUV',
+    'MUV_MT_CopyPasteUV_SelSeqCopyUV',
+    'MUV_OT_CopyPasteUV_SelSeqPasteUV',
+    'MUV_MT_CopyPasteUV_SelSeqPasteUV',
+]
+
+
+@PropertyClassRegistry()
+class Properties:
+    idname = "copy_paste_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            src_info = None
+
+        scene.muv_props.copy_paste_uv = Props()
+        scene.muv_props.copy_paste_uv_selseq = Props()
+
+        scene.muv_copy_paste_uv_enabled = BoolProperty(
+            name="Copy/Paste UV Enabled",
+            description="Copy/Paste UV is enabled",
+            default=False
+        )
+        scene.muv_copy_paste_uv_copy_seams = BoolProperty(
+            name="Seams",
+            description="Copy Seams",
+            default=True
+        )
+        scene.muv_copy_paste_uv_mode = EnumProperty(
+            items=[
+                ('DEFAULT', "Default", "Default Mode"),
+                ('SEL_SEQ', "Selection Sequence", "Selection Sequence Mode")
+            ],
+            name="Copy/Paste UV Mode",
+            description="Copy/Paste UV Mode",
+            default='DEFAULT'
+        )
+        scene.muv_copy_paste_uv_strategy = EnumProperty(
+            name="Strategy",
+            description="Paste Strategy",
+            items=[
+                ('N_N', 'N:N', 'Number of faces must be equal to source'),
+                ('N_M', 'N:M', 'Number of faces must not be equal to source')
+            ],
+            default='N_M'
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_props.copy_paste_uv
+        del scene.muv_props.copy_paste_uv_selseq
+        del scene.muv_copy_paste_uv_enabled
+        del scene.muv_copy_paste_uv_copy_seams
+        del scene.muv_copy_paste_uv_mode
+        del scene.muv_copy_paste_uv_strategy
 
 
-class MUV_CPUVCopyUV(bpy.types.Operator):
+@BlClassRegistry()
+class MUV_OT_CopyPasteUV_CopyUV(bpy.types.Operator):
     """
     Operation class: Copy UV coordinate
     """
 
-    bl_idname = "uv.muv_cpuv_copy_uv"
-    bl_label = "Copy UV (Operation)"
-    bl_description = "Copy UV coordinate (Operation)"
+    bl_idname = "uv.muv_copy_paste_uv_operator_copy_uv"
+    bl_label = "Copy UV"
+    bl_description = "Copy UV coordinate"
     bl_options = {'REGISTER', 'UNDO'}
 
-    uv_map = StringProperty(options={'HIDDEN'})
+    uv_map: StringProperty(default="__default", options={'HIDDEN'})
+
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return impl.is_valid_context(context)
 
     def execute(self, context):
-        props = context.scene.muv_props.cpuv
-        if self.uv_map == "":
-            self.report({'INFO'}, "Copy UV coordinate")
-        else:
-            self.report(
-                {'INFO'}, "Copy UV coordinate (UV map:%s)" % (self.uv_map))
+        props = context.scene.muv_props.copy_paste_uv
         obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
+        bm = common.create_bmesh(obj)
 
         # get UV layer
-        if self.uv_map == "":
-            if not bm.loops.layers.uv:
-                self.report(
-                    {'WARNING'}, "Object must have more than one UV map")
-                return {'CANCELLED'}
-            uv_layer = bm.loops.layers.uv.verify()
-        else:
-            uv_layer = bm.loops.layers.uv[self.uv_map]
+        uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map)
+        if not uv_layers:
+            return {'CANCELLED'}
 
         # get selected face
-        props.src_uvs = []
-        props.src_pin_uvs = []
-        props.src_seams = []
-        for face in bm.faces:
-            if face.select:
-                uvs = [l[uv_layer].uv.copy() for l in face.loops]
-                pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
-                seams = [l.edge.seam for l in face.loops]
-                props.src_uvs.append(uvs)
-                props.src_pin_uvs.append(pin_uvs)
-                props.src_seams.append(seams)
-        if not props.src_uvs or not props.src_pin_uvs:
-            self.report({'WARNING'}, "No faces are selected")
+        src_info = impl.get_src_face_info(self, bm, uv_layers)
+        if src_info is None:
             return {'CANCELLED'}
-        self.report({'INFO'}, "%d face(s) are selected" % len(props.src_uvs))
+        props.src_info = src_info
+
+        face_count = len(props.src_info[list(props.src_info.keys())[0]])
+        self.report({'INFO'}, "{} face(s) are copied".format(face_count))
 
         return {'FINISHED'}
 
 
-class MUV_CPUVCopyUVMenu(bpy.types.Menu):
+@BlClassRegistry()
+class MUV_MT_CopyPasteUV_CopyUV(bpy.types.Menu):
     """
     Menu class: Copy UV coordinate
     """
 
-    bl_idname = "uv.muv_cpuv_copy_uv_menu"
-    bl_label = "Copy UV"
-    bl_description = "Copy UV coordinate"
+    bl_idname = "uv.muv_copy_paste_uv_menu_copy_uv"
+    bl_label = "Copy UV (Menu)"
+    bl_description = "Menu of Copy UV coordinate"
+
+    @classmethod
+    def poll(cls, context):
+        return impl.is_valid_context(context)
 
     def draw(self, context):
         layout = self.layout
         # create sub menu
         obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
+        bm = common.create_bmesh(obj)
         uv_maps = bm.loops.layers.uv.keys()
-        layout.operator(
-            MUV_CPUVCopyUV.bl_idname,
-            text="[Default]",
-            icon="IMAGE_COL"
-        ).uv_map = ""
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname,
+                              text="[Default]")
+        ops.uv_map = "__default"
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+
         for m in uv_maps:
-            layout.operator(
-                MUV_CPUVCopyUV.bl_idname,
-                text=m,
-                icon="IMAGE_COL"
-            ).uv_map = m
+            ops = layout.operator(MUV_OT_CopyPasteUV_CopyUV.bl_idname, text=m)
+            ops.uv_map = m
 
 
-class MUV_CPUVPasteUV(bpy.types.Operator):
+@BlClassRegistry()
+class MUV_OT_CopyPasteUV_PasteUV(bpy.types.Operator):
     """
     Operation class: Paste UV coordinate
     """
 
-    bl_idname = "uv.muv_cpuv_paste_uv"
-    bl_label = "Paste UV (Operation)"
-    bl_description = "Paste UV coordinate (Operation)"
+    bl_idname = "uv.muv_copy_paste_uv_operator_paste_uv"
+    bl_label = "Paste UV"
+    bl_description = "Paste UV coordinate"
     bl_options = {'REGISTER', 'UNDO'}
 
-    uv_map = StringProperty(options={'HIDDEN'})
-    strategy = EnumProperty(
+    uv_map: StringProperty(default="__default", options={'HIDDEN'})
+    strategy: EnumProperty(
         name="Strategy",
         description="Paste Strategy",
         items=[
@@ -141,353 +199,209 @@ class MUV_CPUVPasteUV(bpy.types.Operator):
         ],
         default="N_M"
     )
-    flip_copied_uv = BoolProperty(
+    flip_copied_uv: BoolProperty(
         name="Flip Copied UV",
         description="Flip Copied UV...",
         default=False
     )
-    rotate_copied_uv = IntProperty(
+    rotate_copied_uv: IntProperty(
         default=0,
         name="Rotate Copied UV",
         min=0,
         max=30
     )
-    copy_seams = BoolProperty(
-        name="Copy Seams",
+    copy_seams: BoolProperty(
+        name="Seams",
         description="Copy Seams",
         default=True
     )
 
+    @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
+        if not props.src_info:
+            return False
+        return impl.is_valid_context(context)
+
     def execute(self, context):
-        props = context.scene.muv_props.cpuv
-        if not props.src_uvs or not props.src_pin_uvs:
+        props = context.scene.muv_props.copy_paste_uv
+        if not props.src_info:
             self.report({'WARNING'}, "Need copy UV at first")
             return {'CANCELLED'}
-        if self.uv_map == "":
-            self.report({'INFO'}, "Paste UV coordinate")
-        else:
-            self.report(
-                {'INFO'}, "Paste UV coordinate (UV map:%s)" % (self.uv_map))
         obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
+        bm = common.create_bmesh(obj)
 
         # get UV layer
-        if self.uv_map == "":
-            if not bm.loops.layers.uv:
-                self.report(
-                    {'WARNING'}, "Object must have more than one UV map")
-                return {'CANCELLED'}
-            uv_layer = bm.loops.layers.uv.verify()
-        else:
-            uv_layer = bm.loops.layers.uv[self.uv_map]
+        uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info,
+                                             self.uv_map)
+        if not uv_layers:
+            return {'CANCELLED'}
 
         # get selected face
-        dest_uvs = []
-        dest_pin_uvs = []
-        dest_seams = []
-        dest_face_indices = []
-        for face in bm.faces:
-            if face.select:
-                dest_face_indices.append(face.index)
-                uvs = [l[uv_layer].uv.copy() for l in face.loops]
-                pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
-                seams = [l.edge.seam for l in face.loops]
-                dest_uvs.append(uvs)
-                dest_pin_uvs.append(pin_uvs)
-                dest_seams.append(seams)
-        if not dest_uvs or not dest_pin_uvs:
-            self.report({'WARNING'}, "No faces are selected")
-            return {'CANCELLED'}
-        if self.strategy == 'N_N' and len(props.src_uvs) != len(dest_uvs):
-            self.report(
-                {'WARNING'},
-                "Number of selected faces is different from copied" +
-                "(src:%d, dest:%d)" %
-                (len(props.src_uvs), len(dest_uvs)))
+        dest_info = impl.get_dest_face_info(self, bm, uv_layers,
+                                            props.src_info, self.strategy)
+        if dest_info is None:
             return {'CANCELLED'}
 
         # paste
-        for i, idx in enumerate(dest_face_indices):
-            suv = None
-            spuv = None
-            ss = None
-            duv = None
-            if self.strategy == 'N_N':
-                suv = props.src_uvs[i]
-                spuv = props.src_pin_uvs[i]
-                ss = props.src_seams[i]
-                duv = dest_uvs[i]
-            elif self.strategy == 'N_M':
-                suv = props.src_uvs[i % len(props.src_uvs)]
-                spuv = props.src_pin_uvs[i % len(props.src_pin_uvs)]
-                ss = props.src_seams[i % len(props.src_seams)]
-                duv = dest_uvs[i]
-            if len(suv) != len(duv):
-                self.report({'WARNING'}, "Some faces are different size")
-                return {'CANCELLED'}
-            suvs_fr = [uv for uv in suv]
-            spuvs_fr = [pin_uv for pin_uv in spuv]
-            ss_fr = [s for s in ss]
-            # flip UVs
-            if self.flip_copied_uv is True:
-                suvs_fr.reverse()
-                spuvs_fr.reverse()
-                ss_fr.reverse()
-            # rotate UVs
-            for _ in range(self.rotate_copied_uv):
-                uv = suvs_fr.pop()
-                pin_uv = spuvs_fr.pop()
-                s = ss_fr.pop()
-                suvs_fr.insert(0, uv)
-                spuvs_fr.insert(0, pin_uv)
-                ss_fr.insert(0, s)
-            # paste UVs
-            for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr,
-                                        spuvs_fr, ss_fr):
-                l[uv_layer].uv = suv
-                l[uv_layer].pin_uv = spuv
-                if self.copy_seams is True:
-                    l.edge.seam = ss
-        self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs))
+        ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers,
+                            self.strategy, self.flip_copied_uv,
+                            self.rotate_copied_uv, self.copy_seams)
+        if ret:
+            return {'CANCELLED'}
+
+        face_count = len(props.src_info[list(dest_info.keys())[0]])
+        self.report({'INFO'}, "{} face(s) are pasted".format(face_count))
 
         bmesh.update_edit_mesh(obj.data)
-        if self.copy_seams is True:
-            obj.data.show_edge_seams = True
 
         return {'FINISHED'}
 
 
-class MUV_CPUVPasteUVMenu(bpy.types.Menu):
+@BlClassRegistry()
+class MUV_MT_CopyPasteUV_PasteUV(bpy.types.Menu):
     """
     Menu class: Paste UV coordinate
     """
 
-    bl_idname = "uv.muv_cpuv_paste_uv_menu"
-    bl_label = "Paste UV"
-    bl_description = "Paste UV coordinate"
+    bl_idname = "uv.muv_copy_paste_uv_menu_paste_uv"
+    bl_label = "Paste UV (Menu)"
+    bl_description = "Menu of Paste UV coordinate"
+
+    @classmethod
+    def poll(cls, context):
+        sc = context.scene
+        props = sc.muv_props.copy_paste_uv
+        if not props.src_info:
+            return False
+        return impl.is_valid_context(context)
 
     def draw(self, context):
         sc = context.scene
         layout = self.layout
         # create sub menu
         obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
+        bm = common.create_bmesh(obj)
         uv_maps = bm.loops.layers.uv.keys()
-        ops = layout.operator(MUV_CPUVPasteUV.bl_idname, text="[Default]")
-        ops.uv_map = ""
-        ops.copy_seams = sc.muv_cpuv_copy_seams
-        ops.strategy = sc.muv_cpuv_strategy
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname,
+                              text="[Default]")
+        ops.uv_map = "__default"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname,
+                              text="[New]")
+        ops.uv_map = "__new"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
         for m in uv_maps:
-            ops = layout.operator(MUV_CPUVPasteUV.bl_idname, text=m)
+            ops = layout.operator(MUV_OT_CopyPasteUV_PasteUV.bl_idname, text=m)
             ops.uv_map = m
-            ops.copy_seams = sc.muv_cpuv_copy_seams
-            ops.strategy = sc.muv_cpuv_strategy
+            ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+            ops.strategy = sc.muv_copy_paste_uv_strategy
 
 
-class MUV_CPUVIECopyUV(bpy.types.Operator):
+@BlClassRegistry()
+class MUV_OT_CopyPasteUV_SelSeqCopyUV(bpy.types.Operator):
     """
-    Operation class: Copy UV coordinate on UV/Image Editor
+    Operation class: Copy UV coordinate by selection sequence
     """
 
-    bl_idname = "uv.muv_cpuv_ie_copy_uv"
-    bl_label = "Copy UV"
-    bl_description = "Copy UV coordinate (only selected in UV/Image Editor)"
+    bl_idname = "uv.muv_copy_paste_uv_operator_selseq_copy_uv"
+    bl_label = "Copy UV (Selection Sequence)"
+    bl_description = "Copy UV data by selection sequence"
     bl_options = {'REGISTER', 'UNDO'}
 
-    @classmethod
-    def poll(cls, context):
-        return context.mode == 'EDIT_MESH'
-
-    def execute(self, context):
-        props = context.scene.muv_props.cpuv
-        obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        uv_layer = bm.loops.layers.uv.verify()
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
-
-        for face in bm.faces:
-            if not face.select:
-                continue
-            skip = False
-            for l in face.loops:
-                if not l[uv_layer].select:
-                    skip = True
-                    break
-            if skip:
-                continue
-            props.src_uvs.append([l[uv_layer].uv.copy() for l in face.loops])
-
-        return {'FINISHED'}
-
-
-class MUV_CPUVIEPasteUV(bpy.types.Operator):
-    """
-    Operation class: Paste UV coordinate on UV/Image Editor
-    """
-
-    bl_idname = "uv.muv_cpuv_ie_paste_uv"
-    bl_label = "Paste UV"
-    bl_description = "Paste UV coordinate (only selected in UV/Image Editor)"
-    bl_options = {'REGISTER', 'UNDO'}
+    uv_map: StringProperty(default="__default", options={'HIDDEN'})
 
     @classmethod
     def poll(cls, context):
-        return context.mode == 'EDIT_MESH'
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return impl.is_valid_context(context)
 
     def execute(self, context):
-        props = context.scene.muv_props.cpuv
+        props = context.scene.muv_props.copy_paste_uv_selseq
         obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        uv_layer = bm.loops.layers.uv.verify()
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
-
-        dest_uvs = []
-        dest_face_indices = []
-        for face in bm.faces:
-            if not face.select:
-                continue
-            skip = False
-            for l in face.loops:
-                if not l[uv_layer].select:
-                    skip = True
-                    break
-            if skip:
-                continue
-            dest_face_indices.append(face.index)
-            uvs = [l[uv_layer].uv.copy() for l in face.loops]
-            dest_uvs.append(uvs)
-
-        for suvs, duvs in zip(props.src_uvs, dest_uvs):
-            src_diff = suvs[1] - suvs[0]
-            dest_diff = duvs[1] - duvs[0]
-
-            src_base = suvs[0]
-            dest_base = duvs[0]
-
-            src_rad = atan2(src_diff.y, src_diff.x)
-            dest_rad = atan2(dest_diff.y, dest_diff.x)
-            if src_rad < dest_rad:
-                radian = dest_rad - src_rad
-            elif src_rad > dest_rad:
-                radian = math.pi * 2 - (src_rad - dest_rad)
-            else:       # src_rad == dest_rad
-                radian = 0.0
-
-            ratio = dest_diff.length / src_diff.length
-            break
-
-        for suvs, fidx in zip(props.src_uvs, dest_face_indices):
-            for l, suv in zip(bm.faces[fidx].loops, suvs):
-                base = suv - src_base
-                radian_ref = atan2(base.y, base.x)
-                radian_fin = (radian + radian_ref)
-                length = base.length
-                turn = Vector((length * cos(radian_fin),
-                               length * sin(radian_fin)))
-                target_uv = Vector((turn.x * ratio, turn.y * ratio)) + \
-                    dest_base
-                l[uv_layer].uv = target_uv
-
-        bmesh.update_edit_mesh(obj.data)
-
-        return {'FINISHED'}
-
-
-class MUV_CPUVSelSeqCopyUV(bpy.types.Operator):
-    """
-    Operation class: Copy UV coordinate by selection sequence
-    """
-
-    bl_idname = "uv.muv_cpuv_selseq_copy_uv"
-    bl_label = "Copy UV (Selection Sequence) (Operation)"
-    bl_description = "Copy UV data by selection sequence (Operation)"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    uv_map = StringProperty(options={'HIDDEN'})
-
-    def execute(self, context):
-        props = context.scene.muv_props.cpuv_selseq
-        if self.uv_map == "":
-            self.report({'INFO'}, "Copy UV coordinate (selection sequence)")
-        else:
-            self.report(
-                {'INFO'},
-                "Copy UV coordinate (selection sequence) (UV map:%s)"
-                % (self.uv_map))
-        obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
+        bm = common.create_bmesh(obj)
 
         # get UV layer
-        if self.uv_map == "":
-            if not bm.loops.layers.uv:
-                self.report(
-                    {'WARNING'}, "Object must have more than one UV map")
-                return {'CANCELLED'}
-            uv_layer = bm.loops.layers.uv.verify()
-        else:
-            uv_layer = bm.loops.layers.uv[self.uv_map]
+        uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map)
+        if not uv_layers:
+            return {'CANCELLED'}
 
         # get selected face
-        props.src_uvs = []
-        props.src_pin_uvs = []
-        props.src_seams = []
-        for hist in bm.select_history:
-            if isinstance(hist, bmesh.types.BMFace) and hist.select:
-                uvs = [l[uv_layer].uv.copy() for l in hist.loops]
-                pin_uvs = [l[uv_layer].pin_uv for l in hist.loops]
-                seams = [l.edge.seam for l in hist.loops]
-                props.src_uvs.append(uvs)
-                props.src_pin_uvs.append(pin_uvs)
-                props.src_seams.append(seams)
-        if not props.src_uvs or not props.src_pin_uvs:
-            self.report({'WARNING'}, "No faces are selected")
+        src_info = impl.get_select_history_src_face_info(self, bm, uv_layers)
+        if src_info is None:
             return {'CANCELLED'}
-        self.report({'INFO'}, "%d face(s) are selected" % len(props.src_uvs))
+        props.src_info = src_info
+
+        face_count = len(props.src_info[list(props.src_info.keys())[0]])
+        self.report({'INFO'}, "{} face(s) are selected".format(face_count))
 
         return {'FINISHED'}
 
 
-class MUV_CPUVSelSeqCopyUVMenu(bpy.types.Menu):
+@BlClassRegistry()
+class MUV_MT_CopyPasteUV_SelSeqCopyUV(bpy.types.Menu):
     """
     Menu class: Copy UV coordinate by selection sequence
     """
 
-    bl_idname = "uv.muv_cpuv_selseq_copy_uv_menu"
-    bl_label = "Copy UV (Selection Sequence)"
-    bl_description = "Copy UV coordinate by selection sequence"
+    bl_idname = "uv.muv_copy_paste_uv_menu_selseq_copy_uv"
+    bl_label = "Copy UV (Selection Sequence) (Menu)"
+    bl_description = "Menu of Copy UV coordinate by selection sequence"
+
+    @classmethod
+    def poll(cls, context):
+        return impl.is_valid_context(context)
 
     def draw(self, context):
         layout = self.layout
         obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
+        bm = common.create_bmesh(obj)
         uv_maps = bm.loops.layers.uv.keys()
-        layout.operator(
-            MUV_CPUVSelSeqCopyUV.bl_idname,
-            text="[Default]", icon="IMAGE_COL").uv_map = ""
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname,
+                              text="[Default]")
+        ops.uv_map = "__default"
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+
         for m in uv_maps:
-            layout.operator(
-                MUV_CPUVSelSeqCopyUV.bl_idname,
-                text=m, icon="IMAGE_COL").uv_map = m
+            ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqCopyUV.bl_idname,
+                                  text=m)
+            ops.uv_map = m
 
 
-class MUV_CPUVSelSeqPasteUV(bpy.types.Operator):
+@BlClassRegistry()
+class MUV_OT_CopyPasteUV_SelSeqPasteUV(bpy.types.Operator):
     """
     Operation class: Paste UV coordinate by selection sequence
     """
 
-    bl_idname = "uv.muv_cpuv_selseq_paste_uv"
-    bl_label = "Paste UV (Selection Sequence) (Operation)"
-    bl_description = "Paste UV coordinate by selection sequence (Operation)"
+    bl_idname = "uv.muv_copy_paste_uv_operator_selseq_paste_uv"
+    bl_label = "Paste UV (Selection Sequence)"
+    bl_description = "Paste UV coordinate by selection sequence"
     bl_options = {'REGISTER', 'UNDO'}
 
-    uv_map = StringProperty(options={'HIDDEN'})
-    strategy = EnumProperty(
+    uv_map: StringProperty(default="__default", options={'HIDDEN'})
+    strategy: EnumProperty(
         name="Strategy",
         description="Paste Strategy",
         items=[
@@ -496,151 +410,117 @@ class MUV_CPUVSelSeqPasteUV(bpy.types.Operator):
         ],
         default="N_M"
     )
-    flip_copied_uv = BoolProperty(
+    flip_copied_uv: BoolProperty(
         name="Flip Copied UV",
         description="Flip Copied UV...",
         default=False
     )
-    rotate_copied_uv = IntProperty(
+    rotate_copied_uv: IntProperty(
         default=0,
         name="Rotate Copied UV",
         min=0,
         max=30
     )
-    copy_seams = BoolProperty(
-        name="Copy Seams",
+    copy_seams: BoolProperty(
+        name="Seams",
         description="Copy Seams",
         default=True
     )
 
+    @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_selseq
+        if not props.src_info:
+            return False
+        return impl.is_valid_context(context)
+
     def execute(self, context):
-        props = context.scene.muv_props.cpuv_selseq
-        if not props.src_uvs or not props.src_pin_uvs:
+        props = context.scene.muv_props.copy_paste_uv_selseq
+        if not props.src_info:
             self.report({'WARNING'}, "Need copy UV at first")
             return {'CANCELLED'}
-        if self.uv_map == "":
-            self.report({'INFO'}, "Paste UV coordinate (selection sequence)")
-        else:
-            self.report(
-                {'INFO'},
-                "Paste UV coordinate (selection sequence) (UV map:%s)"
-                % (self.uv_map))
-
         obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
+        bm = common.create_bmesh(obj)
 
         # get UV layer
-        if self.uv_map == "":
-            if not bm.loops.layers.uv:
-                self.report(
-                    {'WARNING'}, "Object must have more than one UV map")
-                return {'CANCELLED'}
-            uv_layer = bm.loops.layers.uv.verify()
-        else:
-            uv_layer = bm.loops.layers.uv[self.uv_map]
+        uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info,
+                                             self.uv_map)
+        if not uv_layers:
+            return {'CANCELLED'}
 
         # get selected face
-        dest_uvs = []
-        dest_pin_uvs = []
-        dest_seams = []
-        dest_face_indices = []
-        for hist in bm.select_history:
-            if isinstance(hist, bmesh.types.BMFace) and hist.select:
-                dest_face_indices.append(hist.index)
-                uvs = [l[uv_layer].uv.copy() for l in hist.loops]
-                pin_uvs = [l[uv_layer].pin_uv for l in hist.loops]
-                seams = [l.edge.seam for l in hist.loops]
-                dest_uvs.append(uvs)
-                dest_pin_uvs.append(pin_uvs)
-                dest_seams.append(seams)
-        if not dest_uvs or not dest_pin_uvs:
-            self.report({'WARNING'}, "No faces are selected")
-            return {'CANCELLED'}
-        if self.strategy == 'N_N' and len(props.src_uvs) != len(dest_uvs):
-            self.report(
-                {'WARNING'},
-                "Number of selected faces is different from copied faces " +
-                "(src:%d, dest:%d)"
-                % (len(props.src_uvs), len(dest_uvs)))
+        dest_info = impl.get_select_history_dest_face_info(self, bm, uv_layers,
+                                                           props.src_info,
+                                                           self.strategy)
+        if dest_info is None:
             return {'CANCELLED'}
 
         # paste
-        for i, idx in enumerate(dest_face_indices):
-            suv = None
-            spuv = None
-            ss = None
-            duv = None
-            if self.strategy == 'N_N':
-                suv = props.src_uvs[i]
-                spuv = props.src_pin_uvs[i]
-                ss = props.src_seams[i]
-                duv = dest_uvs[i]
-            elif self.strategy == 'N_M':
-                suv = props.src_uvs[i % len(props.src_uvs)]
-                spuv = props.src_pin_uvs[i % len(props.src_pin_uvs)]
-                ss = props.src_seams[i % len(props.src_seams)]
-                duv = dest_uvs[i]
-            if len(suv) != len(duv):
-                self.report({'WARNING'}, "Some faces are different size")
-                return {'CANCELLED'}
-            suvs_fr = [uv for uv in suv]
-            spuvs_fr = [pin_uv for pin_uv in spuv]
-            ss_fr = [s for s in ss]
-            # flip UVs
-            if self.flip_copied_uv is True:
-                suvs_fr.reverse()
-                spuvs_fr.reverse()
-                ss_fr.reverse()
-            # rotate UVs
-            for _ in range(self.rotate_copied_uv):
-                uv = suvs_fr.pop()
-                pin_uv = spuvs_fr.pop()
-                s = ss_fr.pop()
-                suvs_fr.insert(0, uv)
-                spuvs_fr.insert(0, pin_uv)
-                ss_fr.insert(0, s)
-            # paste UVs
-            for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr,
-                                        spuvs_fr, ss_fr):
-                l[uv_layer].uv = suv
-                l[uv_layer].pin_uv = spuv
-                if self.copy_seams is True:
-                    l.edge.seam = ss
-
-        self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs))
+        ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers,
+                            self.strategy, self.flip_copied_uv,
+                            self.rotate_copied_uv, self.copy_seams)
+        if ret:
+            return {'CANCELLED'}
+
+        face_count = len(props.src_info[list(dest_info.keys())[0]])
+        self.report({'INFO'}, "{} face(s) are pasted".format(face_count))
 
         bmesh.update_edit_mesh(obj.data)
-        if self.copy_seams is True:
-            obj.data.show_edge_seams = True
 
         return {'FINISHED'}
 
 
-class MUV_CPUVSelSeqPasteUVMenu(bpy.types.Menu):
+@BlClassRegistry()
+class MUV_MT_CopyPasteUV_SelSeqPasteUV(bpy.types.Menu):
     """
     Menu class: Paste UV coordinate by selection sequence
     """
 
-    bl_idname = "uv.muv_cpuv_selseq_paste_uv_menu"
-    bl_label = "Paste UV (Selection Sequence)"
-    bl_description = "Paste UV coordinate by selection sequence"
+    bl_idname = "uv.muv_copy_paste_uv_menu_selseq_paste_uv"
+    bl_label = "Paste UV (Selection Sequence) (Menu)"
+    bl_description = "Menu of Paste UV coordinate by selection sequence"
+
+    @classmethod
+    def poll(cls, context):
+        sc = context.scene
+        props = sc.muv_props.copy_paste_uv_selseq
+        if not props.src_uvs or not props.src_pin_uvs:
+            return False
+        return impl.is_valid_context(context)
 
     def draw(self, context):
         sc = context.scene
         layout = self.layout
         # create sub menu
         obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
+        bm = common.create_bmesh(obj)
         uv_maps = bm.loops.layers.uv.keys()
-        ops = layout.operator(MUV_CPUVSelSeqPasteUV.bl_idname,
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname,
                               text="[Default]")
-        ops.uv_map = ""
-        ops.copy_seams = sc.muv_cpuv_copy_seams
-        ops.strategy = sc.muv_cpuv_strategy
+        ops.uv_map = "__default"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname,
+                              text="[New]")
+        ops.uv_map = "__new"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
+        ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+        ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+        ops.strategy = sc.muv_copy_paste_uv_strategy
+
         for m in uv_maps:
-            ops = layout.operator(MUV_CPUVSelSeqPasteUV.bl_idname, text=m)
+            ops = layout.operator(MUV_OT_CopyPasteUV_SelSeqPasteUV.bl_idname,
+                                  text=m)
             ops.uv_map = m
-            ops.copy_seams = sc.muv_cpuv_copy_seams
-            ops.strategy = sc.muv_cpuv_strategy
+            ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+            ops.strategy = sc.muv_copy_paste_uv_strategy
diff --git a/uv_magic_uv/op/copy_paste_uv_object.py b/uv_magic_uv/op/copy_paste_uv_object.py
index d80ee4152a4ffc133c9b6eeb801968eb28b230ca..d9f42447a538735b5d8c0515cb729e8f249ea14a 100644
--- a/uv_magic_uv/op/copy_paste_uv_object.py
+++ b/uv_magic_uv/op/copy_paste_uv_object.py
@@ -20,17 +20,72 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
-import bpy
 import bmesh
+import bpy
 from bpy.props import (
     StringProperty,
     BoolProperty,
 )
 
+from ..impl import copy_paste_uv_impl as impl
 from .. import common
+from ..utils.bl_class_registry import BlClassRegistry
+from ..utils.property_class_registry import PropertyClassRegistry
+
+__all__ = [
+    'Properties',
+    'MUV_OT_CopyPasteUVObject_CopyUV',
+    'MUV_MT_CopyPasteUVObject_CopyUV',
+    'MUV_OT_CopyPasteUVObject_PasteUV',
+    'MUV_MT_CopyPasteUVObject_PasteUV',
+]
+
+
+def is_valid_context(context):
+    obj = context.object
+
+    # only object mode is allowed to execute
+    if obj is None:
+        return False
+    if obj.type != 'MESH':
+        return False
+    if context.object.mode != 'OBJECT':
+        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
+
+
+@PropertyClassRegistry()
+class Properties:
+    idname = "copy_paste_uv_object"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            src_info = None
+
+        scene.muv_props.copy_paste_uv_object = Props()
+
+        scene.muv_copy_paste_uv_object_copy_seams = BoolProperty(
+            name="Seams",
+            description="Copy Seams",
+            default=True
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_props.copy_paste_uv_object
+        del scene.muv_copy_paste_uv_object_copy_seams
 
 
 def memorize_view_3d_mode(fn):
@@ -42,197 +97,173 @@ def memorize_view_3d_mode(fn):
     return __memorize_view_3d_mode
 
 
-class MUV_CPUVObjCopyUV(bpy.types.Operator):
+@BlClassRegistry()
+class MUV_OT_CopyPasteUVObject_CopyUV(bpy.types.Operator):
     """
-    Operation class: Copy UV coordinate per object
+    Operation class: Copy UV coordinate among objects
     """
 
-    bl_idname = "object.muv_cpuv_obj_copy_uv"
-    bl_label = "Copy UV"
-    bl_description = "Copy UV coordinate"
+    bl_idname = "object.muv_copy_paste_uv_object_operator_copy_uv"
+    bl_label = "Copy UV (Among Objects)"
+    bl_description = "Copy UV coordinate (Among Objects)"
     bl_options = {'REGISTER', 'UNDO'}
 
-    uv_map = StringProperty(options={'HIDDEN'})
+    uv_map: StringProperty(default="__default", options={'HIDDEN'})
+
+    @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)
 
     @memorize_view_3d_mode
     def execute(self, context):
-        props = context.scene.muv_props.cpuv_obj
-        if self.uv_map == "":
-            self.report({'INFO'}, "Copy UV coordinate per object")
-        else:
-            self.report(
-                {'INFO'},
-                "Copy UV coordinate per object (UV map:%s)" % (self.uv_map))
+        props = context.scene.muv_props.copy_paste_uv_object
         bpy.ops.object.mode_set(mode='EDIT')
-
         obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
+        bm = common.create_bmesh(obj)
 
         # get UV layer
-        if self.uv_map == "":
-            if not bm.loops.layers.uv:
-                self.report(
-                    {'WARNING'}, "Object must have more than one UV map")
-                return {'CANCELLED'}
-            uv_layer = bm.loops.layers.uv.verify()
-        else:
-            uv_layer = bm.loops.layers.uv[self.uv_map]
+        uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map)
+        if not uv_layers:
+            return {'CANCELLED'}
 
         # get selected face
-        props.src_uvs = []
-        props.src_pin_uvs = []
-        props.src_seams = []
-        for face in bm.faces:
-            uvs = [l[uv_layer].uv.copy() for l in face.loops]
-            pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
-            seams = [l.edge.seam for l in face.loops]
-            props.src_uvs.append(uvs)
-            props.src_pin_uvs.append(pin_uvs)
-            props.src_seams.append(seams)
-
-        self.report({'INFO'}, "%s's UV coordinates are copied" % (obj.name))
+        src_info = impl.get_src_face_info(self, bm, uv_layers)
+        if src_info is None:
+            return {'CANCELLED'}
+        props.src_info = src_info
+
+        self.report({'INFO'},
+                    "{}'s UV coordinates are copied".format(obj.name))
 
         return {'FINISHED'}
 
 
-class MUV_CPUVObjCopyUVMenu(bpy.types.Menu):
+@BlClassRegistry()
+class MUV_MT_CopyPasteUVObject_CopyUV(bpy.types.Menu):
     """
-    Menu class: Copy UV coordinate per object
+    Menu class: Copy UV coordinate among objects
     """
 
-    bl_idname = "object.muv_cpuv_obj_copy_uv_menu"
-    bl_label = "Copy UV"
-    bl_description = "Copy UV coordinate per object"
+    bl_idname = "object.muv_copy_paste_uv_object_menu_copy_uv"
+    bl_label = "Copy UV (Among Objects) (Menu)"
+    bl_description = "Menu of Copy UV coordinate (Among Objects)"
+
+    @classmethod
+    def poll(cls, context):
+        return is_valid_context(context)
 
     def draw(self, _):
         layout = self.layout
         # create sub menu
-        uv_maps = bpy.context.active_object.data.uv_textures.keys()
-        layout.operator(MUV_CPUVObjCopyUV.bl_idname, text="[Default]")\
-            .uv_map = ""
+        uv_maps = bpy.context.active_object.data.uv_layers.keys()
+
+        ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname,
+                              text="[Default]")
+        ops.uv_map = "__default"
+
+        ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+
         for m in uv_maps:
-            layout.operator(MUV_CPUVObjCopyUV.bl_idname, text=m).uv_map = m
+            ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname,
+                                  text=m)
+            ops.uv_map = m
 
 
-class MUV_CPUVObjPasteUV(bpy.types.Operator):
+@BlClassRegistry()
+class MUV_OT_CopyPasteUVObject_PasteUV(bpy.types.Operator):
     """
-    Operation class: Paste UV coordinate per object
+    Operation class: Paste UV coordinate among objects
     """
 
-    bl_idname = "object.muv_cpuv_obj_paste_uv"
-    bl_label = "Paste UV"
-    bl_description = "Paste UV coordinate"
+    bl_idname = "object.muv_copy_paste_uv_object_operator_paste_uv"
+    bl_label = "Paste UV (Among Objects)"
+    bl_description = "Paste UV coordinate (Among Objects)"
     bl_options = {'REGISTER', 'UNDO'}
 
-    uv_map = StringProperty(options={'HIDDEN'})
-    copy_seams = BoolProperty(
-        name="Copy Seams",
+    uv_map: StringProperty(default="__default", options={'HIDDEN'})
+    copy_seams: BoolProperty(
+        name="Seams",
         description="Copy Seams",
         default=True
     )
 
+    @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_object
+        if not props.src_info:
+            return False
+        return is_valid_context(context)
+
     @memorize_view_3d_mode
     def execute(self, context):
-        props = context.scene.muv_props.cpuv_obj
-        if not props.src_uvs or not props.src_pin_uvs:
+        props = context.scene.muv_props.copy_paste_uv_object
+        if not props.src_info:
             self.report({'WARNING'}, "Need copy UV at first")
             return {'CANCELLED'}
 
         for o in bpy.data.objects:
-            if not hasattr(o.data, "uv_textures") or not o.select:
+            if not hasattr(o.data, "uv_layers") or not o.select_get():
                 continue
 
             bpy.ops.object.mode_set(mode='OBJECT')
-            bpy.context.scene.objects.active = o
+            bpy.context.view_layer.objects.active = o
             bpy.ops.object.mode_set(mode='EDIT')
 
             obj = context.active_object
-            bm = bmesh.from_edit_mesh(obj.data)
-            if common.check_version(2, 73, 0) >= 0:
-                bm.faces.ensure_lookup_table()
-
-            if (self.uv_map == "" or
-                    self.uv_map not in bm.loops.layers.uv.keys()):
-                self.report({'INFO'}, "Paste UV coordinate per object")
-            else:
-                self.report(
-                    {'INFO'},
-                    "Paste UV coordinate per object (UV map: %s)"
-                    % (self.uv_map))
+            bm = common.create_bmesh(obj)
 
             # get UV layer
-            if (self.uv_map == "" or
-                    self.uv_map not in bm.loops.layers.uv.keys()):
-                if not bm.loops.layers.uv:
-                    self.report(
-                        {'WARNING'}, "Object must have more than one UV map")
-                    return {'CANCELLED'}
-                uv_layer = bm.loops.layers.uv.verify()
-            else:
-                uv_layer = bm.loops.layers.uv[self.uv_map]
+            uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info,
+                                                 self.uv_map)
+            if not uv_layers:
+                return {'CANCELLED'}
 
             # get selected face
-            dest_uvs = []
-            dest_pin_uvs = []
-            dest_seams = []
-            dest_face_indices = []
-            for face in bm.faces:
-                dest_face_indices.append(face.index)
-                uvs = [l[uv_layer].uv.copy() for l in face.loops]
-                pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
-                seams = [l.edge.seam for l in face.loops]
-                dest_uvs.append(uvs)
-                dest_pin_uvs.append(pin_uvs)
-                dest_seams.append(seams)
-            if len(props.src_uvs) != len(dest_uvs):
-                self.report(
-                    {'WARNING'},
-                    "Number of faces is different from copied " +
-                    "(src:%d, dest:%d)"
-                    % (len(props.src_uvs), len(dest_uvs))
-                )
+            dest_info = impl.get_dest_face_info(self, bm, uv_layers,
+                                                props.src_info, 'N_N')
+            if dest_info is None:
                 return {'CANCELLED'}
 
             # paste
-            for i, idx in enumerate(dest_face_indices):
-                suv = props.src_uvs[i]
-                spuv = props.src_pin_uvs[i]
-                ss = props.src_seams[i]
-                duv = dest_uvs[i]
-                if len(suv) != len(duv):
-                    self.report({'WARNING'}, "Some faces are different size")
-                    return {'CANCELLED'}
-                suvs_fr = [uv for uv in suv]
-                spuvs_fr = [pin_uv for pin_uv in spuv]
-                ss_fr = [s for s in ss]
-                # paste UVs
-                for l, suv, spuv, ss in zip(
-                        bm.faces[idx].loops, suvs_fr, spuvs_fr, ss_fr):
-                    l[uv_layer].uv = suv
-                    l[uv_layer].pin_uv = spuv
-                    if self.copy_seams is True:
-                        l.edge.seam = ss
+            ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers,
+                                'N_N', 0, 0, self.copy_seams)
+            if ret:
+                return {'CANCELLED'}
 
             bmesh.update_edit_mesh(obj.data)
-            if self.copy_seams is True:
-                obj.data.show_edge_seams = True
 
             self.report(
-                {'INFO'}, "%s's UV coordinates are pasted" % (obj.name))
+                {'INFO'}, "{}'s UV coordinates are pasted".format(obj.name))
 
         return {'FINISHED'}
 
 
-class MUV_CPUVObjPasteUVMenu(bpy.types.Menu):
+@BlClassRegistry()
+class MUV_MT_CopyPasteUVObject_PasteUV(bpy.types.Menu):
     """
-    Menu class: Paste UV coordinate per object
+    Menu class: Paste UV coordinate among objects
     """
 
-    bl_idname = "object.muv_cpuv_obj_paste_uv_menu"
-    bl_label = "Paste UV"
-    bl_description = "Paste UV coordinate per object"
+    bl_idname = "object.muv_copy_paste_uv_object_menu_paste_uv"
+    bl_label = "Paste UV (Among Objects) (Menu)"
+    bl_description = "Menu of Paste UV coordinate (Among Objects)"
+
+    @classmethod
+    def poll(cls, context):
+        sc = context.scene
+        props = sc.muv_props.copy_paste_uv_object
+        if not props.src_info:
+            return False
+        return is_valid_context(context)
 
     def draw(self, context):
         sc = context.scene
@@ -240,13 +271,26 @@ class MUV_CPUVObjPasteUVMenu(bpy.types.Menu):
         # create sub menu
         uv_maps = []
         for obj in bpy.data.objects:
-            if hasattr(obj.data, "uv_textures") and obj.select:
-                uv_maps.extend(obj.data.uv_textures.keys())
-        uv_maps = list(set(uv_maps))
-        ops = layout.operator(MUV_CPUVObjPasteUV.bl_idname, text="[Default]")
-        ops.uv_map = ""
-        ops.copy_seams = sc.muv_cpuv_copy_seams
+            if hasattr(obj.data, "uv_layers") and obj.select_get():
+                uv_maps.extend(obj.data.uv_layers.keys())
+
+        ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname,
+                              text="[Default]")
+        ops.uv_map = "__default"
+        ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
+
+        ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname,
+                              text="[New]")
+        ops.uv_map = "__new"
+        ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
+
+        ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname,
+                              text="[All]")
+        ops.uv_map = "__all"
+        ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
+
         for m in uv_maps:
-            ops = layout.operator(MUV_CPUVObjPasteUV.bl_idname, text=m)
+            ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname,
+                                  text=m)
             ops.uv_map = m
-            ops.copy_seams = sc.muv_cpuv_copy_seams
+            ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
diff --git a/uv_magic_uv/op/copy_paste_uv_uvedit.py b/uv_magic_uv/op/copy_paste_uv_uvedit.py
index 96908020a1028ee159f387525bc6baf2e1d9e8a3..719687a6016e510212c10ac05f2c9fa8994bda7d 100644
--- a/uv_magic_uv/op/copy_paste_uv_uvedit.py
+++ b/uv_magic_uv/op/copy_paste_uv_uvedit.py
@@ -18,127 +18,80 @@
 #
 # ##### END GPL LICENSE BLOCK #####
 
-__author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester"
+__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
-
-import math
-from math import atan2, sin, cos
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
-import bmesh
-from mathutils import Vector
 
-from .. import common
+from ..utils.bl_class_registry import BlClassRegistry
+from ..utils.property_class_registry import PropertyClassRegistry
+from ..impl import copy_paste_uv_uvedit_impl as impl
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_CopyPasteUVUVEdit_CopyUV',
+    'MUV_OT_CopyPasteUVUVEdit_PasteUV',
+]
+
+
+@PropertyClassRegistry()
+class Properties:
+    idname = "copy_paste_uv_uvedit"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            src_uvs = None
+
+        scene.muv_props.copy_paste_uv_uvedit = Props()
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_props.copy_paste_uv_uvedit
 
 
-class MUV_CPUVIECopyUV(bpy.types.Operator):
+@BlClassRegistry()
+class MUV_OT_CopyPasteUVUVEdit_CopyUV(bpy.types.Operator):
     """
     Operation class: Copy UV coordinate on UV/Image Editor
     """
 
-    bl_idname = "uv.muv_cpuv_ie_copy_uv"
-    bl_label = "Copy UV"
+    bl_idname = "uv.muv_copy_paste_uv_uvedit_operator_copy_uv"
+    bl_label = "Copy UV (UV/Image Editor)"
     bl_description = "Copy UV coordinate (only selected in UV/Image Editor)"
     bl_options = {'REGISTER', 'UNDO'}
 
+    def __init__(self):
+        self.__impl = impl.CopyUVImpl()
+
     @classmethod
     def poll(cls, context):
-        return context.mode == 'EDIT_MESH'
+        return impl.CopyUVImpl.poll(context)
 
     def execute(self, context):
-        props = context.scene.muv_props.cpuv
-        obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        uv_layer = bm.loops.layers.uv.verify()
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
-
-        for face in bm.faces:
-            if not face.select:
-                continue
-            skip = False
-            for l in face.loops:
-                if not l[uv_layer].select:
-                    skip = True
-                    break
-            if skip:
-                continue
-            props.src_uvs.append([l[uv_layer].uv.copy() for l in face.loops])
-
-        return {'FINISHED'}
-
-
-class MUV_CPUVIEPasteUV(bpy.types.Operator):
+        return self.__impl.execute(self, context)
+
+
+@BlClassRegistry()
+class MUV_OT_CopyPasteUVUVEdit_PasteUV(bpy.types.Operator):
     """
     Operation class: Paste UV coordinate on UV/Image Editor
     """
 
-    bl_idname = "uv.muv_cpuv_ie_paste_uv"
-    bl_label = "Paste UV"
+    bl_idname = "uv.muv_copy_paste_uv_uvedit_operator_paste_uv"
+    bl_label = "Paste UV (UV/Image Editor)"
     bl_description = "Paste UV coordinate (only selected in UV/Image Editor)"
     bl_options = {'REGISTER', 'UNDO'}
 
+    def __init__(self):
+        self.__impl = impl.PasteUVImpl()
+
     @classmethod
     def poll(cls, context):
-        return context.mode == 'EDIT_MESH'
+        return impl.PasteUVImpl.poll(context)
 
     def execute(self, context):
-        props = context.scene.muv_props.cpuv
-        obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        uv_layer = bm.loops.layers.uv.verify()
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
-
-        dest_uvs = []
-        dest_face_indices = []
-        for face in bm.faces:
-            if not face.select:
-                continue
-            skip = False
-            for l in face.loops:
-                if not l[uv_layer].select:
-                    skip = True
-                    break
-            if skip:
-                continue
-            dest_face_indices.append(face.index)
-            uvs = [l[uv_layer].uv.copy() for l in face.loops]
-            dest_uvs.append(uvs)
-
-        for suvs, duvs in zip(props.src_uvs, dest_uvs):
-            src_diff = suvs[1] - suvs[0]
-            dest_diff = duvs[1] - duvs[0]
-
-            src_base = suvs[0]
-            dest_base = duvs[0]
-
-            src_rad = atan2(src_diff.y, src_diff.x)
-            dest_rad = atan2(dest_diff.y, dest_diff.x)
-            if src_rad < dest_rad:
-                radian = dest_rad - src_rad
-            elif src_rad > dest_rad:
-                radian = math.pi * 2 - (src_rad - dest_rad)
-            else:       # src_rad == dest_rad
-                radian = 0.0
-
-            ratio = dest_diff.length / src_diff.length
-            break
-
-        for suvs, fidx in zip(props.src_uvs, dest_face_indices):
-            for l, suv in zip(bm.faces[fidx].loops, suvs):
-                base = suv - src_base
-                radian_ref = atan2(base.y, base.x)
-                radian_fin = (radian + radian_ref)
-                length = base.length
-                turn = Vector((length * cos(radian_fin),
-                               length * sin(radian_fin)))
-                target_uv = Vector((turn.x * ratio, turn.y * ratio)) + \
-                    dest_base
-                l[uv_layer].uv = target_uv
-
-        bmesh.update_edit_mesh(obj.data)
-
-        return {'FINISHED'}
+        return self.__impl.execute(self, context)
diff --git a/uv_magic_uv/op/flip_rotate_uv.py b/uv_magic_uv/op/flip_rotate_uv.py
index 30f6b0f7c9fb35446fa83919dfa3a1bad22c98c9..d16370528b1927d9df1fadf5bd713253036dd1a4 100644
--- a/uv_magic_uv/op/flip_rotate_uv.py
+++ b/uv_magic_uv/op/flip_rotate_uv.py
@@ -20,8 +20,8 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 import bmesh
@@ -31,35 +31,74 @@ from bpy.props import (
 )
 
 from .. import common
+from ..utils.bl_class_registry import BlClassRegistry
+from ..utils.property_class_registry import PropertyClassRegistry
+from ..impl import flip_rotate_impl as impl
 
+__all__ = [
+    'Properties',
+    'MUV_OT_FlipRotate',
+]
 
-class MUV_FlipRot(bpy.types.Operator):
+
+@PropertyClassRegistry()
+class Properties:
+    idname = "flip_rotate_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_flip_rotate_uv_enabled = BoolProperty(
+            name="Flip/Rotate UV Enabled",
+            description="Flip/Rotate UV is enabled",
+            default=False
+        )
+        scene.muv_flip_rotate_uv_seams = BoolProperty(
+            name="Seams",
+            description="Seams",
+            default=True
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_flip_rotate_uv_enabled
+        del scene.muv_flip_rotate_uv_seams
+
+
+@BlClassRegistry()
+class MUV_OT_FlipRotate(bpy.types.Operator):
     """
     Operation class: Flip and Rotate UV coordinate
     """
 
-    bl_idname = "uv.muv_fliprot"
+    bl_idname = "uv.muv_flip_rotate_uv_operator"
     bl_label = "Flip/Rotate UV"
     bl_description = "Flip/Rotate UV coordinate"
     bl_options = {'REGISTER', 'UNDO'}
 
-    flip = BoolProperty(
+    flip: BoolProperty(
         name="Flip UV",
         description="Flip UV...",
         default=False
     )
-    rotate = IntProperty(
+    rotate: IntProperty(
         default=0,
         name="Rotate UV",
         min=0,
         max=30
     )
-    seams = BoolProperty(
+    seams: BoolProperty(
         name="Seams",
         description="Seams",
         default=True
     )
 
+    @classmethod
+    def poll(cls, context):
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return impl.is_valid_context(context)
+
     def execute(self, context):
         self.report({'INFO'}, "Flip/Rotate UV")
         obj = context.active_object
@@ -68,61 +107,24 @@ class MUV_FlipRot(bpy.types.Operator):
             bm.faces.ensure_lookup_table()
 
         # get UV layer
-        if not bm.loops.layers.uv:
-            self.report({'WARNING'}, "Object must have more than one UV map")
+        uv_layer = impl.get_uv_layer(self, bm)
+        if not uv_layer:
             return {'CANCELLED'}
-        uv_layer = bm.loops.layers.uv.verify()
 
         # get selected face
-        dest_uvs = []
-        dest_pin_uvs = []
-        dest_seams = []
-        dest_face_indices = []
-        for face in bm.faces:
-            if face.select:
-                dest_face_indices.append(face.index)
-                uvs = [l[uv_layer].uv.copy() for l in face.loops]
-                pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
-                seams = [l.edge.seam for l in face.loops]
-                dest_uvs.append(uvs)
-                dest_pin_uvs.append(pin_uvs)
-                dest_seams.append(seams)
-        if not dest_uvs or not dest_pin_uvs:
-            self.report({'WARNING'}, "No faces are selected")
+        src_info = impl.get_src_face_info(self, bm, [uv_layer], True)
+        if not src_info:
             return {'CANCELLED'}
-        self.report({'INFO'}, "%d face(s) are selected" % len(dest_uvs))
+
+        face_count = len(src_info[list(src_info.keys())[0]])
+        self.report({'INFO'}, "{} face(s) are selected".format(face_count))
 
         # paste
-        for idx, duvs, dpuvs, dss in zip(dest_face_indices, dest_uvs,
-                                         dest_pin_uvs, dest_seams):
-            duvs_fr = [uv for uv in duvs]
-            dpuvs_fr = [pin_uv for pin_uv in dpuvs]
-            dss_fr = [s for s in dss]
-            # flip UVs
-            if self.flip is True:
-                duvs_fr.reverse()
-                dpuvs_fr.reverse()
-                dss_fr.reverse()
-            # rotate UVs
-            for _ in range(self.rotate):
-                uv = duvs_fr.pop()
-                pin_uv = dpuvs_fr.pop()
-                s = dss_fr.pop()
-                duvs_fr.insert(0, uv)
-                dpuvs_fr.insert(0, pin_uv)
-                dss_fr.insert(0, s)
-            # paste UVs
-            for l, duv, dpuv, ds in zip(
-                    bm.faces[idx].loops, duvs_fr, dpuvs_fr, dss_fr):
-                l[uv_layer].uv = duv
-                l[uv_layer].pin_uv = dpuv
-                if self.seams is True:
-                    l.edge.seam = ds
-
-        self.report({'INFO'}, "%d face(s) are flipped/rotated" % len(dest_uvs))
+        ret = impl.paste_uv(self, bm, src_info, src_info, [uv_layer], 'N_N',
+                            self.flip, self.rotate, self.seams)
+        if ret:
+            return {'CANCELLED'}
 
         bmesh.update_edit_mesh(obj.data)
-        if self.seams is True:
-            obj.data.show_edge_seams = True
 
         return {'FINISHED'}
diff --git a/uv_magic_uv/op/mirror_uv.py b/uv_magic_uv/op/mirror_uv.py
index f4849d18bb7135c7b94db78daaa358ffa80dc3a8..6793ca232e2bdcafbb4a95c02dbc1edc0cd3bbbb 100644
--- a/uv_magic_uv/op/mirror_uv.py
+++ b/uv_magic_uv/op/mirror_uv.py
@@ -20,30 +20,66 @@
 
 __author__ = "Keith (Wahooney) Boshoff, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 from bpy.props import (
     EnumProperty,
     FloatProperty,
+    BoolProperty,
 )
-import bmesh
-from mathutils import Vector
 
-from .. import common
+from ..utils.bl_class_registry import BlClassRegistry
+from ..utils.property_class_registry import PropertyClassRegistry
+from ..impl import mirror_uv_impl as impl
 
 
-class MUV_MirrorUV(bpy.types.Operator):
+__all__ = [
+    'Properties',
+    'MUV_OT_MirrorUV',
+]
+
+
+@PropertyClassRegistry()
+class Properties:
+    idname = "mirror_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_mirror_uv_enabled = BoolProperty(
+            name="Mirror UV Enabled",
+            description="Mirror UV is enabled",
+            default=False
+        )
+        scene.muv_mirror_uv_axis = EnumProperty(
+            items=[
+                ('X', "X", "Mirror Along X axis"),
+                ('Y', "Y", "Mirror Along Y axis"),
+                ('Z', "Z", "Mirror Along Z axis")
+            ],
+            name="Axis",
+            description="Mirror Axis",
+            default='X'
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_mirror_uv_enabled
+        del scene.muv_mirror_uv_axis
+
+
+@BlClassRegistry()
+class MUV_OT_MirrorUV(bpy.types.Operator):
     """
     Operation class: Mirror UV
     """
 
-    bl_idname = "uv.muv_mirror_uv"
+    bl_idname = "uv.muv_mirror_uv_operator"
     bl_label = "Mirror UV"
     bl_options = {'REGISTER', 'UNDO'}
 
-    axis = EnumProperty(
+    axis: EnumProperty(
         items=(
             ('X', "X", "Mirror Along X axis"),
             ('Y', "Y", "Mirror Along Y axis"),
@@ -53,7 +89,7 @@ class MUV_MirrorUV(bpy.types.Operator):
         description="Mirror Axis",
         default='X'
     )
-    error = FloatProperty(
+    error: FloatProperty(
         name="Error",
         description="Error threshold",
         default=0.001,
@@ -63,93 +99,12 @@ class MUV_MirrorUV(bpy.types.Operator):
         soft_max=1.0
     )
 
-    def __is_vector_similar(self, v1, v2, error):
-        """
-        Check if two vectors are similar, within an error threshold
-        """
-        within_err_x = abs(v2.x - v1.x) < error
-        within_err_y = abs(v2.y - v1.y) < error
-        within_err_z = abs(v2.z - v1.z) < error
-
-        return within_err_x and within_err_y and within_err_z
-
-    def __mirror_uvs(self, uv_layer, src, dst, axis, error):
-        """
-        Copy UV coordinates from one UV face to another
-        """
-        for sl in src.loops:
-            suv = sl[uv_layer].uv.copy()
-            svco = sl.vert.co.copy()
-            for dl in dst.loops:
-                dvco = dl.vert.co.copy()
-                if axis == 'X':
-                    dvco.x = -dvco.x
-                elif axis == 'Y':
-                    dvco.y = -dvco.y
-                elif axis == 'Z':
-                    dvco.z = -dvco.z
-
-                if self.__is_vector_similar(svco, dvco, error):
-                    dl[uv_layer].uv = suv.copy()
-
-    def __get_face_center(self, face):
-        """
-        Get center coordinate of the face
-        """
-        center = Vector((0.0, 0.0, 0.0))
-        for v in face.verts:
-            center = center + v.co
-
-        return center / len(face.verts)
+    def __init__(self):
+        self.__impl = impl.MirrorUVImpl()
 
     @classmethod
     def poll(cls, context):
-        obj = context.active_object
-        return obj and obj.type == 'MESH'
+        return impl.MirrorUVImpl.poll(context)
 
     def execute(self, context):
-        obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-
-        error = self.error
-        axis = self.axis
-
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
-        if not bm.loops.layers.uv:
-            self.report({'WARNING'}, "Object must have more than one UV map")
-            return {'CANCELLED'}
-        uv_layer = bm.loops.layers.uv.verify()
-
-        faces = [f for f in bm.faces if f.select]
-        for f_dst in faces:
-            count = len(f_dst.verts)
-            for f_src in bm.faces:
-                # check if this is a candidate to do mirror UV
-                if f_src.index == f_dst.index:
-                    continue
-                if count != len(f_src.verts):
-                    continue
-
-                # test if the vertices x values are the same sign
-                dst = self.__get_face_center(f_dst)
-                src = self.__get_face_center(f_src)
-                if (dst.x > 0 and src.x > 0) or (dst.x < 0 and src.x < 0):
-                    continue
-
-                # invert source axis
-                if axis == 'X':
-                    src.x = -src.x
-                elif axis == 'Y':
-                    src.y = -src.z
-                elif axis == 'Z':
-                    src.z = -src.z
-
-                # do mirror UV
-                if self.__is_vector_similar(dst, src, error):
-                    self.__mirror_uvs(
-                        uv_layer, f_src, f_dst, self.axis, self.error)
-
-        bmesh.update_edit_mesh(obj.data)
-
-        return {'FINISHED'}
+        return self.__impl.execute(self, context)
diff --git a/uv_magic_uv/op/move_uv.py b/uv_magic_uv/op/move_uv.py
index a5cc401c724c45280ec2c24e7556e2d97c6b9641..653918d350e20c1f82e3f7b110efe73e7900dd10 100644
--- a/uv_magic_uv/op/move_uv.py
+++ b/uv_magic_uv/op/move_uv.py
@@ -20,114 +20,63 @@
 
 __author__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
-import bmesh
-from mathutils import Vector
+from bpy.props import BoolProperty
 
+from ..impl import move_uv_impl as impl
+from ..utils.bl_class_registry import BlClassRegistry
+from ..utils.property_class_registry import PropertyClassRegistry
 
-class MUV_MVUV(bpy.types.Operator):
+
+__all__ = [
+    'Properties',
+    'MUV_OT_MoveUV',
+]
+
+
+@PropertyClassRegistry()
+class Properties:
+    idname = "move_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_move_uv_enabled = BoolProperty(
+            name="Move UV Enabled",
+            description="Move UV is enabled",
+            default=False
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_move_uv_enabled
+
+
+@BlClassRegistry()
+class MUV_OT_MoveUV(bpy.types.Operator):
     """
-    Operator class: Move UV from View3D
+    Operator class: Move UV
     """
 
-    bl_idname = "view3d.muv_mvuv"
-    bl_label = "Move the UV from View3D"
+    bl_idname = "uv.muv_move_uv_operator"
+    bl_label = "Move UV"
     bl_options = {'REGISTER', 'UNDO'}
 
     def __init__(self):
-        self.__topology_dict = []
-        self.__prev_mouse = Vector((0.0, 0.0))
-        self.__offset_uv = Vector((0.0, 0.0))
-        self.__prev_offset_uv = Vector((0.0, 0.0))
-        self.__first_time = True
-        self.__ini_uvs = []
-        self.__running = False
-
-    def __find_uv(self, context):
-        bm = bmesh.from_edit_mesh(context.object.data)
-        topology_dict = []
-        uvs = []
-        active_uv = bm.loops.layers.uv.active
-        for fidx, f in enumerate(bm.faces):
-            for vidx, v in enumerate(f.verts):
-                if v.select:
-                    uvs.append(f.loops[vidx][active_uv].uv.copy())
-                    topology_dict.append([fidx, vidx])
-
-        return topology_dict, uvs
+        self.__impl = impl.MoveUVImpl()
 
     @classmethod
     def poll(cls, context):
-        return context.edit_object
+        return impl.MoveUVImpl.poll(context)
+
+    @classmethod
+    def is_running(cls, _):
+        return impl.MoveUVImpl.is_running(_)
 
     def modal(self, context, event):
-        props = context.scene.muv_props.mvuv
-        if self.__first_time is True:
-            self.__prev_mouse = Vector((
-                event.mouse_region_x, event.mouse_region_y))
-            self.__first_time = False
-            return {'RUNNING_MODAL'}
-
-        # move UV
-        div = 10000
-        self.__offset_uv += Vector((
-            (event.mouse_region_x - self.__prev_mouse.x) / div,
-            (event.mouse_region_y - self.__prev_mouse.y) / div))
-        ouv = self.__offset_uv
-        pouv = self.__prev_offset_uv
-        vec = Vector((ouv.x - ouv.y, ouv.x + ouv.y))
-        dv = vec - pouv
-        self.__prev_offset_uv = vec
-        self.__prev_mouse = Vector((
-            event.mouse_region_x, event.mouse_region_y))
-
-        # check if operation is started
-        if self.__running:
-            if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
-                self.__running = False
-            return {'RUNNING_MODAL'}
-
-        # update UV
-        obj = context.object
-        bm = bmesh.from_edit_mesh(obj.data)
-        active_uv = bm.loops.layers.uv.active
-        for fidx, vidx in self.__topology_dict:
-            l = bm.faces[fidx].loops[vidx]
-            l[active_uv].uv = l[active_uv].uv + dv
-        bmesh.update_edit_mesh(obj.data)
-
-        # check mouse preference
-        wm = context.window_manager
-        keyconfig = wm.keyconfigs.active
-        select_mouse = getattr(keyconfig.preferences, "select_mouse", "LEFT")
-        if 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
-            props.running = False
-            return {'FINISHED'}
-        # confirmed
-        if event.type == confirm_btn and event.value == 'PRESS':
-            props.running = False
-            return {'FINISHED'}
-
-        return {'RUNNING_MODAL'}
+        return self.__impl.modal(self, context, event)
 
     def execute(self, context):
-        props = context.scene.muv_props.mvuv
-        props.running = True
-        self.__running = True
-        self.__first_time = True
-        context.window_manager.modal_handler_add(self)
-        self.__topology_dict, self.__ini_uvs = self.__find_uv(context)
-        return {'RUNNING_MODAL'}
+        return self.__impl.execute(self, context)
diff --git a/uv_magic_uv/op/texture_projection.py b/uv_magic_uv/op/texture_projection.py
deleted file mode 100644
index 77a81aa0a0162b1672d337f143edaf73cdc3b531..0000000000000000000000000000000000000000
--- a/uv_magic_uv/op/texture_projection.py
+++ /dev/null
@@ -1,296 +0,0 @@
-# <pep8-80 compliant>
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-#  This program is free software; you can redistribute it and/or
-#  modify it under the terms of the GNU General Public License
-#  as published by the Free Software Foundation; either version 2
-#  of the License, or (at your option) any later version.
-#
-#  This program is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License
-#  along with this program; if not, write to the Free Software Foundation,
-#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-__author__ = "Nutti <nutti.metro@gmail.com>"
-__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
-
-from collections import namedtuple
-
-import bpy
-import bgl
-import bmesh
-import mathutils
-from bpy_extras import view3d_utils
-
-from .. import common
-
-
-Rect = namedtuple('Rect', 'x0 y0 x1 y1')
-Rect2 = namedtuple('Rect2', 'x y width height')
-
-
-def get_canvas(context, magnitude):
-    """
-    Get canvas to be renderred texture
-    """
-    sc = context.scene
-    prefs = context.user_preferences.addons["uv_magic_uv"].preferences
-
-    region_w = context.region.width
-    region_h = context.region.height
-    canvas_w = region_w - prefs.texproj_canvas_padding[0] * 2.0
-    canvas_h = region_h - prefs.texproj_canvas_padding[1] * 2.0
-
-    img = bpy.data.images[sc.muv_texproj_tex_image]
-    tex_w = img.size[0]
-    tex_h = img.size[1]
-
-    center_x = region_w * 0.5
-    center_y = region_h * 0.5
-
-    if sc.muv_texproj_adjust_window:
-        ratio_x = canvas_w / tex_w
-        ratio_y = canvas_h / tex_h
-        if sc.muv_texproj_apply_tex_aspect:
-            ratio = ratio_y if ratio_x > ratio_y else ratio_x
-            len_x = ratio * tex_w
-            len_y = ratio * tex_h
-        else:
-            len_x = canvas_w
-            len_y = canvas_h
-    else:
-        if sc.muv_texproj_apply_tex_aspect:
-            len_x = tex_w * magnitude
-            len_y = tex_h * magnitude
-        else:
-            len_x = region_w * magnitude
-            len_y = region_h * magnitude
-
-    x0 = int(center_x - len_x * 0.5)
-    y0 = int(center_y - len_y * 0.5)
-    x1 = int(center_x + len_x * 0.5)
-    y1 = int(center_y + len_y * 0.5)
-
-    return Rect(x0, y0, x1, y1)
-
-
-def rect_to_rect2(rect):
-    """
-    Convert Rect1 to Rect2
-    """
-
-    return Rect2(rect.x0, rect.y0, rect.x1 - rect.x0, rect.y1 - rect.y0)
-
-
-def region_to_canvas(rg_vec, canvas):
-    """
-    Convert screen region to canvas
-    """
-
-    cv_rect = rect_to_rect2(canvas)
-    cv_vec = mathutils.Vector()
-    cv_vec.x = (rg_vec.x - cv_rect.x) / cv_rect.width
-    cv_vec.y = (rg_vec.y - cv_rect.y) / cv_rect.height
-
-    return cv_vec
-
-
-class MUV_TexProjRenderer(bpy.types.Operator):
-    """
-    Operation class: Render selected texture
-    No operation (only rendering texture)
-    """
-
-    bl_idname = "uv.muv_texproj_renderer"
-    bl_description = "Render selected texture"
-    bl_label = "Texture renderer"
-
-    __handle = None
-
-    @staticmethod
-    def handle_add(obj, context):
-        MUV_TexProjRenderer.__handle = bpy.types.SpaceView3D.draw_handler_add(
-            MUV_TexProjRenderer.draw_texture,
-            (obj, context), 'WINDOW', 'POST_PIXEL')
-
-    @staticmethod
-    def handle_remove():
-        if MUV_TexProjRenderer.__handle is not None:
-            bpy.types.SpaceView3D.draw_handler_remove(
-                MUV_TexProjRenderer.__handle, 'WINDOW')
-            MUV_TexProjRenderer.__handle = None
-
-    @staticmethod
-    def draw_texture(_, context):
-        sc = context.scene
-
-        # no textures are selected
-        if sc.muv_texproj_tex_image == "None":
-            return
-
-        # get texture to be renderred
-        img = bpy.data.images[sc.muv_texproj_tex_image]
-
-        # setup rendering region
-        rect = get_canvas(context, sc.muv_texproj_tex_magnitude)
-        positions = [
-            [rect.x0, rect.y0],
-            [rect.x0, rect.y1],
-            [rect.x1, rect.y1],
-            [rect.x1, rect.y0]
-        ]
-        tex_coords = [
-            [0.0, 0.0],
-            [0.0, 1.0],
-            [1.0, 1.0],
-            [1.0, 0.0]
-        ]
-
-        # OpenGL configuration
-        bgl.glEnable(bgl.GL_BLEND)
-        bgl.glEnable(bgl.GL_TEXTURE_2D)
-        if img.bindcode:
-            bind = img.bindcode[0]
-            bgl.glBindTexture(bgl.GL_TEXTURE_2D, bind)
-            bgl.glTexParameteri(
-                bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR)
-            bgl.glTexParameteri(
-                bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR)
-            bgl.glTexEnvi(
-                bgl.GL_TEXTURE_ENV, bgl.GL_TEXTURE_ENV_MODE, bgl.GL_MODULATE)
-
-        # render texture
-        bgl.glBegin(bgl.GL_QUADS)
-        bgl.glColor4f(1.0, 1.0, 1.0, sc.muv_texproj_tex_transparency)
-        for (v1, v2), (u, v) in zip(positions, tex_coords):
-            bgl.glTexCoord2f(u, v)
-            bgl.glVertex2f(v1, v2)
-        bgl.glEnd()
-
-
-class MUV_TexProjStart(bpy.types.Operator):
-    """
-    Operation class: Start Texture Projection
-    """
-
-    bl_idname = "uv.muv_texproj_start"
-    bl_label = "Start Texture Projection"
-    bl_description = "Start Texture Projection"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    def execute(self, context):
-        props = context.scene.muv_props.texproj
-        if props.running is False:
-            MUV_TexProjRenderer.handle_add(self, context)
-            props.running = True
-        if context.area:
-            context.area.tag_redraw()
-
-        return {'FINISHED'}
-
-
-class MUV_TexProjStop(bpy.types.Operator):
-    """
-    Operation class: Stop Texture Projection
-    """
-
-    bl_idname = "uv.muv_texproj_stop"
-    bl_label = "Stop Texture Projection"
-    bl_description = "Stop Texture Projection"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    def execute(self, context):
-        props = context.scene.muv_props.texproj
-        if props.running is True:
-            MUV_TexProjRenderer.handle_remove()
-            props.running = False
-        if context.area:
-            context.area.tag_redraw()
-
-        return {'FINISHED'}
-
-
-class MUV_TexProjProject(bpy.types.Operator):
-    """
-    Operation class: Project texture
-    """
-
-    bl_idname = "uv.muv_texproj_project"
-    bl_label = "Project Texture"
-    bl_description = "Project Texture"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    @classmethod
-    def poll(cls, context):
-        obj = context.active_object
-        return obj is not None and obj.type == "MESH"
-
-    def execute(self, context):
-        sc = context.scene
-
-        if sc.muv_texproj_tex_image == "None":
-            self.report({'WARNING'}, "No textures are selected")
-            return {'CANCELLED'}
-
-        _, region, space = common.get_space(
-            'VIEW_3D', 'WINDOW', 'VIEW_3D')
-
-        # get faces to be texture projected
-        obj = context.active_object
-        world_mat = obj.matrix_world
-        bm = bmesh.from_edit_mesh(obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
-
-        # get UV and texture layer
-        if not bm.loops.layers.uv:
-            if sc.muv_texproj_assign_uvmap:
-                bm.loops.layers.uv.new()
-            else:
-                self.report({'WARNING'},
-                            "Object must have more than one UV map")
-                return {'CANCELLED'}
-
-        uv_layer = bm.loops.layers.uv.verify()
-        tex_layer = bm.faces.layers.tex.verify()
-
-        sel_faces = [f for f in bm.faces if f.select]
-
-        # transform 3d space to screen region
-        v_screen = [
-            view3d_utils.location_3d_to_region_2d(
-                region,
-                space.region_3d,
-                world_mat * l.vert.co)
-            for f in sel_faces for l in f.loops
-        ]
-
-        # transform screen region to canvas
-        v_canvas = [
-            region_to_canvas(
-                v,
-                get_canvas(bpy.context, sc.muv_texproj_tex_magnitude))
-            for v in v_screen
-        ]
-
-        # project texture to object
-        i = 0
-        for f in sel_faces:
-            f[tex_layer].image = bpy.data.images[sc.muv_texproj_tex_image]
-            for l in f.loops:
-                l[uv_layer].uv = v_canvas[i].to_2d()
-                i = i + 1
-
-        common.redraw_all_areas()
-        bmesh.update_edit_mesh(obj.data)
-
-        return {'FINISHED'}
diff --git a/uv_magic_uv/op/transfer_uv.py b/uv_magic_uv/op/transfer_uv.py
index 132f395eda81c291bda778d6f14a7c1ce85c3df9..db05b34379ef8c6da680abc5292b4515802c23e5 100644
--- a/uv_magic_uv/op/transfer_uv.py
+++ b/uv_magic_uv/op/transfer_uv.py
@@ -20,339 +20,149 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>, Mifth, MaxRobinot"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
-
-from collections import OrderedDict
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 import bmesh
 from bpy.props import BoolProperty
 
 from .. import common
-
-
-class MUV_TransUVCopy(bpy.types.Operator):
+from ..impl import transfer_uv_impl as impl
+from ..utils.bl_class_registry import BlClassRegistry
+from ..utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_TransferUV_CopyUV',
+    'MUV_OT_TransferUV_PasteUV',
+]
+
+
+@PropertyClassRegistry()
+class Properties:
+    idname = "transfer_uv"
+
+    @classmethod
+    def init_props(cls, scene):
+        class Props():
+            topology_copied = None
+
+        scene.muv_props.transfer_uv = Props()
+
+        scene.muv_transfer_uv_enabled = BoolProperty(
+            name="Transfer UV Enabled",
+            description="Transfer UV is enabled",
+            default=False
+        )
+        scene.muv_transfer_uv_invert_normals = BoolProperty(
+            name="Invert Normals",
+            description="Invert Normals",
+            default=False
+        )
+        scene.muv_transfer_uv_copy_seams = BoolProperty(
+            name="Copy Seams",
+            description="Copy Seams",
+            default=True
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_transfer_uv_enabled
+        del scene.muv_transfer_uv_invert_normals
+        del scene.muv_transfer_uv_copy_seams
+
+
+@BlClassRegistry()
+class MUV_OT_TransferUV_CopyUV(bpy.types.Operator):
     """
         Operation class: Transfer UV copy
         Topological based copy
     """
 
-    bl_idname = "uv.muv_transuv_copy"
-    bl_label = "Transfer UV Copy"
-    bl_description = "Transfer UV Copy (Topological based copy)"
+    bl_idname = "uv.muv_transfer_uv_operator_copy_uv"
+    bl_label = "Transfer UV Copy UV"
+    bl_description = "Transfer UV Copy UV (Topological based copy)"
     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 impl.is_valid_context(context)
+
     def execute(self, context):
-        props = context.scene.muv_props.transuv
-        active_obj = context.scene.objects.active
+        props = context.scene.muv_props.transfer_uv
+        active_obj = context.active_object
         bm = bmesh.from_edit_mesh(active_obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
+        bm.faces.ensure_lookup_table()
 
-        # get UV layer
-        if not bm.loops.layers.uv:
-            self.report({'WARNING'}, "Object must have more than one UV map")
+        uv_layer = impl.get_uv_layer(self, bm)
+        if uv_layer is None:
             return {'CANCELLED'}
-        uv_layer = bm.loops.layers.uv.verify()
-
-        props.topology_copied.clear()
 
-        # get selected faces
-        active_face = bm.faces.active
-        sel_faces = [face for face in bm.faces if face.select]
-        if len(sel_faces) != 2:
-            self.report({'WARNING'}, "Two faces must be selected")
-            return {'CANCELLED'}
-        if not active_face or active_face not in sel_faces:
-            self.report({'WARNING'}, "Two faces must be active")
+        faces = impl.get_selected_src_faces(self, bm, uv_layer)
+        if faces is None:
             return {'CANCELLED'}
-
-        # parse all faces according to selection
-        active_face_nor = active_face.normal.copy()
-        all_sorted_faces = main_parse(
-            self, uv_layer, sel_faces, active_face,
-            active_face_nor)
-
-        if all_sorted_faces:
-            for face_data in all_sorted_faces.values():
-                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]
-                props.topology_copied.append([uvs, pin_uvs, seams])
+        props.topology_copied = faces
 
         bmesh.update_edit_mesh(active_obj.data)
 
         return {'FINISHED'}
 
 
-class MUV_TransUVPaste(bpy.types.Operator):
+@BlClassRegistry()
+class MUV_OT_TransferUV_PasteUV(bpy.types.Operator):
     """
         Operation class: Transfer UV paste
         Topological based paste
     """
 
-    bl_idname = "uv.muv_transuv_paste"
-    bl_label = "Transfer UV Paste"
-    bl_description = "Transfer UV Paste (Topological based paste)"
+    bl_idname = "uv.muv_transfer_uv_operator_paste_uv"
+    bl_label = "Transfer UV Paste UV"
+    bl_description = "Transfer UV Paste UV (Topological based paste)"
     bl_options = {'REGISTER', 'UNDO'}
 
-    invert_normals = BoolProperty(
+    invert_normals: BoolProperty(
         name="Invert Normals",
         description="Invert Normals",
         default=False
     )
-    copy_seams = BoolProperty(
+    copy_seams: BoolProperty(
         name="Copy Seams",
         description="Copy Seams",
         default=True
     )
 
+    @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.transfer_uv
+        if not props.topology_copied:
+            return False
+        return impl.is_valid_context(context)
+
     def execute(self, context):
-        props = context.scene.muv_props.transuv
-        active_obj = context.scene.objects.active
+        props = context.scene.muv_props.transfer_uv
+        active_obj = context.active_object
         bm = bmesh.from_edit_mesh(active_obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
+        bm.faces.ensure_lookup_table()
 
         # get UV layer
-        if not bm.loops.layers.uv:
-            self.report({'WARNING'}, "Object must have more than one UV map")
+        uv_layer = impl.get_uv_layer(self, bm)
+        if uv_layer is None:
             return {'CANCELLED'}
-        uv_layer = bm.loops.layers.uv.verify()
 
-        # get selection history
-        all_sel_faces = [
-            e for e in bm.select_history
-            if isinstance(e, bmesh.types.BMFace) and e.select]
-        if len(all_sel_faces) % 2 != 0:
-            self.report({'WARNING'}, "Two faces must be selected")
+        ret = impl.paste_uv(self, bm, uv_layer, props.topology_copied,
+                            self.invert_normals, self.copy_seams)
+        if ret:
             return {'CANCELLED'}
 
-        # 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 self.invert_normals:
-                active_face_nor.negate()
-            all_sorted_faces = main_parse(
-                self, uv_layer, sel_faces, active_face,
-                active_face_nor)
-
-            if all_sorted_faces:
-                # check amount of copied/pasted faces
-                if len(all_sorted_faces) != len(props.topology_copied):
-                    self.report(
-                        {'WARNING'},
-                        "Mesh has different amount of faces"
-                    )
-                    return {'FINISHED'}
-
-                for j, face_data in enumerate(all_sorted_faces.values()):
-                    copied_data = props.topology_copied[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
-                        self.report(
-                            {'WARNING'},
-                            "Face have different amount of vertices"
-                        )
-                        return {'FINISHED'}
-
-                    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 self.copy_seams:
-                            edge.seam = copied_data[2][k]
-
         bmesh.update_edit_mesh(active_obj.data)
-        if self.copy_seams:
-            active_obj.data.show_edge_seams = True
 
         return {'FINISHED'}
-
-
-def main_parse(
-        self, uv_layer, sel_faces,
-        active_face, active_face_nor):
-    all_sorted_faces = OrderedDict()  # This is the main stuff
-
-    used_verts = set()
-    used_edges = set()
-
-    faces_to_parse = []
-
-    # get shared edge of two faces
-    cross_edges = []
-    for edge in active_face.edges:
-        if edge in sel_faces[0].edges and edge in sel_faces[1].edges:
-            cross_edges.append(edge)
-
-    # parse two selected faces
-    if cross_edges and len(cross_edges) == 1:
-        shared_edge = cross_edges[0]
-        vert1 = None
-        vert2 = None
-
-        dot_n = active_face_nor.normalized()
-        edge_vec_1 = (shared_edge.verts[1].co - shared_edge.verts[0].co)
-        edge_vec_len = edge_vec_1.length
-        edge_vec_1 = edge_vec_1.normalized()
-
-        af_center = active_face.calc_center_median()
-        af_vec = shared_edge.verts[0].co + (edge_vec_1 * (edge_vec_len * 0.5))
-        af_vec = (af_vec - af_center).normalized()
-
-        if af_vec.cross(edge_vec_1).dot(dot_n) > 0:
-            vert1 = shared_edge.verts[0]
-            vert2 = shared_edge.verts[1]
-        else:
-            vert1 = shared_edge.verts[1]
-            vert2 = shared_edge.verts[0]
-
-        # get active face stuff and uvs
-        face_stuff = get_other_verts_edges(
-            active_face, vert1, vert2, shared_edge, uv_layer)
-        all_sorted_faces[active_face] = face_stuff
-        used_verts.update(active_face.verts)
-        used_edges.update(active_face.edges)
-
-        # get first selected face stuff and uvs as they share shared_edge
-        second_face = sel_faces[0]
-        if second_face is active_face:
-            second_face = sel_faces[1]
-        face_stuff = get_other_verts_edges(
-            second_face, vert1, vert2, shared_edge, uv_layer)
-        all_sorted_faces[second_face] = face_stuff
-        used_verts.update(second_face.verts)
-        used_edges.update(second_face.edges)
-
-        # first Grow
-        faces_to_parse.append(active_face)
-        faces_to_parse.append(second_face)
-
-    else:
-        self.report({'WARNING'}, "Two faces should share one edge")
-        return None
-
-    # parse all faces
-    while True:
-        new_parsed_faces = []
-        if not faces_to_parse:
-            break
-        for face in faces_to_parse:
-            face_stuff = all_sorted_faces.get(face)
-            new_faces = parse_faces(
-                face, face_stuff, used_verts, used_edges, all_sorted_faces,
-                uv_layer)
-            if new_faces == 'CANCELLED':
-                self.report({'WARNING'}, "More than 2 faces share edge")
-                return None
-
-            new_parsed_faces += new_faces
-        faces_to_parse = new_parsed_faces
-
-    return all_sorted_faces
-
-
-def parse_faces(
-        check_face, face_stuff, used_verts, used_edges, all_sorted_faces,
-        uv_layer):
-    """recurse faces around the new_grow only"""
-
-    new_shared_faces = []
-    for sorted_edge in face_stuff[1]:
-        shared_faces = sorted_edge.link_faces
-        if shared_faces:
-            if len(shared_faces) > 2:
-                bpy.ops.mesh.select_all(action='DESELECT')
-                for face_sel in shared_faces:
-                    face_sel.select = True
-                shared_faces = []
-                return 'CANCELLED'
-
-            clear_shared_faces = get_new_shared_faces(
-                check_face, sorted_edge, shared_faces, all_sorted_faces.keys())
-            if clear_shared_faces:
-                shared_face = clear_shared_faces[0]
-                # get vertices of the edge
-                vert1 = sorted_edge.verts[0]
-                vert2 = sorted_edge.verts[1]
-
-                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.DEBUG:
-                    shared_face.select = True  # test which faces are parsed
-
-                new_shared_faces.append(shared_face)
-
-    return new_shared_faces
-
-
-def get_new_shared_faces(orig_face, shared_edge, check_faces, used_faces):
-    shared_faces = []
-
-    for face in check_faces:
-        is_shared_edge = shared_edge in face.edges
-        not_used = face not in used_faces
-        not_orig = face is not orig_face
-        not_hide = face.hide is False
-        if is_shared_edge and not_used and not_orig and not_hide:
-            shared_faces.append(face)
-
-    return shared_faces
-
-
-def get_other_verts_edges(face, vert1, vert2, first_edge, uv_layer):
-    face_edges = [first_edge]
-    face_verts = [vert1, vert2]
-    face_loops = []
-
-    other_edges = [edge for edge in face.edges if edge not in face_edges]
-
-    for _ in range(len(other_edges)):
-        found_edge = None
-        # get sorted verts and edges
-        for edge in other_edges:
-            if face_verts[-1] in edge.verts:
-                other_vert = edge.other_vert(face_verts[-1])
-
-                if other_vert not in face_verts:
-                    face_verts.append(other_vert)
-
-                found_edge = edge
-                if found_edge not in face_edges:
-                    face_edges.append(edge)
-                break
-
-        other_edges.remove(found_edge)
-
-    # get sorted uvs
-    for vert in face_verts:
-        for loop in face.loops:
-            if loop.vert is vert:
-                face_loops.append(loop[uv_layer])
-                break
-
-    return [face_verts, face_edges, face_loops]
diff --git a/uv_magic_uv/op/uv_inspection.py b/uv_magic_uv/op/uv_inspection.py
deleted file mode 100644
index 60a754a30a293f685a945ca32eff1dea18c706bc..0000000000000000000000000000000000000000
--- a/uv_magic_uv/op/uv_inspection.py
+++ /dev/null
@@ -1,623 +0,0 @@
-# <pep8-80 compliant>
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-#  This program is free software; you can redistribute it and/or
-#  modify it under the terms of the GNU General Public License
-#  as published by the Free Software Foundation; either version 2
-#  of the License, or (at your option) any later version.
-#
-#  This program is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License
-#  along with this program; if not, write to the Free Software Foundation,
-#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-__author__ = "Nutti <nutti.metro@gmail.com>"
-__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
-
-import bpy
-import bmesh
-import bgl
-from mathutils import Vector
-
-from .. import common
-
-
-def is_polygon_same(points1, points2):
-    if len(points1) != len(points2):
-        return False
-
-    pts1 = points1.as_list()
-    pts2 = points2.as_list()
-
-    for p1 in pts1:
-        for p2 in pts2:
-            diff = p2 - p1
-            if diff.length < 0.0000001:
-                pts2.remove(p2)
-                break
-        else:
-            return False
-
-    return True
-
-
-def is_segment_intersect(start1, end1, start2, end2):
-    seg1 = end1 - start1
-    seg2 = end2 - start2
-
-    a1 = -seg1.y
-    b1 = seg1.x
-    d1 = -(a1 * start1.x + b1 * start1.y)
-
-    a2 = -seg2.y
-    b2 = seg2.x
-    d2 = -(a2 * start2.x + b2 * start2.y)
-
-    seg1_line2_start = a2 * start1.x + b2 * start1.y + d2
-    seg1_line2_end = a2 * end1.x + b2 * end1.y + d2
-
-    seg2_line1_start = a1 * start2.x + b1 * start2.y + d1
-    seg2_line1_end = a1 * end2.x + b1 * end2.y + d1
-
-    if (seg1_line2_start * seg1_line2_end >= 0) or \
-            (seg2_line1_start * seg2_line1_end >= 0):
-        return False, None
-
-    u = seg1_line2_start / (seg1_line2_start - seg1_line2_end)
-    out = start1 + u * seg1
-
-    return True, out
-
-
-class RingBuffer:
-    def __init__(self, arr):
-        self.__buffer = arr.copy()
-        self.__pointer = 0
-
-    def __repr__(self):
-        return repr(self.__buffer)
-
-    def __len__(self):
-        return len(self.__buffer)
-
-    def insert(self, val, offset=0):
-        self.__buffer.insert(self.__pointer + offset, val)
-
-    def head(self):
-        return self.__buffer[0]
-
-    def tail(self):
-        return self.__buffer[-1]
-
-    def get(self, offset=0):
-        size = len(self.__buffer)
-        val = self.__buffer[(self.__pointer + offset) % size]
-        return val
-
-    def next(self):
-        size = len(self.__buffer)
-        self.__pointer = (self.__pointer + 1) % size
-
-    def reset(self):
-        self.__pointer = 0
-
-    def find(self, obj):
-        try:
-            idx = self.__buffer.index(obj)
-        except ValueError:
-            return None
-        return self.__buffer[idx]
-
-    def find_and_next(self, obj):
-        size = len(self.__buffer)
-        idx = self.__buffer.index(obj)
-        self.__pointer = (idx + 1) % size
-
-    def find_and_set(self, obj):
-        idx = self.__buffer.index(obj)
-        self.__pointer = idx
-
-    def as_list(self):
-        return self.__buffer.copy()
-
-    def reverse(self):
-        self.__buffer.reverse()
-        self.reset()
-
-
-# clip: reference polygon
-# subject: tested polygon
-def do_weiler_atherton_cliping(clip, subject, uv_layer, mode):
-
-    clip_uvs = RingBuffer([l[uv_layer].uv.copy() for l in clip.loops])
-    if is_polygon_flipped(clip_uvs):
-        clip_uvs.reverse()
-    subject_uvs = RingBuffer([l[uv_layer].uv.copy() for l in subject.loops])
-    if is_polygon_flipped(subject_uvs):
-        subject_uvs.reverse()
-
-    common.debug_print("===== Clip UV List =====")
-    common.debug_print(clip_uvs)
-    common.debug_print("===== Subject UV List =====")
-    common.debug_print(subject_uvs)
-
-    # check if clip and subject is overlapped completely
-    if is_polygon_same(clip_uvs, subject_uvs):
-        polygons = [subject_uvs.as_list()]
-        common.debug_print("===== Polygons Overlapped Completely =====")
-        common.debug_print(polygons)
-        return True, polygons
-
-    # check if subject is in clip
-    if is_points_in_polygon(subject_uvs, clip_uvs):
-        polygons = [subject_uvs.as_list()]
-        return True, polygons
-
-    # check if clip is in subject
-    if is_points_in_polygon(clip_uvs, subject_uvs):
-        polygons = [subject_uvs.as_list()]
-        return True, polygons
-
-    # check if clip and subject is overlapped partially
-    intersections = []
-    while True:
-        subject_uvs.reset()
-        while True:
-            uv_start1 = clip_uvs.get()
-            uv_end1 = clip_uvs.get(1)
-            uv_start2 = subject_uvs.get()
-            uv_end2 = subject_uvs.get(1)
-            intersected, point = is_segment_intersect(uv_start1, uv_end1,
-                                                      uv_start2, uv_end2)
-            if intersected:
-                clip_uvs.insert(point, 1)
-                subject_uvs.insert(point, 1)
-                intersections.append([point,
-                                      [clip_uvs.get(), clip_uvs.get(1)]])
-            subject_uvs.next()
-            if subject_uvs.get() == subject_uvs.head():
-                break
-        clip_uvs.next()
-        if clip_uvs.get() == clip_uvs.head():
-            break
-
-    common.debug_print("===== Intersection List =====")
-    common.debug_print(intersections)
-
-    # no intersection, so subject and clip is not overlapped
-    if not intersections:
-        return False, None
-
-    def get_intersection_pair(intersections, key):
-        for sect in intersections:
-            if sect[0] == key:
-                return sect[1]
-
-        return None
-
-    # make enter/exit pair
-    subject_uvs.reset()
-    subject_entering = []
-    subject_exiting = []
-    clip_entering = []
-    clip_exiting = []
-    intersect_uv_list = []
-    while True:
-        pair = get_intersection_pair(intersections, subject_uvs.get())
-        if pair:
-            sub = subject_uvs.get(1) - subject_uvs.get(-1)
-            inter = pair[1] - pair[0]
-            cross = sub.x * inter.y - inter.x * sub.y
-            if cross < 0:
-                subject_entering.append(subject_uvs.get())
-                clip_exiting.append(subject_uvs.get())
-            else:
-                subject_exiting.append(subject_uvs.get())
-                clip_entering.append(subject_uvs.get())
-            intersect_uv_list.append(subject_uvs.get())
-
-        subject_uvs.next()
-        if subject_uvs.get() == subject_uvs.head():
-            break
-
-    common.debug_print("===== Enter List =====")
-    common.debug_print(clip_entering)
-    common.debug_print(subject_entering)
-    common.debug_print("===== Exit List =====")
-    common.debug_print(clip_exiting)
-    common.debug_print(subject_exiting)
-
-    # for now, can't handle the situation when fulfill all below conditions
-    #        * two faces have common edge
-    #        * each face is intersected
-    #        * Show Mode is "Part"
-    #       so for now, ignore this situation
-    if len(subject_entering) != len(subject_exiting):
-        if mode == 'FACE':
-            polygons = [subject_uvs.as_list()]
-            return True, polygons
-        return False, None
-
-    def traverse(current_list, entering, exiting, poly, current, other_list):
-        result = current_list.find(current)
-        if not result:
-            return None
-        if result != current:
-            print("Internal Error")
-            return None
-
-        # enter
-        if entering.count(current) >= 1:
-            entering.remove(current)
-
-        current_list.find_and_next(current)
-        current = current_list.get()
-
-        while exiting.count(current) == 0:
-            poly.append(current.copy())
-            current_list.find_and_next(current)
-            current = current_list.get()
-
-        # exit
-        poly.append(current.copy())
-        exiting.remove(current)
-
-        other_list.find_and_set(current)
-        return other_list.get()
-
-    # Traverse
-    polygons = []
-    current_uv_list = subject_uvs
-    other_uv_list = clip_uvs
-    current_entering = subject_entering
-    current_exiting = subject_exiting
-
-    poly = []
-    current_uv = current_entering[0]
-
-    while True:
-        current_uv = traverse(current_uv_list, current_entering,
-                              current_exiting, poly, current_uv, other_uv_list)
-
-        if current_uv_list == subject_uvs:
-            current_uv_list = clip_uvs
-            other_uv_list = subject_uvs
-            current_entering = clip_entering
-            current_exiting = clip_exiting
-            common.debug_print("-- Next: Clip --")
-        else:
-            current_uv_list = subject_uvs
-            other_uv_list = clip_uvs
-            current_entering = subject_entering
-            current_exiting = subject_exiting
-            common.debug_print("-- Next: Subject --")
-
-        common.debug_print(clip_entering)
-        common.debug_print(clip_exiting)
-        common.debug_print(subject_entering)
-        common.debug_print(subject_exiting)
-
-        if not clip_entering and not clip_exiting \
-                and not subject_entering and not subject_exiting:
-            break
-
-    polygons.append(poly)
-
-    common.debug_print("===== Polygons Overlapped Partially =====")
-    common.debug_print(polygons)
-
-    return True, polygons
-
-
-class MUV_UVInspRenderer(bpy.types.Operator):
-    """
-    Operation class: Render UV Inspection
-    No operation (only rendering)
-    """
-
-    bl_idname = "uv.muv_uvinsp_renderer"
-    bl_description = "Render overlapped/flipped UVs"
-    bl_label = "Overlapped/Flipped UV renderer"
-
-    __handle = None
-
-    @staticmethod
-    def handle_add(obj, context):
-        sie = bpy.types.SpaceImageEditor
-        MUV_UVInspRenderer.__handle = sie.draw_handler_add(
-            MUV_UVInspRenderer.draw, (obj, context), 'WINDOW', 'POST_PIXEL')
-
-    @staticmethod
-    def handle_remove():
-        if MUV_UVInspRenderer.__handle is not None:
-            bpy.types.SpaceImageEditor.draw_handler_remove(
-                MUV_UVInspRenderer.__handle, 'WINDOW')
-            MUV_UVInspRenderer.__handle = None
-
-    @staticmethod
-    def draw(_, context):
-        sc = context.scene
-        props = sc.muv_props.uvinsp
-        prefs = context.user_preferences.addons["uv_magic_uv"].preferences
-
-        # OpenGL configuration
-        bgl.glEnable(bgl.GL_BLEND)
-
-        # render overlapped UV
-        if sc.muv_uvinsp_show_overlapped:
-            color = prefs.uvinsp_overlapped_color
-            for info in props.overlapped_info:
-                if sc.muv_uvinsp_show_mode == 'PART':
-                    for poly in info["polygons"]:
-                        bgl.glBegin(bgl.GL_TRIANGLE_FAN)
-                        bgl.glColor4f(color[0], color[1], color[2], color[3])
-                        for uv in poly:
-                            x, y = context.region.view2d.view_to_region(
-                                uv.x, uv.y)
-                            bgl.glVertex2f(x, y)
-                        bgl.glEnd()
-                elif sc.muv_uvinsp_show_mode == 'FACE':
-                    bgl.glBegin(bgl.GL_TRIANGLE_FAN)
-                    bgl.glColor4f(color[0], color[1], color[2], color[3])
-                    for uv in info["subject_uvs"]:
-                        x, y = context.region.view2d.view_to_region(uv.x, uv.y)
-                        bgl.glVertex2f(x, y)
-                    bgl.glEnd()
-
-        # render flipped UV
-        if sc.muv_uvinsp_show_flipped:
-            color = prefs.uvinsp_flipped_color
-            for info in props.flipped_info:
-                if sc.muv_uvinsp_show_mode == 'PART':
-                    for poly in info["polygons"]:
-                        bgl.glBegin(bgl.GL_TRIANGLE_FAN)
-                        bgl.glColor4f(color[0], color[1], color[2], color[3])
-                        for uv in poly:
-                            x, y = context.region.view2d.view_to_region(
-                                uv.x, uv.y)
-                            bgl.glVertex2f(x, y)
-                        bgl.glEnd()
-                elif sc.muv_uvinsp_show_mode == 'FACE':
-                    bgl.glBegin(bgl.GL_TRIANGLE_FAN)
-                    bgl.glColor4f(color[0], color[1], color[2], color[3])
-                    for uv in info["uvs"]:
-                        x, y = context.region.view2d.view_to_region(uv.x, uv.y)
-                        bgl.glVertex2f(x, y)
-                    bgl.glEnd()
-
-
-def is_polygon_flipped(points):
-    area = 0.0
-    for i in range(len(points)):
-        uv1 = points.get(i)
-        uv2 = points.get(i + 1)
-        a = uv1.x * uv2.y - uv1.y * uv2.x
-        area = area + a
-    if area < 0:
-        # clock-wise
-        return True
-    return False
-
-
-def is_point_in_polygon(point, subject_points):
-    count = 0
-    for i in range(len(subject_points)):
-        uv_start1 = subject_points.get(i)
-        uv_end1 = subject_points.get(i + 1)
-        uv_start2 = point
-        uv_end2 = Vector((1000000.0, point.y))
-        intersected, _ = is_segment_intersect(uv_start1, uv_end1,
-                                              uv_start2, uv_end2)
-        if intersected:
-            count = count + 1
-
-    return count % 2
-
-
-def is_points_in_polygon(points, subject_points):
-    for i in range(len(points)):
-        internal = is_point_in_polygon(points.get(i), subject_points)
-        if not internal:
-            return False
-
-    return True
-
-
-def get_overlapped_uv_info(bm, faces, uv_layer, mode):
-    # at first, check island overlapped
-    isl = common.get_island_info_from_faces(bm, faces, uv_layer)
-    overlapped_isl_pairs = []
-    for i, i1 in enumerate(isl):
-        for i2 in isl[i + 1:]:
-            if (i1["max"].x < i2["min"].x) or (i2["max"].x < i1["min"].x) or \
-               (i1["max"].y < i2["min"].y) or (i2["max"].y < i1["min"].y):
-                continue
-            overlapped_isl_pairs.append([i1, i2])
-
-    # next, check polygon overlapped
-    overlapped_uvs = []
-    for oip in overlapped_isl_pairs:
-        for clip in oip[0]["faces"]:
-            f_clip = clip["face"]
-            for subject in oip[1]["faces"]:
-                f_subject = subject["face"]
-
-                # fast operation, apply bounding box algorithm
-                if (clip["max_uv"].x < subject["min_uv"].x) or \
-                   (subject["max_uv"].x < clip["min_uv"].x) or \
-                   (clip["max_uv"].y < subject["min_uv"].y) or \
-                   (subject["max_uv"].y < clip["min_uv"].y):
-                    continue
-
-                # slow operation, apply Weiler-Atherton cliping algorithm
-                result, polygons = do_weiler_atherton_cliping(f_clip,
-                                                              f_subject,
-                                                              uv_layer, mode)
-                if result:
-                    subject_uvs = [l[uv_layer].uv.copy()
-                                   for l in f_subject.loops]
-                    overlapped_uvs.append({"clip_face": f_clip,
-                                           "subject_face": f_subject,
-                                           "subject_uvs": subject_uvs,
-                                           "polygons": polygons})
-
-    return overlapped_uvs
-
-
-def get_flipped_uv_info(faces, uv_layer):
-    flipped_uvs = []
-    for f in faces:
-        polygon = RingBuffer([l[uv_layer].uv.copy() for l in f.loops])
-        if is_polygon_flipped(polygon):
-            uvs = [l[uv_layer].uv.copy() for l in f.loops]
-            flipped_uvs.append({"face": f, "uvs": uvs,
-                                "polygons": [polygon.as_list()]})
-
-    return flipped_uvs
-
-
-def update_uvinsp_info(context):
-    sc = context.scene
-    props = sc.muv_props.uvinsp
-
-    obj = context.active_object
-    bm = bmesh.from_edit_mesh(obj.data)
-    if common.check_version(2, 73, 0) >= 0:
-        bm.faces.ensure_lookup_table()
-    uv_layer = bm.loops.layers.uv.verify()
-
-    if context.tool_settings.use_uv_select_sync:
-        sel_faces = [f for f in bm.faces]
-    else:
-        sel_faces = [f for f in bm.faces if f.select]
-    props.overlapped_info = get_overlapped_uv_info(bm, sel_faces, uv_layer,
-                                                   sc.muv_uvinsp_show_mode)
-    props.flipped_info = get_flipped_uv_info(sel_faces, uv_layer)
-
-
-class MUV_UVInspUpdate(bpy.types.Operator):
-    """
-    Operation class: Update
-    """
-
-    bl_idname = "uv.muv_uvinsp_update"
-    bl_label = "Update"
-    bl_description = "Update Overlapped/Flipped UV"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    def execute(self, context):
-        update_uvinsp_info(context)
-
-        if context.area:
-            context.area.tag_redraw()
-
-        return {'FINISHED'}
-
-
-class MUV_UVInspDisplay(bpy.types.Operator):
-    """
-    Operation class: Display
-    """
-
-    bl_idname = "uv.muv_uvinsp_display"
-    bl_label = "Display"
-    bl_description = "Display Overlapped/Flipped UV"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    def execute(self, context):
-        sc = context.scene
-        props = sc.muv_props.uvinsp
-        if not props.display_running:
-            update_uvinsp_info(context)
-            MUV_UVInspRenderer.handle_add(self, context)
-            props.display_running = True
-        else:
-            MUV_UVInspRenderer.handle_remove()
-            props.display_running = False
-
-        if context.area:
-            context.area.tag_redraw()
-
-        return {'FINISHED'}
-
-
-class MUV_UVInspSelectOverlapped(bpy.types.Operator):
-    """
-    Operation class: Select faces which have overlapped UVs
-    """
-
-    bl_idname = "uv.muv_uvinsp_select_overlapped"
-    bl_label = "Overlapped"
-    bl_description = "Select faces which have overlapped UVs"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    def execute(self, context):
-        obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
-        uv_layer = bm.loops.layers.uv.verify()
-
-        if context.tool_settings.use_uv_select_sync:
-            sel_faces = [f for f in bm.faces]
-        else:
-            sel_faces = [f for f in bm.faces if f.select]
-
-        overlapped_info = get_overlapped_uv_info(bm, sel_faces, uv_layer,
-                                                 'FACE')
-
-        for info in overlapped_info:
-            if context.tool_settings.use_uv_select_sync:
-                info["subject_face"].select = True
-            else:
-                for l in info["subject_face"].loops:
-                    l[uv_layer].select = True
-
-        bmesh.update_edit_mesh(obj.data)
-
-        return {'FINISHED'}
-
-
-class MUV_UVInspSelectFlipped(bpy.types.Operator):
-    """
-    Operation class: Select faces which have flipped UVs
-    """
-
-    bl_idname = "uv.muv_uvinsp_select_flipped"
-    bl_label = "Flipped"
-    bl_description = "Select faces which have flipped UVs"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    def execute(self, context):
-        obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.faces.ensure_lookup_table()
-        uv_layer = bm.loops.layers.uv.verify()
-
-        if context.tool_settings.use_uv_select_sync:
-            sel_faces = [f for f in bm.faces]
-        else:
-            sel_faces = [f for f in bm.faces if f.select]
-
-        flipped_info = get_flipped_uv_info(sel_faces, uv_layer)
-
-        for info in flipped_info:
-            if context.tool_settings.use_uv_select_sync:
-                info["face"].select = True
-            else:
-                for l in info["face"].loops:
-                    l[uv_layer].select = True
-
-        bmesh.update_edit_mesh(obj.data)
-
-        return {'FINISHED'}
diff --git a/uv_magic_uv/op/uvw.py b/uv_magic_uv/op/uvw.py
index 10202677663d1fb6240ea66072df44fd84df50b1..c97e0e042e2f1b3f3c47264306a381ed393d96ff 100644
--- a/uv_magic_uv/op/uvw.py
+++ b/uv_magic_uv/op/uvw.py
@@ -20,10 +20,8 @@
 
 __author__ = "Alexander Milovsky, Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
-
-from math import sin, cos, pi
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 import bmesh
@@ -32,37 +30,70 @@ from bpy.props import (
     FloatVectorProperty,
     BoolProperty
 )
-from mathutils import Vector
 
 from .. import common
+from ..impl import uvw_impl as impl
+from ..utils.bl_class_registry import BlClassRegistry
+from ..utils.property_class_registry import PropertyClassRegistry
+
+
+__all__ = [
+    'Properties',
+    'MUV_OT_UVW_BoxMap',
+    'MUV_OT_UVW_BestPlanerMap',
+]
+
+
+@PropertyClassRegistry()
+class Properties:
+    idname = "uvw"
+
+    @classmethod
+    def init_props(cls, scene):
+        scene.muv_uvw_enabled = BoolProperty(
+            name="UVW Enabled",
+            description="UVW is enabled",
+            default=False
+        )
+        scene.muv_uvw_assign_uvmap = BoolProperty(
+            name="Assign UVMap",
+            description="Assign UVMap when no UVmaps are available",
+            default=True
+        )
+
+    @classmethod
+    def del_props(cls, scene):
+        del scene.muv_uvw_enabled
+        del scene.muv_uvw_assign_uvmap
 
 
-class MUV_UVWBoxMap(bpy.types.Operator):
-    bl_idname = "uv.muv_uvw_box_map"
+@BlClassRegistry()
+class MUV_OT_UVW_BoxMap(bpy.types.Operator):
+    bl_idname = "uv.muv_uvw_operator_box_map"
     bl_label = "Box Map"
     bl_options = {'REGISTER', 'UNDO'}
 
-    size = FloatProperty(
+    size: FloatProperty(
         name="Size",
         default=1.0,
         precision=4
     )
-    rotation = FloatVectorProperty(
+    rotation: FloatVectorProperty(
         name="XYZ Rotation",
         size=3,
         default=(0.0, 0.0, 0.0)
     )
-    offset = FloatVectorProperty(
+    offset: FloatVectorProperty(
         name="XYZ Offset",
         size=3,
         default=(0.0, 0.0, 0.0)
     )
-    tex_aspect = FloatProperty(
+    tex_aspect: FloatProperty(
         name="Texture Aspect",
         default=1.0,
         precision=4
     )
-    assign_uvmap = BoolProperty(
+    assign_uvmap: BoolProperty(
         name="Assign UVMap",
         description="Assign UVMap when no UVmaps are available",
         default=True
@@ -70,8 +101,10 @@ class MUV_UVWBoxMap(bpy.types.Operator):
 
     @classmethod
     def poll(cls, context):
-        obj = context.active_object
-        return obj and obj.type == 'MESH'
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return impl.is_valid_context(context)
 
     def execute(self, context):
         obj = context.active_object
@@ -80,102 +113,43 @@ class MUV_UVWBoxMap(bpy.types.Operator):
             bm.faces.ensure_lookup_table()
 
         # get UV layer
-        if not bm.loops.layers.uv:
-            if self.assign_uvmap:
-                bm.loops.layers.uv.new()
-            else:
-                self.report(
-                    {'WARNING'}, "Object must have more than one UV map")
-                return {'CANCELLED'}
-        uv_layer = bm.loops.layers.uv.verify()
-
-        scale = 1.0 / self.size
-
-        sx = 1.0 * scale
-        sy = 1.0 * scale
-        sz = 1.0 * scale
-        ofx = self.offset[0]
-        ofy = self.offset[1]
-        ofz = self.offset[2]
-        rx = self.rotation[0] * pi / 180.0
-        ry = self.rotation[1] * pi / 180.0
-        rz = self.rotation[2] * pi / 180.0
-        aspect = self.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))
+        uv_layer = impl.get_uv_layer(self, bm, self.assign_uvmap)
+        if not uv_layer:
+            return {'CANCELLED'}
 
+        impl.apply_box_map(bm, uv_layer, self.size, self.offset,
+                           self.rotation, self.tex_aspect)
         bmesh.update_edit_mesh(obj.data)
 
         return {'FINISHED'}
 
 
-class MUV_UVWBestPlanerMap(bpy.types.Operator):
-    bl_idname = "uv.muv_uvw_best_planer_map"
+@BlClassRegistry()
+class MUV_OT_UVW_BestPlanerMap(bpy.types.Operator):
+    bl_idname = "uv.muv_uvw_operator_best_planer_map"
     bl_label = "Best Planer Map"
     bl_options = {'REGISTER', 'UNDO'}
 
-    size = FloatProperty(
+    size: FloatProperty(
         name="Size",
         default=1.0,
         precision=4
     )
-    rotation = FloatProperty(
+    rotation: FloatProperty(
         name="XY Rotation",
         default=0.0
     )
-    offset = FloatVectorProperty(
+    offset: FloatVectorProperty(
         name="XY Offset",
         size=2,
         default=(0.0, 0.0)
     )
-    tex_aspect = FloatProperty(
+    tex_aspect: FloatProperty(
         name="Texture Aspect",
         default=1.0,
         precision=4
     )
-    assign_uvmap = BoolProperty(
+    assign_uvmap: BoolProperty(
         name="Assign UVMap",
         description="Assign UVMap when no UVmaps are available",
         default=True
@@ -183,8 +157,10 @@ class MUV_UVWBestPlanerMap(bpy.types.Operator):
 
     @classmethod
     def poll(cls, context):
-        obj = context.active_object
-        return obj and obj.type == 'MESH'
+        # we can not get area/space/region from console
+        if common.is_console_mode():
+            return True
+        return impl.is_valid_context(context)
 
     def execute(self, context):
         obj = context.active_object
@@ -193,44 +169,12 @@ class MUV_UVWBestPlanerMap(bpy.types.Operator):
             bm.faces.ensure_lookup_table()
 
         # get UV layer
-        if not bm.loops.layers.uv:
-            if self.assign_uvmap:
-                bm.loops.layers.uv.new()
-            else:
-                self.report(
-                    {'WARNING'}, "Object must have more than one UV map")
-                return {'CANCELLED'}
-
-        uv_layer = bm.loops.layers.uv.verify()
-
-        scale = 1.0 / self.size
-
-        sx = 1.0 * scale
-        sy = 1.0 * scale
-        ofx = self.offset[0]
-        ofy = self.offset[1]
-        rz = self.rotation * pi / 180.0
-        aspect = self.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))
+        uv_layer = impl.get_uv_layer(self, bm, self.assign_uvmap)
+        if not uv_layer:
+            return {'CANCELLED'}
+
+        impl.apply_planer_map(bm, uv_layer, self.size, self.offset,
+                              self.rotation, self.tex_aspect)
 
         bmesh.update_edit_mesh(obj.data)
 
diff --git a/uv_magic_uv/op/world_scale_uv.py b/uv_magic_uv/op/world_scale_uv.py
deleted file mode 100644
index e256fbac87c59effcd15acc268d0a78a5d88e414..0000000000000000000000000000000000000000
--- a/uv_magic_uv/op/world_scale_uv.py
+++ /dev/null
@@ -1,236 +0,0 @@
-# <pep8-80 compliant>
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-#  This program is free software; you can redistribute it and/or
-#  modify it under the terms of the GNU General Public License
-#  as published by the Free Software Foundation; either version 2
-#  of the License, or (at your option) any later version.
-#
-#  This program is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License
-#  along with this program; if not, write to the Free Software Foundation,
-#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-__author__ = "McBuff, Nutti <nutti.metro@gmail.com>"
-__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
-
-from math import sqrt
-
-import bpy
-import bmesh
-from mathutils import Vector
-from bpy.props import EnumProperty
-
-from .. import common
-
-
-def measure_wsuv_info(obj):
-    mesh_area = common.measure_mesh_area(obj)
-    uv_area = common.measure_uv_area(obj)
-
-    if not uv_area:
-        return None, None, None
-
-    if mesh_area == 0.0:
-        density = 0.0
-    else:
-        density = sqrt(uv_area) / sqrt(mesh_area)
-
-    return uv_area, mesh_area, density
-
-
-class MUV_WSUVMeasure(bpy.types.Operator):
-    """
-    Operation class: Measure face size
-    """
-
-    bl_idname = "uv.muv_wsuv_measure"
-    bl_label = "Measure"
-    bl_description = "Measure face size for scale calculation"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    def execute(self, context):
-        sc = context.scene
-        obj = context.active_object
-
-        uv_area, mesh_area, density = measure_wsuv_info(obj)
-        if not uv_area:
-            self.report({'WARNING'},
-                        "Object must have more than one UV map and texture")
-            return {'CANCELLED'}
-
-        sc.muv_wsuv_src_uv_area = uv_area
-        sc.muv_wsuv_src_mesh_area = mesh_area
-        sc.muv_wsuv_src_density = density
-
-        self.report({'INFO'},
-                    "UV Area: {0}, Mesh Area: {1}, Texel Density: {2}"
-                    .format(uv_area, mesh_area, density))
-
-        return {'FINISHED'}
-
-
-class MUV_WSUVApply(bpy.types.Operator):
-    """
-    Operation class: Apply scaled UV
-    """
-
-    bl_idname = "uv.muv_wsuv_apply"
-    bl_label = "Apply"
-    bl_description = "Apply scaled UV based on scale calculation"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    origin = EnumProperty(
-        name="Origin",
-        description="Aspect Origin",
-        items=[
-            ('CENTER', 'Center', 'Center'),
-            ('LEFT_TOP', 'Left Top', 'Left Bottom'),
-            ('LEFT_CENTER', 'Left Center', 'Left Center'),
-            ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'),
-            ('CENTER_TOP', 'Center Top', 'Center Top'),
-            ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'),
-            ('RIGHT_TOP', 'Right Top', 'Right Top'),
-            ('RIGHT_CENTER', 'Right Center', 'Right Center'),
-            ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom')
-
-        ],
-        default="CENTER"
-    )
-
-    def draw(self, _):
-        layout = self.layout
-
-        layout.prop(self, "origin")
-
-    def execute(self, context):
-        sc = context.scene
-        obj = context.active_object
-        bm = bmesh.from_edit_mesh(obj.data)
-        if common.check_version(2, 73, 0) >= 0:
-            bm.verts.ensure_lookup_table()
-            bm.edges.ensure_lookup_table()
-            bm.faces.ensure_lookup_table()
-
-        sel_faces = [f for f in bm.faces if f.select]
-
-        uv_area, mesh_area, density = measure_wsuv_info(obj)
-        if not uv_area:
-            self.report({'WARNING'},
-                        "Object must have more than one UV map and texture")
-            return {'CANCELLED'}
-
-        uv_layer = bm.loops.layers.uv.verify()
-
-        if sc.muv_wsuv_mode == 'PROPORTIONAL':
-            tgt_density = sc.muv_wsuv_src_density * sqrt(mesh_area) / \
-                sqrt(sc.muv_wsuv_src_mesh_area)
-        elif sc.muv_wsuv_mode == 'SCALING':
-            tgt_density = sc.muv_wsuv_src_density * sc.muv_wsuv_scaling_factor
-        elif sc.muv_wsuv_mode == 'USER':
-            tgt_density = sc.muv_wsuv_tgt_density
-        elif sc.muv_wsuv_mode == 'CONSTANT':
-            tgt_density = sc.muv_wsuv_src_density
-
-        factor = tgt_density / density
-
-        # calculate origin
-        if self.origin == 'CENTER':
-            origin = Vector((0.0, 0.0))
-            num = 0
-            for f in sel_faces:
-                for l in f.loops:
-                    uv = l[uv_layer].uv
-                    origin = origin + uv
-                    num = num + 1
-            origin = origin / num
-        elif self.origin == 'LEFT_TOP':
-            origin = Vector((100000.0, -100000.0))
-            for f in sel_faces:
-                for l in f.loops:
-                    uv = l[uv_layer].uv
-                    origin.x = min(origin.x, uv.x)
-                    origin.y = max(origin.y, uv.y)
-        elif self.origin == 'LEFT_CENTER':
-            origin = Vector((100000.0, 0.0))
-            num = 0
-            for f in sel_faces:
-                for l in f.loops:
-                    uv = l[uv_layer].uv
-                    origin.x = min(origin.x, uv.x)
-                    origin.y = origin.y + uv.y
-                    num = num + 1
-            origin.y = origin.y / num
-        elif self.origin == 'LEFT_BOTTOM':
-            origin = Vector((100000.0, 100000.0))
-            for f in sel_faces:
-                for l in f.loops:
-                    uv = l[uv_layer].uv
-                    origin.x = min(origin.x, uv.x)
-                    origin.y = min(origin.y, uv.y)
-        elif self.origin == 'CENTER_TOP':
-            origin = Vector((0.0, -100000.0))
-            num = 0
-            for f in sel_faces:
-                for l in f.loops:
-                    uv = l[uv_layer].uv
-                    origin.x = origin.x + uv.x
-                    origin.y = max(origin.y, uv.y)
-                    num = num + 1
-            origin.x = origin.x / num
-        elif self.origin == 'CENTER_BOTTOM':
-            origin = Vector((0.0, 100000.0))
-            num = 0
-            for f in sel_faces:
-                for l in f.loops:
-                    uv = l[uv_layer].uv
-                    origin.x = origin.x + uv.x
-                    origin.y = min(origin.y, uv.y)
-                    num = num + 1
-            origin.x = origin.x / num
-        elif self.origin == 'RIGHT_TOP':
-            origin = Vector((-100000.0, -100000.0))
-            for f in sel_faces:
-                for l in f.loops:
-                    uv = l[uv_layer].uv
-                    origin.x = max(origin.x, uv.x)
-                    origin.y = max(origin.y, uv.y)
-        elif self.origin == 'RIGHT_CENTER':
-            origin = Vector((-100000.0, 0.0))
-            num = 0
-            for f in sel_faces:
-                for l in f.loops:
-                    uv = l[uv_layer].uv
-                    origin.x = max(origin.x, uv.x)
-                    origin.y = origin.y + uv.y
-                    num = num + 1
-            origin.y = origin.y / num
-        elif self.origin == 'RIGHT_BOTTOM':
-            origin = Vector((-100000.0, 100000.0))
-            for f in sel_faces:
-                for l in f.loops:
-                    uv = l[uv_layer].uv
-                    origin.x = max(origin.x, uv.x)
-                    origin.y = min(origin.y, uv.y)
-
-        # update UV coordinate
-        for f in sel_faces:
-            for l in f.loops:
-                uv = l[uv_layer].uv
-                diff = uv - origin
-                l[uv_layer].uv = origin + diff * factor
-
-        bmesh.update_edit_mesh(obj.data)
-
-        self.report({'INFO'}, "Scaling factor: {0}".format(factor))
-
-        return {'FINISHED'}
diff --git a/uv_magic_uv/preferences.py b/uv_magic_uv/preferences.py
index d8cdf86bb54024229c9dd2233456019576e9f2b4..3ba943761dfc76fa5340bbf90025f4abadbd8398 100644
--- a/uv_magic_uv/preferences.py
+++ b/uv_magic_uv/preferences.py
@@ -20,23 +20,114 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
+import bpy
 from bpy.props import (
     FloatProperty,
     FloatVectorProperty,
+    BoolProperty,
+    EnumProperty,
+    IntProperty,
 )
 from bpy.types import AddonPreferences
 
+from . import op
+from . import ui
+from . import addon_updater_ops
 
-class MUV_Preferences(AddonPreferences):
+__all__ = [
+    'add_builtin_menu',
+    'remove_builtin_menu',
+    'Preferences'
+]
+
+
+def view3d_uvmap_menu_fn(self, context):
+    layout = self.layout
+    sc = context.scene
+
+    layout.separator()
+    layout.label(text="Copy/Paste UV", icon='IMAGE')
+    # Copy/Paste UV
+    layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_CopyPasteUV.bl_idname,
+                text="Copy/Paste UV")
+    # Transfer UV
+    layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_TransferUV.bl_idname,
+                text="Transfer UV")
+
+    layout.separator()
+    layout.label(text="UV Manipulation", icon='IMAGE')
+    # Flip/Rotate UV
+    ops = layout.operator(op.flip_rotate_uv.MUV_OT_FlipRotate.bl_idname,
+                          text="Flip/Rotate UV")
+    ops.seams = sc.muv_flip_rotate_uv_seams
+    # Mirror UV
+    ops = layout.operator(op.mirror_uv.MUV_OT_MirrorUV.bl_idname,
+                          text="Mirror UV")
+    ops.axis = sc.muv_mirror_uv_axis
+    # Move UV
+    layout.operator(op.move_uv.MUV_OT_MoveUV.bl_idname, text="Move UV")
+
+    layout.separator()
+    # UVW
+    layout.menu(ui.VIEW3D_MT_uv_map.MUV_MT_UVW.bl_idname, text="UVW")
+
+
+def view3d_object_menu_fn(self, _):
+    layout = self.layout
+
+    layout.separator()
+    layout.label(text="Copy/Paste UV", icon='IMAGE')
+    # Copy/Paste UV (Among Objecct)
+    layout.menu(ui.VIEW3D_MT_object.MUV_MT_CopyPasteUV_Object.bl_idname,
+                text="Copy/Paste UV")
+
+
+def image_uvs_menu_fn(self, _):
+    layout = self.layout
+
+    layout.separator()
+    # Copy/Paste UV (on UV/Image Editor)
+    layout.label(text="Copy/Paste UV", icon='IMAGE')
+    layout.menu(ui.IMAGE_MT_uvs.MUV_MT_CopyPasteUV_UVEdit.bl_idname,
+                text="Copy/Paste UV")
+
+
+def add_builtin_menu():
+    bpy.types.VIEW3D_MT_uv_map.append(view3d_uvmap_menu_fn)
+    bpy.types.VIEW3D_MT_object.append(view3d_object_menu_fn)
+    bpy.types.IMAGE_MT_uvs.append(image_uvs_menu_fn)
+
+
+def remove_builtin_menu():
+    bpy.types.IMAGE_MT_uvs.remove(image_uvs_menu_fn)
+    bpy.types.VIEW3D_MT_object.append(view3d_object_menu_fn)
+    bpy.types.VIEW3D_MT_uv_map.remove(view3d_uvmap_menu_fn)
+
+
+class Preferences(AddonPreferences):
     """Preferences class: Preferences for this add-on"""
 
-    bl_idname = __package__
+    bl_idname = "uv_magic_uv"
+
+    def update_enable_builtin_menu(self, _):
+        if self['enable_builtin_menu']:
+            add_builtin_menu()
+        else:
+            remove_builtin_menu()
+
+    # enable to add features to built-in menu
+    enable_builtin_menu = BoolProperty(
+        name="Built-in Menu",
+        description="Enable built-in menu",
+        default=True,
+        update=update_enable_builtin_menu
+    )
 
     # for UV Sculpt
-    uvsculpt_brush_color = FloatVectorProperty(
+    uv_sculpt_brush_color = FloatVectorProperty(
         name="Color",
         description="Color",
         default=(1.0, 0.4, 0.4, 1.0),
@@ -47,7 +138,7 @@ class MUV_Preferences(AddonPreferences):
     )
 
     # for Overlapped UV
-    uvinsp_overlapped_color = FloatVectorProperty(
+    uv_inspection_overlapped_color = FloatVectorProperty(
         name="Color",
         description="Color",
         default=(0.0, 0.0, 1.0, 0.3),
@@ -58,7 +149,7 @@ class MUV_Preferences(AddonPreferences):
     )
 
     # for Flipped UV
-    uvinsp_flipped_color = FloatVectorProperty(
+    uv_inspection_flipped_color = FloatVectorProperty(
         name="Color",
         description="Color",
         default=(1.0, 0.0, 0.0, 0.3),
@@ -69,7 +160,7 @@ class MUV_Preferences(AddonPreferences):
     )
 
     # for Texture Projection
-    texproj_canvas_padding = FloatVectorProperty(
+    texture_projection_canvas_padding = FloatVectorProperty(
         name="Canvas Padding",
         description="Canvas Padding",
         size=2,
@@ -78,139 +169,245 @@ class MUV_Preferences(AddonPreferences):
         default=(20.0, 20.0))
 
     # for UV Bounding Box
-    uvbb_cp_size = FloatProperty(
+    uv_bounding_box_cp_size = FloatProperty(
         name="Size",
         description="Control Point Size",
         default=6.0,
         min=3.0,
         max=100.0)
-    uvbb_cp_react_size = FloatProperty(
+    uv_bounding_box_cp_react_size = FloatProperty(
         name="React Size",
         description="Size event fired",
         default=10.0,
         min=3.0,
         max=100.0)
 
-    def draw(self, _):
+    # for UI
+    category = EnumProperty(
+        name="Category",
+        description="Preferences Category",
+        items=[
+            ('INFO', "Information", "Information about this add-on"),
+            ('CONFIG', "Configuration", "Configuration about this add-on"),
+            ('UPDATE', "Update", "Update this add-on"),
+        ],
+        default='INFO'
+    )
+    info_desc_expanded = BoolProperty(
+        name="Description",
+        description="Description",
+        default=False
+    )
+    info_loc_expanded = BoolProperty(
+        name="Location",
+        description="Location",
+        default=False
+    )
+    conf_uv_sculpt_expanded = BoolProperty(
+        name="UV Sculpt",
+        description="UV Sculpt",
+        default=False
+    )
+    conf_uv_inspection_expanded = BoolProperty(
+        name="UV Inspection",
+        description="UV Inspection",
+        default=False
+    )
+    conf_texture_projection_expanded = BoolProperty(
+        name="Texture Projection",
+        description="Texture Projection",
+        default=False
+    )
+    conf_uv_bounding_box_expanded = BoolProperty(
+        name="UV Bounding Box",
+        description="UV Bounding Box",
+        default=False
+    )
+
+    # for add-on updater
+    auto_check_update = BoolProperty(
+        name="Auto-check for Update",
+        description="If enabled, auto-check for updates using an interval",
+        default=False
+    )
+    updater_intrval_months = IntProperty(
+        name='Months',
+        description="Number of months between checking for updates",
+        default=0,
+        min=0
+    )
+    updater_intrval_days = IntProperty(
+        name='Days',
+        description="Number of days between checking for updates",
+        default=7,
+        min=0
+    )
+    updater_intrval_hours = IntProperty(
+        name='Hours',
+        description="Number of hours between checking for updates",
+        default=0,
+        min=0,
+        max=23
+    )
+    updater_intrval_minutes = IntProperty(
+        name='Minutes',
+        description="Number of minutes between checking for updates",
+        default=0,
+        min=0,
+        max=59
+    )
+
+    def draw(self, context):
         layout = self.layout
 
-        layout.label("[Configuration]")
-
-        layout.label("UV Sculpt:")
-        sp = layout.split(percentage=0.05)
-        col = sp.column()  # spacer
-        sp = sp.split(percentage=0.3)
-        col = sp.column()
-        col.label("Brush Color:")
-        col.prop(self, "uvsculpt_brush_color", text="")
-
-        layout.separator()
-
-        layout.label("UV Inspection:")
-        sp = layout.split(percentage=0.05)
-        col = sp.column()  # spacer
-        sp = sp.split(percentage=0.3)
-        col = sp.column()
-        col.label("Overlapped UV Color:")
-        col.prop(self, "uvinsp_overlapped_color", text="")
-        sp = sp.split(percentage=0.45)
-        col = sp.column()
-        col.label("Flipped UV Color:")
-        col.prop(self, "uvinsp_flipped_color", text="")
-
-        layout.separator()
-
-        layout.label("Texture Projection:")
-        sp = layout.split(percentage=0.05)
-        col = sp.column()       # spacer
-        sp = sp.split(percentage=0.3)
-        col = sp.column()
-        col.prop(self, "texproj_canvas_padding")
-
-        layout.separator()
-
-        layout.label("UV Bounding Box:")
-        sp = layout.split(percentage=0.05)
-        col = sp.column()       # spacer
-        sp = sp.split(percentage=0.3)
-        col = sp.column()
-        col.label("Control Point:")
-        col.prop(self, "uvbb_cp_size")
-        col.prop(self, "uvbb_cp_react_size")
-
-        layout.label("--------------------------------------")
-
-        layout.label("[Description]")
-        column = layout.column(align=True)
-        column.label("Magic UV is composed of many UV editing features.")
-        column.label("See tutorial page if you are new to this add-on.")
-        column.label("https://github.com/nutti/Magic-UV/wiki/Tutorial")
-
-        layout.label("--------------------------------------")
-
-        layout.label("[Location]")
-
-        row = layout.row(align=True)
-        sp = row.split(percentage=0.5)
-        sp.label("3D View > Tool shelf > Copy/Paste UV (Object mode)")
-        sp = sp.split(percentage=1.0)
-        col = sp.column(align=True)
-        col.label("Copy/Paste UV (Among objects)")
-
-        row = layout.row(align=True)
-        sp = row.split(percentage=0.5)
-        sp.label("3D View > Tool shelf > Copy/Paste UV (Edit mode)")
-        sp = sp.split(percentage=1.0)
-        col = sp.column(align=True)
-        col.label("Copy/Paste UV (Among faces in 3D View)")
-        col.label("Transfer UV")
-
-        row = layout.row(align=True)
-        sp = row.split(percentage=0.5)
-        sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)")
-        sp = sp.split(percentage=1.0)
-        col = sp.column(align=True)
-        col.label("Flip/Rotate UV")
-        col.label("Mirror UV")
-        col.label("Move UV")
-        col.label("World Scale UV")
-        col.label("Preserve UV Aspect")
-        col.label("Texture Lock")
-        col.label("Texture Wrap")
-        col.label("UV Sculpt")
-
-        row = layout.row(align=True)
-        sp = row.split(percentage=0.5)
-        sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)")
-        sp = sp.split(percentage=1.0)
-        col = sp.column(align=True)
-        col.label("Unwrap Constraint")
-        col.label("Texture Projection")
-        col.label("UVW")
-
-        row = layout.row(align=True)
-        sp = row.split(percentage=0.5)
-        sp.label("UV/Image Editor > Tool shelf > Copy/Paste UV")
-        sp = sp.split(percentage=1.0)
-        col = sp.column(align=True)
-        col.label("Copy/Paste UV (Among faces in UV/Image Editor)")
-
-        row = layout.row(align=True)
-        sp = row.split(percentage=0.5)
-        sp.label("UV/Image Editor > Tool shelf > UV Manipulation")
-        sp = sp.split(percentage=1.0)
-        col = sp.column(align=True)
-        col.label("Align UV")
-        col.label("Smooth UV")
-        col.label("Select UV")
-        col.label("Pack UV (Extension)")
-
-        row = layout.row(align=True)
-        sp = row.split(percentage=0.5)
-        sp.label("UV/Image Editor > Tool shelf > Editor Enhancement")
-        sp = sp.split(percentage=1.0)
-        col = sp.column(align=True)
-        col.label("Align UV Cursor")
-        col.label("UV Cursor Location")
-        col.label("UV Bounding Box")
-        col.label("UV Inspection")
+        layout.row().prop(self, "category", expand=True)
+
+        if self.category == 'INFO':
+            layout.prop(
+                self, "info_desc_expanded", text="Description",
+                icon='DISCLOSURE_TRI_DOWN' if self.info_desc_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.info_desc_expanded:
+                column = layout.column(align=True)
+                column.label("Magic UV is composed of many UV editing" +
+                             " features.")
+                column.label("See tutorial page if you are new to this" +
+                             " add-on.")
+                column.label("https://github.com/nutti/Magic-UV/wiki/Tutorial")
+
+            layout.prop(
+                self, "info_loc_expanded", text="Location",
+                icon='DISCLOSURE_TRI_DOWN' if self.info_loc_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.info_loc_expanded:
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("3D View > Tool shelf > Copy/Paste UV (Object mode)")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Copy/Paste UV (Among objects)")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("3D View > Tool shelf > Copy/Paste UV (Edit mode)")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Copy/Paste UV (Among faces in 3D View)")
+                col.label("Transfer UV")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Flip/Rotate UV")
+                col.label("Mirror UV")
+                col.label("Move UV")
+                col.label("World Scale UV")
+                col.label("Preserve UV Aspect")
+                col.label("Texture Lock")
+                col.label("Texture Wrap")
+                col.label("UV Sculpt")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Unwrap Constraint")
+                col.label("Texture Projection")
+                col.label("UVW")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("UV/Image Editor > Tool shelf > Copy/Paste UV")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Copy/Paste UV (Among faces in UV/Image Editor)")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("UV/Image Editor > Tool shelf > UV Manipulation")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Align UV")
+                col.label("Smooth UV")
+                col.label("Select UV")
+                col.label("Pack UV (Extension)")
+
+                row = layout.row(align=True)
+                sp = row.split(percentage=0.5)
+                sp.label("UV/Image Editor > Tool shelf > Editor Enhancement")
+                sp = sp.split(percentage=1.0)
+                col = sp.column(align=True)
+                col.label("Align UV Cursor")
+                col.label("UV Cursor Location")
+                col.label("UV Bounding Box")
+                col.label("UV Inspection")
+
+        elif self.category == 'CONFIG':
+            layout.prop(self, "enable_builtin_menu", text="Built-in Menu")
+
+            layout.separator()
+
+            layout.prop(
+                self, "conf_uv_sculpt_expanded", text="UV Sculpt",
+                icon='DISCLOSURE_TRI_DOWN' if self.conf_uv_sculpt_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.conf_uv_sculpt_expanded:
+                sp = layout.split(percentage=0.05)
+                col = sp.column()  # spacer
+                sp = sp.split(percentage=0.3)
+                col = sp.column()
+                col.label("Brush Color:")
+                col.prop(self, "uv_sculpt_brush_color", text="")
+                layout.separator()
+
+            layout.prop(
+                self, "conf_uv_inspection_expanded", text="UV Inspection",
+                icon='DISCLOSURE_TRI_DOWN' if self.conf_uv_inspection_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.conf_uv_inspection_expanded:
+                sp = layout.split(percentage=0.05)
+                col = sp.column()  # spacer
+                sp = sp.split(percentage=0.3)
+                col = sp.column()
+                col.label("Overlapped UV Color:")
+                col.prop(self, "uv_inspection_overlapped_color", text="")
+                sp = sp.split(percentage=0.45)
+                col = sp.column()
+                col.label("Flipped UV Color:")
+                col.prop(self, "uv_inspection_flipped_color", text="")
+                layout.separator()
+
+            layout.prop(
+                self, "conf_texture_projection_expanded",
+                text="Texture Projection",
+                icon='DISCLOSURE_TRI_DOWN'
+                if self.conf_texture_projection_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.conf_texture_projection_expanded:
+                sp = layout.split(percentage=0.05)
+                col = sp.column()       # spacer
+                sp = sp.split(percentage=0.3)
+                col = sp.column()
+                col.prop(self, "texture_projection_canvas_padding")
+                layout.separator()
+
+            layout.prop(
+                self, "conf_uv_bounding_box_expanded", text="UV Bounding Box",
+                icon='DISCLOSURE_TRI_DOWN'
+                if self.conf_uv_bounding_box_expanded
+                else 'DISCLOSURE_TRI_RIGHT')
+            if self.conf_uv_bounding_box_expanded:
+                sp = layout.split(percentage=0.05)
+                col = sp.column()       # spacer
+                sp = sp.split(percentage=0.3)
+                col = sp.column()
+                col.label("Control Point:")
+                col.prop(self, "uv_bounding_box_cp_size")
+                col.prop(self, "uv_bounding_box_cp_react_size")
+                layout.separator()
+
+        elif self.category == 'UPDATE':
+            addon_updater_ops.update_settings_ui(self, context)
diff --git a/uv_magic_uv/properites.py b/uv_magic_uv/properites.py
index d882063af7a8fa1192f4c804e8fc4c4b1886bbe2..60ce26eb10922b797645122d854ad52666941767 100644
--- a/uv_magic_uv/properites.py
+++ b/uv_magic_uv/properites.py
@@ -20,746 +20,43 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
-import bpy
-from bpy.props import (
-    FloatProperty,
-    EnumProperty,
-    BoolProperty,
-    FloatVectorProperty,
-    IntProperty
-)
-from mathutils import Vector
 
-from . import common
+from .utils.property_class_registry import PropertyClassRegistry
 
 
-def get_loaded_texture_name(_, __):
-    items = [(key, key, "") for key in bpy.data.images.keys()]
-    items.append(("None", "None", ""))
-    return items
+__all__ = [
+    'MUV_Properties',
+    'init_props',
+    'clear_props',
+]
 
 
 # Properties used in this add-on.
+# pylint: disable=W0612
 class MUV_Properties():
-    cpuv = None
-    cpuv_obj = None
-    cpuv_selseq = None
-    transuv = None
-    uvbb = None
-    texlock = None
-    texproj = None
-    texwrap = None
-    mvuv = None
-    uvinsp = None
-    uvsculpt = None
-
     def __init__(self):
-        self.cpuv = MUV_CPUVProps()
-        self.cpuv_obj = MUV_CPUVProps()
-        self.cpuv_selseq = MUV_CPUVSelSeqProps()
-        self.transuv = MUV_TransUVProps()
-        self.uvbb = MUV_UVBBProps()
-        self.texlock = MUV_TexLockProps()
-        self.texproj = MUV_TexProjProps()
-        self.texwrap = MUV_TexWrapProps()
-        self.mvuv = MUV_MVUVProps()
-        self.uvinsp = MUV_UVInspProps()
-        self.uvsculpt = MUV_UVSculptProps()
-
-
-class MUV_CPUVProps():
-    src_uvs = []
-    src_pin_uvs = []
-    src_seams = []
-
-
-class MUV_CPUVSelSeqProps():
-    src_uvs = []
-    src_pin_uvs = []
-    src_seams = []
-
-
-class MUV_TransUVProps():
-    topology_copied = []
-
-
-class MUV_TexProjProps():
-    running = False
-
-
-class MUV_UVBBProps():
-    uv_info_ini = []
-    ctrl_points_ini = []
-    ctrl_points = []
-    running = False
-
-
-class MUV_TexLockProps():
-    verts_orig = None
-    intr_verts_orig = None
-    intr_running = False
-
-
-class MUV_TexWrapProps():
-    ref_face_index = -1
-    ref_obj = None
-
+        self.prefs = MUV_Prefs()
 
-class MUV_MVUVProps():
-    running = False
 
-
-class MUV_UVInspProps():
-    display_running = False
-    overlapped_info = []
-    flipped_info = []
-
-
-class MUV_UVSculptProps():
-    running = False
+class MUV_Prefs():
+    expanded = {
+        "info_desc": False,
+        "info_loc": False,
+        "conf_uvsculpt": False,
+        "conf_uvinsp": False,
+        "conf_texproj": False,
+        "conf_uvbb": False
+    }
 
 
 def init_props(scene):
     scene.muv_props = MUV_Properties()
-
-    # UV Sculpt
-    scene.muv_uvsculpt_enabled = BoolProperty(
-        name="UV Sculpt",
-        description="UV Sculpt is enabled",
-        default=False
-    )
-    scene.muv_uvsculpt_radius = IntProperty(
-        name="Radius",
-        description="Radius of the brush",
-        min=1,
-        max=500,
-        default=30
-    )
-    scene.muv_uvsculpt_strength = FloatProperty(
-        name="Strength",
-        description="How powerful the effect of the brush when applied",
-        min=0.0,
-        max=1.0,
-        default=0.03,
-    )
-    scene.muv_uvsculpt_tools = EnumProperty(
-        name="Tools",
-        description="Select Tools for the UV sculpt brushes",
-        items=[
-            ('GRAB', "Grab", "Grab UVs"),
-            ('RELAX', "Relax", "Relax UVs"),
-            ('PINCH', "Pinch", "Pinch UVs")
-        ],
-        default='GRAB'
-    )
-    scene.muv_uvsculpt_show_brush = BoolProperty(
-        name="Show Brush",
-        description="Show Brush",
-        default=True
-    )
-    scene.muv_uvsculpt_pinch_invert = BoolProperty(
-        name="Invert",
-        description="Pinch UV to invert direction",
-        default=False
-    )
-    scene.muv_uvsculpt_relax_method = EnumProperty(
-        name="Method",
-        description="Algorithm used for relaxation",
-        items=[
-            ('HC', "HC", "Use HC method for relaxation"),
-            ('LAPLACIAN', "Laplacian", "Use laplacian method for relaxation")
-        ],
-        default='HC'
-    )
-
-    # Texture Wrap
-    scene.muv_texwrap_enabled = BoolProperty(
-        name="Texture Wrap",
-        description="Texture Wrap is enabled",
-        default=False
-    )
-    scene.muv_texwrap_set_and_refer = BoolProperty(
-        name="Set and Refer",
-        description="Refer and set UV",
-        default=True
-    )
-    scene.muv_texwrap_selseq = BoolProperty(
-        name="Selection Sequence",
-        description="Set UV sequentially",
-        default=False
-    )
-
-    # UV inspection
-    scene.muv_seluv_enabled = BoolProperty(
-        name="Select UV Enabled",
-        description="Select UV is enabled",
-        default=False
-    )
-    scene.muv_uvinsp_enabled = BoolProperty(
-        name="UV Inspection Enabled",
-        description="UV Inspection is enabled",
-        default=False
-    )
-    scene.muv_uvinsp_show_overlapped = BoolProperty(
-        name="Overlapped",
-        description="Show overlapped UVs",
-        default=False
-    )
-    scene.muv_uvinsp_show_flipped = BoolProperty(
-        name="Flipped",
-        description="Show flipped UVs",
-        default=False
-    )
-    scene.muv_uvinsp_show_mode = EnumProperty(
-        name="Mode",
-        description="Show mode",
-        items=[
-            ('PART', "Part", "Show only overlapped/flipped part"),
-            ('FACE', "Face", "Show overlapped/flipped face")
-        ],
-        default='PART'
-    )
-
-    # Align UV
-    scene.muv_auv_enabled = BoolProperty(
-        name="Align UV Enabled",
-        description="Align UV is enabled",
-        default=False
-    )
-    scene.muv_auv_transmission = BoolProperty(
-        name="Transmission",
-        description="Align linked UVs",
-        default=False
-    )
-    scene.muv_auv_select = BoolProperty(
-        name="Select",
-        description="Select UVs which are aligned",
-        default=False
-    )
-    scene.muv_auv_vertical = BoolProperty(
-        name="Vert-Infl (Vertical)",
-        description="Align vertical direction influenced "
-                    "by mesh vertex proportion",
-        default=False
-    )
-    scene.muv_auv_horizontal = BoolProperty(
-        name="Vert-Infl (Horizontal)",
-        description="Align horizontal direction influenced "
-                    "by mesh vertex proportion",
-        default=False
-    )
-    scene.muv_auv_location = EnumProperty(
-        name="Location",
-        description="Align location",
-        items=[
-            ('LEFT_TOP', "Left/Top", "Align to Left or Top"),
-            ('MIDDLE', "Middle", "Align to middle"),
-            ('RIGHT_BOTTOM', "Right/Bottom", "Align to Right or Bottom")
-        ],
-        default='MIDDLE'
-    )
-
-    # Smooth UV
-    scene.muv_smuv_enabled = BoolProperty(
-        name="Smooth UV Enabled",
-        description="Smooth UV is enabled",
-        default=False
-    )
-    scene.muv_smuv_transmission = BoolProperty(
-        name="Transmission",
-        description="Smooth linked UVs",
-        default=False
-    )
-    scene.muv_smuv_mesh_infl = FloatProperty(
-        name="Mesh Influence",
-        description="Influence rate of mesh vertex",
-        min=0.0,
-        max=1.0,
-        default=0.0
-    )
-    scene.muv_smuv_select = BoolProperty(
-        name="Select",
-        description="Select UVs which are smoothed",
-        default=False
-    )
-
-    # UV Bounding Box
-    scene.muv_uvbb_enabled = BoolProperty(
-        name="UV Bounding Box Enabled",
-        description="UV Bounding Box is enabled",
-        default=False
-    )
-    scene.muv_uvbb_uniform_scaling = BoolProperty(
-        name="Uniform Scaling",
-        description="Enable Uniform Scaling",
-        default=False
-    )
-    scene.muv_uvbb_boundary = EnumProperty(
-        name="Boundary",
-        description="Boundary",
-        default='UV_SEL',
-        items=[
-            ('UV', "UV", "Boundary is decided by UV"),
-            ('UV_SEL', "UV (Selected)", "Boundary is decided by Selected UV")
-        ]
-    )
-
-    # Pack UV
-    scene.muv_packuv_enabled = BoolProperty(
-        name="Pack UV Enabled",
-        description="Pack UV is enabled",
-        default=False
-    )
-    scene.muv_packuv_allowable_center_deviation = FloatVectorProperty(
-        name="Allowable Center Deviation",
-        description="Allowable center deviation to judge same UV island",
-        min=0.000001,
-        max=0.1,
-        default=(0.001, 0.001),
-        size=2
-    )
-    scene.muv_packuv_allowable_size_deviation = FloatVectorProperty(
-        name="Allowable Size Deviation",
-        description="Allowable sizse deviation to judge same UV island",
-        min=0.000001,
-        max=0.1,
-        default=(0.001, 0.001),
-        size=2
-    )
-
-    # Move UV
-    scene.muv_mvuv_enabled = BoolProperty(
-        name="Move UV Enabled",
-        description="Move UV is enabled",
-        default=False
-    )
-
-    # UVW
-    scene.muv_uvw_enabled = BoolProperty(
-        name="UVW Enabled",
-        description="UVW is enabled",
-        default=False
-    )
-    scene.muv_uvw_assign_uvmap = BoolProperty(
-        name="Assign UVMap",
-        description="Assign UVMap when no UVmaps are available",
-        default=True
-    )
-
-    # Texture Projection
-    scene.muv_texproj_enabled = BoolProperty(
-        name="Texture Projection Enabled",
-        description="Texture Projection is enabled",
-        default=False
-    )
-    scene.muv_texproj_tex_magnitude = FloatProperty(
-        name="Magnitude",
-        description="Texture Magnitude",
-        default=0.5,
-        min=0.0,
-        max=100.0
-    )
-    scene.muv_texproj_tex_image = EnumProperty(
-        name="Image",
-        description="Texture Image",
-        items=get_loaded_texture_name
-    )
-    scene.muv_texproj_tex_transparency = FloatProperty(
-        name="Transparency",
-        description="Texture Transparency",
-        default=0.2,
-        min=0.0,
-        max=1.0
-    )
-    scene.muv_texproj_adjust_window = BoolProperty(
-        name="Adjust Window",
-        description="Size of renderered texture is fitted to window",
-        default=True
-    )
-    scene.muv_texproj_apply_tex_aspect = BoolProperty(
-        name="Texture Aspect Ratio",
-        description="Apply Texture Aspect ratio to displayed texture",
-        default=True
-    )
-    scene.muv_texproj_assign_uvmap = BoolProperty(
-        name="Assign UVMap",
-        description="Assign UVMap when no UVmaps are available",
-        default=True
-    )
-
-    # Texture Lock
-    scene.muv_texlock_enabled = BoolProperty(
-        name="Texture Lock Enabled",
-        description="Texture Lock is enabled",
-        default=False
-    )
-    scene.muv_texlock_connect = BoolProperty(
-        name="Connect UV",
-        default=True
-    )
-
-    # World Scale UV
-    scene.muv_wsuv_enabled = BoolProperty(
-        name="World Scale UV Enabled",
-        description="World Scale UV is enabled",
-        default=False
-    )
-    scene.muv_wsuv_src_mesh_area = FloatProperty(
-        name="Mesh Area",
-        description="Source Mesh Area",
-        default=0.0,
-        min=0.0
-    )
-    scene.muv_wsuv_src_uv_area = FloatProperty(
-        name="UV Area",
-        description="Source UV Area",
-        default=0.0,
-        min=0.0
-    )
-    scene.muv_wsuv_src_density = FloatProperty(
-        name="Density",
-        description="Source Texel Density",
-        default=0.0,
-        min=0.0
-    )
-    scene.muv_wsuv_tgt_density = FloatProperty(
-        name="Density",
-        description="Target Texel Density",
-        default=0.0,
-        min=0.0
-    )
-    scene.muv_wsuv_mode = EnumProperty(
-        name="Mode",
-        description="Density calculation mode",
-        items=[
-            ('PROPORTIONAL', 'Proportional', 'Scale proportionally by mesh'),
-            ('SCALING', 'Scaling', 'Specify scale factor'),
-            ('USER', 'User', 'Specify density'),
-            ('CONSTANT', 'Constant', 'Constant density')
-        ],
-        default='CONSTANT'
-    )
-    scene.muv_wsuv_scaling_factor = FloatProperty(
-        name="Scaling Factor",
-        default=1.0,
-        max=1000.0,
-        min=0.00001
-    )
-    scene.muv_wsuv_origin = EnumProperty(
-        name="Origin",
-        description="Aspect Origin",
-        items=[
-            ('CENTER', 'Center', 'Center'),
-            ('LEFT_TOP', 'Left Top', 'Left Bottom'),
-            ('LEFT_CENTER', 'Left Center', 'Left Center'),
-            ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'),
-            ('CENTER_TOP', 'Center Top', 'Center Top'),
-            ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'),
-            ('RIGHT_TOP', 'Right Top', 'Right Top'),
-            ('RIGHT_CENTER', 'Right Center', 'Right Center'),
-            ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom')
-
-        ],
-        default='CENTER'
-    )
-
-    # Unwrap Constraint
-    scene.muv_unwrapconst_enabled = BoolProperty(
-        name="Unwrap Constraint Enabled",
-        description="Unwrap Constraint is enabled",
-        default=False
-    )
-    scene.muv_unwrapconst_u_const = BoolProperty(
-        name="U-Constraint",
-        description="Keep UV U-axis coordinate",
-        default=False
-    )
-    scene.muv_unwrapconst_v_const = BoolProperty(
-        name="V-Constraint",
-        description="Keep UV V-axis coordinate",
-        default=False
-    )
-
-    # Preserve UV Aspect
-    scene.muv_preserve_uv_enabled = BoolProperty(
-        name="Preserve UV Aspect Enabled",
-        description="Preserve UV Aspect is enabled",
-        default=False
-    )
-    scene.muv_preserve_uv_tex_image = EnumProperty(
-        name="Image",
-        description="Texture Image",
-        items=get_loaded_texture_name
-    )
-    scene.muv_preserve_uv_origin = EnumProperty(
-        name="Origin",
-        description="Aspect Origin",
-        items=[
-            ('CENTER', 'Center', 'Center'),
-            ('LEFT_TOP', 'Left Top', 'Left Bottom'),
-            ('LEFT_CENTER', 'Left Center', 'Left Center'),
-            ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'),
-            ('CENTER_TOP', 'Center Top', 'Center Top'),
-            ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'),
-            ('RIGHT_TOP', 'Right Top', 'Right Top'),
-            ('RIGHT_CENTER', 'Right Center', 'Right Center'),
-            ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom')
-
-        ],
-        default="CENTER"
-    )
-
-    # Flip/Rotate UV
-    scene.muv_fliprot_enabled = BoolProperty(
-        name="Flip/Rotate UV Enabled",
-        description="Flip/Rotate UV is enabled",
-        default=False
-    )
-    scene.muv_fliprot_seams = BoolProperty(
-        name="Seams",
-        description="Seams",
-        default=True
-    )
-
-    # Mirror UV
-    scene.muv_mirroruv_enabled = BoolProperty(
-        name="Mirror UV Enabled",
-        description="Mirror UV is enabled",
-        default=False
-    )
-    scene.muv_mirroruv_axis = EnumProperty(
-        items=[
-            ('X', "X", "Mirror Along X axis"),
-            ('Y', "Y", "Mirror Along Y axis"),
-            ('Z', "Z", "Mirror Along Z axis")
-        ],
-        name="Axis",
-        description="Mirror Axis",
-        default='X'
-    )
-
-    # Copy/Paste UV
-    scene.muv_cpuv_enabled = BoolProperty(
-        name="Copy/Paste UV Enabled",
-        description="Copy/Paste UV is enabled",
-        default=False
-    )
-    scene.muv_cpuv_copy_seams = BoolProperty(
-        name="Copy Seams",
-        description="Copy Seams",
-        default=True
-    )
-    scene.muv_cpuv_mode = EnumProperty(
-        items=[
-            ('DEFAULT', "Default", "Default Mode"),
-            ('SEL_SEQ', "Selection Sequence", "Selection Sequence Mode")
-        ],
-        name="Copy/Paste UV Mode",
-        description="Copy/Paste UV Mode",
-        default='DEFAULT'
-    )
-    scene.muv_cpuv_strategy = EnumProperty(
-        name="Strategy",
-        description="Paste Strategy",
-        items=[
-            ('N_N', 'N:N', 'Number of faces must be equal to source'),
-            ('N_M', 'N:M', 'Number of faces must not be equal to source')
-        ],
-        default='N_M'
-    )
-
-    # Transfer UV
-    scene.muv_transuv_enabled = BoolProperty(
-        name="Transfer UV Enabled",
-        description="Transfer UV is enabled",
-        default=False
-    )
-    scene.muv_transuv_invert_normals = BoolProperty(
-        name="Invert Normals",
-        description="Invert Normals",
-        default=False
-    )
-    scene.muv_transuv_copy_seams = BoolProperty(
-        name="Copy Seams",
-        description="Copy Seams",
-        default=True
-    )
-
-    # Align UV Cursor
-    def auvc_get_cursor_loc(self):
-        area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
-                                          'IMAGE_EDITOR')
-        bd_size = common.get_uvimg_editor_board_size(area)
-        loc = space.cursor_location
-        if bd_size[0] < 0.000001:
-            cx = 0.0
-        else:
-            cx = loc[0] / bd_size[0]
-        if bd_size[1] < 0.000001:
-            cy = 0.0
-        else:
-            cy = loc[1] / bd_size[1]
-        self['muv_auvc_cursor_loc'] = Vector((cx, cy))
-        return self.get('muv_auvc_cursor_loc', (0.0, 0.0))
-
-    def auvc_set_cursor_loc(self, value):
-        self['muv_auvc_cursor_loc'] = value
-        area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
-                                          'IMAGE_EDITOR')
-        bd_size = common.get_uvimg_editor_board_size(area)
-        cx = bd_size[0] * value[0]
-        cy = bd_size[1] * value[1]
-        space.cursor_location = Vector((cx, cy))
-
-    scene.muv_auvc_enabled = BoolProperty(
-        name="Align UV Cursor Enabled",
-        description="Align UV Cursor is enabled",
-        default=False
-    )
-    scene.muv_auvc_cursor_loc = FloatVectorProperty(
-        name="UV Cursor Location",
-        size=2,
-        precision=4,
-        soft_min=-1.0,
-        soft_max=1.0,
-        step=1,
-        default=(0.000, 0.000),
-        get=auvc_get_cursor_loc,
-        set=auvc_set_cursor_loc
-    )
-    scene.muv_auvc_align_menu = EnumProperty(
-        name="Align Method",
-        description="Align Method",
-        default='TEXTURE',
-        items=[
-            ('TEXTURE', "Texture", "Align to texture"),
-            ('UV', "UV", "Align to UV"),
-            ('UV_SEL', "UV (Selected)", "Align to Selected UV")
-        ]
-    )
-
-    # UV Cursor Location
-    scene.muv_uvcloc_enabled = BoolProperty(
-        name="UV Cursor Location Enabled",
-        description="UV Cursor Location is enabled",
-        default=False
-    )
+    PropertyClassRegistry.init_props(scene)
 
 
 def clear_props(scene):
+    PropertyClassRegistry.del_props(scene)
     del scene.muv_props
-
-    # UV Sculpt
-    del scene.muv_uvsculpt_enabled
-    del scene.muv_uvsculpt_radius
-    del scene.muv_uvsculpt_strength
-    del scene.muv_uvsculpt_tools
-    del scene.muv_uvsculpt_show_brush
-    del scene.muv_uvsculpt_pinch_invert
-    del scene.muv_uvsculpt_relax_method
-
-    # Texture Wrap
-    del scene.muv_texwrap_enabled
-    del scene.muv_texwrap_set_and_refer
-    del scene.muv_texwrap_selseq
-
-    # UV Inspection
-    del scene.muv_seluv_enabled
-    del scene.muv_uvinsp_enabled
-    del scene.muv_uvinsp_show_overlapped
-    del scene.muv_uvinsp_show_flipped
-    del scene.muv_uvinsp_show_mode
-
-    # Align UV
-    del scene.muv_auv_enabled
-    del scene.muv_auv_transmission
-    del scene.muv_auv_select
-    del scene.muv_auv_vertical
-    del scene.muv_auv_horizontal
-    del scene.muv_auv_location
-
-    # Smooth UV
-    del scene.muv_smuv_enabled
-    del scene.muv_smuv_transmission
-    del scene.muv_smuv_mesh_infl
-    del scene.muv_smuv_select
-
-    # UV Bounding Box
-    del scene.muv_uvbb_enabled
-    del scene.muv_uvbb_uniform_scaling
-    del scene.muv_uvbb_boundary
-
-    # Pack UV
-    del scene.muv_packuv_enabled
-    del scene.muv_packuv_allowable_center_deviation
-    del scene.muv_packuv_allowable_size_deviation
-
-    # Move UV
-    del scene.muv_mvuv_enabled
-
-    # UVW
-    del scene.muv_uvw_enabled
-    del scene.muv_uvw_assign_uvmap
-
-    # Texture Projection
-    del scene.muv_texproj_enabled
-    del scene.muv_texproj_tex_magnitude
-    del scene.muv_texproj_tex_image
-    del scene.muv_texproj_tex_transparency
-    del scene.muv_texproj_adjust_window
-    del scene.muv_texproj_apply_tex_aspect
-    del scene.muv_texproj_assign_uvmap
-
-    # Texture Lock
-    del scene.muv_texlock_enabled
-    del scene.muv_texlock_connect
-
-    # World Scale UV
-    del scene.muv_wsuv_enabled
-    del scene.muv_wsuv_src_mesh_area
-    del scene.muv_wsuv_src_uv_area
-    del scene.muv_wsuv_src_density
-    del scene.muv_wsuv_tgt_density
-    del scene.muv_wsuv_mode
-    del scene.muv_wsuv_scaling_factor
-    del scene.muv_wsuv_origin
-
-    # Unwrap Constraint
-    del scene.muv_unwrapconst_enabled
-    del scene.muv_unwrapconst_u_const
-    del scene.muv_unwrapconst_v_const
-
-    # Preserve UV Aspect
-    del scene.muv_preserve_uv_enabled
-    del scene.muv_preserve_uv_tex_image
-    del scene.muv_preserve_uv_origin
-
-    # Flip/Rotate UV
-    del scene.muv_fliprot_enabled
-    del scene.muv_fliprot_seams
-
-    # Mirror UV
-    del scene.muv_mirroruv_enabled
-    del scene.muv_mirroruv_axis
-
-    # Copy/Paste UV
-    del scene.muv_cpuv_enabled
-    del scene.muv_cpuv_copy_seams
-    del scene.muv_cpuv_mode
-    del scene.muv_cpuv_strategy
-
-    # Transfer UV
-    del scene.muv_transuv_enabled
-    del scene.muv_transuv_invert_normals
-    del scene.muv_transuv_copy_seams
-
-    # Align UV Cursor
-    del scene.muv_auvc_enabled
-    del scene.muv_auvc_cursor_loc
-    del scene.muv_auvc_align_menu
-
-    # UV Cursor Location
-    del scene.muv_uvcloc_enabled
diff --git a/uv_magic_uv/ui/IMAGE_MT_uvs.py b/uv_magic_uv/ui/IMAGE_MT_uvs.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7dda379cba8e375de9e8f7b826fd45f6ab14ac2
--- /dev/null
+++ b/uv_magic_uv/ui/IMAGE_MT_uvs.py
@@ -0,0 +1,56 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from ..op import (
+    copy_paste_uv_uvedit,
+)
+from ..utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_MT_CopyPasteUV_UVEdit',
+]
+
+
+@BlClassRegistry()
+class MUV_MT_CopyPasteUV_UVEdit(bpy.types.Menu):
+    """
+    Menu class: Master menu of Copy/Paste UV coordinate on UV/ImageEditor
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_uvedit_menu"
+    bl_label = "Copy/Paste UV"
+    bl_description = "Copy and Paste UV coordinate among object"
+
+    def draw(self, _):
+        layout = self.layout
+
+        layout.operator(
+            copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_CopyUV.bl_idname,
+            text="Copy")
+        layout.operator(
+            copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_PasteUV.bl_idname,
+            text="Paste")
diff --git a/uv_magic_uv/ui/VIEW3D_MT_object.py b/uv_magic_uv/ui/VIEW3D_MT_object.py
new file mode 100644
index 0000000000000000000000000000000000000000..318cd82c6ea6506a1176da5413359906ff4e116a
--- /dev/null
+++ b/uv_magic_uv/ui/VIEW3D_MT_object.py
@@ -0,0 +1,54 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from ..op import copy_paste_uv_object
+from ..utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_MT_CopyPasteUV_Object',
+]
+
+
+@BlClassRegistry()
+class MUV_MT_CopyPasteUV_Object(bpy.types.Menu):
+    """
+    Menu class: Master menu of Copy/Paste UV coordinate among object
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_object_menu"
+    bl_label = "Copy/Paste UV"
+    bl_description = "Copy and Paste UV coordinate among object"
+
+    def draw(self, _):
+        layout = self.layout
+
+        layout.menu(
+            copy_paste_uv_object.MUV_MT_CopyPasteUVObject_CopyUV.bl_idname,
+            text="Copy")
+        layout.menu(
+            copy_paste_uv_object.MUV_MT_CopyPasteUVObject_PasteUV.bl_idname,
+            text="Paste")
diff --git a/uv_magic_uv/ui/VIEW3D_MT_uv_map.py b/uv_magic_uv/ui/VIEW3D_MT_uv_map.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5698504450765dd7aa3dcd02729c9862ff11159
--- /dev/null
+++ b/uv_magic_uv/ui/VIEW3D_MT_uv_map.py
@@ -0,0 +1,111 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy.utils
+
+from ..op import (
+    copy_paste_uv,
+    transfer_uv,
+    uvw,
+)
+from ..utils.bl_class_registry import BlClassRegistry
+
+__all__ = [
+    'MUV_MT_CopyPasteUV',
+    'MUV_MT_TransferUV',
+    'MUV_MT_UVW',
+]
+
+
+@BlClassRegistry()
+class MUV_MT_CopyPasteUV(bpy.types.Menu):
+    """
+    Menu class: Master menu of Copy/Paste UV coordinate
+    """
+
+    bl_idname = "uv.muv_copy_paste_uv_menu"
+    bl_label = "Copy/Paste UV"
+    bl_description = "Copy and Paste UV coordinate"
+
+    def draw(self, _):
+        layout = self.layout
+
+        layout.label(text="Default")
+        layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_CopyUV.bl_idname,
+                    text="Copy")
+        layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_PasteUV.bl_idname,
+                    text="Paste")
+
+        layout.separator()
+
+        layout.label(text="Selection Sequence")
+        layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqCopyUV.bl_idname,
+                    text="Copy")
+        layout.menu(copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqPasteUV.bl_idname,
+                    text="Paste")
+
+
+@BlClassRegistry()
+class MUV_MT_TransferUV(bpy.types.Menu):
+    """
+    Menu class: Master menu of Transfer UV coordinate
+    """
+
+    bl_idname = "uv.muv_transfer_uv_menu"
+    bl_label = "Transfer UV"
+    bl_description = "Transfer UV coordinate"
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        layout.operator(transfer_uv.MUV_OT_TransferUV_CopyUV.bl_idname,
+                        text="Copy")
+        ops = layout.operator(transfer_uv.MUV_OT_TransferUV_PasteUV.bl_idname,
+                              text="Paste")
+        ops.invert_normals = sc.muv_transfer_uv_invert_normals
+        ops.copy_seams = sc.muv_transfer_uv_copy_seams
+
+
+@BlClassRegistry()
+class MUV_MT_UVW(bpy.types.Menu):
+    """
+    Menu class: Master menu of UVW
+    """
+
+    bl_idname = "uv.muv_uvw_menu"
+    bl_label = "UVW"
+    bl_description = ""
+
+    def draw(self, context):
+        layout = self.layout
+        sc = context.scene
+
+        ops = layout.operator(uvw.MUV_OT_UVW_BoxMap.bl_idname, text="Box")
+        ops.assign_uvmap = sc.muv_uvw_assign_uvmap
+
+        ops = layout.operator(uvw.MUV_OT_UVW_BestPlanerMap.bl_idname,
+                              text="Best Planner")
+        ops.assign_uvmap = sc.muv_uvw_assign_uvmap
diff --git a/uv_magic_uv/ui/__init__.py b/uv_magic_uv/ui/__init__.py
index ad56aeb3c47e71c556442649e35b7ff42a2c7f13..5f7e0c5ef62402521134aa07473cd60e903ef919 100644
--- a/uv_magic_uv/ui/__init__.py
+++ b/uv_magic_uv/ui/__init__.py
@@ -20,25 +20,27 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 if "bpy" in locals():
     import importlib
-    importlib.reload(view3d_copy_paste_uv_objectmode)
     importlib.reload(view3d_copy_paste_uv_editmode)
+    importlib.reload(view3d_copy_paste_uv_objectmode)
     importlib.reload(view3d_uv_manipulation)
     importlib.reload(view3d_uv_mapping)
     importlib.reload(uvedit_copy_paste_uv)
-    importlib.reload(uvedit_uv_manipulation)
-    importlib.reload(uvedit_editor_enhance)
+    importlib.reload(VIEW3D_MT_object)
+    importlib.reload(VIEW3D_MT_uv_map)
+    importlib.reload(IMAGE_MT_uvs)
 else:
-    from . import view3d_copy_paste_uv_objectmode
     from . import view3d_copy_paste_uv_editmode
+    from . import view3d_copy_paste_uv_objectmode
     from . import view3d_uv_manipulation
     from . import view3d_uv_mapping
     from . import uvedit_copy_paste_uv
-    from . import uvedit_uv_manipulation
-    from . import uvedit_editor_enhance
+    from . import VIEW3D_MT_object
+    from . import VIEW3D_MT_uv_map
+    from . import IMAGE_MT_uvs
 
 import bpy
diff --git a/uv_magic_uv/ui/uvedit_copy_paste_uv.py b/uv_magic_uv/ui/uvedit_copy_paste_uv.py
index d87dbef3e86855bd50eb9350952f32ef9a188210..e21a5abd336d6af9c7eb259582a29de1ad89a452 100644
--- a/uv_magic_uv/ui/uvedit_copy_paste_uv.py
+++ b/uv_magic_uv/ui/uvedit_copy_paste_uv.py
@@ -20,21 +20,27 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 
 from ..op import copy_paste_uv_uvedit
+from ..utils.bl_class_registry import BlClassRegistry
 
+__all__ = [
+    'MUV_PT_UVEdit_CopyPasteUV',
+]
 
-class IMAGE_PT_MUV_CPUV(bpy.types.Panel):
+
+@BlClassRegistry()
+class MUV_PT_UVEdit_CopyPasteUV(bpy.types.Panel):
     """
     Panel class: Copy/Paste UV on Property Panel on UV/ImageEditor
     """
 
     bl_space_type = 'IMAGE_EDITOR'
-    bl_region_type = 'TOOLS'
+    bl_region_type = 'UI'
     bl_label = "Copy/Paste UV"
     bl_category = "Magic UV"
     bl_context = 'mesh_edit'
@@ -42,13 +48,15 @@ class IMAGE_PT_MUV_CPUV(bpy.types.Panel):
 
     def draw_header(self, _):
         layout = self.layout
-        layout.label(text="", icon='IMAGE_COL')
+        layout.label(text="", icon='IMAGE')
 
     def draw(self, _):
         layout = self.layout
 
         row = layout.row(align=True)
-        row.operator(copy_paste_uv_uvedit.MUV_CPUVIECopyUV.bl_idname,
-                     text="Copy")
-        row.operator(copy_paste_uv_uvedit.MUV_CPUVIEPasteUV.bl_idname,
-                     text="Paste")
+        row.operator(
+            copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_CopyUV.bl_idname,
+            text="Copy")
+        row.operator(
+            copy_paste_uv_uvedit.MUV_OT_CopyPasteUVUVEdit_PasteUV.bl_idname,
+            text="Paste")
diff --git a/uv_magic_uv/ui/uvedit_editor_enhance.py b/uv_magic_uv/ui/uvedit_editor_enhance.py
deleted file mode 100644
index 88a2492c64230fd8f7bc2ce56e12cfe4a97870f7..0000000000000000000000000000000000000000
--- a/uv_magic_uv/ui/uvedit_editor_enhance.py
+++ /dev/null
@@ -1,136 +0,0 @@
-# <pep8-80 compliant>
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-#  This program is free software; you can redistribute it and/or
-#  modify it under the terms of the GNU General Public License
-#  as published by the Free Software Foundation; either version 2
-#  of the License, or (at your option) any later version.
-#
-#  This program is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License
-#  along with this program; if not, write to the Free Software Foundation,
-#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-__author__ = "Nutti <nutti.metro@gmail.com>"
-__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
-
-import bpy
-
-from ..op import align_uv_cursor
-from ..op import uv_bounding_box
-from ..op import uv_inspection
-
-
-class IMAGE_PT_MUV_EE(bpy.types.Panel):
-    """
-    Panel class: UV/Image Editor Enhancement
-    """
-
-    bl_space_type = 'IMAGE_EDITOR'
-    bl_region_type = 'TOOLS'
-    bl_label = "Editor Enhancement"
-    bl_category = "Magic UV"
-    bl_context = 'mesh_edit'
-    bl_options = {'DEFAULT_CLOSED'}
-
-    def draw_header(self, _):
-        layout = self.layout
-        layout.label(text="", icon='IMAGE_COL')
-
-    def draw(self, context):
-        layout = self.layout
-        sc = context.scene
-        props = sc.muv_props
-
-        box = layout.box()
-        box.prop(sc, "muv_auvc_enabled", text="Align UV Cursor")
-        if sc.muv_auvc_enabled:
-            box.prop(sc, "muv_auvc_align_menu", expand=True)
-
-            col = box.column(align=True)
-
-            row = col.row(align=True)
-            ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
-                               text="Left Top")
-            ops.position = 'LEFT_TOP'
-            ops.base = sc.muv_auvc_align_menu
-            ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
-                               text="Middle Top")
-            ops.position = 'MIDDLE_TOP'
-            ops.base = sc.muv_auvc_align_menu
-            ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
-                               text="Right Top")
-            ops.position = 'RIGHT_TOP'
-            ops.base = sc.muv_auvc_align_menu
-
-            row = col.row(align=True)
-            ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
-                               text="Left Middle")
-            ops.position = 'LEFT_MIDDLE'
-            ops.base = sc.muv_auvc_align_menu
-            ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
-                               text="Center")
-            ops.position = 'CENTER'
-            ops.base = sc.muv_auvc_align_menu
-            ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
-                               text="Right Middle")
-            ops.position = 'RIGHT_MIDDLE'
-            ops.base = sc.muv_auvc_align_menu
-
-            row = col.row(align=True)
-            ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
-                               text="Left Bottom")
-            ops.position = 'LEFT_BOTTOM'
-            ops.base = sc.muv_auvc_align_menu
-            ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
-                               text="Middle Bottom")
-            ops.position = 'MIDDLE_BOTTOM'
-            ops.base = sc.muv_auvc_align_menu
-            ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname,
-                               text="Right Bottom")
-            ops.position = 'RIGHT_BOTTOM'
-            ops.base = sc.muv_auvc_align_menu
-
-        box = layout.box()
-        box.prop(sc, "muv_uvcloc_enabled", text="UV Cursor Location")
-        if sc.muv_uvcloc_enabled:
-            box.prop(sc, "muv_auvc_cursor_loc", text="")
-
-        box = layout.box()
-        box.prop(sc, "muv_uvbb_enabled", text="UV Bounding Box")
-        if sc.muv_uvbb_enabled:
-            if props.uvbb.running is False:
-                box.operator(uv_bounding_box.MUV_UVBBUpdater.bl_idname,
-                             text="Display", icon='PLAY')
-            else:
-                box.operator(uv_bounding_box.MUV_UVBBUpdater.bl_idname,
-                             text="Hide", icon='PAUSE')
-            box.prop(sc, "muv_uvbb_uniform_scaling", text="Uniform Scaling")
-            box.prop(sc, "muv_uvbb_boundary", text="Boundary")
-
-        box = layout.box()
-        box.prop(sc, "muv_uvinsp_enabled", text="UV Inspection")
-        if sc.muv_uvinsp_enabled:
-            row = box.row()
-            if not sc.muv_props.uvinsp.display_running:
-                row.operator(uv_inspection.MUV_UVInspDisplay.bl_idname,
-                             text="Display", icon='PLAY')
-            else:
-                row.operator(uv_inspection.MUV_UVInspDisplay.bl_idname,
-                             text="Hide", icon='PAUSE')
-                row.operator(uv_inspection.MUV_UVInspUpdate.bl_idname,
-                             text="Update")
-            row = box.row()
-            row.prop(sc, "muv_uvinsp_show_overlapped")
-            row.prop(sc, "muv_uvinsp_show_flipped")
-            row = box.row()
-            row.prop(sc, "muv_uvinsp_show_mode")
diff --git a/uv_magic_uv/ui/uvedit_uv_manipulation.py b/uv_magic_uv/ui/uvedit_uv_manipulation.py
deleted file mode 100644
index f391c4cbf2062d8b9af542a11e2908915b036e5c..0000000000000000000000000000000000000000
--- a/uv_magic_uv/ui/uvedit_uv_manipulation.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# <pep8-80 compliant>
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-#  This program is free software; you can redistribute it and/or
-#  modify it under the terms of the GNU General Public License
-#  as published by the Free Software Foundation; either version 2
-#  of the License, or (at your option) any later version.
-#
-#  This program is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License
-#  along with this program; if not, write to the Free Software Foundation,
-#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-__author__ = "Nutti <nutti.metro@gmail.com>"
-__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
-
-import bpy
-
-from ..op import uv_inspection
-from ..op import align_uv
-from ..op import smooth_uv
-from ..op import pack_uv
-
-
-class IMAGE_PT_MUV_UVManip(bpy.types.Panel):
-    """
-    Panel class: UV Manipulation on Property Panel on UV/ImageEditor
-    """
-
-    bl_space_type = 'IMAGE_EDITOR'
-    bl_region_type = 'TOOLS'
-    bl_label = "UV Manipulation"
-    bl_category = "Magic UV"
-    bl_context = 'mesh_edit'
-    bl_options = {'DEFAULT_CLOSED'}
-
-    def draw_header(self, _):
-        layout = self.layout
-        layout.label(text="", icon='IMAGE_COL')
-
-    def draw(self, context):
-        sc = context.scene
-        layout = self.layout
-
-        box = layout.box()
-        box.prop(sc, "muv_auv_enabled", text="Align UV")
-        if sc.muv_auv_enabled:
-            col = box.column()
-            row = col.row(align=True)
-            ops = row.operator(align_uv.MUV_AUVCircle.bl_idname, text="Circle")
-            ops.transmission = sc.muv_auv_transmission
-            ops.select = sc.muv_auv_select
-            ops = row.operator(align_uv.MUV_AUVStraighten.bl_idname,
-                               text="Straighten")
-            ops.transmission = sc.muv_auv_transmission
-            ops.select = sc.muv_auv_select
-            ops.vertical = sc.muv_auv_vertical
-            ops.horizontal = sc.muv_auv_horizontal
-            row = col.row()
-            ops = row.operator(align_uv.MUV_AUVAxis.bl_idname, text="XY-axis")
-            ops.transmission = sc.muv_auv_transmission
-            ops.select = sc.muv_auv_select
-            ops.vertical = sc.muv_auv_vertical
-            ops.horizontal = sc.muv_auv_horizontal
-            ops.location = sc.muv_auv_location
-            row.prop(sc, "muv_auv_location", text="")
-
-            col = box.column(align=True)
-            row = col.row(align=True)
-            row.prop(sc, "muv_auv_transmission", text="Transmission")
-            row.prop(sc, "muv_auv_select", text="Select")
-            row = col.row(align=True)
-            row.prop(sc, "muv_auv_vertical", text="Vertical")
-            row.prop(sc, "muv_auv_horizontal", text="Horizontal")
-
-        box = layout.box()
-        box.prop(sc, "muv_smuv_enabled", text="Smooth UV")
-        if sc.muv_smuv_enabled:
-            ops = box.operator(smooth_uv.MUV_AUVSmooth.bl_idname,
-                               text="Smooth")
-            ops.transmission = sc.muv_smuv_transmission
-            ops.select = sc.muv_smuv_select
-            ops.mesh_infl = sc.muv_smuv_mesh_infl
-            col = box.column(align=True)
-            row = col.row(align=True)
-            row.prop(sc, "muv_smuv_transmission", text="Transmission")
-            row.prop(sc, "muv_smuv_select", text="Select")
-            col.prop(sc, "muv_smuv_mesh_infl", text="Mesh Influence")
-
-        box = layout.box()
-        box.prop(sc, "muv_seluv_enabled", text="Select UV")
-        if sc.muv_seluv_enabled:
-            row = box.row(align=True)
-            row.operator(uv_inspection.MUV_UVInspSelectOverlapped.bl_idname)
-            row.operator(uv_inspection.MUV_UVInspSelectFlipped.bl_idname)
-
-        box = layout.box()
-        box.prop(sc, "muv_packuv_enabled", text="Pack UV (Extension)")
-        if sc.muv_packuv_enabled:
-            ops = box.operator(pack_uv.MUV_PackUV.bl_idname, text="Pack UV")
-            ops.allowable_center_deviation = \
-                sc.muv_packuv_allowable_center_deviation
-            ops.allowable_size_deviation = \
-                sc.muv_packuv_allowable_size_deviation
-            box.label("Allowable Center Deviation:")
-            box.prop(sc, "muv_packuv_allowable_center_deviation", text="")
-            box.label("Allowable Size Deviation:")
-            box.prop(sc, "muv_packuv_allowable_size_deviation", text="")
diff --git a/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py b/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py
index a22adf035c282f969bb59084f33894a238a84d4b..14fba24a2c7e80e254726308417ae4e6e30d93f6 100644
--- a/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py
+++ b/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py
@@ -20,22 +20,30 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 
-from ..op import copy_paste_uv
-from ..op import transfer_uv
+from ..op import (
+    copy_paste_uv,
+    transfer_uv,
+)
+from ..utils.bl_class_registry import BlClassRegistry
 
+__all__ = [
+    'MUV_PT_CopyPasteUVEditMode',
+]
 
-class OBJECT_PT_MUV_CPUV(bpy.types.Panel):
+
+@BlClassRegistry()
+class MUV_PT_CopyPasteUVEditMode(bpy.types.Panel):
     """
     Panel class: Copy/Paste UV on Property Panel on View3D
     """
 
     bl_space_type = 'VIEW_3D'
-    bl_region_type = 'TOOLS'
+    bl_region_type = 'UI'
     bl_label = "Copy/Paste UV"
     bl_category = "Magic UV"
     bl_context = 'mesh_edit'
@@ -43,39 +51,43 @@ class OBJECT_PT_MUV_CPUV(bpy.types.Panel):
 
     def draw_header(self, _):
         layout = self.layout
-        layout.label(text="", icon='IMAGE_COL')
+        layout.label(text="", icon='IMAGE')
 
     def draw(self, context):
         sc = context.scene
         layout = self.layout
 
         box = layout.box()
-        box.prop(sc, "muv_cpuv_enabled", text="Copy/Paste UV")
-        if sc.muv_cpuv_enabled:
+        box.prop(sc, "muv_copy_paste_uv_enabled", text="Copy/Paste UV")
+        if sc.muv_copy_paste_uv_enabled:
             row = box.row(align=True)
-            if sc.muv_cpuv_mode == 'DEFAULT':
-                row.menu(copy_paste_uv.MUV_CPUVCopyUVMenu.bl_idname,
-                         text="Copy")
-                row.menu(copy_paste_uv.MUV_CPUVPasteUVMenu.bl_idname,
-                         text="Paste")
-            elif sc.muv_cpuv_mode == 'SEL_SEQ':
-                row.menu(copy_paste_uv.MUV_CPUVSelSeqCopyUVMenu.bl_idname,
+            if sc.muv_copy_paste_uv_mode == 'DEFAULT':
+                row.menu(copy_paste_uv.MUV_MT_CopyPasteUV_CopyUV.bl_idname,
                          text="Copy")
-                row.menu(copy_paste_uv.MUV_CPUVSelSeqPasteUVMenu.bl_idname,
+                row.menu(copy_paste_uv.MUV_MT_CopyPasteUV_PasteUV.bl_idname,
                          text="Paste")
-            box.prop(sc, "muv_cpuv_mode", expand=True)
-            box.prop(sc, "muv_cpuv_copy_seams", text="Seams")
-            box.prop(sc, "muv_cpuv_strategy", text="Strategy")
+            elif sc.muv_copy_paste_uv_mode == 'SEL_SEQ':
+                row.menu(
+                    copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqCopyUV.bl_idname,
+                    text="Copy")
+                row.menu(
+                    copy_paste_uv.MUV_MT_CopyPasteUV_SelSeqPasteUV.bl_idname,
+                    text="Paste")
+            box.prop(sc, "muv_copy_paste_uv_mode", expand=True)
+            box.prop(sc, "muv_copy_paste_uv_copy_seams", text="Seams")
+            box.prop(sc, "muv_copy_paste_uv_strategy", text="Strategy")
 
         box = layout.box()
-        box.prop(sc, "muv_transuv_enabled", text="Transfer UV")
-        if sc.muv_transuv_enabled:
+        box.prop(sc, "muv_transfer_uv_enabled", text="Transfer UV")
+        if sc.muv_transfer_uv_enabled:
             row = box.row(align=True)
-            row.operator(transfer_uv.MUV_TransUVCopy.bl_idname, text="Copy")
-            ops = row.operator(transfer_uv.MUV_TransUVPaste.bl_idname,
+            row.operator(transfer_uv.MUV_OT_TransferUV_CopyUV.bl_idname,
+                         text="Copy")
+            ops = row.operator(transfer_uv.MUV_OT_TransferUV_PasteUV.bl_idname,
                                text="Paste")
-            ops.invert_normals = sc.muv_transuv_invert_normals
-            ops.copy_seams = sc.muv_transuv_copy_seams
+            ops.invert_normals = sc.muv_transfer_uv_invert_normals
+            ops.copy_seams = sc.muv_transfer_uv_copy_seams
             row = box.row()
-            row.prop(sc, "muv_transuv_invert_normals", text="Invert Normals")
-            row.prop(sc, "muv_transuv_copy_seams", text="Seams")
+            row.prop(sc, "muv_transfer_uv_invert_normals",
+                     text="Invert Normals")
+            row.prop(sc, "muv_transfer_uv_copy_seams", text="Seams")
diff --git a/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py b/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py
index f9e2bec0ccf3813fc1e79ace4e4a24d2b8ef58b3..6dd0d3b43584e8bd479da565dbe9b6de34381e95 100644
--- a/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py
+++ b/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py
@@ -20,21 +20,27 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 
 from ..op import copy_paste_uv_object
+from ..utils.bl_class_registry import BlClassRegistry
 
+__all__ = [
+    'MUV_PT_View3D_Object_CopyPasteUV',
+]
 
-class OBJECT_PT_MUV_CPUVObj(bpy.types.Panel):
+
+@BlClassRegistry()
+class MUV_PT_View3D_Object_CopyPasteUV(bpy.types.Panel):
     """
     Panel class: Copy/Paste UV on Property Panel on View3D
     """
 
     bl_space_type = 'VIEW_3D'
-    bl_region_type = 'TOOLS'
+    bl_region_type = 'UI'
     bl_label = "Copy/Paste UV"
     bl_category = "Magic UV"
     bl_context = 'objectmode'
@@ -42,15 +48,18 @@ class OBJECT_PT_MUV_CPUVObj(bpy.types.Panel):
 
     def draw_header(self, _):
         layout = self.layout
-        layout.label(text="", icon='IMAGE_COL')
+        layout.label(text="", icon='IMAGE')
 
     def draw(self, context):
         sc = context.scene
         layout = self.layout
 
         row = layout.row(align=True)
-        row.menu(copy_paste_uv_object.MUV_CPUVObjCopyUVMenu.bl_idname,
-                 text="Copy")
-        row.menu(copy_paste_uv_object.MUV_CPUVObjPasteUVMenu.bl_idname,
-                 text="Paste")
-        layout.prop(sc, "muv_cpuv_copy_seams", text="Copy Seams")
+        row.menu(
+            copy_paste_uv_object.MUV_MT_CopyPasteUVObject_CopyUV.bl_idname,
+            text="Copy")
+        row.menu(
+            copy_paste_uv_object.MUV_MT_CopyPasteUVObject_PasteUV.bl_idname,
+            text="Paste")
+        layout.prop(sc, "muv_copy_paste_uv_object_copy_seams",
+                    text="Seams")
diff --git a/uv_magic_uv/ui/view3d_uv_manipulation.py b/uv_magic_uv/ui/view3d_uv_manipulation.py
index 1e9b7d7e85557b38449da1fd60e4f09c2cb63f4b..365a0dc8a795a15755ec868655bfbd45e287eef9 100644
--- a/uv_magic_uv/ui/view3d_uv_manipulation.py
+++ b/uv_magic_uv/ui/view3d_uv_manipulation.py
@@ -20,28 +20,31 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 
-from ..op import flip_rotate_uv
-from ..op import mirror_uv
-from ..op import move_uv
-from ..op import preserve_uv_aspect
-from ..op import texture_lock
-from ..op import texture_wrap
-from ..op import uv_sculpt
-from ..op import world_scale_uv
+from ..op import (
+    flip_rotate_uv,
+    mirror_uv,
+    move_uv,
+)
+from ..utils.bl_class_registry import BlClassRegistry
 
+__all__ = [
+    'MUV_PT_View3D_UVManipulation',
+]
 
-class OBJECT_PT_MUV_UVManip(bpy.types.Panel):
+
+@BlClassRegistry()
+class MUV_PT_View3D_UVManipulation(bpy.types.Panel):
     """
     Panel class: UV Manipulation on Property Panel on View3D
     """
 
     bl_space_type = 'VIEW_3D'
-    bl_region_type = 'TOOLS'
+    bl_region_type = 'UI'
     bl_label = "UV Manipulation"
     bl_category = "Magic UV"
     bl_context = 'mesh_edit'
@@ -49,132 +52,37 @@ class OBJECT_PT_MUV_UVManip(bpy.types.Panel):
 
     def draw_header(self, _):
         layout = self.layout
-        layout.label(text="", icon='IMAGE_COL')
+        layout.label(text="", icon='IMAGE')
 
     def draw(self, context):
         sc = context.scene
-        props = sc.muv_props
         layout = self.layout
 
         box = layout.box()
-        box.prop(sc, "muv_fliprot_enabled", text="Flip/Rotate UV")
-        if sc.muv_fliprot_enabled:
+        box.prop(sc, "muv_flip_rotate_uv_enabled", text="Flip/Rotate UV")
+        if sc.muv_flip_rotate_uv_enabled:
             row = box.row()
-            ops = row.operator(flip_rotate_uv.MUV_FlipRot.bl_idname,
+            ops = row.operator(flip_rotate_uv.MUV_OT_FlipRotate.bl_idname,
                                text="Flip/Rotate")
-            ops.seams = sc.muv_fliprot_seams
-            row.prop(sc, "muv_fliprot_seams", text="Seams")
+            ops.seams = sc.muv_flip_rotate_uv_seams
+            row.prop(sc, "muv_flip_rotate_uv_seams", text="Seams")
 
         box = layout.box()
-        box.prop(sc, "muv_mirroruv_enabled", text="Mirror UV")
-        if sc.muv_mirroruv_enabled:
+        box.prop(sc, "muv_mirror_uv_enabled", text="Mirror UV")
+        if sc.muv_mirror_uv_enabled:
             row = box.row()
-            ops = row.operator(mirror_uv.MUV_MirrorUV.bl_idname, text="Mirror")
-            ops.axis = sc.muv_mirroruv_axis
-            row.prop(sc, "muv_mirroruv_axis", text="")
+            ops = row.operator(mirror_uv.MUV_OT_MirrorUV.bl_idname,
+                               text="Mirror")
+            ops.axis = sc.muv_mirror_uv_axis
+            row.prop(sc, "muv_mirror_uv_axis", text="")
 
         box = layout.box()
-        box.prop(sc, "muv_mvuv_enabled", text="Move UV")
-        if sc.muv_mvuv_enabled:
+        box.prop(sc, "muv_move_uv_enabled", text="Move UV")
+        if sc.muv_move_uv_enabled:
             col = box.column()
-            col.operator(move_uv.MUV_MVUV.bl_idname, icon='PLAY', text="Start")
-            if props.mvuv.running:
-                col.enabled = False
-            else:
-                col.enabled = True
-
-        box = layout.box()
-        box.prop(sc, "muv_wsuv_enabled", text="World Scale UV")
-        if sc.muv_wsuv_enabled:
-            row = box.row(align=True)
-            row.operator(world_scale_uv.MUV_WSUVMeasure.bl_idname,
-                         text="Measure")
-            ops = row.operator(world_scale_uv.MUV_WSUVApply.bl_idname,
-                               text="Apply")
-            ops.origin = sc.muv_wsuv_origin
-            box.label("Source:")
-            sp = box.split(percentage=0.7)
-            col = sp.column(align=True)
-            col.prop(sc, "muv_wsuv_src_mesh_area", text="Mesh Area")
-            col.prop(sc, "muv_wsuv_src_uv_area", text="UV Area")
-            col.prop(sc, "muv_wsuv_src_density", text="Density")
-            col.enabled = False
-            sp = sp.split(percentage=1.0)
-            col = sp.column(align=True)
-            col.label("cm x cm")
-            col.label("px x px")
-            col.label("px/cm")
-            col.enabled = False
-            sp = box.split(percentage=0.3)
-            sp.label("Mode:")
-            sp = sp.split(percentage=1.0)
-            col = sp.column()
-            col.prop(sc, "muv_wsuv_mode", text="")
-            if sc.muv_wsuv_mode == 'USER':
-                col.prop(sc, "muv_wsuv_tgt_density", text="Density")
-            if sc.muv_wsuv_mode == 'SCALING':
-                col.prop(sc, "muv_wsuv_scaling_factor", text="Scaling Factor")
-            box.prop(sc, "muv_wsuv_origin", text="Origin")
-
-        box = layout.box()
-        box.prop(sc, "muv_preserve_uv_enabled", text="Preserve UV Aspect")
-        if sc.muv_preserve_uv_enabled:
-            row = box.row()
-            ops = row.operator(
-                preserve_uv_aspect.MUV_PreserveUVAspect.bl_idname,
-                text="Change Image")
-            ops.dest_img_name = sc.muv_preserve_uv_tex_image
-            ops.origin = sc.muv_preserve_uv_origin
-            row.prop(sc, "muv_preserve_uv_tex_image", text="")
-            box.prop(sc, "muv_preserve_uv_origin", text="Origin")
-
-        box = layout.box()
-        box.prop(sc, "muv_texlock_enabled", text="Texture Lock")
-        if sc.muv_texlock_enabled:
-            row = box.row(align=True)
-            col = row.column(align=True)
-            col.label("Normal Mode:")
-            col = row.column(align=True)
-            col.operator(texture_lock.MUV_TexLockStart.bl_idname, text="Lock")
-            ops = col.operator(texture_lock.MUV_TexLockStop.bl_idname,
-                               text="Unlock")
-            ops.connect = sc.muv_texlock_connect
-            col.prop(sc, "muv_texlock_connect", text="Connect")
-
-            row = box.row(align=True)
-            row.label("Interactive Mode:")
-            if not props.texlock.intr_running:
-                row.operator(texture_lock.MUV_TexLockIntrStart.bl_idname,
-                             icon='PLAY', text="Start")
-            else:
-                row.operator(texture_lock.MUV_TexLockIntrStop.bl_idname,
-                             icon="PAUSE", text="Stop")
-
-        box = layout.box()
-        box.prop(sc, "muv_texwrap_enabled", text="Texture Wrap")
-        if sc.muv_texwrap_enabled:
-            row = box.row(align=True)
-            row.operator(texture_wrap.MUV_TexWrapRefer.bl_idname, text="Refer")
-            row.operator(texture_wrap.MUV_TexWrapSet.bl_idname, text="Set")
-            box.prop(sc, "muv_texwrap_set_and_refer")
-            box.prop(sc, "muv_texwrap_selseq")
-
-        box = layout.box()
-        box.prop(sc, "muv_uvsculpt_enabled", text="UV Sculpt")
-        if sc.muv_uvsculpt_enabled:
-            if not props.uvsculpt.running:
-                box.operator(uv_sculpt.MUV_UVSculptOps.bl_idname,
-                             icon='PLAY', text="Start")
+            if not move_uv.MUV_OT_MoveUV.is_running(context):
+                col.operator(move_uv.MUV_OT_MoveUV.bl_idname, icon='PLAY',
+                             text="Start")
             else:
-                box.operator(uv_sculpt.MUV_UVSculptOps.bl_idname,
-                             icon='PAUSE', text="Stop")
-            col = box.column()
-            col.label("Brush:")
-            col.prop(sc, "muv_uvsculpt_radius")
-            col.prop(sc, "muv_uvsculpt_strength")
-            box.prop(sc, "muv_uvsculpt_tools")
-            if sc.muv_uvsculpt_tools == 'PINCH':
-                box.prop(sc, "muv_uvsculpt_pinch_invert")
-            elif sc.muv_uvsculpt_tools == 'RELAX':
-                box.prop(sc, "muv_uvsculpt_relax_method")
-            box.prop(sc, "muv_uvsculpt_show_brush")
+                col.operator(move_uv.MUV_OT_MoveUV.bl_idname, icon='PAUSE',
+                             text="Stop")
diff --git a/uv_magic_uv/ui/view3d_uv_mapping.py b/uv_magic_uv/ui/view3d_uv_mapping.py
index 2dc241c0de0c0860e728fa984f4d8f7491b2ac12..c596008ebb8534c5429000e2eed9389953ad5925 100644
--- a/uv_magic_uv/ui/view3d_uv_mapping.py
+++ b/uv_magic_uv/ui/view3d_uv_mapping.py
@@ -20,23 +20,29 @@
 
 __author__ = "Nutti <nutti.metro@gmail.com>"
 __status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
 
 import bpy
 
-from ..op import texture_projection
-from ..op import unwrap_constraint
-from ..op import uvw
+from ..op import (
+    uvw,
+)
+from ..utils.bl_class_registry import BlClassRegistry
 
+__all__ = [
+    'MUV_PT_View3D_UVMapping',
+]
 
-class OBJECT_PT_MUV_UVMapping(bpy.types.Panel):
+
+@BlClassRegistry()
+class MUV_PT_View3D_UVMapping(bpy.types.Panel):
     """
     Panel class: UV Mapping on Property Panel on View3D
     """
 
     bl_space_type = 'VIEW_3D'
-    bl_region_type = 'TOOLS'
+    bl_region_type = 'UI'
     bl_label = "UV Mapping"
     bl_category = "Magic UV"
     bl_context = 'mesh_edit'
@@ -44,56 +50,19 @@ class OBJECT_PT_MUV_UVMapping(bpy.types.Panel):
 
     def draw_header(self, _):
         layout = self.layout
-        layout.label(text="", icon='IMAGE_COL')
+        layout.label(text="", icon='IMAGE')
 
     def draw(self, context):
         sc = context.scene
-        props = sc.muv_props
         layout = self.layout
 
-        box = layout.box()
-        box.prop(sc, "muv_unwrapconst_enabled", text="Unwrap Constraint")
-        if sc.muv_unwrapconst_enabled:
-            ops = box.operator(
-                unwrap_constraint.MUV_UnwrapConstraint.bl_idname,
-                text="Unwrap")
-            ops.u_const = sc.muv_unwrapconst_u_const
-            ops.v_const = sc.muv_unwrapconst_v_const
-            row = box.row(align=True)
-            row.prop(sc, "muv_unwrapconst_u_const", text="U-Constraint")
-            row.prop(sc, "muv_unwrapconst_v_const", text="V-Constraint")
-
-        box = layout.box()
-        box.prop(sc, "muv_texproj_enabled", text="Texture Projection")
-        if sc.muv_texproj_enabled:
-            row = box.row()
-            if not props.texproj.running:
-                row.operator(texture_projection.MUV_TexProjStart.bl_idname,
-                             text="Start", icon='PLAY')
-            else:
-                row.operator(texture_projection.MUV_TexProjStop.bl_idname,
-                             text="Stop", icon='PAUSE')
-            row.prop(sc, "muv_texproj_tex_image", text="")
-            box.prop(sc, "muv_texproj_tex_transparency", text="Transparency")
-            col = box.column(align=True)
-            row = col.row()
-            row.prop(sc, "muv_texproj_adjust_window", text="Adjust Window")
-            if not sc.muv_texproj_adjust_window:
-                row.prop(sc, "muv_texproj_tex_magnitude", text="Magnitude")
-            col.prop(sc, "muv_texproj_apply_tex_aspect",
-                     text="Texture Aspect Ratio")
-            col.prop(sc, "muv_texproj_assign_uvmap", text="Assign UVMap")
-            if props.texproj.running:
-                box.operator(texture_projection.MUV_TexProjProject.bl_idname,
-                             text="Project")
-
         box = layout.box()
         box.prop(sc, "muv_uvw_enabled", text="UVW")
         if sc.muv_uvw_enabled:
             row = box.row(align=True)
-            ops = row.operator(uvw.MUV_UVWBoxMap.bl_idname, text="Box")
+            ops = row.operator(uvw.MUV_OT_UVW_BoxMap.bl_idname, text="Box")
             ops.assign_uvmap = sc.muv_uvw_assign_uvmap
-            ops = row.operator(uvw.MUV_UVWBestPlanerMap.bl_idname,
+            ops = row.operator(uvw.MUV_OT_UVW_BestPlanerMap.bl_idname,
                                text="Best Planner")
             ops.assign_uvmap = sc.muv_uvw_assign_uvmap
             box.prop(sc, "muv_uvw_assign_uvmap", text="Assign UVMap")
diff --git a/uv_magic_uv/utils/__init__.py b/uv_magic_uv/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ce9d9079326be15d662765d96a9c88d1350beca
--- /dev/null
+++ b/uv_magic_uv/utils/__init__.py
@@ -0,0 +1,34 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+if "bpy" in locals():
+    import importlib
+    importlib.reload(bl_class_registry)
+    importlib.reload(property_class_registry)
+else:
+    from . import bl_class_registry
+    from . import property_class_registry
+
+import bpy
diff --git a/uv_magic_uv/utils/bl_class_registry.py b/uv_magic_uv/utils/bl_class_registry.py
new file mode 100644
index 0000000000000000000000000000000000000000..d173061566fd0369e68c57fec302d4f542325fe7
--- /dev/null
+++ b/uv_magic_uv/utils/bl_class_registry.py
@@ -0,0 +1,84 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+
+from .. import common
+
+__all__ = [
+    'BlClassRegistry',
+]
+
+
+class BlClassRegistry:
+    class_list = []
+
+    def __init__(self, *_, **kwargs):
+        self.legacy = kwargs.get('legacy', False)
+
+    def __call__(self, cls):
+        if hasattr(cls, "bl_idname"):
+            BlClassRegistry.add_class(cls.bl_idname, cls, self.legacy)
+        else:
+            bl_idname = "{}{}{}{}".format(cls.bl_space_type,
+                                          cls.bl_region_type,
+                                          cls.bl_context, cls.bl_label)
+            BlClassRegistry.add_class(bl_idname, cls, self.legacy)
+        return cls
+
+    @classmethod
+    def add_class(cls, bl_idname, op_class, legacy):
+        for class_ in cls.class_list:
+            if (class_["bl_idname"] == bl_idname) and \
+               (class_["legacy"] == legacy):
+                raise RuntimeError("{} is already registered"
+                                   .format(bl_idname))
+
+        new_op = {
+            "bl_idname": bl_idname,
+            "class": op_class,
+            "legacy": legacy,
+        }
+        cls.class_list.append(new_op)
+        common.debug_print("{} is registered.".format(bl_idname))
+
+    @classmethod
+    def register(cls):
+        for class_ in cls.class_list:
+            bpy.utils.register_class(class_["class"])
+            common.debug_print("{} is registered to Blender."
+                               .format(class_["bl_idname"]))
+
+    @classmethod
+    def unregister(cls):
+        for class_ in cls.class_list:
+            bpy.utils.unregister_class(class_["class"])
+            common.debug_print("{} is unregistered from Blender."
+                               .format(class_["bl_idname"]))
+
+    @classmethod
+    def cleanup(cls):
+        cls.class_list = []
+        common.debug_print("Cleanup registry.")
diff --git a/uv_magic_uv/utils/property_class_registry.py b/uv_magic_uv/utils/property_class_registry.py
new file mode 100644
index 0000000000000000000000000000000000000000..20df03422af86198fba12c33d3d493b0a9fabd66
--- /dev/null
+++ b/uv_magic_uv/utils/property_class_registry.py
@@ -0,0 +1,72 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+from .. import common
+
+__all__ = [
+    'PropertyClassRegistry',
+]
+
+
+class PropertyClassRegistry:
+    class_list = []
+
+    def __init__(self, *_, **kwargs):
+        self.legacy = kwargs.get('legacy', False)
+
+    def __call__(self, cls):
+        PropertyClassRegistry.add_class(cls.idname, cls, self.legacy)
+        return cls
+
+    @classmethod
+    def add_class(cls, idname, prop_class, legacy):
+        for class_ in cls.class_list:
+            if (class_["idname"] == idname) and (class_["legacy"] == legacy):
+                raise RuntimeError("{} is already registered".format(idname))
+
+        new_op = {
+            "idname": idname,
+            "class": prop_class,
+            "legacy": legacy,
+        }
+        cls.class_list.append(new_op)
+        common.debug_print("{} is registered.".format(idname))
+
+    @classmethod
+    def init_props(cls, scene):
+        for class_ in cls.class_list:
+            class_["class"].init_props(scene)
+            common.debug_print("{} is initialized.".format(class_["idname"]))
+
+    @classmethod
+    def del_props(cls, scene):
+        for class_ in cls.class_list:
+            class_["class"].del_props(scene)
+            common.debug_print("{} is cleared.".format(class_["idname"]))
+
+    @classmethod
+    def cleanup(cls):
+        cls.class_list = []
+        common.debug_print("Cleanup registry.")