Objects: new 3D cursor alignment option when adding objects
authorHans Goudey <h.goudey@me.com>
Wed, 15 May 2019 14:56:22 +0000 (16:56 +0200)
committerBrecht Van Lommel <brechtvanlommel@gmail.com>
Wed, 15 May 2019 16:10:58 +0000 (18:10 +0200)
The choices are now World, View and 3D Cursor.

This breaks Python API compatibility, add-ons that add objects with this
parameter will need to be updated.

Differential Revision: https://developer.blender.org/D4706

release/scripts/modules/bpy_extras/object_utils.py
release/scripts/startup/bl_operators/add_mesh_torus.py
release/scripts/startup/bl_operators/object.py
release/scripts/templates_py/operator_mesh_add.py
source/blender/editors/object/object_add.c
source/blender/makesdna/DNA_userdef_types.h
source/blender/makesrna/intern/rna_userdef.c
tests/python/bl_mesh_modifiers.py

index a5d80fd..48cdbb4 100644 (file)
@@ -34,6 +34,7 @@ import bpy
 from bpy.props import (
     BoolProperty,
     FloatVectorProperty,
+    EnumProperty,
 )
 
 
@@ -66,36 +67,32 @@ def add_object_align_init(context, operator):
             properties.location = location.to_translation()
 
     # rotation
-    view_align = (context.preferences.edit.object_align == 'VIEW')
-    view_align_force = False
+    add_align_preference = context.preferences.edit.object_align
     if operator:
-        if properties.is_property_set("view_align"):
-            view_align = view_align_force = operator.view_align
+        if not properties.is_property_set("rotation"):
+            # So one of "align" and "rotation" will be set
+            properties.align = add_align_preference
+
+        if properties.align == 'WORLD':
+            rotation = properties.rotation.to_matrix().to_4x4()
+        elif properties.align == 'VIEW':
+            rotation = space_data.region_3d.view_matrix.to_3x3().inverted()
+            rotation.resize_4x4()
+            properties.rotation = rotation.to_euler()
+        elif properties.align == 'CURSOR':
+            rotation = context.scene.cursor.rotation_euler.to_matrix().to_4x4()
+            properties.rotation = rotation.to_euler()
         else:
-            if properties.is_property_set("rotation"):
-                # ugh, 'view_align' callback resets
-                value = properties.rotation[:]
-                properties.view_align = view_align
-                properties.rotation = value
-                del value
-            else:
-                properties.view_align = view_align
-
-    if operator and (properties.is_property_set("rotation") and
-                     not view_align_force):
-
-        rotation = Euler(properties.rotation).to_matrix().to_4x4()
+            rotation = properties.rotation.to_matrix().to_4x4()
     else:
-        if view_align and space_data:
+        if (add_align_preference == 'VIEW') and space_data:
             rotation = space_data.region_3d.view_matrix.to_3x3().inverted()
             rotation.resize_4x4()
+        elif add_align_preference == 'CURSOR':
+            rotation = context.scene.cursor.rotation_euler.to_matrix().to_4x4()
         else:
             rotation = Matrix()
 
-        # set the operator properties
-        if operator:
-            properties.rotation = rotation.to_euler()
-
     return location @ rotation
 
 
@@ -168,14 +165,20 @@ def object_data_add(context, obdata, operator=None, name=None):
 
 
 class AddObjectHelper:
-    def view_align_update_callback(self, _context):
-        if not self.view_align:
+    def align_update_callback(self, _context):
+        if self.align == 'WORLD':
             self.rotation.zero()
 
