Code clean-up: Use standard-conformal {'sets'} for .report and operator return values
[blender-addons-contrib.git] / space_view3d_enhanced_3d_cursor.py
index 31dc5955f98b391dfc8122cc786d21cba37fda6b..88b1cf063811cc9442ac092fce000f4a6eb2aae1 100644 (file)
@@ -21,7 +21,7 @@ bl_info = {
     "name": "Enhanced 3D Cursor",
     "description": "Cursor history and bookmarks; drag/snap cursor.",
     "author": "dairin0d",
-    "version": (2, 8, 5),
+    "version": (2, 8, 7),
     "blender": (2, 6, 3),
     "location": "View3D > Action mouse; F10; Properties panel",
     "warning": "",
@@ -40,8 +40,11 @@ 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.
-Seems like mesh.calc_tessface() is the proper way now (not reflected
-in the docs, though)
+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
@@ -485,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]
@@ -1402,14 +1420,10 @@ class EnhancedSetCursor(bpy.types.Operator):
             
             snapshot = bpy.data.objects.new("normal_snapshot", None)
             
-            m = Matrix()
             if tangential:
-                #m = Matrix((x, y, _z))
-                m = Matrix((_z, y, x))
+                m = MatrixCompose(_z, y, x, p0)
             else:
-                m = Matrix((_x, y, z))
-            m.resize_4x4()
-            m.translation[:3] = p0
+                m = MatrixCompose(_x, y, z, p0)
             snapshot.matrix_world = m
             
             snapshot.empty_draw_type = 'SINGLE_ARROW'
@@ -1558,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.polygons:
-                #    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:
@@ -1616,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
                 
@@ -1680,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
@@ -1702,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)
@@ -1939,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:
@@ -1968,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
@@ -2022,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:
@@ -2150,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:
@@ -2197,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):
@@ -2332,7 +2361,8 @@ class SnapUtilityBase:
                     ]
                 
                 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)
@@ -2395,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
@@ -2454,6 +2487,7 @@ class Snap3DUtility(SnapUtilityBase):
         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
@@ -2543,10 +2577,10 @@ class Snap3DUtility(SnapUtilityBase):
         
         half = (bbox[1] - bbox[0]) * 0.5
         
-        m = Matrix(((half[0], 0, 0), (0, half[1], 0), (0, 0, half[2])))
+        m = MatrixCompose(half[0], half[1], half[2])
         m = sys_matrix.to_3x3() * m
         m.resize_4x4()
-        m.translation[:3] = sys_matrix * (bbox[0] + half)
+        m.translation = sys_matrix * (bbox[0] + half)
         self.bbox_obj.matrix_world = m
         
         return self.bbox_obj
@@ -2713,11 +2747,12 @@ class Snap3DUtility(SnapUtilityBase):
         
         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
             
@@ -2786,7 +2821,7 @@ class Snap3DUtility(SnapUtilityBase):
                         L = l
             '''
         
-        n = ln#.copy()
+        n = ln.copy()
         n.rotate(m)
         n.normalize()
         
@@ -2805,11 +2840,7 @@ 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)
     
@@ -2934,6 +2965,8 @@ class MeshCache:
                     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.
@@ -2953,7 +2986,10 @@ class MeshCache:
         
         self.object_cache[obj] = rco
         if rco:
-            pass#rco.data.update(calc_tessface=True)
+            #rco.data.update(calc_tessface=True)
+            #rco.data.calc_tessface()
+            rco.data.calc_normals()
+            pass
         
         return rco
     
@@ -3188,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"),
@@ -3208,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):
@@ -3397,7 +3439,7 @@ class NewCursor3DBookmark(bpy.types.Operator):
             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'}
@@ -3445,7 +3487,7 @@ class OverwriteCursor3DBookmark(bpy.types.Operator):
             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')
@@ -3476,7 +3518,7 @@ class RecallCursor3DBookmark(bpy.types.Operator):
                                                   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)
@@ -3514,7 +3556,7 @@ class SwapCursor3DBookmark(bpy.types.Operator):
                                                     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)
@@ -3552,7 +3594,7 @@ class AddEmptyAtCursor3DBookmark(bpy.types.Operator):
                                         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)
@@ -3902,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):
@@ -4057,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'''
@@ -4302,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)
@@ -4884,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)
@@ -5004,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"