Shot Viewer: camera markers in a sidebar panel

Shots are now timeline markers bound to a camera instead of sequencer
scene strips, all in one scene. The Shot Viewer is a fixed panel in the
WTD tab instead of a separate window: choosing a shot moves the playhead
and puts the current 3D view in its camera. Adding a shot creates a
marker and camera at the end of the scene, deleting removes the marker
(and optionally the camera). Loop Shot is now off by default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nero Frame 13andClaude Opus 5.5 committed 2026-10-20 15:56:28 +02:00
1 parent a83bc959d0
commit c376a92591
3 files changed
+171 -499

No files matched your search

+17 -34
View File
@@ -188,8 +188,6 @@ class _ToolsPanel:
# Adds a separator line
layout.separator()
layout.operator(shot_viewer.WTD_OT_shot_viewer_open.bl_idname, icon="CAMERA_DATA")
class _RenamePanel:
bl_region_type = "UI"
@@ -241,32 +239,31 @@ class _RenamePanel:
class WTD_UL_shots(bpy.types.UIList):
"""Scene strips of the edit, in edit order."""
"""Camera markers of the scene, in time order."""
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
camera = shot_viewer.shot_camera(item)
start, end = shot_viewer.strip_range(item)
start, end = shot_viewer.shot_range(item)
split = layout.split(factor=0.4)
split.label(text=item.name, icon="SEQUENCE")
split.label(text=item.name, icon="MARKER_HLT")
split = split.split(factor=0.6)
split.label(text=camera.name if camera else "(no camera)")
split.label(text=f"{start}-{end - 1}")
split.label(text=item.camera.name)
split.label(text=f"{start}-{end}")
def filter_items(self, context, data, propname):
strips = getattr(data, propname)
ordered = [strip.as_pointer() for strip in shot_viewer.shots(data.id_data)]
markers = getattr(data, propname)
ordered = [marker.as_pointer() for marker in shot_viewer.shots(data)]
position = {pointer: index for index, pointer in enumerate(ordered)}
flags = [self.bitflag_filter_item if strip.as_pointer() in position else 0 for strip in strips]
flags = [self.bitflag_filter_item if marker.as_pointer() in position else 0 for marker in markers]
if self.filter_name:
by_name = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name, self.bitflag_filter_item, strips, "name"
self.filter_name, self.bitflag_filter_item, markers, "name"
)
flags = [flag & name_flag for flag, name_flag in zip(flags, by_name)]
# Shots in edit order, the other strips after them.
# Shots in time order, the markers without camera after them.
order = []
hidden = len(ordered)
for strip in strips:
pointer = strip.as_pointer()
for marker in markers:
pointer = marker.as_pointer()
if pointer in position:
order.append(position[pointer])
else:
@@ -280,18 +277,10 @@ class _ShotViewerPanel:
bl_category = "WTD"
bl_label = "Shot Viewer"
@classmethod
def poll(cls, context):
return shot_viewer.is_viewer(context.screen)
def draw(self, context) -> None:
layout = self.layout
settings = context.window_manager.wtd_shot_viewer
scene = shot_viewer.edit_scene(context)
layout.prop(settings, "edit_scene", icon="SEQUENCE")
if scene is None:
layout.label(text="Pick the scene holding the edit", icon="ERROR")
return
scene = context.scene
has_shots = bool(shot_viewer.shots(scene))
row = layout.row(align=True)
@@ -300,22 +289,17 @@ class _ShotViewerPanel:
row.operator(shot_viewer.WTD_OT_shot_viewer_step.bl_idname, text="Previous", icon="TRIA_LEFT").step = -1
row.operator(shot_viewer.WTD_OT_shot_viewer_step.bl_idname, text="Next", icon="TRIA_RIGHT").step = 1
row = layout.row()
if scene.sequence_editor is not None:
row.template_list(
"WTD_UL_shots", "", scene.sequence_editor, "strips_all", settings, "shot_index", rows=5
)
else:
row.label(text="")
row.template_list("WTD_UL_shots", "", scene, "timeline_markers", settings, "shot_index", rows=5)
col = row.column(align=True)
col.operator(shot_viewer.WTD_OT_shot_viewer_new_shot.bl_idname, text="", icon="ADD")
col.operator(shot_viewer.WTD_OT_shot_viewer_delete_shot.bl_idname, text="", icon="REMOVE")
if not has_shots:
layout.label(text="No shots yet: add one with +", icon="INFO")
layout.label(text="No camera marker yet: add a shot with +", icon="INFO")
layout.prop(settings, "loop_shot")
groups = shot_viewer.rig_groups(context.view_layer)
if not groups:
layout.label(text="No rig in this shot", icon="INFO")
layout.label(text="No rig in the scene", icon="INFO")
for label, rigs in groups.items():
box = layout.box()
box.label(text=label, icon="ARMATURE_DATA")
@@ -360,10 +344,9 @@ class WTD_PT_sanity_sequencer(_SanityPanel, bpy.types.Panel):
classes = (
WTD_UL_shots,
# First, so it tops the WTD tab of the viewer window.
WTD_PT_shot_viewer_view3d,
WTD_PT_sanity_view3d,
WTD_PT_tools_view3d,
WTD_PT_shot_viewer_view3d,
WTD_PT_rename_view3d,
WTD_PT_rename_node_editor,
WTD_PT_sanity_sequencer,