Fixed issues related to internal matrix representation.
authorDima Glib <dima.glib@gmail.com>
Thu, 19 Jan 2012 18:34:43 +0000 (18:34 +0000)
committerDima Glib <dima.glib@gmail.com>
Thu, 19 Jan 2012 18:34:43 +0000 (18:34 +0000)
space_view3d_enhanced_3d_cursor.py

index 7567f7298414438362d9b570c15c64ca050491cc..51ce0f9fe6da86b72f6d3522a8df103da46f616f 100644 (file)
@@ -21,8 +21,9 @@ bl_info = {
     "name": "Enhanced 3D Cursor",
     "description": "Cursor history and bookmarks; drag/snap cursor.",
     "author": "dairin0d",
-    "version": (2, 8, 0),
+    "version": (2, 8, 1),
     "blender": (2, 6, 0),
+    "api": 35853, # just copied from some Blender 2.59 script # 31236 ?
     "location": "View3D > Action mouse; F10; Properties panel",
     "warning": "",
     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
@@ -100,7 +101,7 @@ class EnhancedSetCursor(bpy.types.Operator):
     """Cursor history and bookmarks; drag/snap cursor."""
     bl_idname = "view3d.cursor3d_enhanced"
     bl_label = "Enhanced Set Cursor"
-
+    
     key_char_map = {
         'PERIOD':".", 'NUMPAD_PERIOD':".",
         'MINUS':"-", 'NUMPAD_MINUS':"-",
@@ -120,7 +121,7 @@ class EnhancedSetCursor(bpy.types.Operator):
         'SLASH':"/", 'NUMPAD_SLASH':"/",
         'NUMPAD_ASTERIX':"*",
     }
-
+    
     key_coordsys_map = {
         'LEFT_BRACKET':-1,
         'RIGHT_BRACKET':1,
@@ -131,7 +132,7 @@ class EnhancedSetCursor(bpy.types.Operator):
         'N':'NORMAL',
         'M':"Scaled",
     }
-
+    
     key_pivot_map = {
         'H':'ACTIVE',
         'U':'CURSOR',
@@ -139,20 +140,20 @@ class EnhancedSetCursor(bpy.types.Operator):
         'O':'CENTER',
         'P':'MEDIAN',
     }
-
+    
     key_snap_map = {
         'C':'INCREMENT',
         'V':'VERTEX',
         'E':'EDGE',
         'F':'FACE',
     }
-
+    
     key_tfm_mode_map = {
         'G':'MOVE',
         'R':'ROTATE',
         'S':'SCALE',
     }
-
+    
     key_map = {
         "confirm":{'ACTIONMOUSE'}, # also 'RET' ?
         "cancel":{'SELECTMOUSE', 'ESC'},
@@ -177,66 +178,66 @@ class EnhancedSetCursor(bpy.types.Operator):
         "paste_axes":{'V'},
         "cut_axes":{'X'},
     }
-
+    
     gizmo_factor = 0.15
     click_period = 0.25
-
+    
     angle_grid_steps = {True:1.0, False:5.0}
     scale_grid_steps = {True:0.01, False:0.1}
-
+    
     # ====== OPERATOR METHOD OVERLOADS ====== #
     @classmethod
     def poll(cls, context):
         area_types = {'VIEW_3D',} # also: IMAGE_EDITOR ?
         return (context.area.type in area_types) and \
                (context.region.type == "WINDOW")
-
+    
     def modal(self, context, event):
         context.area.tag_redraw()
         return self.try_process_input(context, event)
-
+    
     def invoke(self, context, event):
         # Attempt to launch the monitor
         if bpy.ops.view3d.cursor3d_monitor.poll():
             bpy.ops.view3d.cursor3d_monitor()
-
+        
         # Don't interfere with these modes when only mouse is pressed
         if ('SCULPT' in context.mode) or ('PAINT' in context.mode):
             if "MOUSE" in event.type:
                 return {'CANCELLED'}
-
+        
         CursorDynamicSettings.active_transform_operator = self
-
+        
         tool_settings = context.tool_settings
-
+        
         settings = find_settings()
         tfm_opts = settings.transform_options
-
+        
         settings_scene = context.scene.cursor_3d_tools_settings
-
+        
         self.setup_keymaps(context)
-
+        
         # Coordinate System Utility
         self.particles, self.csu = gather_particles(context=context)
         self.particles = [View3D_Cursor(context)]
-
+        
         self.csu.source_pos = self.particles[0].get_location()
         self.csu.source_rot = self.particles[0].get_rotation()
         self.csu.source_scale = self.particles[0].get_scale()
-
+        
         # View3D Utility
         self.vu = ViewUtility(context.region, context.space_data,
             context.region_data)
-
+        
         # Snap Utility
         self.su = SnapUtility(context)
-
+        
         # turn off view locking for the duration of the operator
         self.view_pos = self.vu.get_position(True)
         self.vu.set_position(self.vu.get_position(), True)
         self.view_locks = self.vu.get_locks()
         self.vu.set_locks({})
-
+        
         # Initialize runtime states
         self.initiated_by_mouse = ("MOUSE" in event.type)
         self.free_mouse = not self.initiated_by_mouse
@@ -250,37 +251,37 @@ class EnhancedSetCursor(bpy.types.Operator):
         self.coord_format = "{:." + str(settings.free_coord_precision) + "f}"
         self.transform_mode = 'MOVE'
         self.init_xy_angle_distance(context, event)
-
+        
         self.click_start = time.time()
         if not self.initiated_by_mouse:
             self.click_start -= self.click_period
-
+        
         self.stick_obj_name = settings_scene.stick_obj_name
         self.stick_obj_pos = settings_scene.stick_obj_pos
-
+        
         # Initial run
         self.try_process_input(context, event, True)
-
+        
         context.window_manager.modal_handler_add(self)
         return {'RUNNING_MODAL'}
-
+    
     def cancel(self, context):
         for particle in self.particles:
             particle.revert()
-
+        
         set_stick_obj(context.scene, self.stick_obj_name, self.stick_obj_pos)
-
+        
         self.finalize(context)
         return {'CANCELLED'}
-
+    
     # ====== CLEANUP/FINALIZE ====== #
     def finalize(self, context):
         # restore view locking
         self.vu.set_locks(self.view_locks)
         self.vu.set_position(self.view_pos, True)
-
+        
         self.cleanup(context)
-
+        
         # This is to avoid "blinking" of
         # between-history-positions line
         settings = find_settings()
@@ -288,12 +289,12 @@ class EnhancedSetCursor(bpy.types.Operator):
         # make sure the most recent history entry is displayed
         history.curr_id = 0
         history.last_id = 0
-
+        
         # Ensure there are no leftovers from draw_callback
         context.area.tag_redraw()
-
+        
         return {'FINISHED'}
-
+    
     def cleanup(self, context):
         self.particles = None
         self.csu = None
@@ -301,13 +302,13 @@ class EnhancedSetCursor(bpy.types.Operator):
         if self.su is not None:
             self.su.dispose()
         self.su = None
-
+        
         CursorDynamicSettings.active_transform_operator = None
-
+    
     # ====== USER INPUT PROCESSING ====== #
     def setup_keymaps(self, context):
         self.key_map = self.key_map.copy()
-
+        
         # There is no such event as 'ACTIONMOUSE',
         # it's always 'LEFTMOUSE' or 'RIGHTMOUSE'
         select_mouse = context.user_preferences.inputs.select_mouse
@@ -317,7 +318,7 @@ class EnhancedSetCursor(bpy.types.Operator):
         else:
             self.key_map["confirm"] = {'RIGHTMOUSE'}
             self.key_map["cancel"] = {'LEFTMOUSE', 'ESC'}
-
+        
         # Use user-defined "free mouse" key, if it exists
         wm = context.window_manager
         if '3D View' in wm.keyconfigs.user.keymaps:
@@ -327,7 +328,7 @@ class EnhancedSetCursor(bpy.types.Operator):
                     if kmi.map_type == 'KEYBOARD':
                         self.key_map["free_mouse"] = {kmi.type,}
                         break
-
+    
     def try_process_input(self, context, event, initial_run=False):
         try:
             return self.process_input(context, event, initial_run)
@@ -335,31 +336,31 @@ class EnhancedSetCursor(bpy.types.Operator):
             # If anything fails, at least dispose the resources
             self.cleanup(context)
             raise
-
+    
     def process_input(self, context, event, initial_run=False):
         wm = context.window_manager
         v3d = context.space_data
-
+        
         if event.type in self.key_map["confirm"]:
             if self.free_mouse:
                 finished = (event.value == 'PRESS')
             else:
                 finished = (event.value == 'RELEASE')
-
+            
             if finished:
                 return self.finalize(context)
-
+        
         if event.type in self.key_map["cancel"]:
             return self.cancel(context)
-
+        
         tool_settings = context.tool_settings
-
+        
         settings = find_settings()
         tfm_opts = settings.transform_options
-
+        
         make_snapshot = False
         tangential_snapshot = False
-
+        
         if event.value == 'PRESS':
             if event.type in self.key_map["free_mouse"]:
                 if self.free_mouse and (not initial_run):
@@ -367,10 +368,10 @@ class EnhancedSetCursor(bpy.types.Operator):
                     return self.finalize(context)
                 else:
                     self.free_mouse = True
-
+            
             if event.type in self.key_tfm_mode_map:
                 new_mode = self.key_tfm_mode_map[event.type]
-
+                
                 if self.transform_mode != new_mode:
                     # snap cursor to its initial state
                     if new_mode != 'MOVE':
@@ -379,37 +380,37 @@ class EnhancedSetCursor(bpy.types.Operator):
                             particle.set_matrix(initial_matrix)
                     # reset intial mouse position
                     self.init_xy_angle_distance(context, event)
-
+                
                 self.transform_mode = new_mode
-
+            
             if event.type in self.key_map["make_normal_snapshot"]:
                 make_snapshot = True
                 tangential_snapshot = False
-
+            
             if event.type in self.key_map["make_tangential_snapshot"]:
                 make_snapshot = True
                 tangential_snapshot = True
-
+            
             if event.type in self.key_map["snap_to_raw_mesh"]:
                 tool_settings.use_snap_self = \
                     not tool_settings.use_snap_self
-
+            
             if (not event.alt) and (event.type in {'X', 'Y', 'Z'}):
                 axis_lock = [(event.type == 'X') != event.shift,
                              (event.type == 'Y') != event.shift,
                              (event.type == 'Z') != event.shift]
-
+                
                 if self.allowed_axes != axis_lock:
                     self.allowed_axes = axis_lock
                 else:
                     self.allowed_axes = [True, True, True]
-
+            
             if event.type in self.key_map["use_absolute_coords"]:
                 tfm_opts.use_relative_coords = \
                     not tfm_opts.use_relative_coords
-
+                
                 self.update_origin_projection(context)
-
+            
             incr = 0
             if event.type in self.key_map["change_current_axis"]:
                 incr = (-1 if event.shift else 1)
@@ -417,27 +418,27 @@ class EnhancedSetCursor(bpy.types.Operator):
                 incr = 1
             elif event.type in self.key_map["prev_axis"]:
                 incr = -1
-
+            
             if incr != 0:
                 self.current_axis = (self.current_axis + incr) % 3
                 self.caret_pos = len(self.axes_values[self.current_axis])
-
+            
             incr = 0
             if event.type in self.key_map["precision_up"]:
                 incr = 1
             elif event.type in self.key_map["precision_down"]:
                 incr = -1
-
+            
             if incr != 0:
                 settings.free_coord_precision += incr
                 self.coord_format = "{:." + \
                     str(settings.free_coord_precision) + "f}"
-
+            
             if (event.type == 'ZERO') and event.ctrl:
                 self.snap_to_system_origin()
             else:
                 self.process_axis_input(event)
-
+            
             if event.alt:
                 if event.type in self.key_map["copy_axes"]:
                     wm.clipboard = "\t".join(self.get_axes_text(True))
@@ -446,27 +447,27 @@ class EnhancedSetCursor(bpy.types.Operator):
                     self.set_axes_text("\t\t\t")
                 elif event.type in self.key_map["paste_axes"]:
                     self.set_axes_text(wm.clipboard, True)
-
+            
             if event.type in self.key_coordsys_map:
                 new_orientation = self.key_coordsys_map[event.type]
                 self.csu.set_orientation(new_orientation)
-
+                
                 self.update_origin_projection(context)
-
+                
                 if event.ctrl:
                     self.snap_to_system_origin()
-
+            
             if event.type in self.key_map["use_object_centers"]:
                 v3d.use_pivot_point_align = not v3d.use_pivot_point_align
-
+            
             if event.type in self.key_pivot_map:
                 self.csu.set_pivot(self.key_pivot_map[event.type])
-
+                
                 self.update_origin_projection(context)
-
+                
                 if event.ctrl:
                     self.snap_to_system_origin(force_pivot=True)
-
+            
             if (not event.alt) and (event.type in self.key_snap_map):
                 snap_element = self.key_snap_map[event.type]
                 if tool_settings.snap_element == snap_element:
@@ -476,7 +477,7 @@ class EnhancedSetCursor(bpy.types.Operator):
                         snap_element = 'VERTEX'
                 tool_settings.snap_element = snap_element
         # end if
-
+        
         if initial_run or (('MOVE' not in event.type) and \
                 ('TIMER' not in event.type)):
             use_snap = (tool_settings.use_snap != event.ctrl)
@@ -484,7 +485,7 @@ class EnhancedSetCursor(bpy.types.Operator):
                 snap_type = tool_settings.snap_element
             else:
                 snap_type = None
-
+            
             axes_coords = [None, None, None]
             if self.transform_mode == 'MOVE':
                 for i in range(3):
@@ -492,7 +493,7 @@ class EnhancedSetCursor(bpy.types.Operator):
                         axes_coords[i] = self.axes_coords[i]
                     elif not self.allowed_axes[i]:
                         axes_coords[i] = 0.0
-
+            
             self.su.set_modes(
                 interpolation=tfm_opts.snap_interpolate_normals_mode,
                 use_relative_coords=tfm_opts.use_relative_coords,
@@ -501,89 +502,89 @@ class EnhancedSetCursor(bpy.types.Operator):
                 snap_align=tool_settings.use_snap_align_rotation,
                 axes_coords=axes_coords,
                 )
-
+        
         self.do_raycast = ("MOUSE" in event.type)
         self.grid_substep = event.shift
         self.modify_surface_orientation = (len(self.particles) == 1)
         self.xy = Vector((event.mouse_region_x, event.mouse_region_y))
-
+        
         self.use_object_centers = v3d.use_pivot_point_align
-
+        
         if event.type == 'MOUSEMOVE':
             self.update_transform_mousemove()
-
+        
         if self.transform_mode == 'MOVE':
             transform_func = self.transform_move
         elif self.transform_mode == 'ROTATE':
             transform_func = self.transform_rotate
         elif self.transform_mode == 'SCALE':
             transform_func = self.transform_scale
-
+        
         for particle in self.particles:
             transform_func(particle)
-
+        
         if make_snapshot:
             self.make_normal_snapshot(context.scene, tangential_snapshot)
-
+        
         return {'RUNNING_MODAL'}
-
+    
     def update_origin_projection(self, context):
         r = context.region
         rv3d = context.region_data
-
+        
         origin = self.csu.get_origin()
         # prehaps not projection, but intersection with plane?
         self.origin_xy = location_3d_to_region_2d(r, rv3d, origin)
         if self.origin_xy is None:
             self.origin_xy = Vector((r.width / 2, r.height / 2))
-
+        
         self.delta_xy = (self.start_xy - self.origin_xy).to_3d()
         self.prev_delta_xy = self.delta_xy
-
+    
     def init_xy_angle_distance(self, context, event):
         self.start_xy = Vector((event.mouse_region_x, event.mouse_region_y))
-
+        
         self.update_origin_projection(context)
-
+        
         # Distinction between angles has to be made because
         # angles can go beyond 360 degrees (we cannot snap
         # to increment the original ones).
         self.raw_angles = [0.0, 0.0, 0.0]
         self.angles = [0.0, 0.0, 0.0]
         self.scales = [1.0, 1.0, 1.0]
-
+    
     def update_transform_mousemove(self):
         delta_xy = (self.xy - self.origin_xy).to_3d()
-
+        
         n_axes = sum(int(v) for v in self.allowed_axes)
         if n_axes == 1:
             # rotate using angle as value
             rd = self.prev_delta_xy.rotation_difference(delta_xy)
             offset = -rd.angle * round(rd.axis[2])
-
+            
             sys_matrix = self.csu.get_matrix()
-
+            
             i_allowed = 0
             for i in range(3):
                 if self.allowed_axes[i]:
                     i_allowed = i
-
+            
             view_dir = self.vu.get_direction()
             if view_dir.dot(sys_matrix[i_allowed][:3]) < 0:
                 offset = -offset
-
+            
             for i in range(3):
                 if self.allowed_axes[i]:
                     self.raw_angles[i] += offset
         elif n_axes == 2:
             # rotate using XY coords as two values
             offset = (delta_xy - self.prev_delta_xy) * (math.pi / 180.0)
-
+            
             if self.grid_substep:
                 offset *= 0.1
             else:
                 offset *= 0.5
-
+            
             j = 0
             for i in range(3):
                 if self.allowed_axes[i]:
@@ -593,130 +594,142 @@ class EnhancedSetCursor(bpy.types.Operator):
             # rotate around view direction
             rd = self.prev_delta_xy.rotation_difference(delta_xy)
             offset = -rd.angle * round(rd.axis[2])
-
+            
             view_dir = self.vu.get_direction()
-
+            
             sys_matrix = self.csu.get_matrix()
-
-            view_dir = sys_matrix.inverted().to_3x3() * view_dir
+            
+            try:
+                view_dir = sys_matrix.inverted().to_3x3() * view_dir
+            except:
+                # this is some degenerate system
+                pass
             view_dir.normalize()
-
+            
             rot = Matrix.Rotation(offset, 3, view_dir)
-
+            
             matrix = Euler(self.raw_angles, 'XYZ').to_matrix()
             matrix.rotate(rot)
-
+            
             euler = matrix.to_euler('XYZ')
             self.raw_angles[0] += clamp_angle(euler.x - self.raw_angles[0])
             self.raw_angles[1] += clamp_angle(euler.y - self.raw_angles[1])
             self.raw_angles[2] += clamp_angle(euler.z - self.raw_angles[2])
-
+        
         scale = delta_xy.length / self.delta_xy.length
         if self.delta_xy.dot(delta_xy) < 0:
             scale *= -1
         for i in range(3):
             if self.allowed_axes[i]:
                 self.scales[i] = scale
-
+        
         self.prev_delta_xy = delta_xy
-
+    
     def transform_move(self, particle):
         src_matrix = particle.get_matrix()
         initial_matrix = particle.get_initial_matrix()
-
+        
         matrix = self.su.snap(
             self.xy, src_matrix, initial_matrix,
             self.do_raycast, self.grid_substep,
             self.vu, self.csu,
             self.modify_surface_orientation,
             self.use_object_centers)
-
+        
         particle.set_matrix(matrix)
-
+    
     def rotate_matrix(self, matrix):
         sys_matrix = self.csu.get_matrix()
-
-        matrix = sys_matrix.inverted() * matrix
-
+        
+        try:
+            matrix = sys_matrix.inverted() * matrix
+        except:
+            # this is some degenerate system
+            pass
+        
         # Blender's order of rotation [in local axes]
         rotation_order = [2, 1, 0]
-
+        
         # Seems that 4x4 matrix cannot be rotated using rotate() ?
         sys_matrix3 = sys_matrix.to_3x3()
-
+        
         for i in range(3):
             j = rotation_order[i]
             axis = sys_matrix3[j]
             angle = self.angles[j]
-
+            
             rot = angle_axis_to_quat(angle, axis)
             # this seems to be buggy too
             #rot = Matrix.Rotation(angle, 3, axis)
-
+            
             sys_matrix3 = rot.to_matrix() * sys_matrix3
             # sys_matrix3.rotate has a bug? or I don't understand how it works?
             #sys_matrix3.rotate(rot)
-
+        
         for i in range(3):
             sys_matrix[i][:3] = sys_matrix3[i]
-
+        
         matrix = sys_matrix * matrix
-
+        
         return matrix
-
+    
     def transform_rotate(self, particle):
         grid_step = self.angle_grid_steps[self.grid_substep]
         grid_step *= (math.pi / 180.0)
-
+        
         for i in range(3):
             if self.axes_values[i] and self.axes_eval_success[i]:
                 self.raw_angles[i] = self.axes_coords[i] * (math.pi / 180.0)
-
+            
             self.angles[i] = self.raw_angles[i]
-
+        
         if self.su.implementation.snap_type == 'INCREMENT':
             for i in range(3):
                 self.angles[i] = round_step(self.angles[i], grid_step)
-
+        
         initial_matrix = particle.get_initial_matrix()
         matrix = self.rotate_matrix(initial_matrix)
-
+        
         particle.set_matrix(matrix)
-
+    
     def scale_matrix(self, matrix):
         sys_matrix = self.csu.get_matrix()
-
-        matrix = sys_matrix.inverted() * matrix
-
+        
+        try:
+            matrix = sys_matrix.inverted() * matrix
+        except:
+            # this is some degenerate system
+            pass
+        
         for i in range(3):
             sys_matrix[i] *= self.scales[i]
-
+        
         matrix = sys_matrix * matrix
-
+        
         return matrix
-
+    
     def transform_scale(self, particle):
         grid_step = self.scale_grid_steps[self.grid_substep]
-
+        
         for i in range(3):
             if self.axes_values[i] and self.axes_eval_success[i]:
                 self.scales[i] = self.axes_coords[i]
-
+        
         if self.su.implementation.snap_type == 'INCREMENT':
             for i in range(3):
                 self.scales[i] = round_step(self.scales[i], grid_step)
-
+        
         initial_matrix = particle.get_initial_matrix()
         matrix = self.scale_matrix(initial_matrix)
-
+        
         particle.set_matrix(matrix)
-
+    
     def set_axis_input(self, axis_id, axis_val):
         if axis_val == self.axes_values[axis_id]:
             return
-
+        
         self.axes_values[axis_id] = axis_val
-
+        
         if len(axis_val) == 0:
             self.axes_coords[axis_id] = None
             self.axes_eval_success[axis_id] = True
@@ -728,13 +741,17 @@ class EnhancedSetCursor(bpy.types.Operator):
                 self.axes_eval_success[axis_id] = True
             except:
                 self.axes_eval_success[axis_id] = False
-
+    
     def snap_to_system_origin(self, force_pivot=False):
         if self.transform_mode == 'MOVE':
             pivot = self.csu.get_pivot_name(raw=force_pivot)
             p = self.csu.get_origin(relative=False, pivot=pivot)
             m = self.csu.get_matrix()
-            p = m.inverted() * p
+            try:
+                p = m.inverted() * p
+            except:
+                # this is some degenerate system
+                pass
             for i in range(3):
                 self.set_axis_input(i, str(p[i]))
         elif self.transform_mode == 'ROTATE':
@@ -743,16 +760,16 @@ class EnhancedSetCursor(bpy.types.Operator):
         elif self.transform_mode == 'SCALE':
             for i in range(3):
                 self.set_axis_input(i, "1")
-
+    
     def get_axes_values(self, as_string=False):
         if self.transform_mode == 'MOVE':
             localmat = CursorDynamicSettings.local_matrix
-            raw_axes = localmat[3]
+            raw_axes = localmat.translation
         elif self.transform_mode == 'ROTATE':
             raw_axes = Vector(self.angles) * (180.0 / math.pi)
         elif self.transform_mode == 'SCALE':
             raw_axes = Vector(self.scales)
-
+        
         axes_values = []
         for i in range(3):
             if as_string and self.axes_values[i]:
@@ -765,22 +782,22 @@ class EnhancedSetCursor(bpy.types.Operator):
                 if as_string:
                     value = self.coord_format.format(value)
             axes_values.append(value)
-
+        
         return axes_values
-
+    
     def get_axes_text(self, offset=False):
         axes_values = self.get_axes_values(as_string=True)
-
+        
         axes_text = []
         for i in range(3):
             j = i
             if offset:
                 j = (i + self.current_axis) % 3
-
+            
             axes_text.append(axes_values[j])
-
+        
         return axes_text
-
+    
     def set_axes_text(self, text, offset=False):
         if "\n" in text:
             text = text.replace("\r", "")
@@ -788,18 +805,18 @@ class EnhancedSetCursor(bpy.types.Operator):
             text = text.replace("\r", "\n")
         text = text.replace("\n", "\t")
         #text = text.replace(",", ".") # ???
-
+        
         axes_text = text.split("\t")
         for i in range(min(len(axes_text), 3)):
             j = i
             if offset:
                 j = (i + self.current_axis) % 3
             self.set_axis_input(j, axes_text[i])
-
+    
     def process_axis_input(self, event):
         axis_id = self.current_axis
         axis_val = self.axes_values[axis_id]
-
+        
         if event.type in self.key_map["remove_next_character"]:
             if event.ctrl:
                 # clear all
@@ -853,11 +870,11 @@ class EnhancedSetCursor(bpy.types.Operator):
             axis_val = axis_val[0:self.caret_pos] + c + \
                        axis_val[self.caret_pos:len(axis_val)]
             self.caret_pos += 1
-
+        
         self.caret_pos = min(max(self.caret_pos, 0), len(axis_val))
-
+        
         self.set_axis_input(axis_id, axis_val)
-
+    
     # ====== DRAWING ====== #
     def gizmo_distance(self, pos):
         rv3d = self.vu.region_data
@@ -868,35 +885,35 @@ class EnhancedSetCursor(bpy.types.Operator):
             view_dir = self.vu.get_direction()
             dist = (pos - view_pos).dot(view_dir)
         return dist
-
+    
     def gizmo_scale(self, pos):
         return self.gizmo_distance(pos) * self.gizmo_factor
-
+    
     def draw_3d(self):
         if time.time() < (self.click_start + self.click_period):
             return
-
+        
         settings = find_settings()
         tfm_opts = settings.transform_options
-
+        
         initial_matrix = self.particles[0].get_initial_matrix()
-
+        
         sys_matrix = self.csu.get_matrix()
         if tfm_opts.use_relative_coords:
-            sys_matrix[3] = initial_matrix[3].copy()
+            sys_matrix.translation = initial_matrix.translation.copy()
         sys_origin = sys_matrix.to_translation()
         dest_point = self.particles[0].get_location()
-
+        
         if self.is_normal_visible():
             p0, x, y, z, _x, _z = \
                 self.get_normal_params(tfm_opts, dest_point)
-
+            
             # use theme colors?
             #ThemeView3D.normal
             #ThemeView3D.vertex_normal
-
+            
             bgl.glDisable(bgl.GL_LINE_STIPPLE)
-
+            
             if settings.draw_N:
                 bgl.glColor4f(0, 1, 1, 1)
                 draw_arrow(p0, _x, y, z) # Z (normal)
@@ -906,10 +923,10 @@ class EnhancedSetCursor(bpy.types.Operator):
             if settings.draw_T2:
                 bgl.glColor4f(1, 1, 0, 1)
                 draw_arrow(p0, _z, x, y) # Y (2nd tangential)
-
+            
             bgl.glEnable(bgl.GL_BLEND)
             bgl.glDisable(bgl.GL_DEPTH_TEST)
-
+            
             if settings.draw_N:
                 bgl.glColor4f(0, 1, 1, 0.25)
                 draw_arrow(p0, _x, y, z) # Z (normal)
@@ -919,17 +936,21 @@ class EnhancedSetCursor(bpy.types.Operator):
             if settings.draw_T2:
                 bgl.glColor4f(1, 1, 0, 0.25)
                 draw_arrow(p0, _z, x, y) # Y (2nd tangential)
-
+        
         if settings.draw_guides:
             p0 = dest_point
-            p00 = sys_matrix.inverted() * p0
-
+            try:
+                p00 = sys_matrix.inverted() * p0
+            except:
+                # this is some degenerate system
+                p00 = p0.copy()
+            
             axes_line_params = [
                 (Vector((0, p00.y, p00.z)), (1, 0, 0)),
                 (Vector((p00.x, 0, p00.z)), (0, 1, 0)),
                 (Vector((p00.x, p00.y, 0)), (0, 0, 1)),
             ]
-
+            
             for i in range(3):
                 p1, color = axes_line_params[i]
                 p1 = sys_matrix * p1
@@ -938,27 +959,27 @@ class EnhancedSetCursor(bpy.types.Operator):
                 alpha = (0.25 if constrained else 1.0)
                 draw_line_hidden_depth(p0, p1, color, \
                     alpha, alpha, False, True)
-
+            
             # line from origin to cursor
             p0 = sys_origin
             p1 = dest_point
-
+            
             bgl.glEnable(bgl.GL_LINE_STIPPLE)
             bgl.glColor4f(1, 1, 0, 1)
-
+            
             draw_line_hidden_depth(p0, p1, (1, 1, 0), 1.0, 0.5, True, True)
-
+        
         if settings.draw_snap_elements:
             sui = self.su.implementation
             if sui.potential_snap_elements and (sui.snap_type == 'EDGE'):
                 bgl.glDisable(bgl.GL_LINE_STIPPLE)
-
+                
                 bgl.glEnable(bgl.GL_BLEND)
                 bgl.glDisable(bgl.GL_DEPTH_TEST)
-
+                
                 bgl.glLineWidth(2)
                 bgl.glColor4f(0, 0, 1, 0.5)
-
+                
                 bgl.glBegin(bgl.GL_LINE_LOOP)
                 for p in sui.potential_snap_elements:
                     bgl.glVertex3f(p[0], p[1], p[2])
@@ -966,9 +987,9 @@ class EnhancedSetCursor(bpy.types.Operator):
             elif sui.potential_snap_elements and (sui.snap_type == 'FACE'):
                 bgl.glEnable(bgl.GL_BLEND)
                 bgl.glDisable(bgl.GL_DEPTH_TEST)
-
+                
                 bgl.glColor4f(0, 1, 0, 0.5)
-
+                
                 co = sui.potential_snap_elements
                 tris = tesselate_polygon([co])
                 bgl.glBegin(bgl.GL_TRIANGLES)
@@ -977,65 +998,65 @@ class EnhancedSetCursor(bpy.types.Operator):
                         p = co[vi]
                         bgl.glVertex3f(p[0], p[1], p[2])
                 bgl.glEnd()
-
+    
     def draw_2d(self, context):
         r = context.region
         rv3d = context.region_data
-
+        
         settings = find_settings()
-
+        
         if settings.draw_snap_elements:
             sui = self.su.implementation
-
+            
             snap_points = []
             if sui.potential_snap_elements and \
                     (sui.snap_type in {'VERTEX', 'VOLUME'}):
                 snap_points.extend(sui.potential_snap_elements)
             if sui.extra_snap_points:
                 snap_points.extend(sui.extra_snap_points)
-
+            
             if snap_points:
                 bgl.glEnable(bgl.GL_BLEND)
-
+                
                 bgl.glPointSize(5)
                 bgl.glColor4f(1, 0, 0, 0.5)
-
+                
                 bgl.glBegin(bgl.GL_POINTS)
                 for p in snap_points:
                     p = location_3d_to_region_2d(r, rv3d, p)
                     if p is not None:
                         bgl.glVertex2f(p[0], p[1])
                 bgl.glEnd()
-
+                
                 bgl.glPointSize(1)
-
+        
         if self.transform_mode == 'MOVE':
             return
-
+        
         bgl.glEnable(bgl.GL_LINE_STIPPLE)
-
+        
         bgl.glLineWidth(1)
-
+        
         bgl.glColor4f(0, 0, 0, 1)
         draw_line_2d(self.origin_xy, self.xy)
-
+        
         bgl.glDisable(bgl.GL_LINE_STIPPLE)
-
+        
         line_width = 3
         bgl.glLineWidth(line_width)
-
+        
         L = 12.0
         arrow_len = 6.0
         arrow_width = 8.0
         arrow_space = 5.0
-
+        
         Lmax = arrow_space * 2 + L * 2 + line_width
-
+        
         pos = self.xy.to_2d()
         normal = self.prev_delta_xy.to_2d().normalized()
         dist = self.prev_delta_xy.length
         tangential = Vector((-normal[1], normal[0]))
-
+        
         if self.transform_mode == 'ROTATE':
             n_axes = sum(int(v) for v in self.allowed_axes)
             if n_axes == 2:
@@ -1044,7 +1065,7 @@ class EnhancedSetCursor(bpy.types.Operator):
                     n = sgn * Vector((0, 1))
                     p0 = pos + arrow_space * n
                     draw_arrow_2d(p0, n, L, arrow_len, arrow_width)
-
+                
                 bgl.glColor4f(0.11, 0.51, 0.11, 1)
                 for sgn in (-1, 1):
                     n = sgn * Vector((1, 0))
@@ -1064,31 +1085,31 @@ class EnhancedSetCursor(bpy.types.Operator):
                 n = sgn * normal
                 p0 = pos + arrow_space * n
                 draw_arrow_2d(p0, n, L, arrow_len, arrow_width)
-
+        
         bgl.glLineWidth(1)
-
+    
     def draw_axes_coords(self, context, header_size):
         if time.time() < (self.click_start + self.click_period):
             return
-
+        
         v3d = context.space_data
-
+        
         userprefs_view = context.user_preferences.view
-
+        
         tool_settings = context.tool_settings
-
+        
         settings = find_settings()
         tfm_opts = settings.transform_options
-
+        
         localmat = CursorDynamicSettings.local_matrix
-
+        
         font_id = 0 # default font
-
+        
         font_size = 11
         blf.size(font_id, font_size, 72) # font, point size, dpi
-
+        
         tet = context.user_preferences.themes[0].text_editor
-
+        
         # Prepare the table...
         if self.transform_mode == 'MOVE':
             axis_prefix = ("D" if tfm_opts.use_relative_coords else "")
@@ -1097,21 +1118,21 @@ class EnhancedSetCursor(bpy.types.Operator):
         else:
             axis_prefix = "R"
         axis_names = ["X", "Y", "Z"]
-
+        
         axis_cells = []
         coord_cells = []
         #caret_cell = TextCell("_", tet.cursor)
         caret_cell = TextCell("|", tet.cursor)
-
+        
         try:
             axes_text = self.get_axes_text()
-
+            
             for i in range(3):
                 color = tet.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))