-    view_align: BoolProperty(
-        name="Align to View",
-        default=False,
-        update=view_align_update_callback,
+    align_items = (
+        ('WORLD', "World", "Align the new object to the world"),
+        ('VIEW', "View", "Align the new object to the view"),
+        ('CURSOR', "3D Cursor", "Use the 3D cursor orientation for the new object")
+    )
+    align: EnumProperty(
+        name="Align",
+        items=align_items,
+        default='WORLD',
+        update=align_update_callback,
     )
     location: FloatVectorProperty(
         name="Location",
index a22161f..bd727ec 100644 (file)
@@ -202,7 +202,7 @@ class AddTorus(Operator, object_utils.AddObjectHelper):
         col = layout.column(align=True)
         col.prop(self, "generate_uvs")
         col.separator()
-        col.prop(self, "view_align")
+        col.prop(self, "align")
 
         col = layout.column(align=True)
         col.label(text="Location")
index f68b591..2832489 100644 (file)
@@ -912,7 +912,7 @@ class LoadImageAsEmpty:
             'INVOKE_REGION_WIN',
             type='IMAGE',
             location=cursor,
-            view_align=self.view_align,
+            align=('VIEW' if self.view_align else 'WORLD'),
         )
 
         obj = context.active_object
index 5a9acce..39402ab 100644 (file)
@@ -75,10 +75,17 @@ class AddBox(bpy.types.Operator):
     )
 
     # generic transform props
