Accelerator keys have been removed from Blender 4.0 in favour of direct searching in menus by typing for a few reasons.
- Scales better for large number of items
- Allows for custom entries like the new custom modifier or nodes groups
- Behaves better for dynamic content generation from libraries and assets
Well, it was actually simpler than I thought it'd be, for the single case of modifiers at least.
I was planing to summon a list of all the "static" modifiers from Blender RNA than build a traditional static menu from it, but as it seems, the legacy menu is still hidden away in Blender source code, albeit removed from visible panels. At least for the time being, until they clean house this should work.
We only need to add it back to the modifiers panel. This doesn't aim to remove the new search menu, as it still serves the purpose of adding Geometry Nodes based custom modifiers which the legacy doesn't cover.
For the 3D View Add menu all I could muster at the moment was a brute force solution of replicating the menus with searching disabled. It is a bit inelegant but should do the trick for now if nothing changes.
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#n
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Classic Add Menus with Accelerator",
"author": "Duarte Farrajota Ramos",
"version": (1, 1),
"blender": (4, 00, 0),
"location": "Properties > Modifiers, 3D View > Add Menu",
"description": "Brings back classic add modifier and add object menus with accelarator keys",
"warning": "",
"doc_url": "http://duarteramos.pt/addons.html",
"category": "UI",
}
import bpy
from bpy.utils import register_class
from bpy.types import (
Header,
Menu,
Panel,
)
from bpy.app.translations import (
pgettext_iface as iface_,
pgettext_tip as tip_,
contexts as i18n_contexts,
)
class VIEW3D_MT_mesh_add_classic(Menu):
bl_idname = "VIEW3D_MT_mesh_add_classic"
bl_label = "Mesh"
##bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, _context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("mesh.primitive_plane_add", text="Plane", icon='MESH_PLANE')
layout.operator("mesh.primitive_cube_add", text="Cube", icon='MESH_CUBE')
layout.operator("mesh.primitive_circle_add", text="Circle", icon='MESH_CIRCLE')
layout.operator("mesh.primitive_uv_sphere_add", text="UV Sphere", icon='MESH_UVSPHERE')
layout.operator("mesh.primitive_ico_sphere_add", text="Ico Sphere", icon='MESH_ICOSPHERE')
layout.operator("mesh.primitive_cylinder_add", text="Cylinder", icon='MESH_CYLINDER')
layout.operator("mesh.primitive_cone_add", text="Cone", icon='MESH_CONE')
layout.operator("mesh.primitive_torus_add", text="Torus", icon='MESH_TORUS')
layout.separator()
layout.operator("mesh.primitive_grid_add", text="Grid", icon='MESH_GRID')
layout.operator("mesh.primitive_monkey_add", text="Monkey", icon='MESH_MONKEY')
layout.template_node_operator_asset_menu_items(catalog_path="Add")
class VIEW3D_MT_curve_add_classic(Menu):
bl_idname = "VIEW3D_MT_curve_add_classic"
bl_label = "Curve"
##bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("curve.primitive_bezier_curve_add", text="Bezier", icon='CURVE_BEZCURVE')
layout.operator("curve.primitive_bezier_circle_add", text="Circle", icon='CURVE_BEZCIRCLE')
layout.separator()
layout.operator("curve.primitive_nurbs_curve_add", text="Nurbs Curve", icon='CURVE_NCURVE')
layout.operator("curve.primitive_nurbs_circle_add", text="Nurbs Circle", icon='CURVE_NCIRCLE')
layout.operator("curve.primitive_nurbs_path_add", text="Path", icon='CURVE_PATH')
layout.separator()
layout.operator("object.curves_empty_hair_add", text="Empty Hair", icon='CURVES_DATA')
layout.operator("object.quick_fur", text="Fur", icon='CURVES_DATA')
experimental = context.preferences.experimental
if experimental.use_new_curves_tools:
layout.operator("object.curves_random_add", text="Random", icon='CURVES_DATA')
class VIEW3D_MT_surface_add_classic(Menu):
bl_idname = "VIEW3D_MT_surface_add_classic"
bl_label = "Surface"
##bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, _context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("surface.primitive_nurbs_surface_curve_add", text="Nurbs Curve", icon='SURFACE_NCURVE')
layout.operator("surface.primitive_nurbs_surface_circle_add", text="Nurbs Circle", icon='SURFACE_NCIRCLE')
layout.operator("surface.primitive_nurbs_surface_surface_add", text="Nurbs Surface", icon='SURFACE_NSURFACE')
layout.operator("surface.primitive_nurbs_surface_cylinder_add",
text="Nurbs Cylinder", icon='SURFACE_NCYLINDER')
layout.operator("surface.primitive_nurbs_surface_sphere_add", text="Nurbs Sphere", icon='SURFACE_NSPHERE')
layout.operator("surface.primitive_nurbs_surface_torus_add", text="Nurbs Torus", icon='SURFACE_NTORUS')
class VIEW3D_MT_edit_metaball_context_menu_classic(Menu):
bl_label = "Metaball"
def draw(self, _context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
# Add
layout.operator("mball.duplicate_move")
layout.separator()
# Modify
layout.menu("VIEW3D_MT_mirror")
layout.menu("VIEW3D_MT_snap")
layout.separator()
# Remove
layout.operator_context = 'EXEC_REGION_WIN'
layout.operator("mball.delete_metaelems", text="Delete")
class VIEW3D_MT_metaball_add_classic(Menu):
bl_idname = "VIEW3D_MT_metaball_add_classic"
bl_label = "Metaball"
##bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, _context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator_enum("object.metaball_add", "type")
class VIEW3D_MT_armature_add_classic(Menu):
bl_idname = "VIEW3D_MT_armature_add_classic"
bl_label = "Armature"
#bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, _context):
layout = self.layout
layout.operator_context = 'EXEC_REGION_WIN'
layout.operator("object.armature_add", text="Single Bone", icon='BONE_DATA')
class VIEW3D_MT_light_add_classic(Menu):
bl_idname = "VIEW3D_MT_light_add_classic"
bl_context = i18n_contexts.id_light
bl_label = "Light"
#bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, _context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator_enum("object.light_add", "type")
class VIEW3D_MT_lightprobe_add_classic(Menu):
bl_idname = "VIEW3D_MT_lightprobe_add_classic"
bl_label = "Light Probe"
#bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, _context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator_enum("object.lightprobe_add", "type")
class VIEW3D_MT_camera_add_classic(Menu):
bl_idname = "VIEW3D_MT_camera_add_classic"
bl_label = "Camera"
#bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, _context):
layout = self.layout
layout.operator_context = 'EXEC_REGION_WIN'
layout.operator("object.camera_add", text="Camera", icon='OUTLINER_OB_CAMERA')
class VIEW3D_MT_volume_add_classic(Menu):
bl_idname = "VIEW3D_MT_volume_add_classic"
bl_label = "Volume"
bl_translation_context = i18n_contexts.id_id
#bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, _context):
layout = self.layout
layout.operator("object.volume_import", text="Import OpenVDB...", icon='OUTLINER_DATA_VOLUME')
layout.operator("object.volume_add", text="Empty",
text_ctxt=i18n_contexts.id_volume,
icon='OUTLINER_DATA_VOLUME')
class VIEW3D_MT_grease_pencil_add_classic(Menu):
bl_idname = "VIEW3D_MT_grease_pencil_add_classic"
bl_label = "Grease Pencil"
#bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, _context):
layout = self.layout
layout.operator("object.grease_pencil_add", text="Empty", icon='EMPTY_AXIS').type = 'EMPTY'
layout.operator("object.grease_pencil_add", text="Stroke", icon='STROKE').type = 'STROKE'
layout.operator("object.grease_pencil_add", text="Suzanne", icon='MONKEY').type = 'MONKEY'
class VIEW3D_MT_add_classic(Menu):
bl_label = "Add"
bl_translation_context = i18n_contexts.operator_default
#bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, context):
layout = self.layout
if layout.operator_context == 'EXEC_REGION_WIN':
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("WM_OT_search_single_menu", text="Search...", icon='VIEWZOOM').menu_idname = "VIEW3D_MT_add"
layout.separator()
# NOTE: don't use 'EXEC_SCREEN' or operators won't get the `v3d` context.
# NOTE: was `EXEC_AREA`, but this context does not have the `rv3d`, which prevents
# "align_view" to work on first call (see #32719).
layout.operator_context = 'EXEC_REGION_WIN'
# layout.operator_menu_enum("object.mesh_add", "type", text="Mesh", icon='OUTLINER_OB_MESH')
layout.menu("VIEW3D_MT_mesh_add_classic", icon='OUTLINER_OB_MESH')
# layout.operator_menu_enum("object.curve_add", "type", text="Curve", icon='OUTLINER_OB_CURVE')
layout.menu("VIEW3D_MT_curve_add_classic", icon='OUTLINER_OB_CURVE')
# layout.operator_menu_enum("object.surface_add", "type", text="Surface", icon='OUTLINER_OB_SURFACE')
layout.menu("VIEW3D_MT_surface_add_classic", icon='OUTLINER_OB_SURFACE')
layout.menu("VIEW3D_MT_metaball_add_classic", text="Metaball", icon='OUTLINER_OB_META')
layout.operator("object.text_add", text="Text", icon='OUTLINER_OB_FONT')
if context.preferences.experimental.use_new_point_cloud_type:
layout.operator("object.pointcloud_add", text="Point Cloud", icon='OUTLINER_OB_POINTCLOUD')
layout.menu("VIEW3D_MT_volume_add_classic", text="Volume", text_ctxt=i18n_contexts.id_id, icon='OUTLINER_OB_VOLUME')
if context.preferences.experimental.use_grease_pencil_version3:
layout.menu("VIEW3D_MT_grease_pencil_add_classic", text="Grease Pencil", icon='OUTLINER_OB_GREASEPENCIL')
else:
layout.operator_menu_enum(
"object.gpencil_add",
"type",
text="Grease Pencil",
icon='OUTLINER_OB_GREASEPENCIL')
layout.separator()
if VIEW3D_MT_armature_add_classic.is_extended():
layout.menu("VIEW3D_MT_armature_add_classic", icon='OUTLINER_OB_ARMATURE')
else:
layout.operator("object.armature_add", text="Armature", icon='OUTLINER_OB_ARMATURE')
layout.operator("object.add", text="Lattice", icon='OUTLINER_OB_LATTICE').type = 'LATTICE'
layout.separator()
layout.operator_menu_enum("object.empty_add", "type", text="Empty",
text_ctxt=i18n_contexts.id_id,
icon='OUTLINER_OB_EMPTY')
layout.menu("VIEW3D_MT_image_add_classic", text="Image", icon='OUTLINER_OB_IMAGE')
layout.separator()
layout.menu("VIEW3D_MT_light_add_classic", icon='OUTLINER_OB_LIGHT')
layout.menu("VIEW3D_MT_lightprobe_add_classic", icon='OUTLINER_OB_LIGHTPROBE')
layout.separator()
if VIEW3D_MT_camera_add_classic.is_extended():
layout.menu("VIEW3D_MT_camera_add_classic", icon='OUTLINER_OB_CAMERA')
else:
VIEW3D_MT_camera_add_classic.draw(self, context)
layout.separator()
layout.operator("object.speaker_add", text="Speaker", icon='OUTLINER_OB_SPEAKER')
layout.separator()
layout.operator_menu_enum("object.effector_add", "type", text="Force Field", icon='OUTLINER_OB_FORCE_FIELD')
layout.separator()
has_collections = bool(bpy.data.collections)
col = layout.column()
col.enabled = has_collections
if not has_collections or len(bpy.data.collections) > 10:
col.operator_context = 'INVOKE_REGION_WIN'
col.operator(
"object.collection_instance_add",
text="Collection Instance..." if has_collections else "No Collections to Instance",
icon='OUTLINER_OB_GROUP_INSTANCE',
)
else:
col.operator_menu_enum(
"object.collection_instance_add",
"collection",
text="Collection Instance",
icon='OUTLINER_OB_GROUP_INSTANCE',
)
class VIEW3D_MT_image_add_classic(Menu):
bl_label = "Add Image"
#bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, _context):
layout = self.layout
layout.operator("object.load_reference_image", text="Reference", icon='IMAGE_REFERENCE')
layout.operator("object.load_background_image", text="Background", icon='IMAGE_BACKGROUND')
#############################################################################################################################
## Registration
classes = (
VIEW3D_MT_mesh_add_classic,
VIEW3D_MT_curve_add_classic,
VIEW3D_MT_surface_add_classic,
VIEW3D_MT_metaball_add_classic,
VIEW3D_MT_armature_add_classic,
VIEW3D_MT_light_add_classic,
VIEW3D_MT_lightprobe_add_classic,
VIEW3D_MT_camera_add_classic,
VIEW3D_MT_volume_add_classic,
VIEW3D_MT_add_classic,
)
# Draw modifiers menu
def drawmodifiers(self, context):
layout = self.layout
layout.operator_menu_enum("object.modifier_add", "type")
#############################################################################################################################
# Registration
# Store keymaps here to access after registration
addon_keymaps = []
def register():
for cls in classes:
register_class(cls)
# Append button to the properties Window > Modifier Tab
bpy.types.DATA_PT_modifiers.prepend(drawmodifiers)
bpy.types
#Keymaps###########################################################
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if not kc:
print('No keyconfig path found. Adding new')
return
#3D View Global
km = kc.keymaps.new(name = "3D View", space_type = "VIEW_3D")
kmi = km.keymap_items.new("wm.call_menu", 'A', value = "PRESS", shift = True, ctrl = True)
kmi.properties.name = "VIEW3D_MT_add_classic"
def unregister():
for cls in reversed(classes):
unregister_class(cls)
bpy.types.DATA_PT_modifiers.remove(drawmodifiers)
if __name__ == "__main__":
register()
Just copy the code into a plain text file, save it in your computer with the extension `.py. Install it as a regular addon from Edit > Preferences > Addons.
Activate it with the check box after installation, and save preferences if you disabled auto-saving.
It registers by default ⇧ Shift + Ctrl + A for the "classic" Add menu with key accelerators.
You can replace the default ⇧ Shift + A entry or add any other key with the classic menu from Edit > Preferences > Keymap. Then add a new key entry.
Set the operator to wm.call_menu
, and the and the name to VIEW3D_MT_add_classic
.
In the meantime I found out how it can be done programmatically, though I don't currently have the time actually implement it all.
You could theoretically make it work by inspecting all classes of menus in all files found at Blender\4.0\scripts\startup\bl_ui
, the ones of interest will be like space_view3d.py
for the 3D view, space_node.py
for the node editor among others.
In them you will have to look for the Add menu definitions which will be named something along the convention of class ****_MT_add(bpy.types.Menu):
where ****
is the name of the space like NODE
or 3DVIEW
, and a suffix like mesh
, curve
. Example class VIEW3D_MT_mesh_add(Menu):
is the add mesh menu in the 3D View.
If you notice each has a property bl_options = {'SEARCH_ON_KEY_PRESS'}
which makes it searchable. What we want in this case would be to remove it from all menus.
To go about it one would have to gather a list of all the relevant menu classes, which should be relatively easy to find all listed at the bottom of each file under class registration, and remove this bl_options
from all of them. Setting it to None
won't suffice.
The easiest method would then be to iterate through all menu classes in a for
loop, unregister them, and re-register them all again but without bl_options
. This is necessary because without re-registration Blender won't pick up the changes.
It is a moderate amount of work, but seems more maintainable than the previous solution. Another option would be to register new menu classes instead, but that would require also making the new classes call the new sub menus, which would be a lot more involved.
I don't have the time or inclination to implement it at the moment, only tested it from the console and it seems to work, so it should in theory work from an addon as well, though I didn't actually test it.