-
+                
                 if self.axes_values[i]:
                     if self.axes_eval_success[i]:
                         color = tet.syntax_numbers
@@ -1123,9 +1144,9 @@ class EnhancedSetCursor(bpy.types.Operator):
                 coord_cells.append(TextCell(text, color))
         except Exception as e:
             print(e)
-
+        
         mode_cells = []
-
+        
         try:
             snap_type = self.su.implementation.snap_type
             if snap_type is None:
@@ -1139,14 +1160,14 @@ class EnhancedSetCursor(bpy.types.Operator):
             if text == 'VOLUME':
                 text = "BBOX"
             mode_cells.append(TextCell(text, color))
-
+            
             if self.csu.tou.is_custom:
                 color = tet.text
             else:
                 color = tet.syntax_builtin
             text = self.csu.tou.get_title()
             mode_cells.append(TextCell(text, color))
-
+            
             color = tet.text
             text = self.csu.get_pivot_name(raw=True)
             if self.use_object_centers:
@@ -1154,22 +1175,22 @@ class EnhancedSetCursor(bpy.types.Operator):
             mode_cells.append(TextCell(text, color))
         except Exception as e:
             print(e)
-
+        
         hdr_w, hdr_h = header_size
-
+        
         try:
             xyz_x_start_min = 12
             xyz_x_start = xyz_x_start_min
             mode_x_start = 6
