This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/release/scripts/startup/bl_ui/space_topbar.py
Brecht Van Lommel 84f21c170d Application Templates: make templates more prominent in the UI.
The goal here is to make app templates usable for default templates
that we can ship with Blender. These only have a custom startup.blend
currently and so are quite limited compared to app templates that fully
customize Blender.

But still it seems like the same kind of concept where we should be
sharing the code and UI. It is useful to be able to save a startup.blend
per template, and I can imagine some scripting being useful in the future
as well.

Changes made:

* File > New and Ctrl+N now list the templates, replacing a separate
  Application Templates menu that was not as easy to discover.
* File menu now shows name of active template above Save Startup File
  and Load Factory Settings to indicate these are saved/loaded per
  template.
* The "Default" template was renamed to "General".
* Workspaces can now be added from any of the template startup.blend
  files when clicking the (+) button in the topbar.

* User preferences are now fully shared between app templates, unless
  the template includes a custom userpref.blend. I think this will be
  useful in general, not all app templates need their own keymaps for
  example.
* Previously Save User Preferences would save the current app template
  and then Blender would start using that template by default. I've
  disabled this, to me it seems it was unintentional, or at least not
  clear at all that saving user preferences also makes the current

Differential Revision: https://developer.blender.org/D3690
2018-09-18 19:38:20 +02:00

726 lines
24 KiB
Python

