# SPDX-License-Identifier: GPL-2.0-or-later import bpy from bpy.types import ( Header, Menu, Panel, ) from bl_ui.properties_grease_pencil_common import ( GreasePencilLayerMasksPanel, GreasePencilLayerTransformPanel, GreasePencilLayerAdjustmentsPanel, GreasePencilLayerRelationsPanel, GreasePencilLayerDisplayPanel, ) from rna_prop_ui import PropertyPanel ####################################### # 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") if hasattr(bpy.data, "hair_curves") and bpy.data.hair_curves: flow.prop(dopesheet, "show_hair_curves", text="Hair Curves") if hasattr(bpy.data, "pointclouds") and bpy.data.pointclouds: flow.prop(dopesheet, "show_pointclouds", text="Point Clouds") if bpy.data.volumes: flow.prop(dopesheet, "show_volumes", text="Volumes") # 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: @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") # Layer management if st.mode == 'GPENCIL': ob = context.active_object selected = st.dopesheet.show_only_selected enable_but = selected and ob is not None and ob.type == 'GPENCIL' row = layout.row(align=True) row.enabled = enable_but row.operator("gpencil.layer_add", icon='ADD', text="") row.operator("gpencil.layer_remove", icon='REMOVE', text="") row.menu("GPENCIL_MT_layer_context_menu", icon='DOWNARROW_HLT', text="") row = layout.row(align=True) row.enabled = enable_but row.operator("gpencil.layer_move", icon='TRIA_UP', text="").type = 'UP' row.operator("gpencil.layer_move", icon='TRIA_DOWN', text="").type = 'DOWN' row = layout.row(align=True) row.enabled = enable_but row.operator("gpencil.layer_isolate", icon='RESTRICT_VIEW_ON', text="").affect_visibility = True row.operator("gpencil.layer_isolate", icon='LOCKED', text="").affect_visibility = False 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_only_selected", text="") row.prop(st.dopesheet, "show_hidden", 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") if st.show_markers: 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_key") 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.prop(st, "show_region_hud") layout.separator() layout.prop(st.dopesheet, "use_multi_word_filter", text="Multi-Word Match Search") layout.separator() layout.prop(st, "use_realtime_update") # Sliders are always shown in the Shape Key Editor regardless of this setting. col = layout.column() col.active = context.space_data.mode != 'SHAPEKEY' col.prop(st, "show_sliders") layout.prop(st, "show_interpolation") layout.prop(st, "show_extremes") layout.prop(st, "use_auto_merge_keyframes") layout.separator() layout.prop(st, "show_markers") layout.separator() 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_view_pie(Menu): bl_label = "View" def draw(self, _context): layout = self.layout pie = layout.menu_pie() pie.operator("action.view_all") pie.operator("action.view_selected", icon='ZOOM_SELECTED') pie.operator("action.view_frame") 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="Box Select (Axis Range)").axis_range = True layout.operator("action.select_circle") layout.operator_menu_enum("action.select_lasso", "mode") 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.operator_menu_enum("action.easing_type", "type", text="Easing Mode") layout.separator() layout.operator("action.clean").channels = False layout.operator("action.clean", text="Clean Channels").channels = True layout.operator("action.sample") layout.separator() layout.operator("graph.euler_filter", text="Discontinuity (Euler) Filter") 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' class DopesheetActionPanelBase: bl_region_type = 'UI' bl_label = "Action" @classmethod def draw_generic_panel(cls, _context, layout, action): layout.label(text=action.name, icon='ACTION') layout.prop(action, "use_frame_range") col = layout.column() col.active = action.use_frame_range row = col.row(align=True) row.prop(action, "frame_start", text="Start") row.prop(action, "frame_end", text="End") col.prop(action, "use_cyclic") class DOPESHEET_PT_custom_props_action(PropertyPanel, Panel): bl_space_type = 'DOPESHEET_EDITOR' bl_category = "Action" bl_region_type = 'UI' bl_context = 'data' _context_path = "active_action" _property_type = bpy.types.Action @classmethod def poll(cls, context): return bool(context.active_action) class DOPESHEET_PT_action(DopesheetActionPanelBase, Panel): bl_space_type = 'DOPESHEET_EDITOR' bl_category = "Action" @classmethod def poll(cls, context): return bool(context.active_action) def draw(self, context): action = context.active_action self.draw_generic_panel(context, self.layout, action) ####################################### # 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_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.delete") layout.operator("gpencil.interpolate_reverse") layout.separator() layout.operator("action.keyframe_type", text="Keyframe Type") 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 st = context.space_data 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") if st.mode != 'GPENCIL': layout.operator_menu_enum("action.handle_type", "type", text="Handle Type") layout.operator_menu_enum("action.interpolation_type", "type", text="Interpolation Mode") layout.operator_menu_enum("action.easing_type", "type", text="Easing Mode") layout.separator() layout.operator("action.keyframe_insert").type = 'SEL' layout.operator("action.duplicate_move") if st.mode == 'GPENCIL': layout.separator() layout.operator_context = 'EXEC_REGION_WIN' layout.operator("action.delete") if st.mode == 'GPENCIL': layout.operator("gpencil.interpolate_reverse") layout.operator("gpencil.frame_clean_duplicate", text="Delete Duplicate Frames") 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 # This menu is used from the graph editor too. is_graph_editor = context.area.type == 'GRAPH_EDITOR' 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") if is_graph_editor: operator = "graph.extrapolation_type" else: operator = "action.extrapolation_type" layout.operator_menu_enum(operator, "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="Selection to Current Frame").type = 'CFRA' pie.operator("action.snap", text="Selection to Nearest Frame").type = 'NEAREST_FRAME' pie.operator("action.snap", text="Selection to Nearest Second").type = 'NEAREST_SECOND' pie.operator("action.snap", text="Selection to Nearest Marker").type = 'NEAREST_MARKER' class LayersDopeSheetPanel: bl_space_type = 'DOPESHEET_EDITOR' bl_region_type = 'UI' bl_category = "View" @classmethod def poll(cls, context): st = context.space_data ob = context.object if st.mode != 'GPENCIL' or ob is None or ob.type != 'GPENCIL': return False gpd = ob.data gpl = gpd.layers.active if gpl: return True return False class DOPESHEET_PT_gpencil_mode(LayersDopeSheetPanel, Panel): # bl_space_type = 'DOPESHEET_EDITOR' # bl_region_type = 'UI' # bl_category = "View" bl_label = "Layer" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False ob = context.object gpd = ob.data gpl = gpd.layers.active if gpl: row = layout.row(align=True) row.prop(gpl, "blend_mode", text="Blend") row = layout.row(align=True) row.prop(gpl, "opacity", text="Opacity", slider=True) row = layout.row(align=True) row.prop(gpl, "use_lights") class DOPESHEET_PT_gpencil_layer_masks(LayersDopeSheetPanel, GreasePencilLayerMasksPanel, Panel): bl_label = "Masks" bl_parent_id = 'DOPESHEET_PT_gpencil_mode' bl_options = {'DEFAULT_CLOSED'} class DOPESHEET_PT_gpencil_layer_transform(LayersDopeSheetPanel, GreasePencilLayerTransformPanel, Panel): bl_label = "Transform" bl_parent_id = 'DOPESHEET_PT_gpencil_mode' bl_options = {'DEFAULT_CLOSED'} class DOPESHEET_PT_gpencil_layer_adjustments(LayersDopeSheetPanel, GreasePencilLayerAdjustmentsPanel, Panel): bl_label = "Adjustments" bl_parent_id = 'DOPESHEET_PT_gpencil_mode' bl_options = {'DEFAULT_CLOSED'} class DOPESHEET_PT_gpencil_layer_relations(LayersDopeSheetPanel, GreasePencilLayerRelationsPanel, Panel): bl_label = "Relations" bl_parent_id = 'DOPESHEET_PT_gpencil_mode' bl_options = {'DEFAULT_CLOSED'} class DOPESHEET_PT_gpencil_layer_display(LayersDopeSheetPanel, GreasePencilLayerDisplayPanel, Panel): bl_label = "Display" bl_parent_id = 'DOPESHEET_PT_gpencil_mode' bl_options = {'DEFAULT_CLOSED'} classes = ( DOPESHEET_HT_header, 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_key, DOPESHEET_MT_delete, DOPESHEET_MT_context_menu, DOPESHEET_MT_channel_context_menu, DOPESHEET_MT_snap_pie, DOPESHEET_MT_view_pie, DOPESHEET_PT_filters, DOPESHEET_PT_action, DOPESHEET_PT_gpencil_mode, DOPESHEET_PT_gpencil_layer_masks, DOPESHEET_PT_gpencil_layer_transform, DOPESHEET_PT_gpencil_layer_adjustments, DOPESHEET_PT_gpencil_layer_relations, DOPESHEET_PT_gpencil_layer_display, DOPESHEET_PT_custom_props_action, ) if __name__ == "__main__": # only for live edit. from bpy.utils import register_class for cls in classes: register_class(cls)