-
+            
             mode_margin = 4
             xyz_margin = 16
             blend_margin = 32
-
+            
             color = tet.back
             bgl.glColor4f(color[0], color[1], color[2], 1.0)
             draw_rect(0, 0, hdr_w, hdr_h)
-
+            
             if tool_settings.use_snap_self:
                 x = hdr_w - mode_x_start
                 y = hdr_h / 2
@@ -1178,53 +1199,53 @@ class EnhancedSetCursor(bpy.types.Operator):
                 y -= cell.h * 0.5
                 bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
                 draw_rect(x, y, cell.w, cell.h, 1, True)
-
+            
             x = hdr_w - mode_x_start
             y = hdr_h / 2
             for cell in mode_cells:
                 cell.draw(x, y, (1, 0.5))
                 x -= (cell.w + mode_margin)
-
+            
             curr_axis_x_start = 0
             curr_axis_x_end = 0
             caret_x = 0
-
+            
             xyz_width = 0
             for i in range(3):
                 if i == self.current_axis:
                     curr_axis_x_start = xyz_width
-
+                
                 xyz_width += axis_cells[i].w
-
+                
                 if i == self.current_axis:
                     char_offset = 0
                     if self.axes_values[i]:
                         char_offset = blf.dimensions(font_id,
                             coord_cells[i].text[:self.caret_pos])[0]
                     caret_x = xyz_width + char_offset
-
+                
                 xyz_width += coord_cells[i].w
-
+                
                 if i == self.current_axis:
                     curr_axis_x_end = xyz_width
-
+                
                 xyz_width += xyz_margin
-
+            
             xyz_width = int(xyz_width)
             xyz_width_ext = xyz_width + blend_margin
-
+            
             offset = (xyz_x_start + curr_axis_x_end) - hdr_w
             if offset > 0:
                 xyz_x_start -= offset
-
+            
             offset = xyz_x_start_min - (xyz_x_start + curr_axis_x_start)
             if offset > 0:
                 xyz_x_start += offset
-
+            
             offset = (xyz_x_start + caret_x) - hdr_w
             if offset > 0:
                 xyz_x_start -= offset
-
+            
             # somewhy GL_BLEND should be set right here
             # to actually draw the box with blending %)
             # (perhaps due to text draw happened before)
@@ -1242,21 +1263,21 @@ class EnhancedSetCursor(bpy.types.Operator):
             bgl.glVertex2i(xyz_width_ext, 0)
             bgl.glVertex2i(xyz_width_ext, hdr_h)
             bgl.glEnd()
-
+            
             x = xyz_x_start
             y = hdr_h / 2
             for i in range(3):
                 cell = axis_cells[i]
                 cell.draw(x, y, (0, 0.5))
                 x += cell.w
-
+                
                 cell = coord_cells[i]
                 cell.draw(x, y, (0, 0.5))
                 x += (cell.w + xyz_margin)
-
+            
             caret_x -= blf.dimensions(font_id, caret_cell.text)[0] * 0.5
             caret_cell.draw(xyz_x_start + caret_x, y, (0, 0.5))
-
+            
             bgl.glEnable(bgl.GL_BLEND)
             bgl.glShadeModel(bgl.GL_SMOOTH)
             gl_enable(bgl.GL_SMOOTH, True)
@@ -1269,55 +1290,55 @@ class EnhancedSetCursor(bpy.types.Operator):
             bgl.glColor4f(color[0], color[1], color[2], 0.0)
             bgl.glVertex2i(xyz_x_start_min, hdr_h)
             bgl.glEnd()
-
+            
         except Exception as e:
             print(e)
-
+        
         return
-
+    
     # ====== NORMAL SNAPSHOT ====== #
     def is_normal_visible(self):
         if self.csu.tou.get() == "Surface":
             return True
-
+        
         if self.use_object_centers:
             return False
-
+        
         return self.su.implementation.snap_type \
             not in {None, 'INCREMENT', 'VOLUME'}
-
+    
     def get_normal_params(self, tfm_opts, dest_point):
         surf_matrix = self.csu.get_matrix("Surface")
         if tfm_opts.use_relative_coords:
             surf_origin = dest_point
         else:
             surf_origin = surf_matrix.to_translation()
-
+        
         m3 = surf_matrix.to_3x3()
         p0 = surf_origin
         scl = self.gizmo_scale(p0)
-
+        
         # Normal and tangential are not always orthogonal
         # (e.g. when normal is interpolated)
         x = (m3 * Vector((1, 0, 0))).normalized()
         y = (m3 * Vector((0, 1, 0))).normalized()
         z = (m3 * Vector((0, 0, 1))).normalized()
-
+        
         _x = z.cross(y)
         _z = y.cross(x)
-
+        
         return p0, x * scl, y * scl, z * scl, _x * scl, _z * scl
-
+    
     def make_normal_snapshot(self, scene, tangential=False):
         settings = find_settings()
         tfm_opts = settings.transform_options
-
+        
         dest_point = self.particles[0].get_location()
-
+        
         if self.is_normal_visible():
             p0, x, y, z, _x, _z = \
                 self.get_normal_params(tfm_opts, dest_point)
-
+            
             snapshot = bpy.data.objects.new("normal_snapshot", None)
             if tangential:
                 #snapshot.matrix_world = Matrix(
@@ -1343,45 +1364,41 @@ class View3D_Cursor(Particle):
         self.v3d = context.space_data
         self.initial_pos = self.get_location()
         self.initial_matrix = Matrix.Translation(self.initial_pos)
-
+    
     def revert(self):
         self.set_location(self.initial_pos)
-
+    
     def get_location(self):
-        return self.v3d.cursor_location.copy()
-
+        return get_cursor_location(v3d=self.v3d)
+    
     def set_location(self, value):
-        # !!! ATTENTION !!!
-        # Accessing scene.cursor_location is SLOW
-        # (well, at least assigning to it).
-        # Accessing v3d.cursor_location is fast.
-        self.v3d.cursor_location = Vector(value).to_3d()
-
+        set_cursor_location(Vector(value), v3d=self.v3d)
+    
     def get_rotation(self):
         return Quaternion()
-
+    
     def set_rotation(self, value):
         pass
-
+    
     def get_scale(self):
         return Vector((1.0, 1.0, 1.0))
-
+    
     def set_scale(self, value):
         pass
-
+    
     def get_matrix(self):
         return Matrix.Translation(self.get_location())
-
+    
     def set_matrix(self, value):
         self.set_location(value.to_translation())
-
+    
     def get_initial_matrix(self):
         return self.initial_matrix
 
 class View3D_Object(Particle):
     def __init__(self, obj):
         self.obj = obj
-
+    
     def get_location(self):
         # obj.location seems to be in parent's system...
         # or even maybe not bounded by constraints %)
@@ -1439,40 +1456,40 @@ class UV_Face(Particle):
 # are a separate issue (they can come and go).
 def gather_particles(**kwargs):
     context = kwargs.get("context", bpy.context)
-
+    
     area_type = kwargs.get("area_type", context.area.type)
-
+    
     scene = kwargs.get("scene", context.scene)
-
+    
     space_data = kwargs.get("space_data", context.space_data)
     region_data = kwargs.get("region_data", context.region_data)
-
+    
     particles = []
     pivots = {}
     normal_system = None
-
+    
     active_element = None
     cursor_pos = None
     median = None
-
+    
     if area_type == 'VIEW_3D':
         context_mode = kwargs.get("context_mode", context.mode)
-
+        
         selected_objects = kwargs.get("selected_objects",
             context.selected_objects)
-
+        
         active_object = kwargs.get("active_object",
             context.active_object)
-
+        
         if context_mode == 'OBJECT':
             for obj in selected_objects:
                 particle = View3D_Object(obj)
                 particles.append(particle)
-
+            
             if active_object:
                 active_element = active_object.\
                     matrix_world.to_translation()
-
+        
         # On Undo/Redo scene hash value is changed ->
         # -> the monitor tries to update the CSU ->
         # -> object.mode_set seem to somehow conflict
@@ -1482,17 +1499,17 @@ def gather_particles(**kwargs):
         '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.
@@ -1510,7 +1527,7 @@ def gather_particles(**kwargs):
                     active_element = active_elem.co.copy()
                     active_element = active_object.\
                         matrix_world * active_element
-
+                
                 # Currently there is no API for element.select
                 #for element in active_object.data.elements:
                 #    if element.select:
