From 7bd19f7efb4bf9bce0c0550b8137e72bfc9fe87b Mon Sep 17 00:00:00 2001 From: John Kiril Swenson Date: Tue, 26 Aug 2025 11:53:49 +0200 Subject: [PATCH] Fix: VSE: Various crashes when sequencer scene is not initialized Most of these crashes happen because it is assumed that the scene will always be present even if we have an uninitialized `Editing`, which is no longer the case with #140271. - Fix crash when clicking and dragging in the scrub area by checking for valid `sequencer_scene` in `change_frame_poll` - Fix crashes when selecting menu items by disabling them in the UI until a `sequencer_scene` is present - Fix crashes running operators from the F3 menu by changing to more restrictive polls that check for `sequencer_scene` - For good measure, check before dereferencing in `channels_displayed_get`, `active_seqbase_get`, and `editing_get` Pull Request: https://projects.blender.org/blender/blender/pulls/145145 --- scripts/startup/bl_operators/sequencer.py | 2 +- scripts/startup/bl_ui/space_sequencer.py | 73 +++++++++++-------- source/blender/editors/animation/anim_ops.cc | 3 + .../blender/editors/animation/keyframing.cc | 4 +- source/blender/editors/sound/sound_ops.cc | 7 +- .../space_sequencer/sequencer_proxy.cc | 2 + .../editors/space_sequencer/sequencer_view.cc | 3 +- source/blender/sequencer/intern/channels.cc | 2 +- source/blender/sequencer/intern/sequencer.cc | 8 +- 9 files changed, 58 insertions(+), 46 deletions(-) diff --git a/scripts/startup/bl_operators/sequencer.py b/scripts/startup/bl_operators/sequencer.py index e0ad42fa509..db98c144a9d 100644 --- a/scripts/startup/bl_operators/sequencer.py +++ b/scripts/startup/bl_operators/sequencer.py @@ -122,7 +122,7 @@ class SequencerDeinterlaceSelectedMovies(Operator): @classmethod def poll(cls, context): - scene = context.scene + scene = context.sequencer_scene return (scene and scene.sequence_editor) def execute(self, context): diff --git a/scripts/startup/bl_ui/space_sequencer.py b/scripts/startup/bl_ui/space_sequencer.py index db86aef8935..0461e8829bd 100644 --- a/scripts/startup/bl_ui/space_sequencer.py +++ b/scripts/startup/bl_ui/space_sequencer.py @@ -930,30 +930,33 @@ class SEQUENCER_MT_strip_transform(Menu): else: layout.operator_context = 'INVOKE_REGION_WIN' + col = layout.column() if has_preview: - layout.operator("transform.translate", text="Move") - layout.operator("transform.rotate", text="Rotate") - layout.operator("transform.resize", text="Scale") + col.operator("transform.translate", text="Move") + col.operator("transform.rotate", text="Rotate") + col.operator("transform.resize", text="Scale") else: - layout.operator("transform.seq_slide", text="Move").view2d_edge_pan = True - layout.operator("transform.transform", text="Move/Extend from Current Frame").mode = 'TIME_EXTEND' - layout.operator("sequencer.slip", text="Slip Strip Contents") + col.operator("transform.seq_slide", text="Move").view2d_edge_pan = True + col.operator("transform.transform", text="Move/Extend from Current Frame").mode = 'TIME_EXTEND' + col.operator("sequencer.slip", text="Slip Strip Contents") # TODO (for preview) if has_sequencer: - layout.separator() - layout.operator("sequencer.snap") - layout.operator("sequencer.offset_clear") + col.separator() + col.operator("sequencer.snap") + col.operator("sequencer.offset_clear") - layout.separator() + col.separator() if has_sequencer: - layout.operator_menu_enum("sequencer.swap", "side") + col.operator_menu_enum("sequencer.swap", "side") - layout.separator() - layout.operator("sequencer.gap_remove").all = False - layout.operator("sequencer.gap_remove", text="Remove Gaps (All)").all = True - layout.operator("sequencer.gap_insert") + col.separator() + col.operator("sequencer.gap_remove").all = False + col.operator("sequencer.gap_remove", text="Remove Gaps (All)").all = True + col.operator("sequencer.gap_insert") + + col.enabled = bool(context.sequencer_scene) class SEQUENCER_MT_strip_text(Menu): @@ -991,31 +994,34 @@ class SEQUENCER_MT_strip_show_hide(Menu): class SEQUENCER_MT_strip_animation(Menu): bl_label = "Animation" - def draw(self, _context): + def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_PREVIEW' - layout.operator("anim.keyframe_insert", text="Insert Keyframe") - layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set").always_prompt = True - layout.operator("anim.keying_set_active_set", text="Change Keying Set...") - layout.operator("anim.keyframe_delete_vse", text="Delete Keyframes...") - layout.operator("anim.keyframe_clear_vse", text="Clear Keyframes...") + col = layout.column() + col.operator("anim.keyframe_insert", text="Insert Keyframe") + col.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set").always_prompt = True + col.operator("anim.keying_set_active_set", text="Change Keying Set...") + col.operator("anim.keyframe_delete_vse", text="Delete Keyframes...") + col.operator("anim.keyframe_clear_vse", text="Clear Keyframes...") + col.enabled = bool(context.sequencer_scene) class SEQUENCER_MT_strip_mirror(Menu): bl_label = "Mirror" - def draw(self, _context): + def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_PREVIEW' - layout.operator("transform.mirror", text="Interactive Mirror") + col = layout.column() + col.operator("transform.mirror", text="Interactive Mirror") - layout.separator() + col.separator() for (space_name, space_id) in (("Global", 'GLOBAL'), ("Local", 'LOCAL')): for axis_index, axis_name in enumerate("XY"): - props = layout.operator( + props = col.operator( "transform.mirror", text="{:s} {:s}".format(axis_name, iface_(space_name)), translate=False, @@ -1024,7 +1030,8 @@ class SEQUENCER_MT_strip_mirror(Menu): props.orient_type = space_id if space_id == 'GLOBAL': - layout.separator() + col.separator() + col.enabled = bool(context.sequencer_scene) class SEQUENCER_MT_strip_input(Menu): @@ -1283,16 +1290,18 @@ class SEQUENCER_MT_image(Menu): class SEQUENCER_MT_image_transform(Menu): bl_label = "Transform" - def draw(self, _context): + def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_PREVIEW' - layout.operator("transform.translate") - layout.operator("transform.rotate") - layout.operator("transform.resize", text="Scale") - layout.separator() - layout.operator("transform.translate", text="Move Origin").translate_origin = True + col = layout.column() + col.operator("transform.translate") + col.operator("transform.rotate") + col.operator("transform.resize", text="Scale") + col.separator() + col.operator("transform.translate", text="Move Origin").translate_origin = True + col.enabled = bool(context.sequencer_scene) class SEQUENCER_MT_image_clear(Menu): diff --git a/source/blender/editors/animation/anim_ops.cc b/source/blender/editors/animation/anim_ops.cc index 996edac5d28..43b627fed75 100644 --- a/source/blender/editors/animation/anim_ops.cc +++ b/source/blender/editors/animation/anim_ops.cc @@ -111,6 +111,9 @@ static bool change_frame_poll(bContext *C) return true; } if (area->spacetype == SPACE_SEQ) { + if (!CTX_data_sequencer_scene(C)) { + return false; + } /* Check the region type so tools (which are shared between preview/strip view) * don't conflict with actions which can have the same key bound (2D cursor for example). */ const ARegion *region = CTX_wm_region(C); diff --git a/source/blender/editors/animation/keyframing.cc b/source/blender/editors/animation/keyframing.cc index 934586f7fd9..7fe537c2e2e 100644 --- a/source/blender/editors/animation/keyframing.cc +++ b/source/blender/editors/animation/keyframing.cc @@ -930,7 +930,7 @@ void ANIM_OT_keyframe_clear_vse(wmOperatorType *ot) ot->invoke = clear_anim_vse_invoke; ot->exec = clear_anim_vse_exec; - ot->poll = ED_operator_areaactive; + ot->poll = ED_operator_sequencer_scene_editable; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -1120,7 +1120,7 @@ void ANIM_OT_keyframe_delete_vse(wmOperatorType *ot) ot->invoke = delete_key_vse_invoke; ot->exec = delete_key_vse_exec; - ot->poll = ED_operator_areaactive; + ot->poll = ED_operator_sequencer_scene_editable; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; diff --git a/source/blender/editors/sound/sound_ops.cc b/source/blender/editors/sound/sound_ops.cc index 77451caf529..27e4e8a811b 100644 --- a/source/blender/editors/sound/sound_ops.cc +++ b/source/blender/editors/sound/sound_ops.cc @@ -39,6 +39,7 @@ #include "RNA_prototypes.hh" #include "SEQ_iterator.hh" +#include "SEQ_sequencer.hh" #include "UI_interface.hh" #include "UI_interface_layout.hh" @@ -776,7 +777,7 @@ static void SOUND_OT_mixdown(wmOperatorType *ot) static bool sound_poll(bContext *C) { - Editing *ed = CTX_data_sequencer_scene(C)->ed; + Editing *ed = blender::seq::editing_get(CTX_data_sequencer_scene(C)); if (!ed || !ed->act_strip || ed->act_strip->type != STRIP_TYPE_SOUND_RAM) { return false; @@ -789,7 +790,7 @@ static bool sound_poll(bContext *C) static wmOperatorStatus sound_pack_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); - Editing *ed = CTX_data_sequencer_scene(C)->ed; + Editing *ed = blender::seq::editing_get(CTX_data_sequencer_scene(C)); bSound *sound; if (!ed || !ed->act_strip || ed->act_strip->type != STRIP_TYPE_SOUND_RAM) { @@ -862,7 +863,7 @@ static wmOperatorStatus sound_unpack_exec(bContext *C, wmOperator *op) static wmOperatorStatus sound_unpack_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/) { - Editing *ed = CTX_data_sequencer_scene(C)->ed; + Editing *ed = blender::seq::editing_get(CTX_data_sequencer_scene(C)); bSound *sound; if (RNA_struct_property_is_set(op->ptr, "id")) { diff --git a/source/blender/editors/space_sequencer/sequencer_proxy.cc b/source/blender/editors/space_sequencer/sequencer_proxy.cc index 17711c42f0d..403150a2c53 100644 --- a/source/blender/editors/space_sequencer/sequencer_proxy.cc +++ b/source/blender/editors/space_sequencer/sequencer_proxy.cc @@ -141,6 +141,7 @@ void SEQUENCER_OT_rebuild_proxy(wmOperatorType *ot) /* API callbacks. */ ot->invoke = sequencer_rebuild_proxy_invoke; ot->exec = sequencer_rebuild_proxy_exec; + ot->poll = sequencer_edit_poll; /* Flags. */ ot->flag = OPTYPE_REGISTER; @@ -236,6 +237,7 @@ void SEQUENCER_OT_enable_proxies(wmOperatorType *ot) /* API callbacks. */ ot->invoke = sequencer_enable_proxies_invoke; ot->exec = sequencer_enable_proxies_exec; + ot->poll = sequencer_edit_poll; /* Flags. */ ot->flag = OPTYPE_REGISTER; diff --git a/source/blender/editors/space_sequencer/sequencer_view.cc b/source/blender/editors/space_sequencer/sequencer_view.cc index 759551e237b..ee8e252c43e 100644 --- a/source/blender/editors/space_sequencer/sequencer_view.cc +++ b/source/blender/editors/space_sequencer/sequencer_view.cc @@ -280,7 +280,8 @@ void SEQUENCER_OT_view_all_preview(wmOperatorType *ot) static wmOperatorStatus sequencer_view_zoom_ratio_exec(bContext *C, wmOperator *op) { - const RenderData *rd = &CTX_data_sequencer_scene(C)->r; + const Scene *scene = CTX_data_sequencer_scene(C); + const RenderData *rd = &scene->r; View2D *v2d = UI_view2d_fromcontext(C); float ratio = RNA_float_get(op->ptr, "ratio"); diff --git a/source/blender/sequencer/intern/channels.cc b/source/blender/sequencer/intern/channels.cc index 978ebbe0965..b8314993b2a 100644 --- a/source/blender/sequencer/intern/channels.cc +++ b/source/blender/sequencer/intern/channels.cc @@ -27,7 +27,7 @@ namespace blender::seq { ListBase *channels_displayed_get(const Editing *ed) { - return ed->current_channels(); + return ed ? ed->current_channels() : nullptr; } void channels_displayed_set(Editing *ed, ListBase *channels) diff --git a/source/blender/sequencer/intern/sequencer.cc b/source/blender/sequencer/intern/sequencer.cc index 20846db8343..9dcb1b8e443 100644 --- a/source/blender/sequencer/intern/sequencer.cc +++ b/source/blender/sequencer/intern/sequencer.cc @@ -274,7 +274,7 @@ void seq_free_strip_recurse(Scene *scene, Strip *strip, const bool do_id_user) Editing *editing_get(const Scene *scene) { - return scene->ed; + return scene ? scene->ed : nullptr; } Editing *editing_ensure(Scene *scene) @@ -422,11 +422,7 @@ int tool_settings_pivot_point_get(Scene *scene) ListBase *active_seqbase_get(const Editing *ed) { - if (ed == nullptr) { - return nullptr; - } - - return ed->current_strips(); + return ed ? ed->current_strips() : nullptr; } void active_seqbase_set(Editing *ed, ListBase *seqbase)