Adds a C++ based FBX importer, using 3rd party ufbx library (design task: #131304). The old Python based importer is still there; the new one is marked as "(experimental)" in the menu item. Drag-and-drop uses the old Python importer; the new one is only in the menu item. The new importer is generally 2x-5x faster than the old one, and often uses less memory too. There's potential to make it several times faster still. - ASCII FBX files are supported now - Binary FBX files older than 7.1 (SDK 2012) version are supported now - Better handling of "geometric transform" (common in 3dsmax), manifesting as wrong rotation for some objects when in a hierarchy (e.g. #131172) - Some FBX files that the old importer was failing to read are supported now (e.g. cases 47344, 134983) - Materials import more shader parameters (IOR, diffuse roughness, anisotropy, subsurface, transmission, coat, sheen, thin film) and shader models (e.g. OpenPBR or glTF2 materials from 3dsmax imports much better) - Importer now creates layered/slotted animation actions. Each "take" inside FBX file creates one action, and animated object within it gets a slot. - Materials that use the same texture several times no longer create duplicate images; the same image is used - Material diffuse color animations were imported, but they only animated the viewport color. Now they also animate the nodetree base color too. - "Ignore Leaf Bones" option no longer ignores leaf bones that are actually skinned to some parts of the mesh. - Previous importer was creating orphan invisible Camera data objects for some files (mostly from MotionBuilder?), new one properly creates these cameras. Import settings that existed in Python importer, but are NOT DONE in the new one (mostly because not sure if they are useful, and no one asked for them from feedback yet): - Manual Orientation & Forward/Up Axis: not sure if actually useful. FBX file itself specifies the axes fairly clearly. USD/glTF/Alembic also do not have settings to override them. - Use Pre/Post Rotation (defaults on): feels like it should just always be on. ufbx handles that internally. - Apply Transform (defaults off, warning icon): not sure if needed at all. - Decal Offset: Cycles specific. None of other importers have it. - Automatic Bone Orientation (defaults off): feels like current behavior (either on or off) often produces "nonsensical bones" where bone direction does not go towards the children with either setting. There are discussions within I/O and Animation modules about different ways of bone visualizations and/or different bone length axes, that would solve this in general. - Force Connect Children (defaults off): not sure when that would be useful. On several animated armatures I tried, it turns armature animation into garbage. - Primary/Secondary Bone Axis: again not sure when would be useful. Importer UI screenshots, performance benchmark details and TODOs for later work are in the PR. Pull Request: https://projects.blender.org/blender/blender/pulls/132406
857 lines
27 KiB
Python
857 lines
27 KiB
Python
# SPDX-FileCopyrightText: 2009-2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import bpy
|
|
from bpy.types import Header, Menu, Panel
|
|
|
|
from bpy.app.translations import (
|
|
pgettext_iface as iface_,
|
|
contexts as i18n_contexts,
|
|
)
|
|
|
|
|
|
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"
|
|
bl_description = "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_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()
|
|
|
|
layout.operator("outliner.orphans_purge", text="Purge Unused Data...")
|
|
layout.operator("outliner.orphans_manage", text="Manage Unused Data...")
|
|
|
|
|
|
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", text_ctxt=i18n_contexts.id_windowmanager, 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
|
|
|
|
sub = layout.row()
|
|
sub.enabled = context.blend_data.is_saved
|
|
sub.operator_context = 'EXEC_AREA'
|
|
sub.operator("wm.save_mainfile", text="Save Incremental").incremental = 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')
|
|
row = layout.row()
|
|
row.operator("wm.collection_export_all")
|
|
row.enabled = context.view_layer.has_export_collections
|
|
|
|
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! #76849.
|
|
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(iface_(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=iface_(bpy.path.display_name(app_template, has_ext=False), i18n_contexts.id_workspace),
|
|
translate=False,
|
|
)
|
|
|
|
layout.operator("wm.save_homefile")
|
|
if app_template:
|
|
display_name = bpy.path.display_name(iface_(app_template))
|
|
props = layout.operator("wm.read_factory_settings", text="Load Factory Blender Settings")
|
|
props.app_template = app_template
|
|
props = layout.operator(
|
|
"wm.read_factory_settings",
|
|
text=iface_("Load Factory {:s} Settings", i18n_contexts.operator_default).format(display_name),
|
|
translate=False,
|
|
)
|
|
props.app_template = app_template
|
|
props.use_factory_startup_app_template_only = True
|
|
del display_name
|
|
else:
|
|
layout.operator("wm.read_factory_settings")
|
|
|
|
|
|
# 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")
|
|
layout.operator("wm.operator_presets_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 (.dae) (Legacy)")
|
|
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*)")
|
|
|
|
if bpy.app.build_options.io_gpencil:
|
|
self.layout.operator("wm.grease_pencil_import_svg", text="SVG as Grease Pencil")
|
|
|
|
if bpy.app.build_options.io_wavefront_obj:
|
|
self.layout.operator("wm.obj_import", text="Wavefront (.obj)")
|
|
if bpy.app.build_options.io_ply:
|
|
self.layout.operator("wm.ply_import", text="Stanford PLY (.ply)")
|
|
if bpy.app.build_options.io_stl:
|
|
self.layout.operator("wm.stl_import", text="STL (.stl)")
|
|
|
|
if bpy.app.build_options.io_fbx:
|
|
self.layout.operator("wm.fbx_import", text="FBX (.fbx) (experimental)")
|
|
|
|
|
|
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 (.dae) (Legacy)")
|
|
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*)")
|
|
|
|
if bpy.app.build_options.io_gpencil:
|
|
# PUGIXML library dependency.
|
|
if bpy.app.build_options.pugixml:
|
|
self.layout.operator("wm.grease_pencil_export_svg", text="Grease Pencil as SVG")
|
|
# HARU library dependency.
|
|
if bpy.app.build_options.haru:
|
|
self.layout.operator("wm.grease_pencil_export_pdf", text="Grease Pencil as PDF")
|
|
|
|
if bpy.app.build_options.io_wavefront_obj:
|
|
self.layout.operator("wm.obj_export", text="Wavefront (.obj)")
|
|
if bpy.app.build_options.io_ply:
|
|
self.layout.operator("wm.ply_export", text="Stanford PLY (.ply)")
|
|
if bpy.app.build_options.io_stl:
|
|
self.layout.operator("wm.stl_export", text="STL (.stl)")
|
|
|
|
|
|
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", text="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", text="Batch-Generate Previews...")
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("wm.previews_clear", text="Clear Data-Block Previews...")
|
|
layout.operator("wm.previews_batch_clear", text="Batch-Clear Previews...")
|
|
|
|
|
|
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.menu("TOPBAR_MT_undo_history")
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("screen.redo_last", text="Adjust Last Operation...")
|
|
layout.operator("screen.repeat_last")
|
|
layout.operator("screen.repeat_history", text="Repeat History...")
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("wm.search_menu", text="Menu Search...", icon='VIEWZOOM')
|
|
if show_developer:
|
|
layout.operator("wm.search_operator", text="Operator Search...")
|
|
|
|
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
|
|
from bl_ui_utils.layout import operator_context
|
|
|
|
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")
|
|
|
|
# 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.
|
|
with operator_context(layout, 'INVOKE_SCREEN'):
|
|
layout.operator("screen.screenshot_area")
|
|
|
|
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='URL').type = 'MANUAL'
|
|
layout.operator("wm.url_open_preset", text="Release Notes").type = 'RELEASE_NOTES'
|
|
layout.operator("wm.url_open", text="Tutorials").url = "https://www.blender.org/tutorials"
|
|
layout.operator("wm.url_open", text="Support").url = "https://www.blender.org/support"
|
|
layout.operator("wm.url_open", text="User Communities").url = "https://www.blender.org/community/"
|
|
|
|
layout.separator()
|
|
|
|
if show_developer:
|
|
layout.operator(
|
|
"wm.url_open",
|
|
text="Developer Documentation",
|
|
icon='URL',
|
|
).url = "https://developer.blender.org/docs/"
|
|
layout.operator("wm.url_open", text="Developer Community").url = "https://devtalk.blender.org"
|
|
layout.operator("wm.url_open_preset", text="Python API Reference").type = 'API'
|
|
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.operator("wm.sysinfo")
|
|
|
|
|
|
class TOPBAR_MT_file_context_menu(Menu):
|
|
bl_label = "File"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.operator_context = 'INVOKE_AREA'
|
|
layout.menu("TOPBAR_MT_file_new", text="New", text_ctxt=i18n_contexts.id_windowmanager, icon='FILE_NEW')
|
|
layout.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER')
|
|
layout.menu("TOPBAR_MT_file_open_recent")
|
|
|
|
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
|
|
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_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
|
|
elif space_type == 'NLA_EDITOR':
|
|
layout.label(text="NLA Strip Name")
|
|
item = next(
|
|
(strip for strip in context.selected_nla_strips if strip.active), None)
|
|
if item:
|
|
row = row_with_icon(layout, 'NLA')
|
|
row.prop(item, "name", 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")
|
|
|
|
|
|
class TOPBAR_PT_name_marker(Panel):
|
|
bl_space_type = 'TOPBAR' # dummy
|
|
bl_region_type = 'HEADER'
|
|
bl_label = "Rename Marker"
|
|
bl_ui_units_x = 14
|
|
|
|
@staticmethod
|
|
def is_using_pose_markers(context):
|
|
sd = context.space_data
|
|
return (sd.type == 'DOPESHEET_EDITOR' and sd.mode in {'ACTION', 'SHAPEKEY'} and
|
|
sd.show_pose_markers and sd.action)
|
|
|
|
@staticmethod
|
|
def get_selected_marker(context):
|
|
if TOPBAR_PT_name_marker.is_using_pose_markers(context):
|
|
markers = context.space_data.action.pose_markers
|
|
else:
|
|
markers = context.scene.timeline_markers
|
|
|
|
for marker in markers:
|
|
if marker.select:
|
|
return marker
|
|
return None
|
|
|
|
@staticmethod
|
|
def row_with_icon(layout, icon):
|
|
row = layout.row()
|
|
row.activate_init = True
|
|
row.label(icon=icon)
|
|
return row
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
layout.label(text="Marker Name")
|
|
|
|
scene = context.scene
|
|
if scene.tool_settings.lock_markers:
|
|
row = self.row_with_icon(layout, 'ERROR')
|
|
label = "Markers are locked"
|
|
row.label(text=label)
|
|
return
|
|
|
|
marker = self.get_selected_marker(context)
|
|
if marker is None:
|
|
row = self.row_with_icon(layout, 'ERROR')
|
|
row.label(text="No active marker")
|
|
return
|
|
|
|
icon = 'TIME'
|
|
if marker.camera is not None:
|
|
icon = 'CAMERA_DATA'
|
|
elif self.is_using_pose_markers(context):
|
|
icon = 'ARMATURE_DATA'
|
|
row = self.row_with_icon(layout, icon)
|
|
row.prop(marker, "name", text="")
|
|
|
|
|
|
class TOPBAR_PT_grease_pencil_layers(Panel):
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'HEADER'
|
|
bl_label = "Layers"
|
|
bl_ui_units_x = 14
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
object = context.object
|
|
if object is None:
|
|
return False
|
|
if object.type != 'GREASEPENCIL':
|
|
return False
|
|
|
|
return True
|
|
|
|
def draw(self, context):
|
|
from .properties_data_grease_pencil import DATA_PT_grease_pencil_layers
|
|
|
|
layout = self.layout
|
|
grease_pencil = context.object.data
|
|
|
|
DATA_PT_grease_pencil_layers.draw_settings(layout, grease_pencil)
|
|
|
|
|
|
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_primitive,
|
|
TOPBAR_PT_name,
|
|
TOPBAR_PT_name_marker,
|
|
TOPBAR_PT_grease_pencil_layers,
|
|
)
|
|
|
|
if __name__ == "__main__": # only for live edit.
|
|
from bpy.utils import register_class
|
|
for cls in classes:
|
|
register_class(cls)
|