@@ -1526,7 +1543,7 @@ def gather_particles(**kwargs):
                     active_element = active_object.\
                         matrix_world * active_element
                 '''
-
+                
                 for bone in active_object.data.edit_bones:
                     if bone.select_head:
                         positions.append(bone.head)
@@ -1535,23 +1552,23 @@ def gather_particles(**kwargs):
             elif context_mode == 'POSE':
                 active_bone = active_object.data.bones.active
                 if active_bone:
-                    active_element = active_bone.matrix_local[3].to_3d()
+                    active_element = active_bone.matrix_local.translation.to_3d()
                     active_element = active_object.\
                         matrix_world * active_element
-
+                
                 # consider only topmost parents
                 bones = set()
                 for bone in active_object.data.bones:
                     if bone.select:
                         bones.add(bone)
-
+                
                 parents = set()
                 for bone in bones:
                     if not set(bone.parent_recursive).intersection(bones):
                         parents.add(bone)
-
+                
                 for bone in parents:
-                    positions.append(bone.matrix_local[3].to_3d())
+                    positions.append(bone.matrix_local.translation.to_3d())
             else:
                 for spline in active_object.data.splines:
                     for point in spline.bezier_points:
@@ -1562,7 +1579,7 @@ def gather_particles(**kwargs):
                                 positions.append(point.handle_left)
                             if point.select_right_handle:
                                 positions.append(point.handle_right)
-
+                        
                         n = None
                         nL = point.co - point.handle_left
                         nR = point.co - point.handle_right
@@ -1578,33 +1595,33 @@ def gather_particles(**kwargs):
                                 n = -nL
                             if point.select_right_handle:
                                 n = nR
-
+                        
                         if n is not None:
                             if n.length_squared < epsilon:
                                 n = -nL
                             normal += n.normalized()
-
+                    
                     for point in spline.points:
                         if point.select:
                             positions.append(point.co)
-
+            
             if len(positions) != 0:
                 if normal.length_squared < epsilon:
                     normal = Vector((0, 0, 1))
                 normal.rotate(m)
                 normal.normalize()
-
+                
                 if (1.0 - abs(normal.z)) < epsilon:
                     t1 = Vector((1, 0, 0))
                 else:
                     t1 = Vector((0, 0, 1)).cross(normal)
                 t2 = t1.cross(normal)
                 normal_system = Matrix((t1, t2, normal))
-
+                
                 median, bbox_center = calc_median_bbox_pivots(positions)
                 median = m * median
                 bbox_center = m * bbox_center
-
+                
                 # Currently I don't know how to get active mesh element
                 if active_element is None:
                     if context_mode == 'EDIT_ARMATURE':
@@ -1616,36 +1633,36 @@ def gather_particles(**kwargs):
                 if active_element is None:
                     active_element = active_object.\
                         matrix_world.to_translation()
-
+                
                 median = active_element
                 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)
         else:
             # paint/sculpt, etc.?
             particle = View3D_Object(active_object)
             particles.append(particle)
-
+            
             if active_object:
                 active_element = active_object.\
                     matrix_world.to_translation()
-
+        
         # These are equivalent (though scene's is slower)
-        #cursor_pos = scene.cursor_location
-        cursor_pos = space_data.cursor_location
-
+        #cursor_pos = get_cursor_location(scene=scene)
+        cursor_pos = get_cursor_location(v3d=space_data)
+    
     #elif area_type == 'IMAGE_EDITOR':
         # currently there is no way to get UV editor's
         # offset (and maybe some other parameters
         # required to implement these operators)
         #cursor_pos = space_data.uv_editor.cursor_location
-
+    
     #elif area_type == 'EMPTY':
     #elif area_type == 'GRAPH_EDITOR':
     #elif area_type == 'OUTLINER':
@@ -1663,40 +1680,40 @@ def gather_particles(**kwargs):
     #elif area_type == 'LOGIC_EDITOR':
     #elif area_type == 'CONSOLE':
     #elif area_type == 'USER_PREFERENCES':
-
+    
     else:
         print("gather_particles() not implemented for '{}'".\
               format(area_type))
         return None, None
-
+    
     # 'INDIVIDUAL_ORIGINS' is not handled here
-
+    
     if cursor_pos:
         pivots['CURSOR'] = cursor_pos.copy()
-
+    
     if active_element:
         # in v3d: ACTIVE_ELEMENT
         pivots['ACTIVE'] = active_element.copy()
-
+    
     if (len(particles) != 0) and (median is None):
         positions = (p.get_location() for p in particles)
         median, bbox_center = calc_median_bbox_pivots(positions)
-
+    
     if median:
         # in v3d: MEDIAN_POINT, in UV editor: MEDIAN
         pivots['MEDIAN'] = median.copy()
         # in v3d: BOUNDING_BOX_CENTER, in UV editor: CENTER
         pivots['CENTER'] = bbox_center.copy()
-
+    
     csu = CoordinateSystemUtility(scene, space_data, region_data, \
         pivots, normal_system)
-
+    
     return particles, csu
 
 def calc_median_bbox_pivots(positions):
     median = None # pos can be 3D or 2D
     bbox = [None, None]
-
+    
     n = 0
     for pos in positions:
         extend_bbox(bbox, pos)
@@ -1705,10 +1722,10 @@ def calc_median_bbox_pivots(positions):
         except:
             median = pos.copy()
         n += 1
-
+    
     median = median / n
     bbox_center = (Vector(bbox[0]) + Vector(bbox[1])) * 0.5
-
+    
     return median, bbox_center
 
 def extend_bbox(bbox, pos):
@@ -1727,7 +1744,7 @@ class CoordinateSystemUtility:
         'BOUNDING_BOX_CENTER':'CENTER',
         'MEDIAN':'MEDIAN',
         'MEDIAN_POINT':'MEDIAN',
-        'CURSOR':'CURSOR',
+        'CURSOR':'CURSOR', 
         'INDIVIDUAL_ORIGINS':'INDIVIDUAL',
         'ACTIVE_ELEMENT':'ACTIVE',
         'WORLD':'WORLD',
@@ -1737,62 +1754,62 @@ class CoordinateSystemUtility:
     pivot_v3d_map = {
         'CENTER':'BOUNDING_BOX_CENTER',
         'MEDIAN':'MEDIAN_POINT',
-        'CURSOR':'CURSOR',
+        'CURSOR':'CURSOR', 
         'INDIVIDUAL':'INDIVIDUAL_ORIGINS',
         'ACTIVE':'ACTIVE_ELEMENT',
     }
-
+    
     def __init__(self, scene, space_data, region_data, \
                  pivots, normal_system):
         self.space_data = space_data
         self.region_data = region_data
-
+        
         if space_data.type == 'VIEW_3D':
             self.pivot_map_inv = self.pivot_v3d_map
-
+        
         self.tou = TransformOrientationUtility(
             scene, space_data, region_data)
         self.tou.normal_system = normal_system
-
+        
         self.pivots = pivots
-
+        
         # Assigned by caller (for cursor or selection)
         self.source_pos = None
         self.source_rot = None
         self.source_scale = None
-
+    
     def set_orientation(self, name):
         self.tou.set(name)
-
+    
     def set_pivot(self, pivot):
         self.space_data.pivot_point = self.pivot_map_inv[pivot]
-
+    
     def get_pivot_name(self, name=None, relative=None, raw=False):
         pivot = self.pivot_name_map[self.space_data.pivot_point]
         if raw:
             return pivot
-
+        
         if not name:
             name = self.tou.get()
-
+        
         if relative is None:
             settings = find_settings()
             tfm_opts = settings.transform_options
             relative = tfm_opts.use_relative_coords
-
+        
         if relative:
             pivot = "RELATIVE"
         elif (name == 'GLOBAL') or (pivot == 'WORLD'):
             pivot = 'WORLD'
         elif (name == "Surface") or (pivot == 'SURFACE'):
             pivot = "SURFACE"
-
+        
         return pivot
-
+    
     def get_origin(self, name=None, relative=None, pivot=None):
         if not pivot:
             pivot = self.get_pivot_name(name, relative)
-
+        
         if relative or (pivot == "RELATIVE"):
             # "relative" parameter overrides "pivot"
             return self.source_pos
@@ -1804,26 +1821,26 @@ class CoordinateSystemUtility:
         else:
             if pivot == 'INDIVIDUAL':
                 pivot = 'MEDIAN'
-
+            
             #if pivot == 'ACTIVE':
             #    print(self.pivots)
-
+            
             try:
                 return self.pivots[pivot]
             except:
                 return Vector()
-
+    
     def get_matrix(self, name=None, relative=None, pivot=None):
         if not name:
             name = self.tou.get()
-
+        
         matrix = self.tou.get_matrix(name)
-
+        
         if isinstance(pivot, Vector):
             pos = pivot
         else:
             pos = self.get_origin(name, relative, pivot)
-
+        
         return to_matrix4x4(matrix, pos)
 
 # ====== TRANSFORM ORIENTATION UTILITIES ====== #
@@ -1833,70 +1850,70 @@ class TransformOrientationUtility:
         'GLOBAL', 'LOCAL', 'VIEW', 'NORMAL', 'GIMBAL',
         "Scaled", "Surface",
     }
-
+    
     def __init__(self, scene, v3d, rv3d):
         self.scene = scene
         self.v3d = v3d
         self.rv3d = rv3d
-
+        
         self.custom_systems = [item for item in scene.orientations \
             if item.name not in self.special_systems]
-
+        
         self.is_custom = False
         self.custom_id = -1
-
+        
         # This is calculated elsewhere
         self.normal_system = None
-
+        
         self.set(v3d.transform_orientation)
-
+    
     def get(self):
         return self.transform_orientation
-
+    
     def get_title(self):
         if self.is_custom:
             return self.transform_orientation
-
+        
         name = self.transform_orientation
         return name[:1].upper() + name[1:].lower()
-
+    
     def set(self, name):
         if isinstance(name, int):
             n = len(self.custom_systems)
             if n == 0:
                 # No custom systems, do nothing
                 return
-
+            
             increment = name
-
+            
             if self.is_custom:
                 # If already custom, switch to next custom system
                 self.custom_id = (self.custom_id + increment) % n
-
+            
             self.is_custom = True
-
+            
             name = self.custom_systems[self.custom_id].name
         else:
             self.is_custom = name not in self.predefined_systems
-
+            
             if self.is_custom:
                 self.custom_id = next((i for i, v in \
                     enumerate(self.custom_systems) if v.name == name), -1)
-
+            
             if name in self.special_systems:
                 # Ensure such system exists
                 self.get_custom(name)
-
+        
         self.transform_orientation = name
-
+        
         self.v3d.transform_orientation = name
-
+    
     def get_matrix(self, name=None):
         active_obj = self.scene.objects.active
-
+        
         if not name:
             name = self.transform_orientation
-
+        
         if self.is_custom:
             matrix = self.custom_systems[self.custom_id].matrix.copy()
         else:
@@ -1916,9 +1933,9 @@ class TransformOrientationUtility:
                     matrix[0].normalize()
                     matrix[1].normalize()
                     matrix[2].normalize()
-
+        
         return matrix
-
+    
     def get_custom(self, name):
         try:
             return self.scene.orientations[name]
@@ -1930,29 +1947,29 @@ class TransformOrientationUtility:
 def create_transform_orientation(scene, name=None, matrix=None):
     active_obj = scene.objects.active
     prev_mode = None
-
+    
     if active_obj:
         prev_mode = active_obj.mode
         bpy.ops.object.mode_set(mode='OBJECT')
     else:
         bpy.ops.object.add()
-
+    
     # ATTENTION! This uses context's scene
     bpy.ops.transform.create_orientation()
-
+    
     tfm_orient = scene.orientations[-1]
-
+    
     if name is not None:
         tfm_orient.name = name
-
+    
     if matrix:
         tfm_orient.matrix = matrix.to_3x3()
-
+    
     if active_obj:
         bpy.ops.object.mode_set(mode=prev_mode)
     else:
         bpy.ops.object.delete()
-
+    
     return tfm_orient
 
 # ====== VIEW UTILITY CLASS ====== #
@@ -1973,18 +1990,18 @@ class ViewUtility:
             Vector((xy[0], xy[1], 1)),
             False),
     )
-
+    
     def __init__(self, region, space_data, region_data):
         self.region = region
         self.space_data = space_data
         self.region_data = region_data
-
+        
         if space_data.type == 'VIEW_3D':
             self.implementation = View3DUtility(
                 region, space_data, region_data)
         else:
             self.implementation = None
-
+        
         if self.implementation:
             for name in self.methods:
                 setattr(self, name,
@@ -1995,30 +2012,30 @@ class ViewUtility:
 
 class View3DUtility:
     lock_types = {"lock_cursor":False, "lock_object":None, "lock_bone":""}
-
+    
     # ====== INITIALIZATION / CLEANUP ====== #
     def __init__(self, region, space_data, region_data):
         self.region = region
         self.space_data = space_data
         self.region_data = region_data
-
+    
     # ====== GET VIEW MATRIX AND ITS COMPONENTS ====== #
     def get_locks(self):
         v3d = self.space_data
         return {k:getattr(v3d, k) for k in self.lock_types}
-
+    
     def set_locks(self, locks):
         v3d = self.space_data
         for k in self.lock_types:
             setattr(v3d, k, locks.get(k, self.lock_types[k]))
-
+    
     def _get_lock_obj_bone(self):
         v3d = self.space_data
-
+        
         obj = v3d.lock_object
         if not obj:
             return None, None
-
+        
         if v3d.lock_bone:
             try:
                 # this is not tested!
@@ -2028,21 +2045,21 @@ class View3DUtility:
                     bone = obj.data.bones[v3d.lock_bone]
             except:
                 bone = None
-
+        
         return obj, bone
-
+    
     # TODO: learn how to get these values from
     # rv3d.perspective_matrix and rv3d.view_matrix ?
     def get_position(self, no_locks=False):
         v3d = self.space_data
         rv3d = self.region_data
-
+        
         if no_locks:
             return rv3d.view_location.copy()
-
+        
         # rv3d.perspective_matrix and rv3d.view_matrix
         # seem to have some weird translation components %)
-
+        
         if rv3d.view_perspective == 'CAMERA':
             p = v3d.camera.matrix_world.to_translation()
             d = self.get_direction()
@@ -2055,92 +2072,95 @@ class View3DUtility:
                 else:
                     return obj.matrix_world.to_translation()
             elif v3d.lock_cursor:
-                return v3d.cursor_location.copy()
+                return get_cursor_location(v3d=v3d)
             else:
                 return rv3d.view_location.copy()
-
+    
     def set_position(self, pos, no_locks=False):
         v3d = self.space_data
         rv3d = self.region_data
-
+        
         pos = pos.copy()
-
+        
         if no_locks:
             rv3d.view_location = pos
             return
-
+        
         if rv3d.view_perspective == 'CAMERA':
             d = self.get_direction()
-            v3d.camera.matrix_world[3][:3] = pos - d * rv3d.view_distance
+            v3d.camera.matrix_world.translation[:3] = pos - d * rv3d.view_distance
         else:
             if v3d.lock_object:
                 obj, bone = self._get_lock_obj_bone()
                 if bone:
-                    bone.matrix[3][:3] = obj.matrix_world.inverted() * pos
+                    try:
+                        bone.matrix.translation[:3] = obj.matrix_world.inverted() * pos
+                    except:
+                        # this is some degenerate object
+                        bone.matrix.translation[:3] = pos
                 else:
-                    obj.matrix_world[3][:3] = pos
+                    obj.matrix_world.translation[:3] = pos
             elif v3d.lock_cursor:
-                #v3d.cursor_location = pos
                 set_cursor_location(pos, v3d=v3d)
             else:
                 rv3d.view_location = pos
-
+    
     def get_rotation(self):
         v3d = self.space_data
         rv3d = self.region_data
-
+        
         if rv3d.view_perspective == 'CAMERA':
             return v3d.camera.matrix_world.to_quaternion()
         else:
             return rv3d.view_rotation
-
+    
     def get_direction(self):
         # Camera (as well as viewport) looks in the direction of -Z;
         # Y is up, X is left
         d = self.get_rotation() * Vector((0, 0, -1))
         d.normalize()
         return d
-
+    
     def get_viewpoint(self):
         v3d = self.space_data
         rv3d = self.region_data
-
+        
         if rv3d.view_perspective == 'CAMERA':
             return v3d.camera.matrix_world.to_translation()
         else:
             p = self.get_position()
             d = self.get_direction()
             return p - d * rv3d.view_distance
-
+    
     def get_matrix(self):
         m = self.get_rotation().to_matrix()
         m.resize_4x4()
-        m[3][:3] = self.get_viewpoint()
+        m.translation[:3] = self.get_viewpoint()
         return m
-
+    
     def get_point(self, xy, pos):
         region = self.region
         rv3d = self.region_data
         return region_2d_to_location_3d(region, rv3d, xy, pos)
-
+    
     def get_ray(self, xy):
         region = self.region
         v3d = self.space_data
         rv3d = self.region_data
-
+        
         viewPos = self.get_viewpoint()
         viewDir = self.get_direction()
-
+        
         near = viewPos + viewDir * v3d.clip_start
         far = viewPos + viewDir * v3d.clip_end
-
+        
         a = region_2d_to_location_3d(region, rv3d, xy, near)
         b = region_2d_to_location_3d(region, rv3d, xy, far)
-
+        
         # When viewed from in-scene camera, near and far
         # planes clip geometry even in orthographic mode.
         clip = rv3d.is_perspective or (rv3d.view_perspective == 'CAMERA')
-
+        
         return a, b, clip
 
 # ====== SNAP UTILITY CLASS ====== #
@@ -2152,19 +2172,19 @@ class SnapUtility:
             self.implementation = Snap3DUtility(context.scene, shade)
             self.implementation.update_targets(
                 context.visible_objects, [])
-
+    
     def dispose(self):
         self.implementation.dispose()
-
+    
     def update_targets(self, to_include, to_exclude):
         self.implementation.update_targets(to_include, to_exclude)
-
+    
     def set_modes(self, **kwargs):
         return self.implementation.set_modes(**kwargs)
-
+    
     def snap(self, *args, **kwargs):
         return self.implementation.snap(*args, **kwargs)
-
+    
 class SnapUtilityBase:
     def __init__(self):
         self.targets = set()
@@ -2175,11 +2195,11 @@ class SnapUtilityBase:
         self.projection = [None, None, None]
         self.potential_snap_elements = None
         self.extra_snap_points = None
-
+    
     def update_targets(self, to_include, to_exclude):
         self.targets.update(to_include)
         self.targets.difference_update(to_exclude)
-
+    
     def set_modes(self, **kwargs):
         if "use_relative_coords" in kwargs:
             self.use_relative_coords = kwargs["use_relative_coords"]
@@ -2196,42 +2216,42 @@ class SnapUtilityBase:
         if "axes_coords" in kwargs:
             # none, point, line, plane
             self.axes_coords = kwargs["axes_coords"]
-
+    
     # ====== CURSOR REPOSITIONING ====== #
     def snap(self, xy, src_matrix, initial_matrix, do_raycast, \
         alt_snap, vu, csu, modify_Surface, use_object_centers):
-
+        
         grid_step = self.grid_steps[alt_snap]
-
+        
         su = self
         use_relative_coords = su.use_relative_coords
         snap_align = su.snap_align
         axes_coords = su.axes_coords
         snap_type = su.snap_type
-
+        
         runtime_settings = find_runtime_settings()
-
+        
         matrix = src_matrix.to_3x3()
         pos = src_matrix.to_translation().copy()
-
+        
         sys_matrix = csu.get_matrix()
         if use_relative_coords:
-            sys_matrix[3] = initial_matrix[3].copy()
-
+            sys_matrix.translation = initial_matrix.translation.copy()
+        
         # Axes of freedom and line/plane parameters
         start = Vector(((0 if v is None else v) for v in axes_coords))
         direction = Vector(((v is not None) for v in axes_coords))
         axes_of_freedom = 3 - int(sum(direction))
-
+        
         # do_raycast is False when mouse is not moving
         if do_raycast:
             su.hide_bbox(True)
-
+            
             self.potential_snap_elements = None
             self.extra_snap_points = None
-
+            
             set_stick_obj(csu.tou.scene, None)
-
+            
             raycast = None
             snap_to_obj = (snap_type != 'INCREMENT') #or use_object_centers
             snap_to_obj = snap_to_obj and (snap_type is not None)
@@ -2239,16 +2259,16 @@ class SnapUtilityBase:
                 a, b, clip = vu.get_ray(xy)
                 view_dir = vu.get_direction()
                 raycast = su.snap_raycast(a, b, clip, view_dir, csu, alt_snap)
-
+            
             if raycast:
                 surf_matrix, face_id, obj, orig_obj = raycast
-
+                
                 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
                     ]
-
+                
                 if use_object_centers:
                     self.extra_snap_points = [obj.matrix_world.to_translation()]
                 elif alt_snap:
@@ -2266,21 +2286,26 @@ class SnapUtilityBase:
                         for v1 in pse:
                             v0 += v1
                         self.extra_snap_points.append(v0 / n)
-
+                
                 if snap_align:
                     matrix = surf_matrix.to_3x3()
-
+                
                 if not use_object_centers:
                     pos = surf_matrix.to_translation()
                 else:
                     pos = orig_obj.matrix_world.to_translation()
-
-                set_stick_obj(csu.tou.scene, orig_obj.name,
-                    orig_obj.matrix_world.inverted() * pos)
-
+                
+                try:
+                    local_pos = orig_obj.matrix_world.inverted() * pos
+                except:
+                    # this is some degenerate object
+                    local_pos = pos
+                
+                set_stick_obj(csu.tou.scene, orig_obj.name, local_pos)
+                
                 modify_Surface = modify_Surface and \
                     (snap_type != 'VOLUME') and (not use_object_centers)
-
+                
                 # === Update "Surface" orientation === #
                 if modify_Surface:
                     # Use raycast[0], not matrix! If snap_align == False,
@@ -2300,13 +2325,13 @@ class SnapUtilityBase:
                 else:
                     a, b, clip = vu.get_ray(xy)
                     view_dir = vu.get_direction()
-
+                    
                     start = sys_matrix * start
-
+                    
                     if axes_of_freedom == 1:
                         direction = Vector((1, 1, 1)) - direction
                     direction.rotate(sys_matrix)
-
+                    
                     if axes_of_freedom == 2:
                         # Constrained in one axis. Find intersection with plane.
                         i_p = intersect_line_plane(a, b, start, direction)
@@ -2318,47 +2343,51 @@ class SnapUtilityBase:
                         if i_p is not None:
                             pos = i_p[1]
         #end if do_raycast
-
-        sys_matrix_inv = sys_matrix.inverted()
-
+        
+        try:
+            sys_matrix_inv = sys_matrix.inverted()
+        except:
+            # this is some degenerate system
+            sys_matrix_inv = Matrix()
+        
         _pos = sys_matrix_inv * pos
-
+        
         # don't snap when mouse hasn't moved
         if (snap_type == 'INCREMENT') and do_raycast:
             for i in range(3):
                 _pos[i] = round_step(_pos[i], grid_step)
-
+        
         for i in range(3):
             if axes_coords[i] is not None:
                 _pos[i] = axes_coords[i]
-
+        
         if (snap_type == 'INCREMENT') or (axes_of_freedom != 3):
             pos = sys_matrix * _pos
-
+        
         res_matrix = to_matrix4x4(matrix, pos)
-
+        
         CursorDynamicSettings.local_matrix = \
             sys_matrix_inv * res_matrix
-
+        
         return res_matrix
 
 class Snap3DUtility(SnapUtilityBase):
     grid_steps = {False:1.0, True:0.1}
-
+    
     cube_verts = [Vector((i, j, k))
         for i in (-1, 1)
         for j in (-1, 1)
         for k in (-1, 1)]
-
+    
     def __init__(self, scene, shade):
         SnapUtilityBase.__init__(self)
-
+        
         self.cache = MeshCache(scene)
-
+        
         # ? seems that dict is enough
         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)
         self.bbox_obj = self.cache.create_temporary_mesh_obj(mesh, Matrix())
@@ -2368,40 +2397,40 @@ class Snap3DUtility(SnapUtilityBase):
         # make it displayable
         #self.cache.scene.objects.link(self.bbox_obj)
         #self.cache.scene.update()
-
+        
         self.shade_bbox = (shade == 'BOUNDBOX')
-
+    
     def update_targets(self, to_include, to_exclude):
         settings = find_settings()
         tfm_opts = settings.transform_options
         only_solid = tfm_opts.snap_only_to_solid
-
+        
         # Ensure this is a set and not some other
         # type of collection
         to_exclude = set(to_exclude)
-
+        
         for target in to_include:
             if only_solid and ((target.draw_type == 'BOUNDS') \
                     or (target.draw_type == 'WIRE')):
                 to_exclude.add(target)
-
+        
         SnapUtilityBase.update_targets(self, to_include, to_exclude)
-
+    
     def dispose(self):
         self.hide_bbox(True)
-
+        
         mesh = self.bbox_obj.data
         bpy.data.objects.remove(self.bbox_obj)
         bpy.data.meshes.remove(mesh)
-
+        
         self.cache.dispose()
-
+    
     def hide_bbox(self, hide):
         if self.bbox_obj.hide == hide:
             return
-
+        
         self.bbox_obj.hide = hide
-
+        
         # We need to unlink bbox until required to show it,
         # because otherwise outliner will blink each
         # time cursor is clicked
@@ -2409,21 +2438,25 @@ class Snap3DUtility(SnapUtilityBase):
             self.cache.scene.objects.unlink(self.bbox_obj)
         else:
             self.cache.scene.objects.link(self.bbox_obj)
-
+    
     def get_bbox_obj(self, obj, sys_matrix, sys_matrix_inv, is_local):
         if is_local:
             bbox = None
         else:
             bbox = self.bbox_cache.get(obj, None)
-
+        
         if bbox is None:
             m = obj.matrix_world
             if is_local:
                 sys_matrix = m.copy()
-                sys_matrix_inv = sys_matrix.inverted()
+                try:
+                    sys_matrix_inv = sys_matrix.inverted()
+                except:
+                    # this is some degenerate system
+                    sys_matrix_inv = Matrix()
             m_combined = sys_matrix_inv * m
             bbox = [None, None]
-
+            
             mesh_obj = self.cache[obj, True, self.editmode]
             if (mesh_obj is None) or self.shade_bbox or \
                     (obj.draw_type == 'BOUNDS'):
@@ -2437,31 +2470,31 @@ class Snap3DUtility(SnapUtilityBase):
             else:
                 for v in mesh_obj.data.vertices:
                     extend_bbox(bbox, m_combined * v.co.copy())
-
+            
             bbox = (Vector(bbox[0]), Vector(bbox[1]))
-
+            
             if not is_local:
                 self.bbox_cache[obj] = bbox
-
+        
         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[3][:3] = sys_matrix * (bbox[0] + half)
+        m.translation[:3] = sys_matrix * (bbox[0] + half)
         self.bbox_obj.matrix_world = m
-
+        
         return self.bbox_obj
-
+    
     # TODO: ?
     # - Sort snap targets according to raycasted distance?
     # - Ignore targets if their bounding sphere is further
     #   than already picked position?
     # Perhaps these "optimizations" aren't worth the overhead.
-
+    
     def raycast(self, a, b, clip, view_dir, is_bbox, \
                 sys_matrix, sys_matrix_inv, is_local, x_ray):
         # If we need to interpolate normals or snap to
@@ -2472,20 +2505,20 @@ class Snap3DUtility(SnapUtilityBase):
         # we need to get face at least to find tangential.
         force = True
         edit = self.editmode
-
+        
         res = None
         L = None
-
+        
         for obj in self.targets:
             orig_obj = obj
-
+            
             if obj.name == self.bbox_obj.name:
                 # is there a better check?
                 # ("a is b" doesn't work here)
                 continue
             if obj.show_x_ray != x_ray:
                 continue
-
+            
             if is_bbox:
                 obj = self.get_bbox_obj(obj, \
                     sys_matrix, sys_matrix_inv, is_local)
@@ -2493,12 +2526,16 @@ class Snap3DUtility(SnapUtilityBase):
                 # Outside of BBox, there is no meaningful visual snapping
                 # for such display mode
                 continue
-
+            
             m = obj.matrix_world.copy()
-            mi = m.inverted()
+            try:
+                mi = m.inverted()
+            except:
+                # this is some degenerate object
+                continue
             la = mi * a
             lb = mi * b
-
+            
             # Bounding sphere check (to avoid unnecesary conversions
             # and to make ray 'infinite')
             bb_min = Vector(obj.bound_box[0])
@@ -2508,37 +2545,37 @@ class Snap3DUtility(SnapUtilityBase):
             sec = intersect_line_sphere(la, lb, c, r, False)
             if sec[0] is None:
                 continue # no intersection with the bounding sphere
-
+            
             if not is_bbox:
                 # Ensure we work with raycastable object.
                 obj = self.cache[obj, force, edit]
                 if obj is None:
                     continue # the object has no geometry
-
+            
             # If ray must be infinite, ensure that
             # endpoints are outside of bounding volume
             if not clip:
                 # Seems that intersect_line_sphere()
                 # returns points in flipped order
                 lb, la = sec
-
+            
             # Does ray actually intersect something?
             lp, ln, face_id = obj.ray_cast(la, lb)
             if face_id == -1:
                 continue
-
+            
             # transform position to global space
             p = m * lp
-
+            
             # This works both for prespective and ortho
             l = p.dot(view_dir)
             if (L is None) or (l < L):
                 res = (lp, ln, face_id, obj, p, m, la, lb, orig_obj)
                 L = l
         #end for
-
+        
         return res
-
+    
     # Returns:
     # Matrix(X -- tangential,
     #        Y -- 2nd tangential,
@@ -2549,16 +2586,16 @@ class Snap3DUtility(SnapUtilityBase):
     def snap_raycast(self, a, b, clip, view_dir, csu, alt_snap):
         settings = find_settings()
         tfm_opts = settings.transform_options
-
+        
         if self.shade_bbox and tfm_opts.snap_only_to_solid:
             return None
-
+        
         # Since introduction of "use object centers",
         # this check is useless (use_object_centers overrides
         # even INCREMENT snapping)
         #if self.snap_type not in {'VERTEX', 'EDGE', 'FACE', 'VOLUME'}:
         #    return None
-
+        
         # key shouldn't depend on system origin;
         # for bbox calculation origin is always zero
         #if csu.tou.get() != "Surface":
@@ -2569,45 +2606,49 @@ class Snap3DUtility(SnapUtilityBase):
         sys_matrix_key = list(c for v in sys_matrix for c in v)
         sys_matrix_key.append(self.editmode)
         sys_matrix = sys_matrix.to_4x4()
-        sys_matrix_inv = sys_matrix.inverted()
-
+        try:
+            sys_matrix_inv = sys_matrix.inverted()
+        except:
+            # this is some degenerate system
+            return None
+        
         if self.sys_matrix_key != sys_matrix_key:
             self.bbox_cache.clear()
             self.sys_matrix_key = sys_matrix_key
-
+        
         # In this context, Volume represents BBox :P
         is_bbox = (self.snap_type == 'VOLUME')
         is_local = (csu.tou.get() in \
             {'LOCAL', "Scaled"})
-
+        
         res = self.raycast(a, b, clip, view_dir, \
             is_bbox, sys_matrix, sys_matrix_inv, is_local, True)
-
+        
         if res is None:
             res = self.raycast(a, b, clip, view_dir, \
                 is_bbox, sys_matrix, sys_matrix_inv, is_local, False)
-
+        
         # Occlusion-based edge/vertex snapping will be
         # too inefficient in Python (well, even without
         # the occlusion, iterating over all edges/vertices
         # of each object is inefficient too)
-
+        
         if not res:
             return None
-
+        
         lp, ln, face_id, obj, p, m, la, lb, orig_obj = res
-
+        
         if is_bbox:
             self.bbox_obj.matrix_world = m.copy()
             self.bbox_obj.show_x_ray = orig_obj.show_x_ray
             self.hide_bbox(False)
-
+        
         _ln = ln.copy()
-
+        
         face = obj.data.faces[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
@@ -2617,7 +2658,7 @@ class Snap3DUtility(SnapUtilityBase):
                     ln = obj.data.vertices[v0].normal.copy()
                     #t1 = ln.cross(_ln)
                     L = l
-
+            
             _ln = ln.copy()
             '''
             if t1.length < epsilon:
@@ -2633,7 +2674,7 @@ class Snap3DUtility(SnapUtilityBase):
                 use_smooth = False
             elif self.interpolation == 'ALWAYS':
                 use_smooth = True
-
+            
             for v0, v1 in face.edge_keys:
                 p0 = obj.data.vertices[v0].co
                 p1 = obj.data.vertices[v1].co
@@ -2654,21 +2695,21 @@ class Snap3DUtility(SnapUtilityBase):
                              obj.data.vertices[v0].normal * (1.0 - q)
                         t1 = dp
                         L = l
-
+            
             p = m * p
         else:
             if alt_snap:
                 lp = face.center
                 p = m * lp
-
+            
             if self.interpolation != 'NEVER':
                 ln = self.interpolate_normal(
                     obj, face_id, lp, la, lb - la)
-
-            # Comment this to make 1st tangential
+            
+            # Comment this to make 1st tangential 
             # always lie in the face's plane
             _ln = ln.copy()
-
+            
             '''
             for v0, v1 in face.edge_keys:
                 p0 = obj.data.vertices[v0].co
@@ -2682,11 +2723,11 @@ class Snap3DUtility(SnapUtilityBase):
                         t1 = dp
                         L = l
             '''
-
+        
         n = ln#.copy()
         n.rotate(m)
         n.normalize()
-
+        
         if t1 is None:
             _ln.rotate(m)
             _ln.normalize()
@@ -2698,45 +2739,45 @@ class Snap3DUtility(SnapUtilityBase):
         else:
             t1.rotate(m)
             t1.normalize()
-
+        
         t2 = t1.cross(n)
         t2.normalize()
-
+        
         matrix = Matrix()
         matrix[0][:3] = t1
         matrix[1][:3] = t2
         matrix[2][:3] = n
-        matrix[3][:3] = p
-
+        matrix.translation[:3] = p
+        
         return (matrix, face_id, obj, orig_obj)
-
+    
     def interpolate_normal(self, obj, face_id, p, orig, ray):
         face = obj.data.faces[face_id]
-
+        
         use_smooth = face.use_smooth
         if self.interpolation == 'NEVER':
             use_smooth = False
         elif self.interpolation == 'ALWAYS':
             use_smooth = True
-
+        
         if not use_smooth:
             return face.normal.copy()
-
+        
         # edge.use_edge_sharp affects smoothness only if
         # mesh has EdgeSplit modifier
-
+        
         # ATTENTION! Coords/Normals MUST be copied
         # (a bug in barycentric_transform implementation ?)
         # Somewhat strangely, the problem also disappears
         # if values passed to barycentric_transform
         # are print()ed beforehand.
-
+        
         co = [obj.data.vertices[vi].co.copy()
             for vi in face.vertices]
-
+        
         normals = [obj.data.vertices[vi].normal.copy()
             for vi in face.vertices]
-
+        
         if len(face.vertices) != 3:
             tris = tesselate_polygon([co])
             for tri in tris:
@@ -2745,11 +2786,11 @@ class Snap3DUtility(SnapUtilityBase):
                     break
         else:
             i0, i1, i2 = 0, 1, 2
-
+        
         n = barycentric_transform(p, co[i0], co[i1], co[i2],
             normals[i0], normals[i1], normals[i2])
         n.normalize()
-
+        
         return n
 
 # ====== CONVERTED-TO-MESH OBJECTS CACHE ====== #
@@ -2757,41 +2798,41 @@ class MeshCache:
     # ====== INITIALIZATION / CLEANUP ====== #
     def __init__(self, scene):
         self.scene = scene
-
+        
         self.mesh_cache = {}
         self.object_cache = {}
         self.edit_object = None
-
+    
     def dispose(self):
         if self.edit_object:
             mesh = self.edit_object.data
             bpy.data.objects.remove(self.edit_object)
             bpy.data.meshes.remove(mesh)
         del self.edit_object
-
+        
         for rco in self.object_cache.values():
             if rco:
                 bpy.data.objects.remove(rco)
         del self.object_cache
-
+    
         for mesh in self.mesh_cache.values():
             bpy.data.meshes.remove(mesh)
         del self.mesh_cache
-
+    
     # ====== GET RELEVANT MESH/OBJECT ====== #
     def __convert(self, obj, force=False, apply_modifiers=True, \
                   add_to_cache=True):
         # In Edit (and Sculpt?) mode mesh will not reflect
         # changes until mode is changed to Object.
         unstable_shape = ('EDIT' in obj.mode) or ('SCULPT' in obj.mode)
-
+        
         force = force or (len(obj.modifiers) != 0)
-
+        
         if (obj.data.bl_rna.name == 'Mesh'):
             if not (force or unstable_shape):
                 # Existing mesh actually satisfies us
                 return obj
-
+        
         #if (not force) and (obj.data in self.mesh_cache):
         if obj.data in self.mesh_cache:
             mesh = self.mesh_cache[obj.data]
@@ -2799,31 +2840,31 @@ class MeshCache:
             if unstable_shape:
                 prev_mode = obj.mode
                 bpy.ops.object.mode_set(mode='OBJECT')
-
+            
             mesh = obj.to_mesh(self.scene, apply_modifiers, 'PREVIEW')
             mesh.name = tmp_name
-
+            
             if unstable_shape:
                 bpy.ops.object.mode_set(mode=prev_mode)
-
+            
             if add_to_cache:
                 self.mesh_cache[obj.data] = mesh
-
+        
         rco = self.create_temporary_mesh_obj(mesh, obj.matrix_world)
         rco.show_x_ray = obj.show_x_ray # necessary for corrent bbox display
-
+        
         return rco
-
+    
     def __getitem__(self, args):
         # If more than one argument is passed to getitem,
         # Python wraps them into tuple.
         if not isinstance(args, tuple):
             args = (args,)
-
+        
         obj = args[0]
         force = (args[1] if len(args) > 1 else True)
         edit = (args[2] if len(args) > 2 else False)
-
+        
         # Currently edited object's raw mesh is a separate issue...
         if edit and obj.data and ('EDIT' in obj.mode):
             if (obj.data.bl_rna.name == 'Mesh'):
@@ -2831,38 +2872,38 @@ class MeshCache:
                     self.edit_object = self.__convert(
                                 obj, True, False, False)
                 return self.edit_object
-
+        
         # A usual object. Cached data will suffice.
         if obj in self.object_cache:
             return self.object_cache[obj]
-
+        
         # Actually, convert and cache.
         try:
             rco = self.__convert(obj, force)
-
+            
             if rco is obj:
                 # Source objects are not added to cache
                 return obj
         except Exception as e:
             # Object has no solid geometry, just ignore it
             rco = None
-
+        
         self.object_cache[obj] = rco
-
+        
         return rco
-
+    
     def create_temporary_mesh_obj(self, mesh, matrix=None):
         rco = bpy.data.objects.new(tmp_name, mesh)
         if matrix:
             rco.matrix_world = matrix
-
+        
         # Make Blender recognize object as having geometry
         # (is there a simpler way to do this?)
         self.scene.objects.link(rco)
         self.scene.update()
         # We don't need this object in scene
         self.scene.objects.unlink(rco)
-
+        
         return rco
 
 #============================================================================#
@@ -2877,24 +2918,24 @@ class PseudoIDBlockBase(bpy.types.PropertyGroup):
             # necessary to avoid recursive calls
             if self._self_update[0]:
                 return
-
+            
             if self._dont_rename[0]:
                 return
-
+            
             if len(self.collection) == 0:
                 return
-
+            
             # prepare data for renaming...
             old_key = (self.enum if self.enum else self.collection[0].name)
             new_key = (self.active if self.active else "Untitled")
-
+            
             if old_key == new_key:
                 return
-
+            
             old_item = None
             new_item = None
             existing_names = []
-
+            
             for item in self.collection:
                 if (item.name == old_key) and (not new_item):
                     new_item = item
@@ -2903,10 +2944,10 @@ class PseudoIDBlockBase(bpy.types.PropertyGroup):
                 else:
                     existing_names.append(item.name)
             existing_names.append(new_key)
-
+            
             # rename current item
             new_item.name = new_key
-
+            
             if old_item:
                 # rename other item if it has that name
                 name = new_key
@@ -2915,25 +2956,25 @@ class PseudoIDBlockBase(bpy.types.PropertyGroup):
                     name = "{}.{:0>3}".format(new_key, i)
                     i += 1
                 old_item.name = name
-
+            
             # update the enum
             self._self_update[0] += 1
             self.update_enum()
             self._self_update[0] -= 1
         # end def
-
+        
         def enum_update(self, context):
             # necessary to avoid recursive calls
             if self._self_update[0]:
                 return
-
+            
             self._dont_rename[0] = True
             self.active = self.enum
             self._dont_rename[0] = False
-
+            
             self.on_item_select()
         # end def
-
+        
         collection = bpy.props.CollectionProperty(
             type=type)
         active = bpy.props.StringProperty(
@@ -2948,47 +2989,47 @@ class PseudoIDBlockBase(bpy.types.PropertyGroup):
             default=set(),
             options={'ENUM_FLAG'},
             update=enum_update)
-
+        
         return collection, active, enum
     # end def
-
+    
     def add(self, name="", **kwargs):
         if not name:
             name = 'Untitled'
         _name = name
-
+        
         existing_names = [item.name for item in self.collection]
         i = 1
         while name in existing_names:
             name = "{}.{:0>3}".format(_name, i)
             i += 1
-
+        
         instance = self.collection.add()
         instance.name = name
-
+        
         for key, value in kwargs.items():
             setattr(instance, key, value)
-
+        
         self._self_update[0] += 1
         self.active = name
         self.update_enum()
         self._self_update[0] -= 1
-
+        
         return instance
-
+    
     def remove(self, key):
         if isinstance(key, int):
             i = key
         else:
             i = self.indexof(key)
-
+        
         # Currently remove() ignores non-existing indices...
         # In the case this behavior changes, we have the try block.
         try:
             self.collection.remove(i)
         except:
             pass
-
+        
         self._self_update[0] += 1
         if len(self.collection) != 0:
             i = min(i, len(self.collection) - 1)
@@ -2997,7 +3038,7 @@ class PseudoIDBlockBase(bpy.types.PropertyGroup):
             self.active = ""
         self.update_enum()
         self._self_update[0] -= 1
-
+    
     def get_item(self, key=None):
         if key is None:
             i = self.indexof(self.active)
@@ -3005,30 +3046,30 @@ class PseudoIDBlockBase(bpy.types.PropertyGroup):
             i = key
         else:
             i = self.indexof(key)
-
+        
         try:
             return self.collection[i]
         except:
             return None
-
+    
     def indexof(self, key):
         return next((i for i, v in enumerate(self.collection) \
             if v.name == key), -1)
-
+        
         # Which is more Pythonic?
-
+        
         #for i, item in enumerate(self.collection):
         #    if item.name == key:
         #        return i
         #return -1 # non-existing index
-
+    
     def update_enum(self):
         names = []
         items = []
         for item in self.collection:
             names.append(item.name)
             items.append((item.name, item.name, ""))
-
+        
         prop_class, prop_params = type(self).enum
         prop_params["items"] = items
         if len(items) == 0:
@@ -3041,22 +3082,22 @@ class PseudoIDBlockBase(bpy.types.PropertyGroup):
                 self.active = items[0][0]
             prop_params["default"] = self.active
             prop_params["options"] = set()
