bl_info = {
'name': 'Move/Rotate/Scale - Vertex/Edge/Face - Workspaces in Context Menu',
'author': 'TBussinger',
'version': (1, 0, 0),
'blender': (4, 0, 0),
'location': 'Right-Click in any 3D View',
'description': 'Adds Move/Rotate/Scale - Vertex/Edge/Face - Workspaces to the 3D View context menus',
'category': '3D View',
}
import bpy
from bpy.props import StringProperty
# --- Workspace switcher (flyout) ---
WORKSPACES = [
'Layout', 'Modeling', 'Sculpting', 'UV Editing', 'Texture Paint',
'Shading', 'Animation', 'Rendering', 'Compositing', 'Geometry Nodes', 'Scripting'
]
class WM_OT_switch_workspace_quick(bpy.types.Operator):
bl_idname = 'wm.switch_workspace_quick'
bl_label = 'Switch Workspace'
bl_description = 'Switch the active workspace'
bl_options = {'INTERNAL'}
workspace: StringProperty(name='Workspace')
@classmethod
def description(cls, context, properties):
ws = getattr(properties, 'workspace', '')
return f'Switch to "{ws}" workspace' if ws else 'Switch the active workspace'
def execute(self, context):
ws = bpy.data.workspaces.get(self.workspace)
if not ws:
self.report({'WARNING'}, 'Workspace "%s" not found' % self.workspace)
return {'CANCELLED'}
context.window.workspace = ws
return {'FINISHED'}
class VIEW3D_MT_workspace_quick(bpy.types.Menu):
bl_label = 'Workspaces'
bl_idname = 'VIEW3D_MT_workspace_quick'
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
for name in WORKSPACES:
col.operator('wm.switch_workspace_quick', text=name).workspace = name
# --- Context menu injection ---
def draw_trs(self, context):
layout = self.layout
layout.separator()
col = layout.column(align=True)
col.label(text='Tools')
col.operator('wm.tool_set_by_id', text='Move').name = 'builtin.move'
col.operator('wm.tool_set_by_id', text='Rotate').name = 'builtin.rotate'
col.operator('wm.tool_set_by_id', text='Scale').name = 'builtin.scale'
col = layout.column(align=True)
col.label(text='Select Mode')
col.enabled = (context.mode == 'EDIT_MESH')
col.operator('mesh.select_mode', text='Vertex', icon='VERTEXSEL').type = 'VERT'
col.operator('mesh.select_mode', text='Edge', icon='EDGESEL').type = 'EDGE'
col.operator('mesh.select_mode', text='Face', icon='FACESEL').type = 'FACE'
layout.separator()
op = layout.operator('wm.call_menu', text='Workspaces...')
op.name = 'VIEW3D_MT_workspace_quick'
MENUS = (
'VIEW3D_MT_object_context_menu',
'VIEW3D_MT_edit_mesh_context_menu',
'VIEW3D_MT_edit_curve_context_menu',
'VIEW3D_MT_edit_armature_context_menu',
'VIEW3D_MT_pose_context_menu',
)
classes = (
WM_OT_switch_workspace_quick,
VIEW3D_MT_workspace_quick,
)
def register():
for c in classes:
bpy.utils.register_class(c)
for menu_id in MENUS:
menu = getattr(bpy.types, menu_id, None)
if menu:
menu.prepend(draw_trs)
def unregister():
for menu_id in MENUS:
menu = getattr(bpy.types, menu_id, None)
if menu:
try:
menu.remove(draw_trs)
except Exception:
pass
for c in reversed(classes):
try:
bpy.utils.unregister_class(c)
except Exception:
pass
if __name__ == '__main__':
try:
unregister()
except Exception:
pass
register()