# ##### 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. # # ##### END GPL LICENSE BLOCK ##### # import bpy from bpy.types import Header, Menu, Panel class TOPBAR_HT_upper_bar(Header): bl_space_type = 'TOPBAR' def draw(self, context): region = context.region if region.alignment == 'RIGHT': self.draw_right(context) else: self.draw_left(context) def draw_left(self, context): layout = self.layout window = context.window screen = context.screen TOPBAR_MT_editor_menus.draw_collapsible(context, layout) layout.separator() if not screen.show_fullscreen: layout.template_ID_tabs( window, "workspace", new="workspace.add", menu="TOPBAR_MT_workspace_menu", ) else: layout.operator( "screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous", ) def draw_right(self, context): layout = self.layout window = context.window screen = context.screen scene = window.scene # If statusbar is hidden, still show messages at the top if not screen.show_statusbar: layout.template_reports_banner() layout.template_running_jobs() # Active workspace view-layer is retrieved through window, not through workspace. layout.template_ID(window, "scene", new="scene.new", unlink="scene.delete") row = layout.row(align=True) row.template_search( window, "view_layer", scene, "view_layers", new="scene.view_layer_add", unlink="scene.view_layer_remove") class TOPBAR_PT_tool_settings_extra(Panel): """ Popover panel for adding extra options that don't fit in the tool settings header """ bl_idname = "TOPBAR_PT_tool_settings_extra" bl_region_type = 'HEADER' bl_space_type = 'TOPBAR' bl_label = "Extra Options" def draw(self, context): from bl_ui.space_toolsystem_common import ToolSelectPanelHelper layout = self.layout # Get the active tool space_type, mode = ToolSelectPanelHelper._tool_key_from_context( context) cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) item, tool, _ = cls._tool_get_active( context, space_type, mode, with_icon=True) if item is None: return # Draw the extra settings item.draw_settings(context, layout, tool, extra=True) class TOPBAR_PT_tool_fallback(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'HEADER' bl_label = "Layers" bl_ui_units_x = 8 def draw(self, context): from bl_ui.space_toolsystem_common import ToolSelectPanelHelper layout = self.layout tool_settings = context.tool_settings ToolSelectPanelHelper.draw_fallback_tool_items(layout, context) if tool_settings.workspace_tool_type == 'FALLBACK': tool = context.tool ToolSelectPanelHelper.draw_active_tool_fallback( context, layout, tool) class TOPBAR_PT_gpencil_layers(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'HEADER' bl_label = "Layers" bl_ui_units_x = 14 @classmethod def poll(cls, context): if context.gpencil_data is None: return False ob = context.object if ob is not None and ob.type == 'GPENCIL': return True return False def draw(self, context): layout = self.layout gpd = context.gpencil_data # Grease Pencil data... if (gpd is None) or (not gpd.layers): layout.operator("gpencil.layer_add", text="New Layer") else: self.draw_layers(context, layout, gpd) def draw_layers(self, context, layout, gpd): row = layout.row() col = row.column() layer_rows = 10 col.template_list("GPENCIL_UL_layer", "", gpd, "layers", gpd.layers, "active_index", rows=layer_rows, sort_reverse=True, sort_lock=True) gpl = context.active_gpencil_layer if gpl: srow = col.row(align=True) srow.prop(gpl, "blend_mode", text="Blend") srow = col.row(align=True) srow.prop(gpl, "opacity", text="Opacity", slider=True) srow.prop(gpl, "use_mask_layer", text="", icon='MOD_MASK' if gpl.use_mask_layer else 'LAYER_ACTIVE') srow = col.row(align=True) srow.prop(gpl, "use_lights") col = row.column() sub = col.column(align=True) sub.operator("gpencil.layer_add", icon='ADD', text="") sub.operator("gpencil.layer_remove", icon='REMOVE', text="") gpl = context.active_gpencil_layer if gpl: sub.menu("GPENCIL_MT_layer_context_menu", icon='DOWNARROW_HLT', text="") if len(gpd.layers) > 1: col.separator() sub = col.column(align=True) sub.operator("gpencil.layer_move", icon='TRIA_UP', text="").type = 'UP' sub.operator("gpencil.layer_move", icon='TRIA_DOWN', text="").type = 'DOWN' col.separator() sub = col.column(align=True) sub.operator("gpencil.layer_isolate", icon='HIDE_OFF', text="").affect_visibility = True sub.operator("gpencil.layer_isolate", icon='LOCKED', text="").affect_visibility = False class TOPBAR_MT_editor_menus(Menu): bl_idname = "TOPBAR_MT_editor_menus" bl_label = "" def draw(self, context): layout = self.layout # Allow calling this menu directly (this might not be a header area). if getattr(context.area, "show_menus", False): layout.menu("TOPBAR_MT_blender", text="", icon='BLENDER') else: layout.menu("TOPBAR_MT_blender", text="Blender") layout.menu("TOPBAR_MT_file") layout.menu("TOPBAR_MT_edit") layout.menu("TOPBAR_MT_render") layout.menu("TOPBAR_MT_window") layout.menu("TOPBAR_MT_help") class TOPBAR_MT_blender(Menu): bl_label = "Blender" def draw(self, _context): layout = self.layout layout.operator("wm.splash") layout.operator("wm.splash_about") layout.separator() layout.operator("preferences.app_template_install", text="Install Application Template...") layout.separator() layout.menu("TOPBAR_MT_blender_system") class TOPBAR_MT_file_cleanup(Menu): bl_label = "Clean Up" def draw(self, _context): layout = self.layout layout.separator() op_props = layout.operator("outliner.orphans_purge", text="Unused Data-Blocks") op_props.do_local_ids = True op_props.do_linked_ids = True op_props.do_recursive = False op_props = layout.operator("outliner.orphans_purge", text="Recursive Unused Data-Blocks") op_props.do_local_ids = True op_props.do_linked_ids = True op_props.do_recursive = True layout.separator() op_props = layout.operator("outliner.orphans_purge", text="Unused Linked Data-Blocks") op_props.do_local_ids = False op_props.do_linked_ids = True op_props.do_recursive = False op_props = layout.operator("outliner.orphans_purge", text="Recursive Unused Linked Data-Blocks") op_props.do_local_ids = False op_props.do_linked_ids = True op_props.do_recursive = True layout.separator() op_props = layout.operator("outliner.orphans_purge", text="Unused Local Data-Blocks") op_props.do_local_ids = True op_props.do_linked_ids = False op_props.do_recursive = False op_props = layout.operator("outliner.orphans_purge", text="Recursive Unused Local Data-Blocks") op_props.do_local_ids = True op_props.do_linked_ids = False op_props.do_recursive = True class TOPBAR_MT_file(Menu): bl_label = "File" def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_AREA' layout.menu("TOPBAR_MT_file_new", text="New", icon='FILE_NEW') layout.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER') layout.menu("TOPBAR_MT_file_open_recent") layout.operator("wm.revert_mainfile") layout.menu("TOPBAR_MT_file_recover") layout.separator() layout.operator_context = 'EXEC_AREA' if context.blend_data.is_saved else 'INVOKE_AREA' layout.operator("wm.save_mainfile", text="Save", icon='FILE_TICK') layout.operator_context = 'INVOKE_AREA' layout.operator("wm.save_as_mainfile", text="Save As...") layout.operator_context = 'INVOKE_AREA' layout.operator("wm.save_as_mainfile", text="Save Copy...").copy = True layout.separator() layout.operator_context = 'INVOKE_AREA' layout.operator("wm.link", text="Link...", icon='LINK_BLEND') layout.operator("wm.append", text="Append...", icon='APPEND_BLEND') layout.menu("TOPBAR_MT_file_previews") layout.separator() layout.menu("TOPBAR_MT_file_import", icon='IMPORT') layout.menu("TOPBAR_MT_file_export", icon='EXPORT') layout.separator() layout.menu("TOPBAR_MT_file_external_data") layout.menu("TOPBAR_MT_file_cleanup") layout.separator() layout.menu("TOPBAR_MT_file_defaults") layout.separator() layout.operator("wm.quit_blender", text="Quit", icon='QUIT') class TOPBAR_MT_file_new(Menu): bl_label = "New File" @staticmethod def app_template_paths(): import os template_paths = bpy.utils.app_template_paths() # Expand template paths. # Use a set to avoid duplicate user/system templates. # This is a corner case, but users managed to do it! T76849. app_templates = set() for path in template_paths: for d in os.listdir(path): if d.startswith(("__", ".")): continue template = os.path.join(path, d) if os.path.isdir(template): app_templates.add(d) return sorted(app_templates) @staticmethod def draw_ex(layout, _context, *, use_splash=False, use_more=False): layout.operator_context = 'INVOKE_DEFAULT' # Limit number of templates in splash screen, spill over into more menu. paths = TOPBAR_MT_file_new.app_template_paths() splash_limit = 5 if use_splash: icon = 'FILE_NEW' show_more = len(paths) > (splash_limit - 1) if show_more: paths = paths[:splash_limit - 2] elif use_more: icon = 'FILE_NEW' paths = paths[splash_limit - 2:] show_more = False else: icon = 'NONE' show_more = False # Draw application templates. if not use_more: props = layout.operator( "wm.read_homefile", text="General", icon=icon) props.app_template = "" for d in paths: props = layout.operator( "wm.read_homefile", text=bpy.path.display_name(d), icon=icon, ) props.app_template = d layout.operator_context = 'EXEC_DEFAULT' if show_more: layout.menu("TOPBAR_MT_templates_more", text="...") def draw(self, context): TOPBAR_MT_file_new.draw_ex(self.layout, context) class TOPBAR_MT_file_recover(Menu): bl_label = "Recover" def draw(self, _context): layout = self.layout layout.operator("wm.recover_last_session", text="Last Session") layout.operator("wm.recover_auto_save", text="Auto Save...") class TOPBAR_MT_file_defaults(Menu): bl_label = "Defaults" def draw(self, context): layout = self.layout prefs = context.preferences layout.operator_context = 'INVOKE_AREA' if any(bpy.utils.app_template_paths()): app_template = prefs.app_template else: app_template = None if app_template: layout.label(text=bpy.path.display_name( app_template, has_ext=False)) layout.operator("wm.save_homefile") props = layout.operator("wm.read_factory_settings") if app_template: props.app_template = app_template # Include technical operators here which would otherwise have no way for users to access. class TOPBAR_MT_blender_system(Menu): bl_label = "System" def draw(self, _context): layout = self.layout layout.operator("script.reload") layout.separator() layout.operator("wm.memory_statistics") layout.operator("wm.debug_menu") layout.operator_menu_enum("wm.redraw_timer", "type") layout.separator() layout.operator("screen.spacedata_cleanup") class TOPBAR_MT_templates_more(Menu): bl_label = "Templates" def draw(self, context): bpy.types.TOPBAR_MT_file_new.draw_ex( self.layout, context, use_more=True) class TOPBAR_MT_file_import(Menu): bl_idname = "TOPBAR_MT_file_import" bl_label = "Import" bl_owner_use_filter = False def draw(self, _context): if bpy.app.build_options.collada: self.layout.operator("wm.collada_import", text="Collada (Default) (.dae)") if bpy.app.build_options.alembic: self.layout.operator("wm.alembic_import", text="Alembic (.abc)") if bpy.app.build_options.usd: self.layout.operator( "wm.usd_import", text="Universal Scene Description (.usd, .usdc, .usda)") self.layout.operator("wm.gpencil_import_svg", text="SVG as Grease Pencil") class TOPBAR_MT_file_export(Menu): bl_idname = "TOPBAR_MT_file_export" bl_label = "Export" bl_owner_use_filter = False def draw(self, _context): if bpy.app.build_options.collada: self.layout.operator("wm.collada_export", text="Collada (Default) (.dae)") if bpy.app.build_options.alembic: self.layout.operator("wm.alembic_export", text="Alembic (.abc)") if bpy.app.build_options.usd: self.layout.operator( "wm.usd_export", text="Universal Scene Description (.usd, .usdc, .usda)") # Pugixml lib dependency if bpy.app.build_options.pugixml: self.layout.operator("wm.gpencil_export_svg", text="Grease Pencil as SVG") # Haru lib dependency if bpy.app.build_options.haru: self.layout.operator("wm.gpencil_export_pdf", text="Grease Pencil as PDF") self.layout.operator("wm.obj_export", text="Wavefront (.obj) (experimental)") class TOPBAR_MT_file_external_data(Menu): bl_label = "External Data" def draw(self, _context): layout = self.layout icon = 'CHECKBOX_HLT' if bpy.data.use_autopack else 'CHECKBOX_DEHLT' layout.operator("file.autopack_toggle", icon=icon) pack_all = layout.row() pack_all.operator("file.pack_all") pack_all.active = not bpy.data.use_autopack unpack_all = layout.row() unpack_all.operator("file.unpack_all") unpack_all.active = not bpy.data.use_autopack layout.separator() layout.operator("file.pack_libraries") layout.operator("file.unpack_libraries") layout.separator() layout.operator("file.make_paths_relative") layout.operator("file.make_paths_absolute") layout.separator() layout.operator("file.report_missing_files") layout.operator("file.find_missing_files") class TOPBAR_MT_file_previews(Menu): bl_label = "Data Previews" def draw(self, _context): layout = self.layout layout.operator("wm.previews_ensure") layout.operator("wm.previews_batch_generate") layout.separator() layout.operator("wm.previews_clear") layout.operator("wm.previews_batch_clear") class TOPBAR_MT_render(Menu): bl_label = "Render" def draw(self, context): layout = self.layout rd = context.scene.render layout.operator("render.render", text="Render Image", icon='RENDER_STILL').use_viewport = True props = layout.operator( "render.render", text="Render Animation", icon='RENDER_ANIMATION') props.animation = True props.use_viewport = True layout.separator() layout.operator("sound.mixdown", text="Render Audio...") layout.separator() layout.operator("render.view_show", text="View Render") layout.operator("render.play_rendered_anim", text="View Animation") layout.separator() layout.prop(rd, "use_lock_interface", text="Lock Interface") class TOPBAR_MT_edit(Menu): bl_label = "Edit" def draw(self, context): layout = self.layout show_developer = context.preferences.view.show_developer_ui layout.operator("ed.undo") layout.operator("ed.redo") layout.separator() layout.menu("TOPBAR_MT_undo_history") layout.separator() layout.operator("screen.repeat_last") layout.operator("screen.repeat_history", text="Repeat History...") layout.separator() layout.operator("screen.redo_last", text="Adjust Last Operation...") layout.separator() layout.operator("wm.search_menu", text="Menu Search...", icon='VIEWZOOM') if show_developer: layout.operator("wm.search_operator", text="Operator Search...", icon='VIEWZOOM') layout.separator() # Mainly to expose shortcut since this depends on the context. props = layout.operator("wm.call_panel", text="Rename Active Item...") props.name = "TOPBAR_PT_name" props.keep_open = False layout.operator("wm.batch_rename", text="Batch Rename...") layout.separator() # Should move elsewhere (impacts outliner & 3D view). tool_settings = context.tool_settings layout.prop(tool_settings, "lock_object_mode") layout.separator() layout.operator("screen.userpref_show", text="Preferences...", icon='PREFERENCES') class TOPBAR_MT_window(Menu): bl_label = "Window" def draw(self, context): import sys layout = self.layout operator_context_default = layout.operator_context layout.operator("wm.window_new") layout.operator("wm.window_new_main") layout.separator() layout.operator("wm.window_fullscreen_toggle", icon='FULLSCREEN_ENTER') layout.separator() layout.operator("screen.workspace_cycle", text="Next Workspace").direction = 'NEXT' layout.operator("screen.workspace_cycle", text="Previous Workspace").direction = 'PREV' layout.separator() layout.prop(context.screen, "show_statusbar") layout.separator() layout.operator("screen.screenshot") # Showing the status in the area doesn't work well in this case. # - From the top-bar, the text replaces the file-menu (not so bad but strange). # - From menu-search it replaces the area that the user may want to screen-shot. # Setting the context to screen causes the status to show in the global status-bar. layout.operator_context = 'INVOKE_SCREEN' layout.operator("screen.screenshot_area") layout.operator_context = operator_context_default if sys.platform[:3] == "win": layout.separator() layout.operator("wm.console_toggle", icon='CONSOLE') if context.scene.render.use_multiview: layout.separator() layout.operator("wm.set_stereo_3d") class TOPBAR_MT_help(Menu): bl_label = "Help" def draw(self, context): layout = self.layout show_developer = context.preferences.view.show_developer_ui layout.operator("wm.url_open_preset", text="Manual", icon='HELP').type = 'MANUAL' layout.operator( "wm.url_open", text="Tutorials", icon='URL', ).url = "https://www.blender.org/tutorials" layout.operator( "wm.url_open", text="Support", icon='URL', ).url = "https://www.blender.org/support" layout.separator() layout.operator( "wm.url_open", text="User Communities", icon='URL', ).url = "https://www.blender.org/community/" layout.operator( "wm.url_open", text="Developer Community", icon='URL', ).url = "https://devtalk.blender.org" layout.separator() layout.operator( "wm.url_open", text="Python API Reference", icon='URL', ).url = bpy.types.WM_OT_doc_view._prefix if show_developer: layout.operator( "wm.url_open", text="Developer Documentation", icon='URL', ).url = "https://wiki.blender.org/wiki/Main_Page" layout.operator("wm.operator_cheat_sheet", icon='TEXT') layout.separator() layout.operator("wm.url_open_preset", text="Report a Bug", icon='URL').type = 'BUG' layout.separator() layout.operator("wm.sysinfo") class TOPBAR_MT_file_context_menu(Menu): bl_label = "File Context Menu" def draw(self, _context): layout = self.layout layout.operator_context = 'INVOKE_AREA' layout.menu("TOPBAR_MT_file_new", text="New", icon='FILE_NEW') layout.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER') layout.separator() layout.operator("wm.link", text="Link...", icon='LINK_BLEND') layout.operator("wm.append", text="Append...", icon='APPEND_BLEND') layout.separator() layout.menu("TOPBAR_MT_file_import", icon='IMPORT') layout.menu("TOPBAR_MT_file_export", icon='EXPORT') layout.separator() layout.operator("screen.userpref_show", text="Preferences...", icon='PREFERENCES') class TOPBAR_MT_workspace_menu(Menu): bl_label = "Workspace" def draw(self, _context): layout = self.layout layout.operator("workspace.duplicate", text="Duplicate", icon='DUPLICATE') if len(bpy.data.workspaces) > 1: layout.operator("workspace.delete", text="Delete", icon='REMOVE') layout.separator() layout.operator("workspace.reorder_to_front", text="Reorder to Front", icon='TRIA_LEFT_BAR') layout.operator("workspace.reorder_to_back", text="Reorder to Back", icon='TRIA_RIGHT_BAR') layout.separator() # For key binding discoverability. props = layout.operator("screen.workspace_cycle", text="Previous Workspace") props.direction = 'PREV' props = layout.operator( "screen.workspace_cycle", text="Next Workspace") props.direction = 'NEXT' # Grease Pencil Object - Primitive curve class TOPBAR_PT_gpencil_primitive(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'HEADER' bl_label = "Primitives" def draw(self, context): settings = context.tool_settings.gpencil_sculpt layout = self.layout # Curve layout.template_curve_mapping( settings, "thickness_primitive_curve", brush=True) # Only a popover class TOPBAR_PT_name(Panel): bl_space_type = 'TOPBAR' # dummy bl_region_type = 'HEADER' bl_label = "Rename Active Item" bl_ui_units_x = 14 def draw(self, context): layout = self.layout # Edit first editable button in popup def row_with_icon(layout, icon): row = layout.row() row.activate_init = True row.label(icon=icon) return row mode = context.mode scene = context.scene space = context.space_data space_type = None if (space is None) else space.type found = False if space_type == 'SEQUENCE_EDITOR': layout.label(text="Sequence Strip Name") item = context.active_sequence_strip if item: row = row_with_icon(layout, 'SEQUENCE') row.prop(item, "name", text="") found = True elif space_type == 'NODE_EDITOR': layout.label(text="Node Label") item = context.active_node if item: row = row_with_icon(layout, 'NODE') row.prop(item, "label", text="") found = True else: if mode == 'POSE' or (mode == 'WEIGHT_PAINT' and context.pose_object): layout.label(text="Bone Name") item = context.active_pose_bone if item: row = row_with_icon(layout, 'BONE_DATA') row.prop(item, "name", text="") found = True elif mode == 'EDIT_ARMATURE': layout.label(text="Bone Name") item = context.active_bone if item: row = row_with_icon(layout, 'BONE_DATA') row.prop(item, "name", text="") found = True else: layout.label(text="Object Name") item = context.object if item: row = row_with_icon(layout, 'OBJECT_DATA') row.prop(item, "name", text="") found = True if not found: row = row_with_icon(layout, 'ERROR') row.label(text="No active item") classes = ( TOPBAR_HT_upper_bar, TOPBAR_MT_file_context_menu, TOPBAR_MT_workspace_menu, TOPBAR_MT_editor_menus, TOPBAR_MT_blender, TOPBAR_MT_blender_system, TOPBAR_MT_file, TOPBAR_MT_file_new, TOPBAR_MT_file_recover, TOPBAR_MT_file_defaults, TOPBAR_MT_templates_more, TOPBAR_MT_file_import, TOPBAR_MT_file_export, TOPBAR_MT_file_external_data, TOPBAR_MT_file_cleanup, TOPBAR_MT_file_previews, TOPBAR_MT_edit, TOPBAR_MT_render, TOPBAR_MT_window, TOPBAR_MT_help, TOPBAR_PT_tool_fallback, TOPBAR_PT_tool_settings_extra, TOPBAR_PT_gpencil_layers, TOPBAR_PT_gpencil_primitive, TOPBAR_PT_name, ) if __name__ == "__main__": # only for live edit. from bpy.utils import register_class for cls in classes: register_class(cls)