-
+        
         # Can this cause problems? In the near future, shouldn't...
         type(self).enum = (prop_class, prop_params)
         #type(self).enum = bpy.props.EnumProperty(**prop_params)
-
+        
         if len(items) != 0:
             self.enum = self.active
-
+    
     def on_item_select(self):
         pass
-
+    
     data_name = ""
     op_new = ""
     op_delete = ""
     icon = 'DOT'
-
+    
     def draw(self, context, layout):
         if len(self.collection) == 0:
             if self.op_new:
@@ -3066,7 +3107,7 @@ class PseudoIDBlockBase(bpy.types.PropertyGroup):
                     text="({})".format(self.data_name),
                     icon=self.icon)
             return
-
+        
         row = layout.row(align=True)
         row.prop_menu_enum(self, "enum", text="", icon=self.icon)
         row.prop(self, "active", text="")
@@ -3081,24 +3122,24 @@ class PseudoIDBlockBase(bpy.types.PropertyGroup):
 # ===== TRANSFORM EXTRA OPTIONS ===== #
 class TransformExtraOptionsProp(bpy.types.PropertyGroup):
     use_relative_coords = bpy.props.BoolProperty(
-        name="Relative coordinates",
-        description="Consider existing transformation as the strating point",
+        name="Relative coordinates", 
+        description="Consider existing transformation as the strating point", 
         default=True)
     snap_interpolate_normals_mode = bpy.props.EnumProperty(
         items=[('NEVER', "Never", "Don't interpolate normals"),
                ('ALWAYS', "Always", "Always interpolate normals"),
                ('SMOOTH', "Smoothness-based", "Interpolate normals only "\
                "for faces with smooth shading"),],
-        name="Normal interpolation",
-        description="Normal interpolation mode for snapping",
+        name="Normal interpolation", 
+        description="Normal interpolation mode for snapping", 
         default='SMOOTH')
     snap_only_to_solid = bpy.props.BoolProperty(
-        name="Snap only to soild",
-        description="Ignore wireframe/non-solid objects during snapping",
+        name="Snap only to soild", 
+        description="Ignore wireframe/non-solid objects during snapping", 
         default=False)
     snap_element_screen_size = bpy.props.IntProperty(
-        name="Snap distance",
-        description="Radius in pixels for snapping to edges/vertices",
+        name="Snap distance", 
+        description="Radius in pixels for snapping to edges/vertices", 
         default=8,
         min=2,
         max=64)
@@ -3112,12 +3153,12 @@ class LocationProp(bpy.types.PropertyGroup):
 # ===== HISTORY ===== #
 def update_history_max_size(self, context):
     settings = find_settings()
-
+    
     history = settings.history
-
+    
     prop_class, prop_params = type(history).current_id
     old_max = prop_params["max"]
-
+    
     size = history.max_size
     try:
         int_size = int(size)
@@ -3125,32 +3166,31 @@ def update_history_max_size(self, context):
         int_size = min(int_size, history.max_size_limit)
     except:
         int_size = old_max
-
+    
     if old_max != int_size:
         prop_params["max"] = int_size
         type(history).current_id = (prop_class, prop_params)
-
+    
     # also: clear immediately?
     for i in range(len(history.entries) - 1, int_size, -1):
         history.entries.remove(i)
-
+    
     if str(int_size) != size:
         # update history.max_size if it's not inside the limits
         history.max_size = str(int_size)
 
 def update_history_id(self, context):
     scene = bpy.context.scene
-
+    
     settings = find_settings()
     history = settings.history
-
+    
     pos = history.get_pos()
     if pos is not None:
-        cursor_pos = scene.cursor_location.copy()
+        cursor_pos = get_cursor_location(scene=scene)
         if pos != cursor_pos:
-            #scene.cursor_location = pos.copy()
             set_cursor_location(pos, scene=scene)
-
+            
             if (history.current_id == 0) and (history.last_id <= 1):
                 history.last_id = 1
             else:
@@ -3159,7 +3199,7 @@ def update_history_id(self, context):
 
 class CursorHistoryProp(bpy.types.PropertyGroup):
     max_size_limit = 500
-
+    
     show_trace = bpy.props.BoolProperty(
         name="Trace",
         description="Show history trace",
@@ -3178,29 +3218,29 @@ class CursorHistoryProp(bpy.types.PropertyGroup):
         update=update_history_id)
     entries = bpy.props.CollectionProperty(
         type=LocationProp)
-
+    
     curr_id = bpy.props.IntProperty(options={'HIDDEN'})
     last_id = bpy.props.IntProperty(options={'HIDDEN'})
-
+    
     def get_pos(self, id = None):
         if id is None:
             id = self.current_id
-
+        
         id = min(max(id, 0), len(self.entries) - 1)
-
+        
         if id < 0:
             # history is empty
             return None
-
+        
         return self.entries[id].pos
-
+    
     # for updating the upper bound on file load
     def update_max_size(self):
         prop_class, prop_params = type(self).current_id
         # self.max_size expected to be always a correct integer
         prop_params["max"] = int(self.max_size)
         type(self).current_id = (prop_class, prop_params)
-
+    
     def draw_trace(self, context):
         bgl.glColor4f(0.75, 1.0, 0.75, 1.0)
         bgl.glBegin(bgl.GL_LINE_STRIP)
@@ -3208,14 +3248,14 @@ class CursorHistoryProp(bpy.types.PropertyGroup):
             p = entry.pos
             bgl.glVertex3f(p[0], p[1], p[2])
         bgl.glEnd()
-
+    
     def draw_offset(self, context):
         bgl.glShadeModel(bgl.GL_SMOOTH)
-
+        
         tfm_operator = CursorDynamicSettings.active_transform_operator
-
+        
         bgl.glBegin(bgl.GL_LINE_STRIP)
-
+        
         if tfm_operator:
             p = tfm_operator.particles[0]. \
                 get_initial_matrix().to_translation()
@@ -3223,11 +3263,11 @@ class CursorHistoryProp(bpy.types.PropertyGroup):
             p = self.get_pos(self.last_id)
         bgl.glColor4f(1.0, 0.75, 0.5, 1.0)
         bgl.glVertex3f(p[0], p[1], p[2])
-
-        p = context.scene.cursor_location.copy()
+        
+        p = get_cursor_location(scene=scene)
         bgl.glColor4f(1.0, 1.0, 0.25, 1.0)
         bgl.glVertex3f(p[0], p[1], p[2])
-
+        
         bgl.glEnd()
 
 # ===== BOOKMARK ===== #
@@ -3244,12 +3284,12 @@ class BookmarkIDBlock(PseudoIDBlockBase):
     # callbacks... but class members are.
     _self_update = [0]
     _dont_rename = [False]
-
+    
     data_name = "Bookmark"
     op_new = "scene.cursor_3d_new_bookmark"
     op_delete = "scene.cursor_3d_delete_bookmark"
     icon = 'CURSOR'
-
+    
     collection, active, enum = PseudoIDBlockBase.create_props(
         BookmarkProp, "Bookmark")
 
@@ -3257,49 +3297,49 @@ class NewCursor3DBookmark(bpy.types.Operator):
     bl_idname = "scene.cursor_3d_new_bookmark"
     bl_label = "New Bookmark"
     bl_description = "Add a new bookmark"
-
+    
     name = bpy.props.StringProperty(
         name="Name",
         description="Name of the new bookmark",
         default="Mark")
-
+    
     @classmethod
     def poll(cls, context):
         return context.area.type == 'VIEW_3D'
-
+    
     def execute(self, context):
         settings = find_settings()
         library = settings.libraries.get_item()
         if not library:
             return {'CANCELLED'}
-
+        
         bookmark = library.bookmarks.add(name=self.name)
-
-        cusor_pos = context.scene.cursor_location.copy()
-
+        
+        cusor_pos = get_cursor_location(scene=context.scene)
+        
         try:
             bookmark.pos = library.convert_from_abs(cusor_pos, True)
         except Exception as exc:
             self.report('ERROR_INVALID_CONTEXT', exc.args[0])
             return {'CANCELLED'}
-
+        
         return {'FINISHED'}
 
 class DeleteCursor3DBookmark(bpy.types.Operator):
     bl_idname = "scene.cursor_3d_delete_bookmark"
     bl_label = "Delete Bookmark"
     bl_description = "Delete active bookmark"
-
+    
     def execute(self, context):
         settings = find_settings()
         library = settings.libraries.get_item()
         if not library:
             return {'CANCELLED'}
-
+        
         name = library.bookmarks.active
-
+        
         library.bookmarks.remove(key=name)
-
+        
         return {'FINISHED'}
 
 class OverwriteCursor3DBookmark(bpy.types.Operator):
@@ -3307,99 +3347,97 @@ class OverwriteCursor3DBookmark(bpy.types.Operator):
     bl_label = "Overwrite"
     bl_description = "Overwrite active bookmark "\
         "with the current cursor location"
-
+    
     @classmethod
     def poll(cls, context):
         return context.area.type == 'VIEW_3D'
-
+    
     def execute(self, context):
         settings = find_settings()
         library = settings.libraries.get_item()
         if not library:
             return {'CANCELLED'}
-
+        
         bookmark = library.bookmarks.get_item()
         if not bookmark:
             return {'CANCELLED'}
-
-        cusor_pos = context.scene.cursor_location.copy()
-
+        
+        cusor_pos = get_cursor_location(scene=context.scene)
+        
         try:
             bookmark.pos = library.convert_from_abs(cusor_pos, True)
         except Exception as exc:
             self.report('ERROR_INVALID_CONTEXT', exc.args[0])
             return {'CANCELLED'}
-
+        
         CursorDynamicSettings.recalc_csu(context, 'PRESS')
-
+        
         return {'FINISHED'}
 
 class RecallCursor3DBookmark(bpy.types.Operator):
     bl_idname = "scene.cursor_3d_recall_bookmark"
     bl_label = "Recall"
     bl_description = "Move cursor to the active bookmark"
-
+    
     @classmethod
     def poll(cls, context):
         return context.area.type == 'VIEW_3D'
-
+    
     def execute(self, context):
         settings = find_settings()
         library = settings.libraries.get_item()
         if not library:
             return {'CANCELLED'}
-
+        
         bookmark = library.bookmarks.get_item()
         if not bookmark:
             return {'CANCELLED'}
-
+        
         try:
             bookmark_pos = library.convert_to_abs(bookmark.pos, True)
-            #context.scene.cursor_location = bookmark_pos.copy()
             set_cursor_location(bookmark_pos, scene=context.scene)
         except Exception as exc:
             self.report('ERROR_INVALID_CONTEXT', exc.args[0])
             return {'CANCELLED'}
-
+        
         CursorDynamicSettings.recalc_csu(context)
-
+        
         return {'FINISHED'}
 
 class SwapCursor3DBookmark(bpy.types.Operator):
     bl_idname = "scene.cursor_3d_swap_bookmark"
     bl_label = "Swap"
     bl_description = "Swap cursor position with the active bookmark"
-
+    
     @classmethod
     def poll(cls, context):
         return context.area.type == 'VIEW_3D'
-
+    
     def execute(self, context):
         settings = find_settings()
         library = settings.libraries.get_item()
         if not library:
             return {'CANCELLED'}
-
+        
         bookmark = library.bookmarks.get_item()
         if not bookmark:
             return {'CANCELLED'}
-
-        cusor_pos = context.scene.cursor_location.copy()
-
+        
+        cusor_pos = get_cursor_location(scene=context.scene)
+        
         try:
             bookmark_pos = library.convert_to_abs(bookmark.pos, True)
-
-            #context.scene.cursor_location = bookmark_pos.copy()
+            
             set_cursor_location(bookmark_pos, scene=context.scene)
-
+            
             bookmark.pos = library.convert_from_abs(cusor_pos, True,
                 use_history=False)
         except Exception as exc:
             self.report('ERROR_INVALID_CONTEXT', exc.args[0])
             return {'CANCELLED'}
-
+        
         CursorDynamicSettings.recalc_csu(context)
-
+        
         return {'FINISHED'}
 
 # Will this be used?
@@ -3413,21 +3451,21 @@ class AddEmptyAtCursor3DBookmark(bpy.types.Operator):
     bl_idname = "scene.cursor_3d_add_empty_at_bookmark"
     bl_label = "Add Empty"
     bl_description = "Add new Empty at the active bookmark"
-
+    
     @classmethod
     def poll(cls, context):
         return context.area.type == 'VIEW_3D'
-
+    
     def execute(self, context):
         settings = find_settings()
         library = settings.libraries.get_item()
         if not library:
             return {'CANCELLED'}
-
+        
         bookmark = library.bookmarks.get_item()
         if not bookmark:
             return {'CANCELLED'}
-
+        
         try:
             #bookmark_pos = library.convert_to_abs(bookmark.pos, True)
             matrix = library.get_matrix(use_history=False, warn=True)
@@ -3435,29 +3473,29 @@ class AddEmptyAtCursor3DBookmark(bpy.types.Operator):
         except Exception as exc:
             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)
-
+        
         """
         for sel_obj in list(context.selected_objects):
             sel_obj.select = False
         obj.select = True
         context.scene.objects.active = obj
-
+        
         # We need this to update bookmark position if
         # library's system is local/scaled/normal/etc.
         CursorDynamicSettings.recalc_csu(context, "PRESS")
         """
-
+        
         # TODO: exit from editmode? It has separate history!
         # If we just link object to scene, it will not trigger
         # addition of new entry to Undo history
         bpy.ops.ed.undo_push(message="Add Object")
-
+        
         return {'FINISHED'}
 
 # ===== BOOKMARK LIBRARY ===== #
@@ -3490,30 +3528,30 @@ class BookmarkLibraryProp(bpy.types.PropertyGroup):
         description="Store/recall relative to the last cursor position",
         default=False,
         options={'HIDDEN'})
-
+    
     # Returned None means "operation is not aplicable"
     def get_matrix(self, use_history, warn=True, **kwargs):
         #particles, csu = gather_particles(**kwargs)
-
+        
         # Ensure we have relevant CSU (Blender will crash
         # if we use the old one after Undo/Redo)
         CursorDynamicSettings.recalc_csu(bpy.context)
-
+        
         csu = CursorDynamicSettings.csu
-
+        
         if self.offset:
             # history? or keep separate for each scene?
             if not use_history:
-                csu.source_pos = csu.tou.scene.cursor_location.copy()
+                csu.source_pos = get_cursor_location(scene=csu.tou.scene)
             else:
                 settings = find_settings()
                 history = settings.history
                 csu.source_pos = history.get_pos(history.last_id)
         else:
             csu.source_pos = Vector()
-
+        
         active_obj = csu.tou.scene.objects.active
-
+        
         if self.system == 'GLOBAL':
             sys_name = 'GLOBAL'
             pivot = 'WORLD'
@@ -3541,13 +3579,13 @@ class BookmarkLibraryProp(bpy.types.PropertyGroup):
         elif self.system == 'CONTEXT':
             sys_name = None # use current orientation
             pivot = None
-
+            
             if active_obj and (active_obj.mode != 'OBJECT'):
                 if len(particles) == 0:
                     pivot = active_obj.matrix_world.to_translation()
-
+        
         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"]
@@ -3555,7 +3593,7 @@ class BookmarkLibraryProp(bpy.types.PropertyGroup):
         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:
@@ -3563,26 +3601,31 @@ class BookmarkLibraryProp(bpy.types.PropertyGroup):
         matrix = self.get_matrix(use_history, warn, **kwargs)
         if not matrix:
             return None
-        return matrix.inverted() * pos
-
+        
+        try:
+            return matrix.inverted() * pos
+        except:
+            # this is some degenerate object
+            return Vector()
+    
     def draw_bookmark(self, context):
         r = context.region
         rv3d = context.region_data
-
+        
         bookmark = self.bookmarks.get_item()
         if not bookmark:
             return
-
+        
         pos = self.convert_to_abs(bookmark.pos)
         if pos is None:
             return
-
+        
         projected = location_3d_to_region_2d(r, rv3d, pos)
-
+        
         if projected:
             # Store previous OpenGL settings
             smooth_prev = gl_get(bgl.GL_SMOOTH)
-
+            
             bgl.glShadeModel(bgl.GL_SMOOTH)
             bgl.glLineWidth(2)
             bgl.glColor4f(0.0, 1.0, 0.0, 1.0)
@@ -3602,7 +3645,7 @@ class BookmarkLibraryProp(bpy.types.PropertyGroup):
                     bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
                 bgl.glVertex2i(x + int(dx), y + int(dy))
             bgl.glEnd()
-
+            
             # Restore previous OpenGL settings
             gl_enable(bgl.GL_SMOOTH, smooth_prev)
 
@@ -3611,15 +3654,15 @@ class BookmarkLibraryIDBlock(PseudoIDBlockBase):
     # callbacks... but class members are.
     _self_update = [0]
     _dont_rename = [False]
-
+    
     data_name = "Bookmark Library"
     op_new = "scene.cursor_3d_new_bookmark_library"
     op_delete = "scene.cursor_3d_delete_bookmark_library"
     icon = 'BOOKMARKS'
-
+    
     collection, active, enum = PseudoIDBlockBase.create_props(
         BookmarkLibraryProp, "Bookmark Library")
-
+    
     def on_item_select(self):
         library = self.get_item()
         library.bookmarks.update_enum()
@@ -3628,31 +3671,31 @@ class NewCursor3DBookmarkLibrary(bpy.types.Operator):
     bl_idname = "scene.cursor_3d_new_bookmark_library"
     bl_label = "New Library"
     bl_description = "Add a new bookmark library"
-
+    
     name = bpy.props.StringProperty(
         name="Name",
         description="Name of the new library",
         default="Lib")
-
+    
     def execute(self, context):
         settings = find_settings()
