Files
test2/release/scripts/startup/bl_ui/space_topbar.py
Campbell Barton fcb534e336 UI: alternate fix for T65702, handling of auto-saving userprefs
The behavior for loading factory settings wasn't clear for users.

This commit changes the behavior:

- Loading factory settings always disables auto-save
  for the current session.
- The internal setting to skip saving on exit is now exposed
  in the preferences (when enabled).
- The menu item "Load Factory Settings (Temporary)" has been removed
  since it's always temporary.

This way users can always reset factory settings without
having to consider the combination of options that might cause their
preferences to be overwritten at exit.

If they want to enable auto-save for the current session
this can be done from the preferences.
2019-06-12 12:30:49 +10:00

796 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
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_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, "clamp_layer", text="",
icon='MOD_MASK' if gpl.clamp_layer else 'LAYER_ACTIVE')
srow = col.row(align=True)
srow.prop(gpl, "use_solo_mode", text="Show Only On Keyframed")
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='LOCKED', text="").affect_visibility = False
sub.operator("gpencil.layer_isolate", icon='HIDE_OFF', text="").affect_visibility = True
class TOPBAR_MT_editor_menus(Menu):
bl_idname = "TOPBAR_MT_editor_menus"
bl_label = ""
def draw(self, _context):
layout = self.layout
layout.menu("TOPBAR_MT_app", text="", icon='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_app(Menu):
bl_label = "Blender"
def draw(self, _context):
layout = self.layout
layout.operator("wm.splash")
layout.separator()
layout.menu("TOPBAR_MT_app_support")
layout.separator()
layout.menu("TOPBAR_MT_app_about")
layout.separator()
layout.operator("preferences.app_template_install", text="Install Application Template...")
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.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
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)
@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
class TOPBAR_MT_app_about(Menu):
bl_label = "About"
def draw(self, _context):
layout = self.layout
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()
layout.operator(
"wm.url_open", text="Blender Website", icon='URL',
).url = "https://www.blender.org/"
layout.operator(
"wm.url_open", text="Credits", icon='URL',
).url = "https://www.blender.org/about/credits/"
layout.separator()
layout.operator(
"wm.url_open", text="License", icon='URL',
).url = "https://www.blender.org/about/license/"
class TOPBAR_MT_app_support(Menu):
bl_label = "Support Blender"
def draw(self, _context):
layout = self.layout
layout.operator(
"wm.url_open", text="Development Fund", icon='URL',
).url = "https://fund.blender.org"
layout.separator()
layout.operator(
"wm.url_open", text="Blender Store", icon='URL',
).url = "https://store.blender.org"
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"
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...", 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.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
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")
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):
# If 'url_prefill_from_blender' becomes slow it could be made into a separate operator
# to avoid constructing the bug report just to show this menu.
from bl_ui_utils.bug_report_url import url_prefill_from_blender
layout = self.layout
show_developer = context.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="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", text="Report a Bug", icon='URL',
).url = url_prefill_from_blender()
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)
# Grease Pencil Fill
class TOPBAR_PT_gpencil_fill(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'HEADER'
bl_label = "Advanced"
def draw(self, context):
paint = context.tool_settings.gpencil_paint
brush = paint.brush
gp_settings = brush.gpencil_settings
layout = self.layout
# Fill
row = layout.row(align=True)
row.prop(gp_settings, "fill_factor", text="Resolution")
if gp_settings.fill_draw_mode != 'STROKE':
row = layout.row(align=True)
row.prop(gp_settings, "show_fill", text="Ignore Transparent Strokes")
row = layout.row(align=True)
row.prop(gp_settings, "fill_threshold", text="Threshold")
# 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 = getattr(scene.sequence_editor, "active_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_app,
TOPBAR_MT_app_about,
TOPBAR_MT_app_support,
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_previews,
TOPBAR_MT_edit,
TOPBAR_MT_render,
TOPBAR_MT_window,
TOPBAR_MT_help,
TOPBAR_PT_gpencil_layers,
TOPBAR_PT_gpencil_primitive,
TOPBAR_PT_gpencil_fill,
TOPBAR_PT_name,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)