123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663 |
- # ##### 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,
- )
- #######################################
- # DopeSheet Filtering - Header Buttons
- # used for DopeSheet, NLA, and Graph Editors
- def dopesheet_filter(layout, context):
- dopesheet = context.space_data.dopesheet
- is_nla = context.area.type == 'NLA_EDITOR'
- row = layout.row(align=True)
- row.prop(dopesheet, "show_only_selected", text="")
- row.prop(dopesheet, "show_hidden", text="")
- if is_nla:
- row.prop(dopesheet, "show_missing_nla", text="")
- else: # graph and dopesheet editors - F-Curves and drivers only
- row.prop(dopesheet, "show_only_errors", text="")
- #######################################
- # Dopesheet Filtering Popovers
- # Generic Layout - Used as base for filtering popovers used in all animation editors
- # Used for DopeSheet, NLA, and Graph Editors
- class DopesheetFilterPopoverBase:
- bl_region_type = 'HEADER'
- bl_label = "Filters"
- # Generic = Affects all datatypes
- # XXX: Perhaps we want these to stay in the header instead, for easy/fast access
- @classmethod
- def draw_generic_filters(cls, context, layout):
- dopesheet = context.space_data.dopesheet
- is_nla = context.area.type == 'NLA_EDITOR'
- col = layout.column(align=True)
- col.prop(dopesheet, "show_only_selected", icon='NONE')
- col.prop(dopesheet, "show_hidden", icon='NONE')
- if is_nla:
- col.prop(dopesheet, "show_missing_nla", icon='NONE')
- else: # graph and dopesheet editors - F-Curves and drivers only
- col.prop(dopesheet, "show_only_errors", icon='NONE')
- # Name/Membership Filters
- # XXX: Perhaps these should just stay in the headers (exclusively)?
- @classmethod
- def draw_search_filters(cls, context, layout, generic_filters_only=False):
- dopesheet = context.space_data.dopesheet
- is_nla = context.area.type == 'NLA_EDITOR'
- col = layout.column(align=True)
- if not is_nla:
- row = col.row(align=True)
- row.prop(dopesheet, "filter_fcurve_name", text="")
- else:
- row = col.row(align=True)
- row.prop(dopesheet, "filter_text", text="")
- if (not generic_filters_only) and (bpy.data.collections):
- col = layout.column(align=True)
- col.prop(dopesheet, "filter_collection", text="")
- # Standard = Present in all panels
- @classmethod
- def draw_standard_filters(cls, context, layout):
- dopesheet = context.space_data.dopesheet
- # datablock filters
- layout.label(text="Filter by Type:")
- flow = layout.grid_flow(row_major=True, columns=2, even_rows=False, align=False)
- flow.prop(dopesheet, "show_scenes", text="Scenes")
- flow.prop(dopesheet, "show_nodes", text="Node Trees")
- # object types
- if bpy.data.armatures:
- flow.prop(dopesheet, "show_armatures", text="Armatures")
- if bpy.data.cameras:
- flow.prop(dopesheet, "show_cameras", text="Cameras")
- if bpy.data.grease_pencils:
- flow.prop(dopesheet, "show_gpencil", text="Grease Pencil Objects")
- if bpy.data.lights:
- flow.prop(dopesheet, "show_lights", text="Lights")
- if bpy.data.meshes:
- flow.prop(dopesheet, "show_meshes", text="Meshes")
- if bpy.data.curves:
- flow.prop(dopesheet, "show_curves", text="Curves")
- if bpy.data.lattices:
- flow.prop(dopesheet, "show_lattices", text="Lattices")
- if bpy.data.metaballs:
- flow.prop(dopesheet, "show_metaballs", text="Metaballs")
- # data types
- flow.prop(dopesheet, "show_worlds", text="Worlds")
- if bpy.data.particles:
- flow.prop(dopesheet, "show_particles", text="Particles")
- if bpy.data.linestyles:
- flow.prop(dopesheet, "show_linestyles", text="Line Styles")
- if bpy.data.speakers:
- flow.prop(dopesheet, "show_speakers", text="Speakers")
- if bpy.data.materials:
- flow.prop(dopesheet, "show_materials", text="Materials")
- if bpy.data.textures:
- flow.prop(dopesheet, "show_textures", text="Textures")
- if bpy.data.shape_keys:
- flow.prop(dopesheet, "show_shapekeys", text="Shape Keys")
- if bpy.data.cache_files:
- flow.prop(dopesheet, "show_cache_files", text="Cache Files")
- if bpy.data.movieclips:
- flow.prop(dopesheet, "show_movieclips", text="Movie Clips")
- layout.separator()
- # Object Data Filters
- # TODO: Add per-channel/axis convenience toggles?
- split = layout.split()
- col = split.column()
- col.prop(dopesheet, "show_transforms", text="Transforms")
- col = split.column()
- col.prop(dopesheet, "show_modifiers", text="Modifiers")
- layout.separator()
- # performance-related options (users will mostly have these enabled)
- col = layout.column(align=True)
- col.label(text="Options:")
- col.prop(dopesheet, "use_datablock_sort", icon='NONE')
- # Popover for Dopesheet Editor(s) - Dopesheet, Action, Shapekey, GPencil, Mask, etc.
- class DOPESHEET_PT_filters(DopesheetFilterPopoverBase, Panel):
- bl_space_type = 'DOPESHEET_EDITOR'
- bl_region_type = 'HEADER'
- bl_label = "Filters"
- def draw(self, context):
- layout = self.layout
- dopesheet = context.space_data.dopesheet
- ds_mode = context.space_data.mode
- layout.prop(dopesheet, "show_summary", text="Summary")
- DopesheetFilterPopoverBase.draw_generic_filters(context, layout)
- if ds_mode in {'DOPESHEET', 'ACTION', 'GPENCIL'}:
- layout.separator()
- generic_filters_only = ds_mode != 'DOPESHEET'
- DopesheetFilterPopoverBase.draw_search_filters(context, layout,
- generic_filters_only=generic_filters_only)
- if ds_mode == 'DOPESHEET':
- layout.separator()
- DopesheetFilterPopoverBase.draw_standard_filters(context, layout)
- #######################################
- # DopeSheet Editor - General/Standard UI
- class DOPESHEET_HT_header(Header):
- bl_space_type = 'DOPESHEET_EDITOR'
- def draw(self, context):
- layout = self.layout
- st = context.space_data
- layout.template_header()
- if st.mode == 'TIMELINE':
- from bl_ui.space_time import (
- TIME_MT_editor_menus,
- TIME_HT_editor_buttons,
- )
- TIME_MT_editor_menus.draw_collapsible(context, layout)
- TIME_HT_editor_buttons.draw_header(context, layout)
- else:
- layout.prop(st, "ui_mode", text="")
- DOPESHEET_MT_editor_menus.draw_collapsible(context, layout)
- DOPESHEET_HT_editor_buttons.draw_header(context, layout)
- # Header for "normal" dopesheet editor modes (e.g. Dope Sheet, Action, Shape Keys, etc.)
- class DOPESHEET_HT_editor_buttons(Header):
- bl_idname = "DOPESHEET_HT_editor_buttons"
- bl_space_type = 'DOPESHEET_EDITOR'
- bl_label = ""
- def draw(self, context):
- pass
- @staticmethod
- def draw_header(context, layout):
- st = context.space_data
- tool_settings = context.tool_settings
- if st.mode in {'ACTION', 'SHAPEKEY'}:
- # TODO: These buttons need some tidying up -
- # Probably by using a popover, and bypassing the template_id() here
- row = layout.row(align=True)
- row.operator("action.layer_prev", text="", icon='TRIA_DOWN')
- row.operator("action.layer_next", text="", icon='TRIA_UP')
- row = layout.row(align=True)
- row.operator("action.push_down", text="Push Down", icon='NLA_PUSHDOWN')
- row.operator("action.stash", text="Stash", icon='FREEZE')
- layout.separator_spacer()
- layout.template_ID(st, "action", new="action.new", unlink="action.unlink")
- layout.separator_spacer()
- if st.mode == 'DOPESHEET':
- dopesheet_filter(layout, context)
- elif st.mode == 'ACTION':
- dopesheet_filter(layout, context)
- elif st.mode == 'GPENCIL':
- row = layout.row(align=True)
- row.prop(st.dopesheet, "show_gpencil_3d_only", text="Active Only")
- if st.dopesheet.show_gpencil_3d_only:
- row = layout.row(align=True)
- row.prop(st.dopesheet, "show_only_selected", text="")
- row.prop(st.dopesheet, "show_hidden", text="")
- row = layout.row(align=True)
- row.prop(st.dopesheet, "filter_text", text="")
- layout.popover(
- panel="DOPESHEET_PT_filters",
- text="",
- icon='FILTER',
- )
- # Grease Pencil mode doesn't need snapping, as it's frame-aligned only
- if st.mode != 'GPENCIL':
- layout.prop(st, "auto_snap", text="")
- row = layout.row(align=True)
- row.prop(tool_settings, "use_proportional_action", text="", icon_only=True)
- sub = row.row(align=True)
- sub.active = tool_settings.use_proportional_action
- sub.prop(tool_settings, "proportional_edit_falloff", text="", icon_only=True)
- class DOPESHEET_MT_editor_menus(Menu):
- bl_idname = "DOPESHEET_MT_editor_menus"
- bl_label = ""
- def draw(self, context):
- layout = self.layout
- st = context.space_data
- layout.menu("DOPESHEET_MT_view")
- layout.menu("DOPESHEET_MT_select")
- layout.menu("DOPESHEET_MT_marker")
- if st.mode == 'DOPESHEET' or (st.mode == 'ACTION' and st.action is not None):
- layout.menu("DOPESHEET_MT_channel")
- elif st.mode == 'GPENCIL':
- layout.menu("DOPESHEET_MT_gpencil_channel")
- if st.mode != 'GPENCIL':
- layout.menu("DOPESHEET_MT_key")
- else:
- layout.menu("DOPESHEET_MT_gpencil_frame")
- class DOPESHEET_MT_view(Menu):
- bl_label = "View"
- def draw(self, context):
- layout = self.layout
- st = context.space_data
- layout.prop(st, "show_region_ui")
- layout.separator()
- layout.prop(st.dopesheet, "use_multi_word_filter", text="Multi-word Match Search")
- layout.separator()
- layout.prop(st, "use_realtime_update")
- layout.prop(st, "show_frame_indicator")
- layout.prop(st, "show_sliders")
- layout.prop(st, "show_group_colors")
- layout.prop(st, "show_interpolation")
- layout.prop(st, "show_extremes")
- layout.prop(st, "show_marker_lines")
- layout.prop(st, "use_auto_merge_keyframes")
- layout.prop(st, "show_seconds")
- layout.prop(st, "show_locked_time")
- layout.separator()
- layout.operator("anim.previewrange_set")
- layout.operator("anim.previewrange_clear")
- layout.operator("action.previewrange_set")
- layout.separator()
- layout.operator("action.view_all")
- layout.operator("action.view_selected")
- layout.operator("action.view_frame")
- # Add this to show key-binding (reverse action in dope-sheet).
- layout.separator()
- props = layout.operator("wm.context_set_enum", text="Toggle Graph Editor", icon='GRAPH')
- props.data_path = "area.type"
- props.value = 'GRAPH_EDITOR'
- layout.separator()
- layout.menu("INFO_MT_area")
- class DOPESHEET_MT_select(Menu):
- bl_label = "Select"
- def draw(self, context):
- layout = self.layout
- layout.operator("action.select_all", text="All").action = 'SELECT'
- layout.operator("action.select_all", text="None").action = 'DESELECT'
- layout.operator("action.select_all", text="Invert").action = 'INVERT'
- layout.separator()
- layout.operator("action.select_box").axis_range = False
- layout.operator("action.select_box", text="Border Axis Range").axis_range = True
- layout.operator("action.select_circle")
- layout.separator()
- layout.operator("action.select_column", text="Columns on Selected Keys").mode = 'KEYS'
- layout.operator("action.select_column", text="Column on Current Frame").mode = 'CFRA'
- layout.operator("action.select_column", text="Columns on Selected Markers").mode = 'MARKERS_COLUMN'
- layout.operator("action.select_column", text="Between Selected Markers").mode = 'MARKERS_BETWEEN'
- layout.separator()
- props = layout.operator("action.select_leftright", text="Before Current Frame")
- props.extend = False
- props.mode = 'LEFT'
- props = layout.operator("action.select_leftright", text="After Current Frame")
- props.extend = False
- props.mode = 'RIGHT'
- # FIXME: grease pencil mode isn't supported for these yet, so skip for that mode only
- if context.space_data.mode != 'GPENCIL':
- layout.separator()
- layout.operator("action.select_more")
- layout.operator("action.select_less")
- layout.separator()
- layout.operator("action.select_linked")
- class DOPESHEET_MT_marker(Menu):
- bl_label = "Marker"
- def draw(self, context):
- layout = self.layout
- from bl_ui.space_time import marker_menu_generic
- marker_menu_generic(layout, context)
- st = context.space_data
- if st.mode in {'ACTION', 'SHAPEKEY'} and st.action:
- layout.separator()
- layout.prop(st, "show_pose_markers")
- if st.show_pose_markers is False:
- layout.operator("action.markers_make_local")
- layout.prop(st, "use_marker_sync")
- #######################################
- # Keyframe Editing
- class DOPESHEET_MT_channel(Menu):
- bl_label = "Channel"
- def draw(self, _context):
- layout = self.layout
- layout.operator_context = 'INVOKE_REGION_CHANNELS'
- layout.operator("anim.channels_delete")
- layout.separator()
- layout.operator("anim.channels_group")
- layout.operator("anim.channels_ungroup")
- layout.separator()
- layout.operator_menu_enum("anim.channels_setting_toggle", "type")
- layout.operator_menu_enum("anim.channels_setting_enable", "type")
- layout.operator_menu_enum("anim.channels_setting_disable", "type")
- layout.separator()
- layout.operator("anim.channels_editable_toggle")
- layout.operator_menu_enum("action.extrapolation_type", "type", text="Extrapolation Mode")
- layout.separator()
- layout.operator("anim.channels_expand")
- layout.operator("anim.channels_collapse")
- layout.separator()
- layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")
- layout.separator()
- layout.operator("anim.channels_fcurves_enable")
- class DOPESHEET_MT_key(Menu):
- bl_label = "Key"
- def draw(self, _context):
- layout = self.layout
- layout.menu("DOPESHEET_MT_key_transform", text="Transform")
- layout.operator_menu_enum("action.snap", "type", text="Snap")
- layout.operator_menu_enum("action.mirror", "type", text="Mirror")
- layout.separator()
- layout.operator("action.keyframe_insert")
- layout.separator()
- layout.operator("action.frame_jump")
- layout.separator()
- layout.operator("action.copy")
- layout.operator("action.paste")
- layout.operator("action.paste", text="Paste Flipped").flipped = True
- layout.operator("action.duplicate_move")
- layout.operator("action.delete")
- layout.separator()
- layout.operator_menu_enum("action.keyframe_type", "type", text="Keyframe Type")
- layout.operator_menu_enum("action.handle_type", "type", text="Handle Type")
- layout.operator_menu_enum("action.interpolation_type", "type", text="Interpolation Mode")
- layout.separator()
- layout.operator("action.clean").channels = False
- layout.operator("action.clean", text="Clean Channels").channels = True
- layout.operator("action.sample")
- class DOPESHEET_MT_key_transform(Menu):
- bl_label = "Transform"
- def draw(self, _context):
- layout = self.layout
- layout.operator("transform.transform", text="Move").mode = 'TIME_TRANSLATE'
- layout.operator("transform.transform", text="Extend").mode = 'TIME_EXTEND'
- layout.operator("transform.transform", text="Slide").mode = 'TIME_SLIDE'
- layout.operator("transform.transform", text="Scale").mode = 'TIME_SCALE'
- #######################################
- # Grease Pencil Editing
- class DOPESHEET_MT_gpencil_channel(Menu):
- bl_label = "Channel"
- def draw(self, _context):
- layout = self.layout
- layout.operator_context = 'INVOKE_REGION_CHANNELS'
- layout.operator("anim.channels_delete")
- layout.separator()
- layout.operator("anim.channels_setting_toggle")
- layout.operator("anim.channels_setting_enable")
- layout.operator("anim.channels_setting_disable")
- layout.separator()
- layout.operator("anim.channels_editable_toggle")
- # XXX: to be enabled when these are ready for use!
- # layout.separator()
- # layout.operator("anim.channels_expand")
- # layout.operator("anim.channels_collapse")
- # layout.separator()
- #layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")
- class DOPESHEET_MT_gpencil_frame(Menu):
- bl_label = "Frame"
- def draw(self, _context):
- layout = self.layout
- layout.menu("DOPESHEET_MT_key_transform", text="Transform")
- layout.operator_menu_enum("action.snap", "type", text="Snap")
- layout.operator_menu_enum("action.mirror", "type", text="Mirror")
- layout.separator()
- layout.operator("action.duplicate")
- layout.operator("action.delete")
- layout.separator()
- layout.operator("action.keyframe_type")
- # layout.separator()
- # layout.operator("action.copy")
- # layout.operator("action.paste")
- class DOPESHEET_MT_delete(Menu):
- bl_label = "Delete"
- def draw(self, _context):
- layout = self.layout
- layout.operator("action.delete")
- layout.separator()
- layout.operator("action.clean").channels = False
- layout.operator("action.clean", text="Clean Channels").channels = True
- class DOPESHEET_MT_context_menu(Menu):
- bl_label = "Dope Sheet Context Menu"
- def draw(self, _context):
- layout = self.layout
- layout.operator_context = 'INVOKE_DEFAULT'
- layout.operator("action.copy", text="Copy", icon='COPYDOWN')
- layout.operator("action.paste", text="Paste", icon='PASTEDOWN')
- layout.operator("action.paste", text="Paste Flipped", icon='PASTEFLIPDOWN').flipped = True
- layout.separator()
- layout.operator_menu_enum("action.keyframe_type", "type", text="Keyframe Type")
- layout.operator_menu_enum("action.handle_type", "type", text="Handle Type")
- layout.operator_menu_enum("action.interpolation_type", "type", text="Interpolation Mode")
- layout.separator()
- layout.operator("action.keyframe_insert").type = 'SEL'
- layout.operator("action.duplicate_move")
- layout.operator_context = 'EXEC_REGION_WIN'
- layout.operator("action.delete")
- layout.separator()
- layout.operator_menu_enum("action.mirror", "type", text="Mirror")
- layout.operator_menu_enum("action.snap", "type", text="Snap")
- class DOPESHEET_MT_channel_context_menu(Menu):
- bl_label = "Dope Sheet Channel Context Menu"
- def draw(self, _context):
- layout = self.layout
- layout.operator("anim.channels_setting_enable", text="Mute Channels").type = 'MUTE'
- layout.operator("anim.channels_setting_disable", text="Unmute Channels").type = 'MUTE'
- layout.separator()
- layout.operator("anim.channels_setting_enable", text="Protect Channels").type = 'PROTECT'
- layout.operator("anim.channels_setting_disable", text="Unprotect Channels").type = 'PROTECT'
- layout.separator()
- layout.operator("anim.channels_group")
- layout.operator("anim.channels_ungroup")
- layout.separator()
- layout.operator("anim.channels_editable_toggle")
- layout.operator_menu_enum("action.extrapolation_type", "type", text="Extrapolation Mode")
- layout.separator()
- layout.operator("anim.channels_expand")
- layout.operator("anim.channels_collapse")
- layout.separator()
- layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")
- layout.separator()
- layout.operator("anim.channels_delete")
- class DOPESHEET_MT_snap_pie(Menu):
- bl_label = "Snap"
- def draw(self, _context):
- layout = self.layout
- pie = layout.menu_pie()
- pie.operator("action.snap", text="Current Frame").type = 'CFRA'
- pie.operator("action.snap", text="Nearest Frame").type = 'NEAREST_FRAME'
- pie.operator("action.snap", text="Nearest Second").type = 'NEAREST_SECOND'
- pie.operator("action.snap", text="Nearest Marker").type = 'NEAREST_MARKER'
- classes = (
- DOPESHEET_HT_header,
- DOPESHEET_HT_editor_buttons,
- DOPESHEET_MT_editor_menus,
- DOPESHEET_MT_view,
- DOPESHEET_MT_select,
- DOPESHEET_MT_marker,
- DOPESHEET_MT_channel,
- DOPESHEET_MT_key,
- DOPESHEET_MT_key_transform,
- DOPESHEET_MT_gpencil_channel,
- DOPESHEET_MT_gpencil_frame,
- DOPESHEET_MT_delete,
- DOPESHEET_MT_context_menu,
- DOPESHEET_MT_channel_context_menu,
- DOPESHEET_MT_snap_pie,
- DOPESHEET_PT_filters,
- )
- if __name__ == "__main__": # only for live edit.
- from bpy.utils import register_class
- for cls in classes:
- register_class(cls)
|