-
+        
         settings.libraries.add(name=self.name)
-
+        
         return {'FINISHED'}
 
 class DeleteCursor3DBookmarkLibrary(bpy.types.Operator):
     bl_idname = "scene.cursor_3d_delete_bookmark_library"
     bl_label = "Delete Library"
     bl_description = "Delete active bookmark library"
-
+    
     def execute(self, context):
         settings = find_settings()
-
+        
         name = settings.libraries.active
-
+        
         settings.libraries.remove(key=name)
-
+        
         return {'FINISHED'}
 
 # ===== MAIN PROPERTIES ===== #
@@ -3662,53 +3705,53 @@ class Cursor3DToolsSettings(bpy.types.PropertyGroup):
     transform_options = bpy.props.PointerProperty(
         type=TransformExtraOptionsProp,
         options={'HIDDEN'})
-
+    
     draw_guides = bpy.props.BoolProperty(
         name="Guides",
         description="Display guides",
         default=True)
-
+    
     draw_snap_elements = bpy.props.BoolProperty(
         name="Snap elements",
         description="Display snap elements",
         default=True)
-
+    
     draw_N = bpy.props.BoolProperty(
         name="Surface normal",
         description="Display surface normal",
         default=True)
-
+    
     draw_T1 = bpy.props.BoolProperty(
         name="Surface 1st tangential",
         description="Display 1st surface tangential",
         default=True)
-
+    
     draw_T2 = bpy.props.BoolProperty(
         name="Surface 2nd tangential",
         description="Display 2nd surface tangential",
         default=True)
-
+    
     stick_to_obj = bpy.props.BoolProperty(
         name="Stick to objects",
         description="Move cursor along with object it was snapped to",
         default=True)
-
+    
     # HISTORY-RELATED
     history = bpy.props.PointerProperty(
         type=CursorHistoryProp,
         options={'HIDDEN'})
-
+    
     # BOOKMARK-RELATED
     libraries = bpy.props.PointerProperty(
         type=BookmarkLibraryIDBlock,
         options={'HIDDEN'})
-
+    
     show_bookmarks = bpy.props.BoolProperty(
         name="Show bookmarks",
         description="Show active bookmark in 3D view",
         default=True,
         options={'HIDDEN'})
-
+    
     free_coord_precision = bpy.props.IntProperty(
         name="Coord precision",
         description="Numer of digits afer comma "\
@@ -3733,7 +3776,7 @@ class CursorRuntimeSettings(bpy.types.PropertyGroup):
     current_monitor_id = bpy.props.IntProperty(
         default=0,
         options={'HIDDEN'})
-
+    
     surface_pos = bpy.props.FloatVectorProperty(
         default=(0.0, 0.0, 0.0),
         options={'HIDDEN'},
@@ -3741,25 +3784,25 @@ class CursorRuntimeSettings(bpy.types.PropertyGroup):
 
 class CursorDynamicSettings:
     local_matrix = Matrix()
-
+    
     active_transform_operator = None
-
+    
     csu = None
-
+    
     active_scene_hash = 0
-
+    
     @classmethod
     def recalc_csu(cls, context, event_value=None):
         scene_hash_changed = (cls.active_scene_hash != hash(context.scene))
         cls.active_scene_hash = hash(context.scene)
-
+        
         # Don't recalc if mouse is over some UI panel!
         # (otherwise, this may lead to applying operator
         # (e.g. Subdivide) in Edit Mode, even if user
         # just wants to change some operator setting)
         clicked = (event_value in {'PRESS', 'RELEASE'}) and \
             (context.region.type == 'WINDOW')
-
+        
         if clicked or scene_hash_changed:
             particles, cls.csu = gather_particles()
 
@@ -3772,13 +3815,13 @@ class TransformExtraOptions(bpy.types.Panel):
     bl_region_type = "UI"
     #bl_context = "object"
     #bl_options = {'DEFAULT_CLOSED'}
-
+    
     def draw(self, context):
         layout = self.layout
-
+        
         settings = find_settings()
         tfm_opts = settings.transform_options
-
+        
         layout.prop(tfm_opts, "use_relative_coords")
         layout.prop(tfm_opts, "snap_only_to_solid")
         layout.prop(tfm_opts, "snap_interpolate_normals_mode", text="")
@@ -3790,14 +3833,14 @@ class Cursor3DTools(bpy.types.Panel):
     bl_space_type = "VIEW_3D"
     bl_region_type = "UI"
     #bl_context = "object"
-
+    
     def draw(self, context):
         layout = self.layout
-
+        
         # Attempt to launch the monitor
         if bpy.ops.view3d.cursor3d_monitor.poll():
             bpy.ops.view3d.cursor3d_monitor()
-
+        
         # If addon is enabled by default, the new scene
         # created on Blender startup will have disabled
         # standard Cursor3D behavior. However, if user
@@ -3805,9 +3848,9 @@ class Cursor3DTools(bpy.types.Panel):
         # as if nothing happened xD
         update_keymap(True)
         #=============================================#
-
+        
         settings = find_settings()
-
+        
         row = layout.split(0.5)
         #row = layout.row()
         row.operator("view3d.set_cursor3d_dialog",
@@ -3820,7 +3863,7 @@ class Cursor3DTools(bpy.types.Panel):
             text="", icon='EDITMODE_HLT', toggle=True)
         row.prop(settings, "stick_to_obj",
             text="", icon='SNAP_ON', toggle=True)
-
+        
         row = layout.row()
         row.label(text="Draw")
         row = row.split(1 / 3, align=True)
@@ -3830,7 +3873,7 @@ class Cursor3DTools(bpy.types.Panel):
             text="T1", toggle=True, index=1)
         row.prop(settings, "draw_T2",
             text="T2", toggle=True, index=2)
-
+        
         # === HISTORY === #
         history = settings.history
         row = layout.row(align=True)
@@ -3838,15 +3881,15 @@ class Cursor3DTools(bpy.types.Panel):
         row = row.split(0.35, True)
         row.prop(history, "max_size", text="")
         row.prop(history, "current_id", text="")
-
+        
         # === BOOKMARK LIBRARIES === #
         settings.libraries.draw(context, layout)
-
+        
         library = settings.libraries.get_item()
-
+        
         if library is None:
             return
-
+        
         row = layout.row()
         row.prop(settings, "show_bookmarks",
             text="", icon='RESTRICT_VIEW_OFF')
@@ -3854,13 +3897,13 @@ class Cursor3DTools(bpy.types.Panel):
         row.prop(library, "system", text="")
         row.prop(library, "offset", text="",
             icon='ARROW_LEFTRIGHT')
-
+        
         # === BOOKMARKS === #
         library.bookmarks.draw(context, layout)
-
+        
         if len(library.bookmarks.collection) == 0:
             return
-
+        
         row = layout.row()
         row = row.split(align=True)
         # PASTEDOWN
@@ -3881,55 +3924,58 @@ class SetCursorDialog(bpy.types.Operator):
     bl_idname = "view3d.set_cursor3d_dialog"
     bl_label = "Set 3D Cursor"
     bl_description = "Set 3D Cursor XYZ values"
-
+    
     pos = bpy.props.FloatVectorProperty(
         name="Location",
         description="3D Cursor location in current coordinate system",
         subtype='XYZ',
         )
-
+    
     @classmethod
     def poll(cls, context):
         return context.area.type == 'VIEW_3D'
-
+    
     def execute(self, context):
         scene = context.scene
-
+        
         # "current system" / "relative" could have changed
         self.matrix = self.csu.get_matrix()
-
+        
         pos = self.matrix * self.pos
-        #scene.cursor_location = pos
         set_cursor_location(pos, scene=context.scene)
-
+        
         return {'FINISHED'}
 
     def invoke(self, context, event):
         scene = context.scene
-
-        cursor_pos = scene.cursor_location.copy()
-
+        
+        cursor_pos = get_cursor_location(scene=scene)
+        
         particles, self.csu = gather_particles(context=context)
         self.csu.source_pos = cursor_pos
-
+        
         self.matrix = self.csu.get_matrix()
-
-        self.pos = self.matrix.inverted() * cursor_pos
-
+        
+        try:
+            self.pos = self.matrix.inverted() * cursor_pos
+        except:
+            # this is some degenerate system
+            self.pos = Vector()
+        
         wm = context.window_manager
         return wm.invoke_props_dialog(self, width=160)
-
+    
     def draw(self, context):
         layout = self.layout
-
+        
         settings = find_settings()
         tfm_opts = settings.transform_options
-
+        
         v3d = context.space_data
-
+        
         col = layout.column()
         col.prop(self, "pos", text="")
-
+        
         row = layout.row()
         row.prop(tfm_opts, "use_relative_coords", text="Relative")
         row.prop(v3d, "transform_orientation", text="")
@@ -3939,19 +3985,19 @@ class CursorMonitor(bpy.types.Operator):
     '''Monitor changes in cursor location and write to history'''
     bl_idname = "view3d.cursor3d_monitor"
     bl_label = "Cursor Monitor"
-
+    
     # A class-level variable (it must be accessed from poll())
     is_running = False
-
+    
     storage = {}
-
+    
     @classmethod
     def poll(cls, context):
         try:
             runtime_settings = find_runtime_settings()
             if not runtime_settings:
                 return False
-
+            
             # When addon is enabled by default and
             # user started another new scene, is_running
             # would still be True
@@ -3960,7 +4006,7 @@ class CursorMonitor(bpy.types.Operator):
         except Exception as e:
             print("Cursor monitor exeption in poll:\n" + str(e))
             return False
-
+    
     def modal(self, context, event):
         try:
             return self._modal(context, event)
@@ -3970,10 +4016,10 @@ class CursorMonitor(bpy.types.Operator):
             self.cancel(context)
             #raise
             return {'CANCELLED'}
-
+    
     def _modal(self, context, event):
         runtime_settings = find_runtime_settings()
-
+        
         # ATTENTION: will this work correctly when another
         # blend is loaded? (it should, since all scripts
         # seem to be reloaded in such case)
@@ -3983,17 +4029,17 @@ class CursorMonitor(bpy.types.Operator):
             # this one should stop.
             # (OR addon was disabled)
             return self.cancel(context)
-
+        
         # Somewhy after addod re-registration this permanently
         # becomes False
         CursorMonitor.is_running = True
-
+        
         if self.update_storage(runtime_settings):
             # hmm... can this cause flickering of menus?
             context.area.tag_redraw()
-
+        
         settings = find_settings()
-
+        
         # ================== #
         # Update bookmark enums when addon is initialized.
         # Since CursorMonitor operator can be called from draw(),
@@ -4008,32 +4054,32 @@ class CursorMonitor(bpy.types.Operator):
             library = settings.libraries.get_item()
             if library:
                 library.bookmarks.update_enum()
-
+            
             self.just_initialized = False
         # ================== #
-
+        
         tfm_operator = CursorDynamicSettings.active_transform_operator
         if tfm_operator:
             CursorDynamicSettings.csu = tfm_operator.csu
         else:
             CursorDynamicSettings.recalc_csu(context, event.value)
-
+        
         return {'PASS_THROUGH'}
-
+    
     def update_storage(self, runtime_settings):
         if CursorDynamicSettings.active_transform_operator:
             # Don't add to history while operator is running
             return False
-
+        
         new_pos = None
-
+        
         last_locations = {}
-
+        
         for scene in bpy.data.scenes:
-            curr_pos = scene.cursor_location.copy()
-
+            curr_pos = get_cursor_location(scene=scene)
+            
             last_locations[scene.name] = curr_pos
-
+            
             # Ignore newly-created or some renamed scenes
             if scene.name in self.last_locations:
                 if curr_pos != self.last_locations[scene.name]:
@@ -4041,7 +4087,7 @@ class CursorMonitor(bpy.types.Operator):
             elif runtime_settings.current_monitor_id == 0:
                 # startup location should be added
                 new_pos = curr_pos
-
+        
         # Seems like scene.cursor_location is fast enough here
         # -> no need to resort to v3d.cursor_location.
         """
@@ -4053,79 +4099,79 @@ class CursorMonitor(bpy.types.Operator):
                 if space.type == 'VIEW_3D':
                     v3d = space
                     break
-
+        
         if v3d is not None:
-            curr_pos = v3d.cursor_location.copy()
-
+            curr_pos = get_cursor_location(v3d=v3d)
+            
             last_locations[scene.name] = curr_pos
-
+            
             # Ignore newly-created or some renamed scenes
             if scene.name in self.last_locations:
                 if curr_pos != self.last_locations[scene.name]:
                     new_pos = curr_pos
         """
-
+        
         self.last_locations = last_locations
-
+        
         if new_pos is not None:
             settings = find_settings()
             history = settings.history
-
+            
             pos = history.get_pos()
             if (pos is not None):# and (history.current_id != 0): # ?
                 if pos == new_pos:
                     return False # self.just_initialized ?
-
+            
             entry = history.entries.add()
             entry.pos = new_pos
-
+            
             last_id = len(history.entries) - 1
             history.entries.move(last_id, 0)
-
+            
             if last_id > int(history.max_size):
                 history.entries.remove(last_id)
-
+            
             # make sure the most recent history entry is displayed
             history.current_id = 0
             history.curr_id = history.current_id
             history.last_id = 1
-
+            
             return True
-
+        
         return False # self.just_initialized ?
-
+    
     def execute(self, context):
         print("Cursor monitor: launched")
-
+        
         runtime_settings = find_runtime_settings()
-
+        
         self.just_initialized = True
-
+        
         self.id = 0
-
+        
         self.last_locations = {}
-
+        
         # Important! Call update_storage() before assigning
         # current_monitor_id (used to add startup cursor location)
         self.update_storage(runtime_settings)
-
+        
         # Indicate that this is the most recent monitor.
         # All others should shut down.
         self.id = runtime_settings.current_monitor_id + 1
         runtime_settings.current_monitor_id = self.id
-
+        
         CursorMonitor.is_running = True
-
+        
         CursorDynamicSettings.recalc_csu(context, 'PRESS')
-
+        
         # Currently there seems to be only one window manager
         context.window_manager.modal_handler_add(self)
-
+        
         # I suppose that cursor position would change
         # only with user interaction.
         #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).\
@@ -4134,27 +4180,27 @@ class CursorMonitor(bpy.types.Operator):
         self._draw_callback_px = find_region(context.area).\
             callback_add(draw_callback_px, \
             (self, context), 'POST_PIXEL')
-
+        
         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!
         return {'RUNNING_MODAL'}
-
+    
     def cancel(self, context):
         CursorMonitor.is_running = False
         #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'}
 
 
@@ -4163,7 +4209,7 @@ def to_matrix4x4(orient, pos):
     if not isinstance(orient, Matrix):
         orient = orient.to_matrix()
     m = orient.to_4x4()
-    m[3] = pos.to_4d()
+    m.translation = pos.to_3d()
     return m
 
 def angle_axis_to_quat(angle, axis):
@@ -4195,7 +4241,7 @@ def prepare_grid_mesh(nx=1, ny=1, sx=1.0, sy=1.0, z=0.0, xyz_indices=(0,1,2)):
             vertices.append(pos[xyz_indices[0]])
             vertices.append(pos[xyz_indices[1]])
             vertices.append(pos[xyz_indices[2]])
-
+    
     faces = []
     nxmax = nx + 1
     for i in range(nx):
@@ -4206,7 +4252,7 @@ def prepare_grid_mesh(nx=1, ny=1, sx=1.0, sy=1.0, z=0.0, xyz_indices=(0,1,2)):
             faces.append(j1 + i * nxmax)
             faces.append(j1 + i1 * nxmax)
             faces.append(j + i1 * nxmax)
-
+    
     return vertices, faces
 
 def prepare_gridbox_mesh(subdiv=1):
@@ -4218,18 +4264,18 @@ def prepare_gridbox_mesh(subdiv=1):
         (-1, (2,0,1)), # -X
         (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))
-
+    
     return vertices, faces
 
 def create_mesh(vertex_coords, faces):
@@ -4246,67 +4292,67 @@ class GfxCell:
     def __init__(self, w, h, color=None, alpha=None, draw=None):
         self.w = w
         self.h = h
-
+        
         self.color = (0, 0, 0, 1)
         self.set_color(color, alpha)
-
+        
         if draw:
             self.draw = draw
-
+    
     def set_color(self, color=None, alpha=None):
         if color is None:
             color = self.color
         if alpha is None:
             alpha = (color[3] if len(color) > 3 else self.color[3])
         self.color = Vector((color[0], color[1], color[2], alpha))
-
+    
     def prepare_draw(self, x, y, align=(0, 0)):
         if self.color[3] <= 0.0:
             return None
-
+        
         if (align[0] != 0) or (align[1] != 0):
             x -= self.w * align[0]
             y -= self.h * align[1]
-
+        
         x = int(math.floor(x + 0.5))
         y = int(math.floor(y + 0.5))
-
+        
         bgl.glColor4f(*self.color)
-
+        
         return x, y
-
+    
     def draw(self, x, y, align=(0, 0)):
         xy = self.prepare_draw(x, y, align)
         if not xy:
             return
-
+        
         draw_rect(xy[0], xy[1], w, h)
 
 class TextCell(GfxCell):
     font_id = 0
-
+    
     def __init__(self, text="", color=None, alpha=None, font_id=None):
         if font_id is None:
             font_id = TextCell.font_id
         self.font_id = font_id
-
+        
         self.set_text(text)
-
+        
         self.color = (0, 0, 0, 1)
         self.set_color(color, alpha)
-
+    
     def set_text(self, text):
         self.text = str(text)
         dims = blf.dimensions(self.font_id, self.text)
         self.w = dims[0]
         dims = blf.dimensions(self.font_id, "dp") # fontheight
         self.h = dims[1]