-    view_align: BoolProperty(
-        name="Align to View",
-        default=False,
+    align_items = (
+            ('WORLD', "World", "Align the new object to the world"),
+            ('VIEW', "View", "Align the new object to the view"),
+            ('CURSOR', "3D Cursor", "Use the 3D cursor orientation for the new object")
     )
+    align: EnumProperty(
+            name="Align",
+            items=align_items,
+            default='WORLD',
+            update=AddObjectHelper.align_update_callback,
+            )
     location: FloatVectorProperty(
         name="Location",
         subtype='TRANSLATION',
index 50a97e3..f8760f9 100644 (file)
@@ -161,6 +161,18 @@ static EnumPropertyItem lightprobe_type_items[] = {
     {0, NULL, 0, NULL, NULL},
 };
 
+enum ObjectAlign {
+  ALIGN_WORLD,
+  ALIGN_VIEW,
+  ALIGN_CURSOR,
+} ALIGN_OPTIONS;
+
+static const EnumPropertyItem align_options[] = {
+    {ALIGN_WORLD, "WORLD", 0, "World", "Align the new object to the world"},
+    {ALIGN_VIEW, "VIEW", 0, "View", "Align the new object to the view"},
+    {ALIGN_CURSOR, "CURSOR", 0, "3D Cursor", "Use the 3D cursor orientation for the new object"},
+    {0, NULL, 0, NULL, NULL}};
+
 /************************** Exported *****************************/
 
 void ED_object_location_from_view(bContext *C, float loc[3])
@@ -291,16 +303,15 @@ void ED_object_add_generic_props(wmOperatorType *ot, bool do_editmode)
 {
   PropertyRNA *prop;
 
-  /* note: this property gets hidden for add-camera operator */
-  prop = RNA_def_boolean(
-      ot->srna, "view_align", 0, "Align to View", "Align the new object to the view");
-  RNA_def_property_update_runtime(prop, view_align_update);
-
   if (do_editmode) {
     prop = RNA_def_boolean(
         ot->srna, "enter_editmode", 0, "Enter Editmode", "Enter editmode when adding this object");
     RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
   }
+  /* note: this property gets hidden for add-camera operator */
+  prop = RNA_def_enum(
+      ot->srna, "align", align_options, ALIGN_WORLD, "Align", "The alignment of the new object");
+  RNA_def_property_update_runtime(prop, view_align_update);
 
   prop = RNA_def_float_vector_xyz(ot->srna,
                                   "location",
@@ -392,23 +403,42 @@ bool ED_object_add_generic_get_opts(bContext *C,
       rot = _rot;
     }
 
+    prop = RNA_struct_find_property(op->ptr, "align");
+    int alignment = RNA_property_enum_get(op->ptr, prop);
+    bool alignment_set = RNA_property_is_set(op->ptr, prop);
+
     if (RNA_struct_property_is_set(op->ptr, "rotation")) {
       *is_view_aligned = false;
     }
-    else if (RNA_struct_property_is_set(op->ptr, "view_align")) {
-      *is_view_aligned = RNA_boolean_get(op->ptr, "view_align");
+    else if (alignment_set) {
+      *is_view_aligned = alignment == ALIGN_VIEW;
     }
     else {
       *is_view_aligned = (U.flag & USER_ADD_VIEWALIGNED) != 0;
-      RNA_boolean_set(op->ptr, "view_align", *is_view_aligned);
+      if (*is_view_aligned) {
+        RNA_property_enum_set(op->ptr, prop, ALIGN_VIEW);
+        alignment = ALIGN_VIEW;
+      }
+      else if (U.flag & USER_ADD_CURSORALIGNED) {
+        RNA_property_enum_set(op->ptr, prop, ALIGN_CURSOR);
+        alignment = ALIGN_CURSOR;
+      }
     }
 
-    if (*is_view_aligned) {
-      ED_object_rotation_from_view(C, rot, view_align_axis);
-      RNA_float_set_array(op->ptr, "rotation", rot);
-    }
-    else {
-      RNA_float_get_array(op->ptr, "rotation", rot);
+    switch (alignment) {
+      case ALIGN_WORLD:
+        RNA_float_get_array(op->ptr, "rotation", rot);
+        break;
+      case ALIGN_VIEW:
+        ED_object_rotation_from_view(C, rot, view_align_axis);
+        RNA_float_set_array(op->ptr, "rotation", rot);
+        break;
+      case ALIGN_CURSOR: {
+        const Scene *scene = CTX_data_scene(C);
+        copy_v3_v3(rot, scene->cursor.rotation_euler);
+        RNA_float_set_array(op->ptr, "rotation", rot);
+        break;
+      }
     }
   }
 
@@ -690,7 +720,7 @@ static int object_camera_add_exec(bContext *C, wmOperator *op)
   float loc[3], rot[3];
 
   /* force view align for cameras */
-  RNA_boolean_set(op->ptr, "view_align", true);
+  RNA_enum_set(op->ptr, "align", ALIGN_VIEW);
 
   if (!ED_object_add_generic_get_opts(
           C, op, 'Z', loc, rot, &enter_editmode, &local_view_bits, NULL)) {
@@ -732,7 +762,7 @@ void OBJECT_OT_camera_add(wmOperatorType *ot)
   ED_object_add_generic_props(ot, true);
 
   /* hide this for cameras, default */
-  prop = RNA_struct_type_find_property(ot->srna, "view_align");
+  prop = RNA_struct_type_find_property(ot->srna, "align");
   RNA_def_property_flag(prop, PROP_HIDDEN);
 }
 
index d422636..2247ec2 100644 (file)
@@ -833,7 +833,7 @@ typedef enum eUserPref_Flag {
   USER_TOOLTIPS = (1 << 11),
   USER_TWOBUTTONMOUSE = (1 << 12),
   USER_NONUMPAD = (1 << 13),
-  USER_FLAG_UNUSED_14 = (1 << 14), /* cleared */
+  USER_ADD_CURSORALIGNED = (1 << 14),
   USER_FILECOMPRESS = (1 << 15),
   USER_SAVE_PREVIEWS = (1 << 16),
   USER_CUSTOM_RANGE = (1 << 17),
index 1e5d59c..38877ba 100644 (file)
@@ -4347,7 +4347,12 @@ static void rna_def_userdef_edit(BlenderRNA *brna)
        "VIEW",
        0,
        "View",
-       "Align newly added objects facing the active 3D View direction"},
+       "Align newly added objects to the active 3D View direction"},
+      {USER_ADD_CURSORALIGNED,
+       "CURSOR",
+       0,
+       "3D Cursor",
+       "Align newly added objects to the 3D Cursor's rotation"},
       {0, NULL, 0, NULL, NULL},
   };
 
index 74eaaeb..80f810f 100644 (file)
@@ -582,7 +582,7 @@ cube_shell_face = (
 
 
 def make_cube(scene):
-    bpy.ops.mesh.primitive_cube_add(view_align=False,
+    bpy.ops.mesh.primitive_cube_add(align='WORLD',
                                     enter_editmode=False,
                                     location=(0, 0, 0),
                                     rotation=(0, 0, 0),
@@ -652,7 +652,7 @@ def make_cube_shell_extra(scene):
 
 
 def make_monkey(scene):
-    bpy.ops.mesh.primitive_monkey_add(view_align=False,
+    bpy.ops.mesh.primitive_monkey_add(align='WORLD',
                                       enter_editmode=False,
                                       location=(0, 0, 0),
                                       rotation=(0, 0, 0),