Added Rename and Shot Viewer tools
Rename panel: batch rename selected objects (or their materials) as prefix_name_side_number_suffix. Numbering continues after existing names, object data follows its object, and a preview shows the result. Shot Viewer (button in the Tools panel): opens a window with its own workspace showing the shots (sequencer scene strips) through their camera, with the edit's sequencer at the bottom. Scrubbing the edit switches shots, playing a shot moves the edit playhead, Loop Shot loops playback on the shot. Shots can be added and deleted, and character / prop rigs selected in pose mode from the sidebar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b800e5a68b
commit
1a1767c984
5 files changed
+1173
-3
No files matched your search
+165
-2
@@ -2,7 +2,7 @@ import textwrap
|
||||
|
||||
import bpy
|
||||
|
||||
from .. import config, tools_ops
|
||||
from .. import config, rename, shot_viewer, tools_ops
|
||||
from ..core import CheckContext, Severity, checks_for_context, contexts, runner
|
||||
from ..core.runner import Status
|
||||
from .operators import EXPANDED
|
||||
@@ -188,6 +188,145 @@ 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"
|
||||
bl_category = "WTD"
|
||||
bl_label = "Rename"
|
||||
bl_options = {"DEFAULT_CLOSED"} # panel starts collapsed
|
||||
|
||||
PREVIEW_COUNT = 10
|
||||
|
||||
def draw(self, context) -> None:
|
||||
layout = self.layout
|
||||
settings = context.scene.wtd_rename
|
||||
layout.row().prop(settings, "target", expand=True)
|
||||
|
||||
col = layout.column()
|
||||
col.use_property_split = True
|
||||
col.use_property_decorate = False
|
||||
col.prop(settings, "base_name")
|
||||
col.prop(settings, "prefix_mode")
|
||||
if settings.prefix_mode == "CUSTOM":
|
||||
col.prop(settings, "prefix_custom", text=" ")
|
||||
col.prop(settings, "side")
|
||||
col.prop(settings, "suffix_mode")
|
||||
if settings.suffix_mode == "CUSTOM":
|
||||
col.prop(settings, "suffix_custom", text=" ")
|
||||
col.prop(settings, "sort")
|
||||
if settings.target == "OBJECTS":
|
||||
col.prop(settings, "rename_data")
|
||||
|
||||
plan, skipped = rename.plan_renames(context, settings)
|
||||
row = layout.row()
|
||||
row.scale_y = 1.4
|
||||
row.operator(
|
||||
rename.WTD_OT_batch_rename.bl_idname,
|
||||
text=f"Rename {len(plan)} Item(s)" if plan else "Rename",
|
||||
icon="SORTALPHA",
|
||||
)
|
||||
if not plan and not skipped:
|
||||
return
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
for id_block, name in plan[: self.PREVIEW_COUNT]:
|
||||
col.label(text=f"{id_block.name} → {name}")
|
||||
hidden = len(plan) - self.PREVIEW_COUNT
|
||||
if hidden > 0:
|
||||
col.label(text=f"... {hidden} more")
|
||||
for name in skipped:
|
||||
col.label(text=f"{name}: no base name, set a name", icon="ERROR")
|
||||
|
||||
|
||||
class WTD_UL_shots(bpy.types.UIList):
|
||||
"""Scene strips of the edit, in edit 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)
|
||||
split = layout.split(factor=0.4)
|
||||
split.label(text=item.name, icon="SEQUENCE")
|
||||
split = split.split(factor=0.6)
|
||||
split.label(text=camera.name if camera else "(no camera)")
|
||||
split.label(text=f"{start}-{end - 1}")
|
||||
|
||||
def filter_items(self, context, data, propname):
|
||||
strips = getattr(data, propname)
|
||||
ordered = [strip.as_pointer() for strip in shot_viewer.shots(data.id_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]
|
||||
if self.filter_name:
|
||||
by_name = bpy.types.UI_UL_list.filter_items_by_name(
|
||||
self.filter_name, self.bitflag_filter_item, strips, "name"
|
||||
)
|
||||
flags = [flag & name_flag for flag, name_flag in zip(flags, by_name)]
|
||||
# Shots in edit order, the other strips after them.
|
||||
order = []
|
||||
hidden = len(ordered)
|
||||
for strip in strips:
|
||||
pointer = strip.as_pointer()
|
||||
if pointer in position:
|
||||
order.append(position[pointer])
|
||||
else:
|
||||
order.append(hidden)
|
||||
hidden += 1
|
||||
return flags, order
|
||||
|
||||
|
||||
class _ShotViewerPanel:
|
||||
bl_region_type = "UI"
|
||||
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
|
||||
has_shots = bool(shot_viewer.shots(scene))
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 1.4
|
||||
row.enabled = has_shots
|
||||
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="")
|
||||
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.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")
|
||||
for label, rigs in groups.items():
|
||||
box = layout.box()
|
||||
box.label(text=label, icon="ARMATURE_DATA")
|
||||
col = box.column(align=True)
|
||||
for rig in rigs:
|
||||
col.operator(
|
||||
shot_viewer.WTD_OT_shot_viewer_select_rig.bl_idname,
|
||||
text=rig.name,
|
||||
depress=rig.select_get(),
|
||||
).name = rig.name
|
||||
|
||||
|
||||
class WTD_PT_sanity_view3d(_SanityPanel, bpy.types.Panel):
|
||||
bl_idname = "WTD_PT_sanity_view3d"
|
||||
@@ -199,12 +338,36 @@ class WTD_PT_tools_view3d(_ToolsPanel, bpy.types.Panel):
|
||||
bl_space_type = "VIEW_3D"
|
||||
|
||||
|
||||
class WTD_PT_rename_view3d(_RenamePanel, bpy.types.Panel):
|
||||
bl_idname = "WTD_PT_rename_view3d"
|
||||
bl_space_type = "VIEW_3D"
|
||||
|
||||
|
||||
class WTD_PT_rename_node_editor(_RenamePanel, bpy.types.Panel):
|
||||
bl_idname = "WTD_PT_rename_node_editor"
|
||||
bl_space_type = "NODE_EDITOR"
|
||||
|
||||
|
||||
class WTD_PT_shot_viewer_view3d(_ShotViewerPanel, bpy.types.Panel):
|
||||
bl_idname = "WTD_PT_shot_viewer_view3d"
|
||||
bl_space_type = "VIEW_3D"
|
||||
|
||||
|
||||
class WTD_PT_sanity_sequencer(_SanityPanel, bpy.types.Panel):
|
||||
bl_idname = "WTD_PT_sanity_sequencer"
|
||||
bl_space_type = "SEQUENCE_EDITOR"
|
||||
|
||||
|
||||
classes = (WTD_PT_sanity_view3d, WTD_PT_tools_view3d, WTD_PT_sanity_sequencer)
|
||||
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_rename_view3d,
|
||||
WTD_PT_rename_node_editor,
|
||||
WTD_PT_sanity_sequencer,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
|
||||
Reference in new issue
Block a user