Code clean-up: Use standard-conformal {'sets'} for .report and operator return values
[blender-addons-contrib.git] / space_view3d_enhanced_3d_cursor.py
index e79df1f5422e74849eafed3fee794e5da05a9ee0..88b1cf063811cc9442ac092fce000f4a6eb2aae1 100644 (file)
@@ -21,7 +21,7 @@ bl_info = {
     "name": "Enhanced 3D Cursor",
     "description": "Cursor history and bookmarks; drag/snap cursor.",
     "author": "dairin0d",
-    "version": (2, 8, 5),
+    "version": (2, 8, 7),
     "blender": (2, 6, 3),
     "location": "View3D > Action mouse; F10; Properties panel",
     "warning": "",
@@ -1572,33 +1572,43 @@ def gather_particles(**kwargs):
         # -> the monitor tries to update the CSU ->
         # -> object.mode_set seem to somehow conflict
         # with Undo/Redo mechanisms.
-        elif False and active_object and active_object.data and \
+        elif active_object and active_object.data and \
         (context_mode in {
         'EDIT_MESH', 'EDIT_METABALL',
         'EDIT_CURVE', 'EDIT_SURFACE',
         'EDIT_ARMATURE', 'POSE'}):
             
-            prev_mode = active_object.mode
-            
-            if context_mode not in {'EDIT_ARMATURE', 'POSE'}:
-                bpy.ops.object.mode_set(mode='OBJECT')
-            
             m = active_object.matrix_world
             
             positions = []
             normal = Vector((0, 0, 0))
             
             if context_mode == 'EDIT_MESH':
-                # We currently don't need to create particles
-                # for these; vertices are enough now.
-                #for face in active_object.data.polygons:
-                #    pass
-                #for edge in active_object.data.edges:
-                #    pass
-                for vertex in active_object.data.vertices:
-                    if vertex.select:
-                        positions.append(vertex.co)
-                        normal += vertex.normal
+                bm = bmesh.from_edit_mesh(active_object.data)
+                
+                if bm.select_history:
+                    elem = bm.select_history[-1]
+                    if isinstance(elem, bmesh.types.BMVert):
+                        active_element = elem.co.copy()
+                    else:
+                        active_element = Vector()
+                        for v in elem.verts:
+                            active_element += v.co
+                        active_element *= 1.0 / len(elem.verts)
+                
+                for v in bm.verts:
+                    if v.select:
+                        positions.append(v.co)
+                        normal += v.normal
+                
+                # mimic Blender's behavior (as of now,
+                # order of selection is ignored)
+                if len(positions) == 2:
+                    normal = positions[1] - positions[0]
+                elif len(positions) == 3:
+                    a = positions[0] - positions[1]
+                    b = positions[2] - positions[1]
+                    normal = a.cross(b)
             elif context_mode == 'EDIT_METABALL':
                 active_elem = active_object.data.elements.active
                 if active_elem:
@@ -1720,9 +1730,6 @@ def gather_particles(**kwargs):
                 normal_system.col[0].normalize()
                 normal_system.col[1].normalize()
                 normal_system.col[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)
@@ -1954,7 +1961,7 @@ class TransformOrientationUtility:
         name = self.transform_orientation
         return name[:1].upper() + name[1:].lower()
     
-    def set(self, name):
+    def set(self, name, set_v3d=True):
         if isinstance(name, int):
             n = len(self.custom_systems)
             if n == 0:
@@ -1983,7 +1990,8 @@ class TransformOrientationUtility:
         
         self.transform_orientation = name
         
-        self.v3d.transform_orientation = name
+        if set_v3d:
+            self.v3d.transform_orientation = name
     
     def get_matrix(self, name=None):
         active_obj = self.scene.objects.active
@@ -2037,6 +2045,11 @@ def create_transform_orientation(scene, name=None, matrix=None):
     tfm_orient = scene.orientations[-1]
     
     if name is not None:
+        basename = name
+        i = 1
+        while name in scene.orientations:
+            name = "%s.%03i" % (basename, i)
+            i += 1
         tfm_orient.name = name
     
     if matrix:
@@ -3211,7 +3224,7 @@ class PseudoIDBlockBase(bpy.types.PropertyGroup):
 class TransformExtraOptionsProp(bpy.types.PropertyGroup):
     use_relative_coords = bpy.props.BoolProperty(
         name="Relative coordinates", 
-        description="Consider existing transformation as the strating point", 
+        description="Consider existing transformation as the starting point", 
         default=True)
     snap_interpolate_normals_mode = bpy.props.EnumProperty(
         items=[('NEVER', "Never", "Don't interpolate normals"),
@@ -3426,7 +3439,7 @@ class NewCursor3DBookmark(bpy.types.Operator):
             bookmark.pos = library.convert_from_abs(context.space_data,
                                                     cusor_pos, True)
         except Exception as exc:
-            self.report('ERROR_INVALID_CONTEXT', exc.args[0])
+            self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
             return {'CANCELLED'}
         
         return {'FINISHED'}
@@ -3474,7 +3487,7 @@ class OverwriteCursor3DBookmark(bpy.types.Operator):
             bookmark.pos = library.convert_from_abs(context.space_data,
                                                     cusor_pos, True)
         except Exception as exc:
-            self.report('ERROR_INVALID_CONTEXT', exc.args[0])
+            self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
             return {'CANCELLED'}
         
         CursorDynamicSettings.recalc_csu(context, 'PRESS')
@@ -3505,7 +3518,7 @@ class RecallCursor3DBookmark(bpy.types.Operator):
                                                   bookmark.pos, True)
             set_cursor_location(bookmark_pos, v3d=context.space_data)
         except Exception as exc:
-            self.report('ERROR_INVALID_CONTEXT', exc.args[0])
+            self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
             return {'CANCELLED'}
         
         CursorDynamicSettings.recalc_csu(context)
@@ -3543,7 +3556,7 @@ class SwapCursor3DBookmark(bpy.types.Operator):
                                                     cusor_pos, True,
                 use_history=False)
         except Exception as exc:
-            self.report('ERROR_INVALID_CONTEXT', exc.args[0])
+            self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
             return {'CANCELLED'}
         
         CursorDynamicSettings.recalc_csu(context)
@@ -3581,7 +3594,7 @@ class AddEmptyAtCursor3DBookmark(bpy.types.Operator):
                                         v3d=context.space_data, warn=True)
             bookmark_pos = matrix * bookmark.pos
         except Exception as exc:
-            self.report('ERROR_INVALID_CONTEXT', exc.args[0])
+            self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
             return {'CANCELLED'}
         
         name = "{}.{}".format(library.name, bookmark.name)
@@ -4087,11 +4100,52 @@ class SetCursorDialog(bpy.types.Operator):
         row.prop(tfm_opts, "use_relative_coords", text="Relative")
         row.prop(v3d, "transform_orientation", text="")
 
+class AlignOrientationProperties(bpy.types.PropertyGroup):
+    axes_items = [
+        ('X', 'X', 'X axis'),
+        ('Y', 'Y', 'Y axis'),
+        ('Z', 'Z', 'Z axis'),
+        ('-X', '-X', '-X axis'),
+        ('-Y', '-Y', '-Y axis'),
+        ('-Z', '-Z', '-Z axis'),
+    ]
+    
+    axes_items_ = [
+        ('X', 'X', 'X axis'),
+        ('Y', 'Y', 'Y axis'),
+        ('Z', 'Z', 'Z axis'),
+        (' ', ' ', 'Same as source axis'),
+    ]
+    
+    def get_orients(self, context):
+        orients = []
+        orients.append(('GLOBAL', "Global", ""))
+        orients.append(('LOCAL', "Local", ""))
+        orients.append(('GIMBAL', "Gimbal", ""))
+        orients.append(('NORMAL', "Normal", ""))
+        orients.append(('VIEW', "View", ""))
+        
+        for orientation in context.scene.orientations:
+            name = orientation.name
+            orients.append((name, name, ""))
+        
+        return orients
+    
+    src_axis = bpy.props.EnumProperty(default='Z', items=axes_items,
+                                      name="Initial axis")
+    #src_orient = bpy.props.EnumProperty(default='GLOBAL', items=get_orients)
+    
+    dest_axis = bpy.props.EnumProperty(default=' ', items=axes_items_,
+                                       name="Final axis")
+    dest_orient = bpy.props.EnumProperty(items=get_orients,
+                                         name="Final orientation")
+
 class AlignOrientation(bpy.types.Operator):
     bl_idname = "view3d.align_orientation"
     bl_label = "Align Orientation"
     bl_description = "Rotates active object to match axis of current "\
         "orientation to axis of another orientation"