-
+    
     def draw(self, x, y, align=(0, 0)):
         xy = self.prepare_draw(x, y, align)
         if not xy:
             return
-
+        
         blf.position(self.font_id, xy[0], xy[1], 0)
         blf.draw(self.font_id, self.text)
 
@@ -4319,17 +4365,17 @@ def find_region(area, region_type='WINDOW'):
 
 def draw_text(x, y, value, font_id=0, align=(0, 0), font_height=None):
     value = str(value)
-
+    
     if (align[0] != 0) or (align[1] != 0):
         dims = blf.dimensions(font_id, value)
         if font_height is not None:
             dims = (dims[0], font_height)
         x -= dims[0] * align[0]
         y -= dims[1] * align[1]
-
+    
     x = int(math.floor(x + 0.5))
     y = int(math.floor(y + 0.5))
-
+    
     blf.position(font_id, x, y, 0)
     blf.draw(font_id, value)
 
@@ -4337,17 +4383,17 @@ def draw_rect(x, y, w, h, margin=0, outline=False):
     if w < 0:
         x += w
         w = abs(w)
-
+    
     if h < 0:
         y += h
         h = abs(h)
-
+    
     x = int(x)
     y = int(y)
     w = int(w)
     h = int(h)
     margin = int(margin)
-
+    
     if outline:
         bgl.glBegin(bgl.GL_LINE_LOOP)
     else:
@@ -4361,39 +4407,39 @@ def draw_rect(x, y, w, h, margin=0, outline=False):
 def append_round_rect(verts, x, y, w, h, rw, rh=None):
     if rh is None:
         rh = rw
-
+    
     if w < 0:
         x += w
         w = abs(w)
-
+    
     if h < 0:
         y += h
         h = abs(h)
-
+    
     if rw < 0:
         rw = min(abs(rw), w * 0.5)
         x += rw
         w -= rw * 2
-
+    
     if rh < 0:
         rh = min(abs(rh), h * 0.5)
         y += rh
         h -= rh * 2
-
+    
     n = int(max(rw, rh) * math.pi / 2.0)
-
+    
     a0 = 0.0
     a1 = math.pi / 2.0
     append_oval_segment(verts, x + w, y + h, rw, rh, a0, a1, n)
-
+    
     a0 = math.pi / 2.0
     a1 = math.pi
     append_oval_segment(verts, x + w, y, rw, rh, a0, a1, n)
-
+    
     a0 = math.pi
     a1 = 3.0 * math.pi / 2.0
     append_oval_segment(verts, x, y, rw, rh, a0, a1, n)
-
+    
     a0 = 3.0 * math.pi / 2.0
     a1 = math.pi * 2.0
     append_oval_segment(verts, x, y + h, rw, rh, a0, a1, n)
@@ -4441,12 +4487,12 @@ def draw_line_hidden_depth(p0, p1, c, a0=1.0, a1=0.5, s0=None, s1=None):
 
 def draw_arrow(p0, x, y, z, n_scl=0.2, ort_scl=0.035):
     p1 = p0 + z
-
+    
     bgl.glBegin(bgl.GL_LINE_STRIP)
     bgl.glVertex3f(p0[0], p0[1], p0[2])
     bgl.glVertex3f(p1[0], p1[1], p1[2])
     bgl.glEnd()
-
+    
     p2 = p1 - z * n_scl
     bgl.glBegin(bgl.GL_TRIANGLE_FAN)
     bgl.glVertex3f(p1[0], p1[1], p1[2])
@@ -4467,18 +4513,18 @@ def draw_arrow_2d(p0, n, L, arrow_len, arrow_width):
     t = Vector((-n[1], n[0]))
     pA = p1 - n * arrow_len + t * arrow_width
     pB = p1 - n * arrow_len - t * arrow_width
-
+    
     bgl.glBegin(bgl.GL_LINES)
-
+    
     bgl.glVertex2f(p0[0], p0[1])
     bgl.glVertex2f(p1[0], p1[1])
-
+    
     bgl.glVertex2f(p1[0], p1[1])
     bgl.glVertex2f(pA[0], pA[1])
-
+    
     bgl.glVertex2f(p1[0], p1[1])
     bgl.glVertex2f(pB[0], pB[1])
-
+    
     bgl.glEnd()
 
 # Store/restore OpenGL settings and working with
@@ -4527,21 +4573,21 @@ def draw_callback_view(self, context):
     settings = find_settings()
     if settings is None:
         return
-
+    
     update_stick_to_obj(context)
-
+    
     if "EDIT" not in context.mode:
         # 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')
-
+    
     history = settings.history
-
+    
     tfm_operator = CursorDynamicSettings.active_transform_operator
-
+    
     is_drawing = history.show_trace or tfm_operator
-
+    
     if is_drawing:
         # Store previous OpenGL settings
         MatrixMode_prev = gl_get(bgl.GL_MATRIX_MODE)
@@ -4553,21 +4599,21 @@ def draw_callback_view(self, context):
         smooth_prev = gl_get(bgl.GL_SMOOTH)
         depth_test_prev = gl_get(bgl.GL_DEPTH_TEST)
         depth_mask_prev = gl_get(bgl.GL_DEPTH_WRITEMASK)
-
+    
     if history.show_trace:
         bgl.glDepthRange(0.0, 0.9999)
-
+        
         history.draw_trace(context)
-
+        
         library = settings.libraries.get_item()
         if library and library.offset:
             history.draw_offset(context)
-
+        
         bgl.glDepthRange(0.0, 1.0)
-
+    
     if tfm_operator:
         tfm_operator.draw_3d()
-
+    
     if is_drawing:
         # Restore previous OpenGL settings
         bgl.glLineWidth(lineWidth_prev)
@@ -4583,17 +4629,17 @@ def draw_callback_view(self, context):
 
 def draw_callback_header_px(self, context):
     r = context.region
-
+    
     tfm_operator = CursorDynamicSettings.active_transform_operator
     if not tfm_operator:
         return
-
+    
     smooth_prev = gl_get(bgl.GL_SMOOTH)
-
+    
     tfm_operator.draw_axes_coords(context, (r.width, r.height))
-
+    
     gl_enable(bgl.GL_SMOOTH, smooth_prev)
-
+    
     bgl.glDisable(bgl.GL_BLEND)
     bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
 
@@ -4602,15 +4648,15 @@ def draw_callback_px(self, context):
     if settings is None:
         return
     library = settings.libraries.get_item()
-
+    
     tfm_operator = CursorDynamicSettings.active_transform_operator
-
+    
     if settings.show_bookmarks and library:
         library.draw_bookmark(context)
-
+    
     if tfm_operator:
         tfm_operator.draw_2d(context)
-
+    
     # restore opengl defaults
     bgl.glLineWidth(1)
     bgl.glDisable(bgl.GL_BLEND)
@@ -4621,42 +4667,54 @@ def draw_callback_px(self, context):
 
 def update_stick_to_obj(context):
     settings = find_settings()
-
+    
     if not settings.stick_to_obj:
         return
-
+    
     scene = context.scene
-
+    
     settings_scene = scene.cursor_3d_tools_settings
-
+    
     name = settings_scene.stick_obj_name
-
+    
     try:
         obj = scene.objects[name]
         pos = settings_scene.stick_obj_pos
         pos = obj.matrix_world * pos
-        #scene.cursor_location = pos
         context.space_data.cursor_location = pos
     except Exception as e:
         pass
 
+def get_cursor_location(v3d=None, scene=None):
+    if v3d:
+        pos = v3d.cursor_location
+    elif scene:
+        pos = scene.cursor_location
+    
+    return pos.copy()
+
 def set_cursor_location(pos, v3d=None, scene=None):
+    pos = pos.to_3d().copy()
+    
     if v3d:
-        v3d.cursor_location = pos.to_3d()
         scene = bpy.context.scene
+        # Accessing scene.cursor_location is SLOW
+        # (well, at least assigning to it).
+        # Accessing v3d.cursor_location is fast.
+        v3d.cursor_location = pos
     elif scene:
-        scene.cursor_location = pos.to_3d()
-
+        scene.cursor_location = pos
+    
     set_stick_obj(scene, None)
 
 def set_stick_obj(scene, name=None, pos=None):
     settings_scene = scene.cursor_3d_tools_settings
-
+    
     if name:
         settings_scene.stick_obj_name = name
     else:
         settings_scene.stick_obj_name = ""
-
+    
     if pos is not None:
         settings_scene.stick_obj_pos = Vector(pos).to_3d()
 
@@ -4675,14 +4733,14 @@ def set_stick_obj(scene, name=None, pos=None):
 def find_settings():
     #wm = bpy.data.window_managers[0]
     #settings = wm.cursor_3d_tools_settings
-
+    
     screen = bpy.data.screens.get("Default", bpy.data.screens[0])
     try:
         settings = screen.cursor_3d_tools_settings
     except:
         # addon was unregistered
         settings = None
-
+    
     return settings
 
 def find_runtime_settings():
@@ -4692,7 +4750,7 @@ def find_runtime_settings():
     except:
         # addon was unregistered
         runtime_settings = None
-
+    
     return runtime_settings
 
 # ===== REGISTRATION ===== #
@@ -4707,9 +4765,9 @@ def update_keymap(activate):
     #if activate:
     #    if bpy.ops.view3d.cursor3d_monitor.poll():
     #        bpy.ops.view3d.cursor3d_monitor()
-
+    
     wm = bpy.context.window_manager
-
+    
     try:
         km = wm.keyconfigs.user.keymaps['3D View']
     except:
@@ -4721,7 +4779,7 @@ def update_keymap(activate):
                 'wm.enhanced_3d_cursor_registration', \
                 'MOUSEMOVE', 'ANY')
         return
-
+    
     # We need for the enhanced operator to take precedence over
     # the default cursor3d, but not over the manipulator.
     # If we add the operator to "addon" keymaps, it will
@@ -4729,7 +4787,7 @@ def update_keymap(activate):
     # keymaps, the default will take precedence.
     # However, we may just simply turn it off or remove
     # (depending on what saves with blend).
-
+    
     items = find_keymap_items(km, 'view3d.cursor3d_enhanced')
     if activate and (len(items) == 0):
         kmi = km.keymap_items.new('view3d.cursor3d_enhanced', \
@@ -4744,16 +4802,16 @@ def update_keymap(activate):
         else:
             for kmi in items:
                 km.keymap_items.remove(kmi)
-
+    
     items = find_keymap_items(km, 'view3d.cursor3d')
     for kmi in items:
         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
-
+    
     km = wm.keyconfigs.default.keymaps['3D View']
     items = find_keymap_items(km, 'view3d.cursor3d')
     for kmi in items:
@@ -4761,10 +4819,10 @@ def update_keymap(activate):
 
 def register():
     bpy.utils.register_class(SetCursorDialog)
-
+    
     bpy.utils.register_class(NewCursor3DBookmarkLibrary)
     bpy.utils.register_class(DeleteCursor3DBookmarkLibrary)
-
+    
     bpy.utils.register_class(NewCursor3DBookmark)
     bpy.utils.register_class(DeleteCursor3DBookmark)
     bpy.utils.register_class(OverwriteCursor3DBookmark)
@@ -4772,90 +4830,90 @@ def register():
     bpy.utils.register_class(SwapCursor3DBookmark)
     bpy.utils.register_class(SnapSelectionToCursor3DBookmark)
     bpy.utils.register_class(AddEmptyAtCursor3DBookmark)
-
+    
     bpy.utils.register_class(TransformExtraOptionsProp)
     bpy.utils.register_class(TransformExtraOptions)
-
+    
     # View properties panel is already long. Appending something
     # to it would make it too inconvenient
     #bpy.types.VIEW3D_PT_view3d_properties.append(draw_cursor_tools)
     bpy.utils.register_class(Cursor3DTools)
-
+    
     bpy.utils.register_class(LocationProp)
     bpy.utils.register_class(CursorHistoryProp)
-
+    
     bpy.utils.register_class(BookmarkProp)
     bpy.utils.register_class(BookmarkIDBlock)
-
+    
     bpy.utils.register_class(BookmarkLibraryProp)
     bpy.utils.register_class(BookmarkLibraryIDBlock)
-
+    
     bpy.utils.register_class(Cursor3DToolsSettings)
-
+    
     bpy.utils.register_class(CursorRuntimeSettings)
     bpy.utils.register_class(CursorMonitor)
-
+    
     bpy.utils.register_class(Cursor3DToolsSceneSettings)
-
+    
     bpy.types.Screen.cursor_3d_tools_settings = \
         bpy.props.PointerProperty(type=Cursor3DToolsSettings)
-
+    
     bpy.types.WindowManager.cursor_3d_runtime_settings = \
         bpy.props.PointerProperty(type=CursorRuntimeSettings)
-
+    
     bpy.types.Scene.cursor_3d_tools_settings = \
         bpy.props.PointerProperty(type=Cursor3DToolsSceneSettings)
-
+    
     bpy.utils.register_class(EnhancedSetCursor)
-
+    
     bpy.utils.register_class(DelayRegistrationOperator)
-
+    
     update_keymap(True)
 
 def unregister():
     # Manually set this to False on unregister
     CursorMonitor.is_running = False
-
+    
     update_keymap(False)
-
+    
     bpy.utils.unregister_class(DelayRegistrationOperator)
-
+    
     bpy.utils.unregister_class(EnhancedSetCursor)
-
+    
     if hasattr(bpy.types.Scene, "cursor_3d_tools_settings"):
         del bpy.types.Scene.cursor_3d_tools_settings
-
+    
     if hasattr(bpy.types.WindowManager, "cursor_3d_runtime_settings"):
         del bpy.types.WindowManager.cursor_3d_runtime_settings
-
+    
     if hasattr(bpy.types.Screen, "cursor_3d_tools_settings"):
         del bpy.types.Screen.cursor_3d_tools_settings
-
+    
     bpy.utils.unregister_class(Cursor3DToolsSceneSettings)
-
+    
     bpy.utils.unregister_class(CursorMonitor)
     bpy.utils.unregister_class(CursorRuntimeSettings)
-
+    
     bpy.utils.unregister_class(Cursor3DToolsSettings)
-
+    
     bpy.utils.unregister_class(BookmarkLibraryIDBlock)
     bpy.utils.unregister_class(BookmarkLibraryProp)
-
+    
     bpy.utils.unregister_class(BookmarkIDBlock)
     bpy.utils.unregister_class(BookmarkProp)
-
+    
     bpy.utils.unregister_class(CursorHistoryProp)
     bpy.utils.unregister_class(LocationProp)
-
+    
     bpy.utils.unregister_class(Cursor3DTools)
     #bpy.types.VIEW3D_PT_view3d_properties.remove(draw_cursor_tools)
-
+    
     bpy.utils.unregister_class(TransformExtraOptions)
     bpy.utils.unregister_class(TransformExtraOptionsProp)
-
+    
     bpy.utils.unregister_class(NewCursor3DBookmarkLibrary)
     bpy.utils.unregister_class(DeleteCursor3DBookmarkLibrary)
-
+    
     bpy.utils.unregister_class(NewCursor3DBookmark)
     bpy.utils.unregister_class(DeleteCursor3DBookmark)
     bpy.utils.unregister_class(OverwriteCursor3DBookmark)
@@ -4863,28 +4921,28 @@ def unregister():
     bpy.utils.unregister_class(SwapCursor3DBookmark)
     bpy.utils.unregister_class(SnapSelectionToCursor3DBookmark)
     bpy.utils.unregister_class(AddEmptyAtCursor3DBookmark)
-
+    
     bpy.utils.unregister_class(SetCursorDialog)
 
 class DelayRegistrationOperator(bpy.types.Operator):
     bl_idname = "wm.enhanced_3d_cursor_registration"
     bl_label = "[Enhanced 3D Cursor] registration delayer"
-
+    
     _timer = None
-
+    
     def modal(self, context, event):
         if (not self.keymap_updated) and \
             ((event.type == 'TIMER') or ("MOVE" in event.type)):
             # clean up (we don't need this operator to run anymore)
             wm = bpy.context.window_manager
-
+            
             for kcfg in wm.keyconfigs.values():
                 for km in kcfg.keymaps.values():
                     items = find_keymap_items(km,
                         'wm.enhanced_3d_cursor_registration')
                     for kmi in items:
                         km.keymap_items.remove(kmi)
-
+            
             """
             try:
                 # A bug when using Maya keymap presets
@@ -4894,41 +4952,41 @@ class DelayRegistrationOperator(bpy.types.Operator):
                 km = wm.keyconfigs.active.keymaps['Window']
             except KeyError:
                 km = None
-
+            
             if km:
                 items = find_keymap_items(km,
                     'wm.enhanced_3d_cursor_registration')
                 for kmi in items:
                     km.keymap_items.remove(kmi)
             """
-
+            
             update_keymap(True)
-
+            
             self.keymap_updated = True
-
+            
             # No, better don't (at least in current version),
             # since the monitor has dependencies on View3D context.
             # Attempt to launch the monitor
             #if bpy.ops.view3d.cursor3d_monitor.poll():
             #    bpy.ops.view3d.cursor3d_monitor()
-
+            
             return self.cancel(context)
-
+        
         return {'PASS_THROUGH'}
-
+    
     def execute(self, context):
         self.keymap_updated = False
-
+        
         context.window_manager.modal_handler_add(self)
-
+        
         self._timer = context.window_manager.\
             event_timer_add(0.1, context.window)
-
+        
         return {'RUNNING_MODAL'}
-
+    
     def cancel(self, context):
         context.window_manager.event_timer_remove(self._timer)
-
+        
         return {'CANCELLED'}
 
 if __name__ == "__main__":