# ##### 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 #####
# <pep8 compliant>
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
layout.operator("wm.splash", text="", icon='BLENDER', emboss=False)
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
scene = window.scene
# 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_HT_lower_bar(Header):
bl_space_type = 'TOPBAR'
bl_region_type = 'WINDOW'
def draw(self, context):
region = context.region
if region.alignment == 'LEFT':
self.draw_left(context)
elif region.alignment == 'RIGHT':
self.draw_right(context)
else:
self.draw_center(context)
def draw_left(self, context):
layout = self.layout
mode = context.mode
# Active Tool
# -----------
from .space_toolsystem_common import ToolSelectPanelHelper
tool = ToolSelectPanelHelper.draw_active_tool_header(context, layout)
# Object Mode Options
# -------------------
# Example of how toolsettings can be accessed as pop-overs.
# TODO(campbell): editing options should be after active tool options
# (obviously separated for from the users POV)
draw_fn = getattr(_draw_left_context_mode, mode, None)
if draw_fn is not None:
draw_fn(context, layout, tool)
# Note: general mode options should be added to 'draw_right'.
if mode == 'SCULPT':
if (tool is not None) and tool.has_datablock:
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".paint_common", category="")
elif mode == 'PAINT_VERTEX':
if (tool is not None) and tool.has_datablock:
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".paint_common", category="")
elif mode == 'PAINT_WEIGHT':
if (tool is not None) and tool.has_datablock:
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".paint_common", category="")
elif mode == 'PAINT_TEXTURE':
if (tool is not None) and tool.has_datablock:
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".paint_common", category="")
elif mode == 'EDIT_ARMATURE':
pass
elif mode == 'EDIT_CURVE':
pass
elif mode == 'EDIT_MESH':
pass
elif mode == 'POSE':
pass
elif mode == 'PARTICLE':
# Disable, only shows "Brush" panel, which is already in the top-bar.
# if tool.has_datablock:
# layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".paint_common", category="")
pass
elif mode == 'GPENCIL_PAINT':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".greasepencil_paint", category="")
def draw_center(self, context):
pass
def draw_right(self, context):
layout = self.layout
# General options, note, these _could_ display at the RHS of the draw_left callback.
# we just want them not to be confused with tool options.
mode = context.mode
if mode == 'SCULPT':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".sculpt_mode", category="")
elif mode == 'PAINT_VERTEX':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".vertexpaint", category="")
elif mode == 'PAINT_WEIGHT':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".weightpaint", category="")
elif mode == 'PAINT_TEXTURE':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".imagepaint", category="")
elif mode == 'EDIT_TEXT':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".text_edit", category="")
elif mode == 'EDIT_ARMATURE':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".armature_edit", category="")
elif mode == 'EDIT_METABALL':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".mball_edit", category="")
elif mode == 'EDIT_LATTICE':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".lattice_edit", category="")
elif mode == 'EDIT_CURVE':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".curve_edit", category="")
elif mode == 'EDIT_MESH':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".mesh_edit", category="")
elif mode == 'POSE':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".posemode", category="")
elif mode == 'PARTICLE':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".particlemode", category="")
elif mode == 'OBJECT':
layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".objectmode", category="")
elif mode == 'GPENCIL_PAINT':
layout.prop(context.tool_settings, "gpencil_stroke_placement_view3d", text='')
if context.tool_settings.gpencil_stroke_placement_view3d in ('ORIGIN', 'CURSOR'):
layout.prop(context.tool_settings.gpencil_sculpt, "lockaxis", text='')
layout.prop(context.tool_settings, "use_gpencil_draw_onback", text="", icon='ORTHO')
layout.prop(context.tool_settings, "add_gpencil_weight_data", text="", icon='WPAINT_HLT')
layout.prop(context.tool_settings, "use_gpencil_additive_drawing", text="", icon='FREEZE')
elif mode == 'GPENCIL_SCULPT':
layout.prop(context.tool_settings.gpencil_sculpt, "lockaxis", text='')
class _draw_left_context_mode:
@staticmethod
def SCULPT(context, layout, tool):
if (tool is None) or (not tool.has_datablock):
return
brush = context.tool_settings.sculpt.brush
if brush is None:
return
from .properties_paint_common import UnifiedPaintPanel
UnifiedPaintPanel.prop_unified_size(layout, context, brush, "size", slider=True, text="Radius")
UnifiedPaintPanel.prop_unified_strength(layout, context, brush, "strength", slider=True, text="Strength")
layout.prop(brush, "direction", text="", expand=True)
def PAINT_TEXTURE(context, layout, tool):
if (tool is None) or (not tool.has_datablock):
return
brush = context.tool_settings.vertex_paint.brush
if brush is None:
return
from .properties_paint_common import UnifiedPaintPanel
layout.prop(brush, "color", text="")
UnifiedPaintPanel.prop_unified_size(layout, context, brush, "size", slider=True, text="Radius")
UnifiedPaintPanel.prop_unified_strength(layout, context, brush, "strength", slider=True, text="Strength")
def PAINT_VERTEX(context, layout, tool):
if (tool is None) or (not tool.has_datablock):
return
brush = context.tool_settings.vertex_paint.brush
if brush is None:
return
from .properties_paint_common import UnifiedPaintPanel
layout.prop(brush, "color", text="")
UnifiedPaintPanel.prop_unified_size(layout, context, brush, "size", slider=True, text="Radius")
UnifiedPaintPanel.prop_unified_strength(layout, context, brush, "strength", slider=True, text="Strength")
def PAINT_WEIGHT(context, layout, tool):
if (tool is None) or (not tool.has_datablock):
return
brush = context.tool_settings.weight_paint.brush
if brush is None:
return
from .properties_paint_common import UnifiedPaintPanel
UnifiedPaintPanel.prop_unified_weight(layout, context, brush, "weight", slider=True, text="Weight")
UnifiedPaintPanel.prop_unified_size(layout, context, brush, "size", slider=True, text="Radius")
UnifiedPaintPanel.prop_unified_strength(layout, context, brush, "strength", slider=True, text="Strength")
def PARTICLE(context, layout, tool):
# See: 'VIEW3D_PT_tools_brush', basically a duplicate
settings = context.tool_settings.particle_edit
brush = settings.brush
tool = settings.tool
if tool != 'NONE':
layout.prop(brush, "size", slider=True)
if tool == 'ADD':
layout.prop(brush, "count")
layout.prop(settings, "use_default_interpolate")
layout.prop(brush, "steps", slider=True)
layout.prop(settings, "default_key_count", slider=True)
else:
layout.prop(brush, "strength", slider=True)
if tool == 'LENGTH':
layout.row().prop(brush, "length_mode", expand=True)
elif tool == 'PUFF':
layout.row().prop(brush, "puff_mode", expand=True)
layout.prop(brush, "use_puff_volume")
class TOPBAR_MT_editor_menus(Menu):
bl_idname = "TOPBAR_MT_editor_menus"
bl_label = ""
def draw(self, context):
self.draw_menus(self.layout, context)
@staticmethod
def draw_menus(layout, context):
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_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='NEW')
layout.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER')
layout.menu("TOPBAR_MT_file_open_recent")
layout.operator("wm.revert_mainfile")
layout.operator("wm.recover_last_session")
layout.operator("wm.recover_auto_save", text="Recover Auto Save...")
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'
if any(bpy.utils.app_template_paths()):
app_template = context.user_preferences.app_template
else:
app_template = None
if app_template:
layout.label(text=bpy.path.display_name(app_template))
layout.operator("wm.save_homefile")
layout.operator(
"wm.read_factory_settings",
text="Load Factory Settings",
).app_template = app_template
else:
layout.operator("wm.save_homefile")
layout.operator("wm.read_factory_settings")
layout.separator()
layout.operator("wm.app_template_install", text="Install Application Template...")
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.separator()
layout.operator_context = 'EXEC_AREA'
if bpy.data.is_dirty and context.user_preferences.view.use_quit_dialog:
layout.operator_context = 'INVOKE_SCREEN' # quit dialog
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
app_templates = []
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):
# template_paths_expand.append(template)
app_templates.append(d)
return sorted(app_templates)
def draw_ex(self, context, *, use_splash=False, use_default=False):
layout = self.layout
# now draw the presets
layout.operator_context = 'EXEC_DEFAULT'
if use_default:
props = layout.operator("wm.read_homefile", text="General")
props.app_template = ""
for d in TOPBAR_MT_file_new.app_template_paths():
props = layout.operator(
"wm.read_homefile",
text=bpy.path.display_name(d),
)
props.app_template = d
def draw(self, context):
self.draw_ex(context, use_splash=False, use_default=True)
class TOPBAR_MT_file_import(Menu):
bl_idname = "TOPBAR_MT_file_import"
bl_label = "Import"
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)")
class TOPBAR_MT_file_export(Menu):
bl_idname = "TOPBAR_MT_file_export"
bl_label = "Export"
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)")
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)
layout.separator()
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.make_paths_relative")
layout.operator("file.make_paths_absolute")
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.prop_menu_enum(rd, "display_mode", text="Display Mode")
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
layout.operator("ed.undo")
layout.operator("ed.redo")
layout.separator()
layout.operator("ed.undo_history", text="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="Operator Search...")
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="User Preferences...", icon='PREFERENCES')
class TOPBAR_MT_window(Menu):
bl_label = "Window"
def draw(self, context):
import sys
layout = self.layout
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_topbar")
layout.prop(context.screen, "show_statusbar")
layout.separator()
layout.operator("screen.screenshot")
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.user_preferences.view.show_developer_ui
layout.operator(
"wm.url_open", text="Manual", icon='HELP',
).url = "https://docs.blender.org/manual/en/dev/"
layout.operator(
"wm.url_open", text="Report a Bug", icon='URL',
).url = "https://developer.blender.org/maniphest/task/edit/form/1"
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://www.blender.org/get-involved/developers/"
layout.separator()
layout.operator(
"wm.url_open", text="Blender Website", icon='URL',
).url = "https://www.blender.org"
layout.operator(
"wm.url_open", text="Blender Store", icon='URL',
).url = "https://store.blender.org"
layout.operator(
"wm.url_open", text="Release Notes", icon='URL',
).url = "https://www.blender.org/download/releases/%d-%d/" % bpy.app.version[:2]
layout.separator()
if show_developer:
layout.operator(
"wm.url_open", text="Python API Reference", icon='URL',
).url = bpy.types.WM_OT_doc_view._prefix
layout.operator("wm.operator_cheat_sheet", icon='TEXT')
layout.operator("wm.sysinfo")
layout.separator()
layout.operator("wm.splash", icon='BLENDER')
class TOPBAR_MT_file_specials(Menu):
bl_label = "File Context Menu"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_AREA'
layout.operator("wm.read_homefile", text="New", icon='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')
class TOPBAR_MT_window_specials(Menu):
bl_label = "Window Context Menu"
def draw(self, context):
layout = self.layout
layout.operator_context = 'EXEC_AREA'
layout.operator("wm.window_new")
layout.operator("wm.window_new_main")
layout.operator_context = 'INVOKE_AREA'
layout.operator("screen.area_dupli")
layout.operator("wm.window_fullscreen_toggle", icon='FULLSCREEN_ENTER')
layout.separator()
layout.operator("screen.area_split", text="Horizontal Split").direction = 'HORIZONTAL'
layout.operator("screen.area_split", text="Vertical Split").direction = 'VERTICAL'
layout.separator()
layout.operator("screen.userpref_show", text="User 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")
if len(bpy.data.workspaces) > 1:
layout.operator("workspace.delete", text="Delete")
layout.separator()
layout.operator("workspace.reorder_to_front", text="Reorder to Front")
layout.operator("workspace.reorder_to_back", text="Reorder to Back")
class TOPBAR_PT_active_tool(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_category = ""
bl_context = ".active_tool" # dot on purpose (access from tool settings)
bl_label = "Active Tool"
def draw(self, context):
layout = self.layout
# Panel display of topbar tool settings.
# currently displays in tool settings, keep here since the same functionality is used for the topbar.
layout.use_property_split = True
layout.use_property_decorate = False
from .space_toolsystem_common import ToolSelectPanelHelper
ToolSelectPanelHelper.draw_active_tool_header(context, layout, show_tool_name=True)
classes = (
TOPBAR_HT_upper_bar,
TOPBAR_HT_lower_bar,
TOPBAR_MT_file_specials,
TOPBAR_MT_window_specials,
TOPBAR_MT_workspace_menu,
TOPBAR_MT_editor_menus,
TOPBAR_MT_file,
TOPBAR_MT_file_new,
TOPBAR_MT_file_import,
TOPBAR_MT_file_export,
TOPBAR_MT_file_external_data,
TOPBAR_MT_file_previews,
TOPBAR_MT_edit,
TOPBAR_MT_render,
TOPBAR_MT_window,
TOPBAR_MT_help,
TOPBAR_PT_active_tool,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)