+    bl_options = {'REGISTER', 'UNDO'}
     
     axes_items = [
         ('X', 'X', 'X axis'),
@@ -4139,26 +4193,32 @@ class AlignOrientation(bpy.types.Operator):
         return (context.area.type == 'VIEW_3D') and context.object
     
     def execute(self, context):
+        wm = context.window_manager
         obj = context.object
         scene = context.scene
         v3d = context.space_data
         rv3d = context.region_data
         
-        tou = TransformOrientationUtility(scene, v3d, rv3d)
+        particles, csu = gather_particles(context=context)
+        tou = csu.tou
+        #tou = TransformOrientationUtility(scene, v3d, rv3d)
         
-        src_matrix = tou.get_matrix(v3d.transform_orientation)
+        aop = wm.align_orientation_properties # self
+        
+        src_matrix = tou.get_matrix()
         src_axes = MatrixDecompose(src_matrix)
-        src_axis_name = self.src_axis
+        src_axis_name = aop.src_axis
         if src_axis_name.startswith("-"):
             src_axis_name = src_axis_name[1:]
             src_axis = -src_axes[self.axes_ids[src_axis_name]]
         else:
             src_axis = src_axes[self.axes_ids[src_axis_name]]
         
-        dest_matrix = tou.get_matrix(self.dest_orient)
+        tou.set(aop.dest_orient, False)
+        dest_matrix = tou.get_matrix()
         dest_axes = MatrixDecompose(dest_matrix)
         if self.dest_axis != ' ':
-            dest_axis_name = self.dest_axis
+            dest_axis_name = aop.dest_axis
         else:
             dest_axis_name = src_axis_name
         dest_axis = dest_axes[self.axes_ids[dest_axis_name]]
@@ -4172,7 +4232,7 @@ class AlignOrientation(bpy.types.Operator):
         
         obj.matrix_world = m
         
-        bpy.ops.ed.undo_push(message="Align Orientation")
+        #bpy.ops.ed.undo_push(message="Align Orientation")
         
         return {'FINISHED'}
     
@@ -4181,7 +4241,15 @@ class AlignOrientation(bpy.types.Operator):
     # the last selected orientation may revert to the previous state
     def invoke(self, context, event):
         wm = context.window_manager
-        return wm.invoke_props_dialog(self)
+        return wm.invoke_props_dialog(self, width=200)
+    
+    def draw(self, context):
+        layout = self.layout
+        wm = context.window_manager
+        aop = wm.align_orientation_properties # self
+        layout.prop(aop, "src_axis")
+        layout.prop(aop, "dest_axis")
+        layout.prop(aop, "dest_orient")
 
 class CopyOrientation(bpy.types.Operator):
     bl_idname = "view3d.copy_orientation"
@@ -4193,7 +4261,9 @@ class CopyOrientation(bpy.types.Operator):
         v3d = context.space_data
         rv3d = context.region_data
         
-        tou = TransformOrientationUtility(scene, v3d, rv3d)
+        particles, csu = gather_particles(context=context)
+        tou = csu.tou
+        #tou = TransformOrientationUtility(scene, v3d, rv3d)
         
         orient = create_transform_orientation(scene,
             name=tou.get()+".copy", matrix=tou.get_matrix())
@@ -5086,23 +5156,31 @@ def update_keymap(activate):
             for kmi in items:
                 km.keymap_items.remove(kmi)
     
-    items = find_keymap_items(km, 'view3d.cursor3d')
-    for kmi in items:
+    for kmi in find_keymap_items(km, 'view3d.cursor3d'):
         kmi.active = not activate
     
-    km = wm.keyconfigs.active.keymaps['3D View']
-    items = find_keymap_items(km, 'view3d.cursor3d')
-    for kmi in items:
-        kmi.active = not activate
+    try:
+        km = wm.keyconfigs.active.keymaps['3D View']
+        for kmi in find_keymap_items(km, 'view3d.cursor3d'):
+            kmi.active = not activate
+    except KeyError:
+        # seems like in recent builds (after 2.63a)
+        # 'bpy_prop_collection[key]: key "3D View" not found'
+        pass
     
-    km = wm.keyconfigs.default.keymaps['3D View']
-    items = find_keymap_items(km, 'view3d.cursor3d')
-    for kmi in items:
-        kmi.active = not activate
+    try:
+        km = wm.keyconfigs.default.keymaps['3D View']
+        for kmi in find_keymap_items(km, 'view3d.cursor3d'):
+            kmi.active = not activate
+    except KeyError:
+        pass
 
 def register():
+    bpy.utils.register_class(AlignOrientationProperties)
     bpy.utils.register_class(AlignOrientation)
     bpy.utils.register_class(CopyOrientation)
+    bpy.types.WindowManager.align_orientation_properties = \
+        bpy.props.PointerProperty(type=AlignOrientationProperties)
     bpy.types.VIEW3D_PT_transform_orientations.append(
         transform_orientations_panel_extension)
     
@@ -5214,8 +5292,10 @@ def unregister():
     
     bpy.types.VIEW3D_PT_transform_orientations.remove(
         transform_orientations_panel_extension)
+    del bpy.types.WindowManager.align_orientation_properties
     bpy.utils.unregister_class(CopyOrientation)
     bpy.utils.unregister_class(AlignOrientation)
+    bpy.utils.unregister_class(AlignOrientationProperties)
 
 class DelayRegistrationOperator(bpy.types.Operator):
     bl_idname = "wm.enhanced_3d_cursor_registration"