Code clean-up: Use standard-conformal {'sets'} for .report and operator return values
[blender-addons-contrib.git] / space_view3d_enhanced_3d_cursor.py
index 68ffa244131be2a21092242444e9a38490e7c6e4..88b1cf063811cc9442ac092fce000f4a6eb2aae1 100644 (file)
@@ -21,8 +21,8 @@ bl_info = {
     "name": "Enhanced 3D Cursor",
     "description": "Cursor history and bookmarks; drag/snap cursor.",
     "author": "dairin0d",
-    "version": (2, 8, 1),
-    "blender": (2, 6, 0),
+    "version": (2, 8, 7),
+    "blender": (2, 6, 3),
     "location": "View3D > Action mouse; F10; Properties panel",
     "warning": "",
     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
@@ -33,12 +33,60 @@ bl_info = {
 #============================================================================#
 
 """
+ATTENTION:
+somewhere around 45447 revision object.ray_cast() starts conflicting with
+mesh.update(calc_tessface=True) -- at least when invoked within one
+operator cycle, object.ray_cast() crashes if object's tessfaces were
+update()d earlier in the code. However, not update()ing the meshes
+seems to work fine -- ray_cast() does its job, and it's possible to
+access tessfaces afterwards.
+mesh.calc_tessface() -- ? crashes too
+
+Seems like now axes are stored in columns instead of rows.
+Perhaps it's better to write utility functions to create/decompose
+matrices from/to 3D-vector axes and a translation component
+
+Breakdown:
+    Addon registration
+    Keymap utils
+    Various utils (e.g. find_region)
+    OpenGL; drawing utils
+    Non-undoable data storage
+    Cursor utils
+    Stick-object
+    Cursor monitor
+    Addon's GUI
+    Addon's properties
+    Addon's operators
+    ID Block emulator
+    Mesh cache
+    Snap utils
+    View3D utils
+    Transform orientation / coordinate system utils
+    Generic transform utils
+    Main operator
+    ...
+.
+
+First step is to re-make the cursor addon (make something usable first).
+CAD tools should be done without the hassle.
+
 TODO:
+    strip trailing space? (one of campbellbarton's commits did that)
+    
     IDEAS:
+        - implement 'GIMBAL' orientation (euler axes)
+        - mini-Z-buffer in the vicinity of mouse coords (using raycasts)
+        - an orientation that points towards cursor
+          (from current selection to cursor)
+        - user coordinate systems (using e.g. empties to store different
+          systems; when user switches to such UCS, origin will be set to
+          "cursor", cursor will be sticked to the empty, and a custom
+          transform orientation will be aligned with the empty)
+          - "Stick" transform orientation that is always aligned with the
+            object cursor is "sticked" to?
         - make 'NORMAL' system also work for bones?
-        - an option to select which normals/tangentials
-          are displayed? (only N, only T1, N+T1, all,
-          only meaningful)
+        - user preferences? (stored in a file)
         - create spline/edge_mesh from history?
         - API to access history/bookmarks/operators from other scripts?
         - Snap selection to bookmark?
@@ -72,13 +120,14 @@ Borrowed code/logic:
 import bpy
 import bgl
 import blf
+import bmesh
 
 from mathutils import Vector, Matrix, Quaternion, Euler
 
 from mathutils.geometry import (intersect_line_sphere,
                                 intersect_ray_tri,
                                 barycentric_transform,
-                                tesselate_polygon,
+                                tessellate_polygon,
                                 intersect_line_line,
                                 intersect_line_plane,
                                 )
@@ -439,13 +488,28 @@ class EnhancedSetCursor(bpy.types.Operator):
                 self.process_axis_input(event)
             
             if event.alt:
+                jc = (", " if tfm_opts.use_comma_separator else "\t")
                 if event.type in self.key_map["copy_axes"]:
-                    wm.clipboard = "\t".join(self.get_axes_text(True))
+                    wm.clipboard = jc.join(self.get_axes_text(True))
                 elif event.type in self.key_map["cut_axes"]:
-                    wm.clipboard = "\t".join(self.get_axes_text(True))
+                    wm.clipboard = jc.join(self.get_axes_text(True))
                     self.set_axes_text("\t\t\t")
                 elif event.type in self.key_map["paste_axes"]:
-                    self.set_axes_text(wm.clipboard, True)
+                    if jc == "\t":
+                        self.set_axes_text(wm.clipboard, True)
+                    else:
+                        jc = jc.strip()
+                        ttext = ""
+                        brackets = 0
+                        for c in wm.clipboard:
+                            if c in "[{(":
+                                brackets += 1
+                            elif c in "]})":
+                                brackets -= 1
+                            if (brackets == 0) and (c == jc):
+                                c = "\t"
+                            ttext += c
+                        self.set_axes_text(ttext, True)
             
             if event.type in self.key_coordsys_map:
                 new_orientation = self.key_coordsys_map[event.type]
@@ -888,7 +952,17 @@ class EnhancedSetCursor(bpy.types.Operator):
     def gizmo_scale(self, pos):
         return self.gizmo_distance(pos) * self.gizmo_factor
     
-    def draw_3d(self):
+    def check_v3d_local(self, context):
+        csu_v3d = self.csu.space_data
+        v3d = context.space_data
+        if csu_v3d.local_view:
+            return csu_v3d != v3d
+        return v3d.local_view
+    
+    def draw_3d(self, context):
+        if self.check_v3d_local(context):
+            return
+        
         if time.time() < (self.click_start + self.click_period):
             return
         
@@ -990,7 +1064,7 @@ class EnhancedSetCursor(bpy.types.Operator):
                 bgl.glColor4f(0, 1, 0, 0.5)
                 
                 co = sui.potential_snap_elements
-                tris = tesselate_polygon([co])
+                tris = tessellate_polygon([co])
                 bgl.glBegin(bgl.GL_TRIANGLES)
                 for tri in tris:
                     for vi in tri:
@@ -999,6 +1073,9 @@ class EnhancedSetCursor(bpy.types.Operator):
                 bgl.glEnd()
     
     def draw_2d(self, context):
+        if self.check_v3d_local(context):
+            return
+        
         r = context.region
         rv3d = context.region_data
         
@@ -1088,6 +1165,9 @@ class EnhancedSetCursor(bpy.types.Operator):
         bgl.glLineWidth(1)
     
     def draw_axes_coords(self, context, header_size):
+        if self.check_v3d_local(context):
+            return
+        
         if time.time() < (self.click_start + self.click_period):
             return
         
@@ -1127,7 +1207,7 @@ class EnhancedSetCursor(bpy.types.Operator):
             axes_text = self.get_axes_text()
             
             for i in range(3):
-                color = tet.text
+                color = tet.space.text
                 alpha = (1.0 if self.allowed_axes[i] else 0.5)
                 text = axis_prefix + axis_names[i] + " : "
                 axis_cells.append(TextCell(text, color, alpha))
@@ -1138,7 +1218,7 @@ class EnhancedSetCursor(bpy.types.Operator):
                     else:
                         color = tet.syntax_string
                 else:
-                    color = tet.text
+                    color = tet.space.text
                 text = axes_text[i]
                 coord_cells.append(TextCell(text, color))
         except Exception as e:
@@ -1149,7 +1229,7 @@ class EnhancedSetCursor(bpy.types.Operator):
         try:
             snap_type = self.su.implementation.snap_type
             if snap_type is None:
-                color = tet.text
+                color = tet.space.text
             elif (not self.use_object_centers) or \
                     (snap_type == 'INCREMENT'):
                 color = tet.syntax_numbers
@@ -1161,13 +1241,13 @@ class EnhancedSetCursor(bpy.types.Operator):
             mode_cells.append(TextCell(text, color))
             
             if self.csu.tou.is_custom:
-                color = tet.text
+                color = tet.space.text
             else:
                 color = tet.syntax_builtin
             text = self.csu.tou.get_title()
             mode_cells.append(TextCell(text, color))
             
-            color = tet.text
+            color = tet.space.text
             text = self.csu.get_pivot_name(raw=True)
             if self.use_object_centers:
                 color = tet.syntax_special
@@ -1186,7 +1266,7 @@ class EnhancedSetCursor(bpy.types.Operator):
             xyz_margin = 16
             blend_margin = 32
             
-            color = tet.back
+            color = tet.space.back
             bgl.glColor4f(color[0], color[1], color[2], 1.0)
             draw_rect(0, 0, hdr_w, hdr_h)
             
@@ -1251,7 +1331,7 @@ class EnhancedSetCursor(bpy.types.Operator):
             bgl.glEnable(bgl.GL_BLEND)
             bgl.glShadeModel(bgl.GL_SMOOTH)
             gl_enable(bgl.GL_SMOOTH, True)
-            color = tet.back
+            color = tet.space.back
             bgl.glBegin(bgl.GL_TRIANGLE_STRIP)
             bgl.glColor4f(color[0], color[1], color[2], 1.0)
             bgl.glVertex2i(0, 0)
@@ -1280,7 +1360,7 @@ class EnhancedSetCursor(bpy.types.Operator):
             bgl.glEnable(bgl.GL_BLEND)
             bgl.glShadeModel(bgl.GL_SMOOTH)
             gl_enable(bgl.GL_SMOOTH, True)
-            color = tet.back
+            color = tet.space.back
             bgl.glBegin(bgl.GL_TRIANGLE_STRIP)
             bgl.glColor4f(color[0], color[1], color[2], 1.0)
             bgl.glVertex2i(0, 0)
@@ -1339,14 +1419,13 @@ class EnhancedSetCursor(bpy.types.Operator):
                 self.get_normal_params(tfm_opts, dest_point)
             
             snapshot = bpy.data.objects.new("normal_snapshot", None)
+            
             if tangential:
-                #snapshot.matrix_world = Matrix(
-                #    (x.to_4d(), y.to_4d(), _z.to_4d(), p0.to_4d()))
-                snapshot.matrix_world = Matrix(
-                    (_z.to_4d(), y.to_4d(), x.to_4d(), p0.to_4d()))
+                m = MatrixCompose(_z, y, x, p0)
             else:
-                snapshot.matrix_world = Matrix(
-                    (_x.to_4d(), y.to_4d(), z.to_4d(), p0.to_4d()))
+                m = MatrixCompose(_x, y, z, p0)
+            snapshot.matrix_world = m
+            
             snapshot.empty_draw_type = 'SINGLE_ARROW'
             #snapshot.empty_draw_type = 'ARROWS'
             #snapshot.layers = [True] * 20 # ?
@@ -1493,33 +1572,43 @@ def gather_particles(**kwargs):
         # -> the monitor tries to update the CSU ->
         # -> object.mode_set seem to somehow conflict
         # with Undo/Redo mechanisms.
-        elif False and active_object and active_object.data and \
+        elif active_object and active_object.data and \
         (context_mode in {
         'EDIT_MESH', 'EDIT_METABALL',
         'EDIT_CURVE', 'EDIT_SURFACE',
         'EDIT_ARMATURE', 'POSE'}):
             
-            prev_mode = active_object.mode
-            
-            if context_mode not in {'EDIT_ARMATURE', 'POSE'}:
-                bpy.ops.object.mode_set(mode='OBJECT')
-            
             m = active_object.matrix_world
             
             positions = []
             normal = Vector((0, 0, 0))
             
             if context_mode == 'EDIT_MESH':
-                # We currently don't need to create particles
-                # for these; vertices are enough now.
-                #for face in active_object.data.faces:
-                #    pass
-                #for edge in active_object.data.edges:
-                #    pass
-                for vertex in active_object.data.vertices:
-                    if vertex.select:
-                        positions.append(vertex.co)
-                        normal += vertex.normal
+                bm = bmesh.from_edit_mesh(active_object.data)
+                
+                if bm.select_history:
+                    elem = bm.select_history[-1]
+                    if isinstance(elem, bmesh.types.BMVert):
+                        active_element = elem.co.copy()
+                    else:
+                        active_element = Vector()
+                        for v in elem.verts:
+                            active_element += v.co
+                        active_element *= 1.0 / len(elem.verts)
+                
+                for v in bm.verts:
+                    if v.select:
+                        positions.append(v.co)
+                        normal += v.normal
+                
+                # mimic Blender's behavior (as of now,
+                # order of selection is ignored)
+                if len(positions) == 2:
+                    normal = positions[1] - positions[0]
+                elif len(positions) == 3:
+                    a = positions[0] - positions[1]
+                    b = positions[2] - positions[1]
+                    normal = a.cross(b)
             elif context_mode == 'EDIT_METABALL':
                 active_elem = active_object.data.elements.active
                 if active_elem:
@@ -1551,7 +1640,8 @@ def gather_particles(**kwargs):
             elif context_mode == 'POSE':
                 active_bone = active_object.data.bones.active
                 if active_bone:
-                    active_element = active_bone.matrix_local.translation.to_3d()
+                    active_element = active_bone.\
+                        matrix_local.translation.to_3d()
                     active_element = active_object.\
                         matrix_world * active_element
                 
@@ -1615,7 +1705,7 @@ def gather_particles(**kwargs):
                 else:
                     t1 = Vector((0, 0, 1)).cross(normal)
                 t2 = t1.cross(normal)
-                normal_system = Matrix((t1, t2, normal))
+                normal_system = MatrixCompose(t1, t2, normal)
                 
                 median, bbox_center = calc_median_bbox_pivots(positions)
                 median = m * median
@@ -1637,12 +1727,9 @@ def gather_particles(**kwargs):
                 bbox_center = active_element
                 
                 normal_system = active_object.matrix_world.to_3x3()
-                normal_system[0].normalize()
-                normal_system[1].normalize()
-                normal_system[2].normalize()
-            
-            if context_mode not in {'EDIT_ARMATURE', 'POSE'}:
-                bpy.ops.object.mode_set(mode=prev_mode)
+                normal_system.col[0].normalize()
+                normal_system.col[1].normalize()
+                normal_system.col[2].normalize()
         else:
             # paint/sculpt, etc.?
             particle = View3D_Object(active_object)
@@ -1652,8 +1739,6 @@ def gather_particles(**kwargs):
                 active_element = active_object.\
                     matrix_world.to_translation()
         
-        # These are equivalent (though scene's is slower)
-        #cursor_pos = get_cursor_location(scene=scene)
         cursor_pos = get_cursor_location(v3d=space_data)
     
     #elif area_type == 'IMAGE_EDITOR':
@@ -1876,7 +1961,7 @@ class TransformOrientationUtility:
         name = self.transform_orientation
         return name[:1].upper() + name[1:].lower()
     
-    def set(self, name):
+    def set(self, name, set_v3d=True):
         if isinstance(name, int):
             n = len(self.custom_systems)
             if n == 0:
@@ -1905,7 +1990,8 @@ class TransformOrientationUtility:
         
         self.transform_orientation = name
         
-        self.v3d.transform_orientation = name
+        if set_v3d:
+            self.v3d.transform_orientation = name
     
     def get_matrix(self, name=None):
         active_obj = self.scene.objects.active
@@ -1959,6 +2045,11 @@ def create_transform_orientation(scene, name=None, matrix=None):
     tfm_orient = scene.orientations[-1]
     
     if name is not None:
+        basename = name
+        i = 1
+        while name in scene.orientations:
+            name = "%s.%03i" % (basename, i)
+            i += 1
         tfm_orient.name = name
     
     if matrix:
@@ -2087,18 +2178,19 @@ class View3DUtility:
         
         if rv3d.view_perspective == 'CAMERA':
             d = self.get_direction()
-            v3d.camera.matrix_world.translation[:3] = pos - d * rv3d.view_distance
+            v3d.camera.matrix_world.translation = pos - d * rv3d.view_distance
         else:
             if v3d.lock_object:
                 obj, bone = self._get_lock_obj_bone()
                 if bone:
                     try:
-                        bone.matrix.translation[:3] = obj.matrix_world.inverted() * pos
+                        bone.matrix.translation = \
+                            obj.matrix_world.inverted() * pos
                     except:
                         # this is some degenerate object
-                        bone.matrix.translation[:3] = pos
+                        bone.matrix.translation = pos
                 else:
-                    obj.matrix_world.translation[:3] = pos
+                    obj.matrix_world.translation = pos
             elif v3d.lock_cursor:
                 set_cursor_location(pos, v3d=v3d)
             else:
@@ -2134,7 +2226,7 @@ class View3DUtility:
     def get_matrix(self):
         m = self.get_rotation().to_matrix()
         m.resize_4x4()
-        m.translation[:3] = self.get_viewpoint()
+        m.translation = self.get_viewpoint()
         return m
     
     def get_point(self, xy, pos):
@@ -2265,11 +2357,12 @@ class SnapUtilityBase:
                 if not use_object_centers:
                     self.potential_snap_elements = [
                         (obj.matrix_world * obj.data.vertices[vi].co)
-                        for vi in obj.data.faces[face_id].vertices
+                        for vi in obj.data.tessfaces[face_id].vertices
                     ]
                 
                 if use_object_centers:
-                    self.extra_snap_points = [obj.matrix_world.to_translation()]
+                    self.extra_snap_points = \
+                        [obj.matrix_world.to_translation()]
                 elif alt_snap:
                     pse = self.potential_snap_elements
                     n = len(pse)
@@ -2332,13 +2425,16 @@ class SnapUtilityBase:
                     direction.rotate(sys_matrix)
                     
                     if axes_of_freedom == 2:
-                        # Constrained in one axis. Find intersection with plane.
+                        # Constrained in one axis.
+                        # Find intersection with plane.
                         i_p = intersect_line_plane(a, b, start, direction)
                         if i_p is not None:
                             pos = i_p
                     elif axes_of_freedom == 1:
-                        # Constrained in two axes. Find nearest point to line.
-                        i_p = intersect_line_line(a, b, start, start + direction)
+                        # Constrained in two axes.
+                        # Find nearest point to line.
+                        i_p = intersect_line_line(a, b, start,
+                                                  start + direction)
                         if i_p is not None:
                             pos = i_p[1]
         #end if do_raycast
@@ -2387,8 +2483,12 @@ class Snap3DUtility(SnapUtilityBase):
         self.bbox_cache = {}#collections.OrderedDict()
         self.sys_matrix_key = [0.0] * 9
         
-        vertex_coords, faces = prepare_gridbox_mesh(subdiv=2)
-        mesh = create_mesh(vertex_coords, faces)
+        bm = prepare_gridbox_mesh(subdiv=2)
+        mesh = bpy.data.meshes.new(tmp_name)
+        bm.to_mesh(mesh)
+        mesh.update(calc_tessface=True)
+        #mesh.calc_tessface()
+        
         self.bbox_obj = self.cache.create_temporary_mesh_obj(mesh, Matrix())
         self.bbox_obj.hide = True
         self.bbox_obj.draw_type = 'WIRE'
@@ -2450,7 +2550,7 @@ class Snap3DUtility(SnapUtilityBase):
                 sys_matrix = m.copy()
                 try:
                     sys_matrix_inv = sys_matrix.inverted()
-                except:
+                except Exception:
                     # this is some degenerate system
                     sys_matrix_inv = Matrix()
             m_combined = sys_matrix_inv * m
@@ -2477,13 +2577,10 @@ class Snap3DUtility(SnapUtilityBase):
         
         half = (bbox[1] - bbox[0]) * 0.5
         
-        sys_matrix3 = sys_matrix.to_3x3()
-        
-        m = Matrix()
-        m[0][:3] = sys_matrix3 * Vector((half[0], 0, 0))
-        m[1][:3] = sys_matrix3 * Vector((0, half[1], 0))
-        m[2][:3] = sys_matrix3 * Vector((0, 0, half[2]))
-        m.translation[:3] = sys_matrix * (bbox[0] + half)
+        m = MatrixCompose(half[0], half[1], half[2])
+        m = sys_matrix.to_3x3() * m
+        m.resize_4x4()
+        m.translation = sys_matrix * (bbox[0] + half)
         self.bbox_obj.matrix_world = m
         
         return self.bbox_obj
@@ -2644,17 +2741,18 @@ class Snap3DUtility(SnapUtilityBase):
         
         _ln = ln.copy()
         
-        face = obj.data.faces[face_id]
+        face = obj.data.tessfaces[face_id]
         L = None
         t1 = None
         
         if self.snap_type == 'VERTEX' or self.snap_type == 'VOLUME':
             for v0 in face.vertices:
-                p0 = obj.data.vertices[v0].co
+                v = obj.data.vertices[v0]
+                p0 = v.co
                 l = (lp - p0).length_squared
                 if (L is None) or (l < L):
                     p = p0
-                    ln = obj.data.vertices[v0].normal.copy()
+                    ln = v.normal.copy()
                     #t1 = ln.cross(_ln)
                     L = l
             
@@ -2723,7 +2821,7 @@ class Snap3DUtility(SnapUtilityBase):
                         L = l
             '''
         
-        n = ln#.copy()
+        n = ln.copy()
         n.rotate(m)
         n.normalize()
         
@@ -2742,16 +2840,12 @@ class Snap3DUtility(SnapUtilityBase):
         t2 = t1.cross(n)
         t2.normalize()
         
-        matrix = Matrix()
-        matrix[0][:3] = t1
-        matrix[1][:3] = t2
-        matrix[2][:3] = n
-        matrix.translation[:3] = p
+        matrix = MatrixCompose(t1, t2, n, p)
         
         return (matrix, face_id, obj, orig_obj)
     
     def interpolate_normal(self, obj, face_id, p, orig, ray):
-        face = obj.data.faces[face_id]
+        face = obj.data.tessfaces[face_id]
         
         use_smooth = face.use_smooth
         if self.interpolation == 'NEVER':
@@ -2778,7 +2872,7 @@ class Snap3DUtility(SnapUtilityBase):
             for vi in face.vertices]
         
         if len(face.vertices) != 3:
-            tris = tesselate_polygon([co])
+            tris = tessellate_polygon([co])
             for tri in tris:
                 i0, i1, i2 = tri
                 if intersect_ray_tri(co[i0], co[i1], co[i2], ray, orig):
@@ -2870,6 +2964,9 @@ class MeshCache:
                 if self.edit_object is None:
                     self.edit_object = self.__convert(
                                 obj, True, False, False)
+                    #self.edit_object.data.update(calc_tessface=True)
+                    #self.edit_object.data.calc_tessface()
+                    self.edit_object.data.calc_normals()
                 return self.edit_object
         
         # A usual object. Cached data will suffice.
@@ -2888,6 +2985,11 @@ class MeshCache:
             rco = None
         
         self.object_cache[obj] = rco
+        if rco:
+            #rco.data.update(calc_tessface=True)
+            #rco.data.calc_tessface()
+            rco.data.calc_normals()
+            pass
         
         return rco
     
@@ -3122,7 +3224,7 @@ class PseudoIDBlockBase(bpy.types.PropertyGroup):
 class TransformExtraOptionsProp(bpy.types.PropertyGroup):
     use_relative_coords = bpy.props.BoolProperty(
         name="Relative coordinates", 
-        description="Consider existing transformation as the strating point", 
+        description="Consider existing transformation as the starting point", 
         default=True)
     snap_interpolate_normals_mode = bpy.props.EnumProperty(
         items=[('NEVER', "Never", "Don't interpolate normals"),
@@ -3142,6 +3244,12 @@ class TransformExtraOptionsProp(bpy.types.PropertyGroup):
         default=8,
         min=2,
         max=64)
+    use_comma_separator = bpy.props.BoolProperty(
+        name="Use comma separator",
+        description="Use comma separator when copying/pasting"\
+                    "coordinate values (instead of Tab character)",
+        default=True,
+        options={'HIDDEN'})
 
 # ===== 3D VECTOR LOCATION ===== #
 class LocationProp(bpy.types.PropertyGroup):
@@ -3186,10 +3294,19 @@ def update_history_id(self, context):
     
     pos = history.get_pos()
     if pos is not None:
+        # History doesn't depend on view (?)
         cursor_pos = get_cursor_location(scene=scene)
+        
+        if CursorHistoryProp.update_cursor_on_id_change:
+            # Set cursor position anyway (we're changing v3d's
+            # cursor, which may be separate from scene's)
+            # This, however, should be done cautiously
+            # from scripts, since, e.g., CursorMonitor
+            # can supply wrong context -> cursor will be set
+            # in a different view than required
+            set_cursor_location(pos, v3d=context.space_data)
+        
         if pos != cursor_pos:
-            set_cursor_location(pos, scene=scene)
-            
             if (history.current_id == 0) and (history.last_id <= 1):
                 history.last_id = 1
             else:
@@ -3199,6 +3316,8 @@ def update_history_id(self, context):
 class CursorHistoryProp(bpy.types.PropertyGroup):
     max_size_limit = 500
     
+    update_cursor_on_id_change = True
+    
     show_trace = bpy.props.BoolProperty(
         name="Trace",
         description="Show history trace",
@@ -3263,7 +3382,7 @@ class CursorHistoryProp(bpy.types.PropertyGroup):
         bgl.glColor4f(1.0, 0.75, 0.5, 1.0)
         bgl.glVertex3f(p[0], p[1], p[2])
         
-        p = get_cursor_location(scene=scene)
+        p = get_cursor_location(v3d=context.space_data)
         bgl.glColor4f(1.0, 1.0, 0.25, 1.0)
         bgl.glVertex3f(p[0], p[1], p[2])
         
@@ -3314,12 +3433,13 @@ class NewCursor3DBookmark(bpy.types.Operator):
         
         bookmark = library.bookmarks.add(name=self.name)
         
-        cusor_pos = get_cursor_location(scene=context.scene)
+        cusor_pos = get_cursor_location(v3d=context.space_data)
         
         try:
-            bookmark.pos = library.convert_from_abs(cusor_pos, True)
+            bookmark.pos = library.convert_from_abs(context.space_data,
+                                                    cusor_pos, True)
         except Exception as exc:
-            self.report('ERROR_INVALID_CONTEXT', exc.args[0])
+            self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
             return {'CANCELLED'}
         
         return {'FINISHED'}
@@ -3361,12 +3481,13 @@ class OverwriteCursor3DBookmark(bpy.types.Operator):
         if not bookmark:
             return {'CANCELLED'}
         
-        cusor_pos = get_cursor_location(scene=context.scene)
+        cusor_pos = get_cursor_location(v3d=context.space_data)
         
         try:
-            bookmark.pos = library.convert_from_abs(cusor_pos, True)
+            bookmark.pos = library.convert_from_abs(context.space_data,
+                                                    cusor_pos, True)
         except Exception as exc:
-            self.report('ERROR_INVALID_CONTEXT', exc.args[0])
+            self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
             return {'CANCELLED'}
         
         CursorDynamicSettings.recalc_csu(context, 'PRESS')
@@ -3393,10 +3514,11 @@ class RecallCursor3DBookmark(bpy.types.Operator):
             return {'CANCELLED'}
         
         try:
-            bookmark_pos = library.convert_to_abs(bookmark.pos, True)
-            set_cursor_location(bookmark_pos, scene=context.scene)
+            bookmark_pos = library.convert_to_abs(context.space_data,
+                                                  bookmark.pos, True)
+            set_cursor_location(bookmark_pos, v3d=context.space_data)
         except Exception as exc:
-            self.report('ERROR_INVALID_CONTEXT', exc.args[0])
+            self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
             return {'CANCELLED'}
         
         CursorDynamicSettings.recalc_csu(context)
@@ -3422,17 +3544,19 @@ class SwapCursor3DBookmark(bpy.types.Operator):
         if not bookmark:
             return {'CANCELLED'}
         
-        cusor_pos = get_cursor_location(scene=context.scene)
+        cusor_pos = get_cursor_location(v3d=context.space_data)
         
         try:
-            bookmark_pos = library.convert_to_abs(bookmark.pos, True)
+            bookmark_pos = library.convert_to_abs(context.space_data,
+                                                  bookmark.pos, True)
             
-            set_cursor_location(bookmark_pos, scene=context.scene)
+            set_cursor_location(bookmark_pos, v3d=context.space_data)
             
-            bookmark.pos = library.convert_from_abs(cusor_pos, True,
+            bookmark.pos = library.convert_from_abs(context.space_data,
+                                                    cusor_pos, True,
                 use_history=False)
         except Exception as exc:
-            self.report('ERROR_INVALID_CONTEXT', exc.args[0])
+            self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
             return {'CANCELLED'}
         
         CursorDynamicSettings.recalc_csu(context)
@@ -3466,17 +3590,16 @@ class AddEmptyAtCursor3DBookmark(bpy.types.Operator):
             return {'CANCELLED'}
         
         try:
-            #bookmark_pos = library.convert_to_abs(bookmark.pos, True)
-            matrix = library.get_matrix(use_history=False, warn=True)
+            matrix = library.get_matrix(use_history=False,
+                                        v3d=context.space_data, warn=True)
             bookmark_pos = matrix * bookmark.pos
         except Exception as exc:
-            self.report('ERROR_INVALID_CONTEXT', exc.args[0])
+            self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
             return {'CANCELLED'}
         
         name = "{}.{}".format(library.name, bookmark.name)
         obj = bpy.data.objects.new(name, None)
         obj.matrix_world = to_matrix4x4(matrix, bookmark_pos)
-        #obj.empty_draw_type = 'SPHERE'
         context.scene.objects.link(obj)
         
         """
@@ -3529,7 +3652,7 @@ class BookmarkLibraryProp(bpy.types.PropertyGroup):
         options={'HIDDEN'})
     
     # Returned None means "operation is not aplicable"
-    def get_matrix(self, use_history, warn=True, **kwargs):
+    def get_matrix(self, use_history, v3d, warn=True, **kwargs):
         #particles, csu = gather_particles(**kwargs)
         
         # Ensure we have relevant CSU (Blender will crash
@@ -3541,7 +3664,7 @@ class BookmarkLibraryProp(bpy.types.PropertyGroup):
         if self.offset:
             # history? or keep separate for each scene?
             if not use_history:
-                csu.source_pos = get_cursor_location(scene=csu.tou.scene)
+                csu.source_pos = get_cursor_location(v3d=v3d)
             else:
                 settings = find_settings()
                 history = settings.history
@@ -3585,19 +3708,16 @@ class BookmarkLibraryProp(bpy.types.PropertyGroup):
         
         return csu.get_matrix(sys_name, self.offset, pivot)
     
-    def convert_to_abs(self, pos, warn=False, **kwargs):
-        if "use_history" in kwargs:
-            del kwargs["use_history"]
-        matrix = self.get_matrix(False, warn, **kwargs)
+    def convert_to_abs(self, v3d, pos, warn=False, **kwargs):
+        kwargs.pop("use_history", None)
+        matrix = self.get_matrix(False, v3d, warn, **kwargs)
         if not matrix:
             return None
         return matrix * pos
     
-    def convert_from_abs(self, pos, warn=False, **kwargs):
-        use_history = kwargs.get("use_history", True)
-        if "use_history" in kwargs:
-            del kwargs["use_history"]
-        matrix = self.get_matrix(use_history, warn, **kwargs)
+    def convert_from_abs(self, v3d, pos, warn=False, **kwargs):
+        use_history = kwargs.pop("use_history", True)
+        matrix = self.get_matrix(use_history, v3d, warn, **kwargs)
         if not matrix:
             return None
         
@@ -3615,7 +3735,7 @@ class BookmarkLibraryProp(bpy.types.PropertyGroup):
         if not bookmark:
             return
         
-        pos = self.convert_to_abs(bookmark.pos)
+        pos = self.convert_to_abs(context.space_data, bookmark.pos)
         if pos is None:
             return
         
@@ -3824,6 +3944,7 @@ class TransformExtraOptions(bpy.types.Panel):
         layout.prop(tfm_opts, "use_relative_coords")
         layout.prop(tfm_opts, "snap_only_to_solid")
         layout.prop(tfm_opts, "snap_interpolate_normals_mode", text="")
+        layout.prop(tfm_opts, "use_comma_separator")
         #layout.prop(tfm_opts, "snap_element_screen_size")
 
 class Cursor3DTools(bpy.types.Panel):
@@ -3941,14 +4062,14 @@ class SetCursorDialog(bpy.types.Operator):
         self.matrix = self.csu.get_matrix()
         
         pos = self.matrix * self.pos
-        set_cursor_location(pos, scene=context.scene)
+        set_cursor_location(pos, v3d=context.space_data)
         
         return {'FINISHED'}
 
     def invoke(self, context, event):
         scene = context.scene
         
-        cursor_pos = get_cursor_location(scene=scene)
+        cursor_pos = get_cursor_location(v3d=context.space_data)
         
         particles, self.csu = gather_particles(context=context)
         self.csu.source_pos = cursor_pos
@@ -3979,6 +4100,183 @@ class SetCursorDialog(bpy.types.Operator):
         row.prop(tfm_opts, "use_relative_coords", text="Relative")
         row.prop(v3d, "transform_orientation", text="")
 
+class AlignOrientationProperties(bpy.types.PropertyGroup):
+    axes_items = [
+        ('X', 'X', 'X axis'),
+        ('Y', 'Y', 'Y axis'),
+        ('Z', 'Z', 'Z axis'),
+        ('-X', '-X', '-X axis'),
+        ('-Y', '-Y', '-Y axis'),
+        ('-Z', '-Z', '-Z axis'),
+    ]
+    
+    axes_items_ = [
+        ('X', 'X', 'X axis'),
+        ('Y', 'Y', 'Y axis'),
+        ('Z', 'Z', 'Z axis'),
+        (' ', ' ', 'Same as source axis'),
+    ]
+    
+    def get_orients(self, context):
+        orients = []
+        orients.append(('GLOBAL', "Global", ""))
+        orients.append(('LOCAL', "Local", ""))
+        orients.append(('GIMBAL', "Gimbal", ""))
+        orients.append(('NORMAL', "Normal", ""))
+        orients.append(('VIEW', "View", ""))
+        
+        for orientation in context.scene.orientations:
+            name = orientation.name
+            orients.append((name, name, ""))
+        
+        return orients
+    
+    src_axis = bpy.props.EnumProperty(default='Z', items=axes_items,
+                                      name="Initial axis")
+    #src_orient = bpy.props.EnumProperty(default='GLOBAL', items=get_orients)
+    
+    dest_axis = bpy.props.EnumProperty(default=' ', items=axes_items_,
+                                       name="Final axis")
+    dest_orient = bpy.props.EnumProperty(items=get_orients,
+                                         name="Final orientation")
+
+class AlignOrientation(bpy.types.Operator):
+    bl_idname = "view3d.align_orientation"
+    bl_label = "Align Orientation"
+    bl_description = "Rotates active object to match axis of current "\
+        "orientation to axis of another orientation"
+    bl_options = {'REGISTER', 'UNDO'}
+    
+    axes_items = [
+        ('X', 'X', 'X axis'),
+        ('Y', 'Y', 'Y axis'),
+        ('Z', 'Z', 'Z axis'),
+        ('-X', '-X', '-X axis'),
+        ('-Y', '-Y', '-Y axis'),
+        ('-Z', '-Z', '-Z axis'),
+    ]
+    
+    axes_items_ = [
+        ('X', 'X', 'X axis'),
+        ('Y', 'Y', 'Y axis'),
+        ('Z', 'Z', 'Z axis'),
+        (' ', ' ', 'Same as source axis'),
+    ]
+    
+    axes_ids = {'X':0, 'Y':1, 'Z':2}
+    
+    def get_orients(self, context):
+        orients = []
+        orients.append(('GLOBAL', "Global", ""))
+        orients.append(('LOCAL', "Local", ""))
+        orients.append(('GIMBAL', "Gimbal", ""))
+        orients.append(('NORMAL', "Normal", ""))
+        orients.append(('VIEW', "View", ""))
+        
+        for orientation in context.scene.orientations:
+            name = orientation.name
+            orients.append((name, name, ""))
+        
+        return orients
+    
+    src_axis = bpy.props.EnumProperty(default='Z', items=axes_items,
+                                      name="Initial axis")
+    #src_orient = bpy.props.EnumProperty(default='GLOBAL', items=get_orients)
+    
+    dest_axis = bpy.props.EnumProperty(default=' ', items=axes_items_,
+                                       name="Final axis")
+    dest_orient = bpy.props.EnumProperty(items=get_orients,
+                                         name="Final orientation")
+    
+    @classmethod
+    def poll(cls, context):
+        return (context.area.type == 'VIEW_3D') and context.object
+    
+    def execute(self, context):
+        wm = context.window_manager
+        obj = context.object
+        scene = context.scene
+        v3d = context.space_data
+        rv3d = context.region_data
+        
+        particles, csu = gather_particles(context=context)
+        tou = csu.tou
+        #tou = TransformOrientationUtility(scene, v3d, rv3d)
+        
+        aop = wm.align_orientation_properties # self
+        
+        src_matrix = tou.get_matrix()
+        src_axes = MatrixDecompose(src_matrix)
+        src_axis_name = aop.src_axis
+        if src_axis_name.startswith("-"):
+            src_axis_name = src_axis_name[1:]
+            src_axis = -src_axes[self.axes_ids[src_axis_name]]
+        else:
+            src_axis = src_axes[self.axes_ids[src_axis_name]]
+        
+        tou.set(aop.dest_orient, False)
+        dest_matrix = tou.get_matrix()
+        dest_axes = MatrixDecompose(dest_matrix)
+        if self.dest_axis != ' ':
+            dest_axis_name = aop.dest_axis
+        else:
+            dest_axis_name = src_axis_name
+        dest_axis = dest_axes[self.axes_ids[dest_axis_name]]
+        
+        q = src_axis.rotation_difference(dest_axis)
+        
+        m = obj.matrix_world.to_3x3()
+        m.rotate(q)
+        m.resize_4x4()
+        m.translation = obj.matrix_world.translation.copy()
+        
+        obj.matrix_world = m
+        
+        #bpy.ops.ed.undo_push(message="Align Orientation")
+        
+        return {'FINISHED'}
+    
+    # ATTENTION!
+    # This _must_ be a dialog, because with 'UNDO' option
+    # the last selected orientation may revert to the previous state
+    def invoke(self, context, event):
+        wm = context.window_manager
+        return wm.invoke_props_dialog(self, width=200)
+    
+    def draw(self, context):
+        layout = self.layout
+        wm = context.window_manager
+        aop = wm.align_orientation_properties # self
+        layout.prop(aop, "src_axis")
+        layout.prop(aop, "dest_axis")
+        layout.prop(aop, "dest_orient")
+
+class CopyOrientation(bpy.types.Operator):
+    bl_idname = "view3d.copy_orientation"
+    bl_label = "Copy Orientation"
+    bl_description = "Makes a copy of current orientation"
+    
+    def execute(self, context):
+        scene = context.scene
+        v3d = context.space_data
+        rv3d = context.region_data
+        
+        particles, csu = gather_particles(context=context)
+        tou = csu.tou
+        #tou = TransformOrientationUtility(scene, v3d, rv3d)
+        
+        orient = create_transform_orientation(scene,
+            name=tou.get()+".copy", matrix=tou.get_matrix())
+        
+        tou.set(orient.name)
+        
+        return {'FINISHED'}
+
+def transform_orientations_panel_extension(self, context):
+    row = self.layout.row()
+    row.operator("view3d.align_orientation", text="Align")
+    row.operator("view3d.copy_orientation", text="Copy")
+
 # ===== CURSOR MONITOR ===== #
 class CursorMonitor(bpy.types.Operator):
     '''Monitor changes in cursor location and write to history'''
@@ -4029,8 +4327,8 @@ class CursorMonitor(bpy.types.Operator):
             # (OR addon was disabled)
             return self.cancel(context)
         
-        # Somewhy after addod re-registration this permanently
-        # becomes False
+        # Somewhy after addon re-registration
+        # this permanently becomes False
         CursorMonitor.is_running = True
         
         if self.update_storage(runtime_settings):
@@ -4057,11 +4355,15 @@ class CursorMonitor(bpy.types.Operator):
             self.just_initialized = False
         # ================== #
         
+        # Seems like recalc_csu() in this place causes trouble
+        # if space type is switched from 3D to e.g. UV
+        '''
         tfm_operator = CursorDynamicSettings.active_transform_operator
         if tfm_operator:
             CursorDynamicSettings.csu = tfm_operator.csu
         else:
             CursorDynamicSettings.recalc_csu(context, event.value)
+        '''
         
         return {'PASS_THROUGH'}
     
@@ -4075,6 +4377,7 @@ class CursorMonitor(bpy.types.Operator):
         last_locations = {}
         
         for scene in bpy.data.scenes:
+            # History doesn't depend on view (?)
             curr_pos = get_cursor_location(scene=scene)
             
             last_locations[scene.name] = curr_pos
@@ -4131,7 +4434,11 @@ class CursorMonitor(bpy.types.Operator):
                 history.entries.remove(last_id)
             
             # make sure the most recent history entry is displayed
+            
+            CursorHistoryProp.update_cursor_on_id_change = False
             history.current_id = 0
+            CursorHistoryProp.update_cursor_on_id_change = True
+            
             history.curr_id = history.current_id
             history.last_id = 1
             
@@ -4171,6 +4478,7 @@ class CursorMonitor(bpy.types.Operator):
         #self._timer = context.window_manager. \
         #    event_timer_add(0.1, context.window)
         
+        #'''
         #self._draw_callback_view = context.region.callback_add( \
         #    draw_callback_view, (self, context), 'POST_VIEW')
         self._draw_callback_view = find_region(context.area).\
@@ -4183,6 +4491,7 @@ class CursorMonitor(bpy.types.Operator):
         self._draw_header_px = find_region(context.area, 'HEADER').\
             callback_add(draw_callback_header_px, \
             (self, context), 'POST_PIXEL')
+        #'''
         
         # Here we cannot return 'PASS_THROUGH',
         # or Blender will crash!
@@ -4193,12 +4502,14 @@ class CursorMonitor(bpy.types.Operator):
         #type(self).is_running = False
         
         # Unregister callbacks...
+        #'''
         #context.region.callback_remove(self._draw_callback_view)
         find_region(context.area).callback_remove(self._draw_callback_view)
         find_region(context.area).callback_remove(self._draw_callback_px)
         
         find_region(context.area, 'HEADER').\
             callback_remove(self._draw_header_px)
+        #'''
         
         return {'CANCELLED'}
 
@@ -4211,6 +4522,58 @@ def to_matrix4x4(orient, pos):
     m.translation = pos.to_3d()
     return m
 
+def MatrixCompose(*args):
+    size = len(args)
+    m = Matrix.Identity(size)
+    axes = m.col # m.row
+    
+    if size == 2:
+        for i in (0, 1):
+            c = args[i]
+            if isinstance(c, Vector):
+                axes[i] = c.to_2d()
+            elif hasattr(c, "__iter__"):
+                axes[i] = Vector(c).to_2d()
+            else:
+                axes[i][i] = c
+    else:
+        for i in (0, 1, 2):
+            c = args[i]
+            if isinstance(c, Vector):
+                axes[i][:3] = c.to_3d()
+            elif hasattr(c, "__iter__"):
+                axes[i][:3] = Vector(c).to_3d()
+            else:
+                axes[i][i] = c
+        
+        if size == 4:
+            c = args[3]
+            if isinstance(c, Vector):
+                m.translation = c.to_3d()
+            elif hasattr(c, "__iter__"):
+                m.translation = Vector(c).to_3d()
+    
+    return m
+
+def MatrixDecompose(m, res_size=None):
+    size = len(m)
+    axes = m.col # m.row
+    if res_size is None:
+        res_size = size
+    
+    if res_size == 2:
+        return (axes[0].to_2d(), axes[1].to_2d())
+    else:
+        x = axes[0].to_3d()
+        y = axes[1].to_3d()
+        z = (axes[2].to_3d() if size > 2 else Vector())
+        if res_size == 3:
+            return (x, y, z)
+        
+        t = (m.translation.to_3d() if size == 4 else Vector())
+        if res_size == 4:
+            return (x, y, z, t)
+
 def angle_axis_to_quat(angle, axis):
     w = math.cos(angle / 2.0)
     xyz = axis.normalized() * math.sin(angle / 2.0)
@@ -4228,7 +4591,8 @@ def clamp_angle(ang):
     ang = (ang % twoPi)
     return ((ang - twoPi) if (ang > math.pi) else ang)
 
-def prepare_grid_mesh(nx=1, ny=1, sx=1.0, sy=1.0, z=0.0, xyz_indices=(0,1,2)):
+def prepare_grid_mesh(bm, nx=1, ny=1, sx=1.0, sy=1.0,
+                      z=0.0, xyz_indices=(0,1,2)):
     vertices = []
     for i in range(nx + 1):
         x = 2 * (i / nx) - 1
@@ -4237,24 +4601,26 @@ def prepare_grid_mesh(nx=1, ny=1, sx=1.0, sy=1.0, z=0.0, xyz_indices=(0,1,2)):
             y = 2 * (j / ny) - 1
             y *= sy
             pos = (x, y, z)
-            vertices.append(pos[xyz_indices[0]])
-            vertices.append(pos[xyz_indices[1]])
-            vertices.append(pos[xyz_indices[2]])
+            vert = bm.verts.new((pos[xyz_indices[0]],
+                                 pos[xyz_indices[1]],
+                                 pos[xyz_indices[2]]))
+            vertices.append(vert)
     
-    faces = []
     nxmax = nx + 1
     for i in range(nx):
         i1 = i + 1
         for j in range(ny):
             j1 = j + 1
-            faces.append(j + i * nxmax)
-            faces.append(j1 + i * nxmax)
-            faces.append(j1 + i1 * nxmax)
-            faces.append(j + i1 * nxmax)
-    
-    return vertices, faces
+            verts = [vertices[j + i * nxmax],
+                     vertices[j1 + i * nxmax],
+                     vertices[j1 + i1 * nxmax],
+                     vertices[j + i1 * nxmax]]
+            bm.faces.new(verts)
+    #return
 
 def prepare_gridbox_mesh(subdiv=1):
+    bm = bmesh.new()
+    
     sides = [
         (-1, (0,1,2)), # -Z
         (1, (1,0,2)), # +Z
@@ -4264,27 +4630,11 @@ def prepare_gridbox_mesh(subdiv=1):
         (1, (2,1,0)), # +X
         ]
     
-    vertices = []
-    faces = []
-    
     for side in sides:
-        vs, fs = prepare_grid_mesh(nx=subdiv, ny=subdiv, z=side[0],
-            xyz_indices=side[1])
-        
-        n = len(vertices) // 3
-        vertices.extend(vs)
-        faces.extend(((f + n) for f in fs))
+        prepare_grid_mesh(bm, nx=subdiv, ny=subdiv,
+            z=side[0], xyz_indices=side[1])
     
-    return vertices, faces
-
-def create_mesh(vertex_coords, faces):
-    mesh = bpy.data.meshes.new(tmp_name)
-    mesh.vertices.add(len(vertex_coords) // 3)
-    mesh.faces.add(len(faces) // 4)
-    mesh.vertices.foreach_set("co", vertex_coords)
-    mesh.faces.foreach_set("vertices_raw", faces)
-    mesh.update()
-    return mesh
+    return bm
 
 # ===== DRAWING UTILITIES ===== #
 class GfxCell:
@@ -4579,7 +4929,11 @@ def draw_callback_view(self, context):
         # It's nice to have bookmark position update interactively
         # However, this still can be slow if there are many
         # selected objects
-        CursorDynamicSettings.recalc_csu(context, 'PRESS')
+        
+        # ATTENTION!!!
+        # This eats a lot of processor time!
+        #CursorDynamicSettings.recalc_csu(context, 'PRESS')
+        pass
     
     history = settings.history
     
@@ -4611,7 +4965,7 @@ def draw_callback_view(self, context):
         bgl.glDepthRange(0.0, 1.0)
     
     if tfm_operator:
-        tfm_operator.draw_3d()
+        tfm_operator.draw_3d(context)
     
     if is_drawing:
         # Restore previous OpenGL settings
@@ -4802,21 +5156,34 @@ def update_keymap(activate):
             for kmi in items:
                 km.keymap_items.remove(kmi)
     
-    items = find_keymap_items(km, 'view3d.cursor3d')
-    for kmi in items:
+    for kmi in find_keymap_items(km, 'view3d.cursor3d'):
         kmi.active = not activate
     
-    km = wm.keyconfigs.active.keymaps['3D View']
-    items = find_keymap_items(km, 'view3d.cursor3d')
-    for kmi in items:
-        kmi.active = not activate
+    try:
+        km = wm.keyconfigs.active.keymaps['3D View']
+        for kmi in find_keymap_items(km, 'view3d.cursor3d'):
+            kmi.active = not activate
+    except KeyError:
+        # seems like in recent builds (after 2.63a)
+        # 'bpy_prop_collection[key]: key "3D View" not found'
+        pass
     
-    km = wm.keyconfigs.default.keymaps['3D View']
-    items = find_keymap_items(km, 'view3d.cursor3d')
-    for kmi in items:
-        kmi.active = not activate
+    try:
+        km = wm.keyconfigs.default.keymaps['3D View']
+        for kmi in find_keymap_items(km, 'view3d.cursor3d'):
+            kmi.active = not activate
+    except KeyError:
+        pass
 
 def register():
+    bpy.utils.register_class(AlignOrientationProperties)
+    bpy.utils.register_class(AlignOrientation)
+    bpy.utils.register_class(CopyOrientation)
+    bpy.types.WindowManager.align_orientation_properties = \
+        bpy.props.PointerProperty(type=AlignOrientationProperties)
+    bpy.types.VIEW3D_PT_transform_orientations.append(
+        transform_orientations_panel_extension)
+    
     bpy.utils.register_class(SetCursorDialog)
     
     bpy.utils.register_class(NewCursor3DBookmarkLibrary)
@@ -4922,6 +5289,13 @@ def unregister():
     bpy.utils.unregister_class(AddEmptyAtCursor3DBookmark)
     
     bpy.utils.unregister_class(SetCursorDialog)
+    
+    bpy.types.VIEW3D_PT_transform_orientations.remove(
+        transform_orientations_panel_extension)
+    del bpy.types.WindowManager.align_orientation_properties
+    bpy.utils.unregister_class(CopyOrientation)
+    bpy.utils.unregister_class(AlignOrientation)
+    bpy.utils.unregister_class(AlignOrientationProperties)
 
 class DelayRegistrationOperator(bpy.types.Operator):
     bl_idname = "wm.enhanced_3d_cursor_registration"