Shot Viewer: build shot cameras with Add Camera Rigs

New shot cameras come from bpy.ops.object.build_camera_rig: a crane rig
when "3D Camera" is ticked, a 2D rig otherwise (SHOT_CAMERA_RIG_MODES).
The camera and its rig are named for the sequence and shot
(SHOT_CAMERA_RIG_NAME_FORMAT). Camera rigs are left out of the rig
quick-select. Requires the Add Camera Rigs extension.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nero Frame 13andClaude Opus 5.5 committed 2026-10-21 13:56:52 +02:00
1 parent 00788bb3bf
commit 14408b2dbf
3 files changed
+57 -25

No files matched your search

+45 -20
View File
@@ -12,7 +12,6 @@ import re
import bpy
from bpy.app.handlers import persistent
from bpy.props import BoolProperty, IntProperty, PointerProperty, StringProperty
from mathutils import Euler, Vector
from . import config
@@ -121,22 +120,30 @@ def camera_name(scene, shot):
)
def _new_camera(scene, shot, source=None):
"""New camera in the scene collection, copying ``source`` (lens and placement) when given."""
name = camera_name(scene, shot)
if source is not None and source.type == "CAMERA":
data = source.data.copy()
data.name = name
else:
source = None
data = bpy.data.cameras.new(name)
camera = bpy.data.objects.new(name, data)
if source is not None:
camera.matrix_world = source.matrix_world.copy()
else:
camera.location = Vector((0.0, -10.0, 1.6))
camera.rotation_euler = Euler((1.5708, 0.0, 0.0))
scene.collection.objects.link(camera)
def camera_rig_available():
"""True when the Add Camera Rigs add-on (bpy.ops.object.build_camera_rig) is enabled."""
return hasattr(bpy.types, "OBJECT_OT_build_camera_rig")
def _new_camera_rig(context, shot, is_3d):
"""Camera rig built by the Add Camera Rigs add-on at the 3D cursor, named for the shot.
Returns the camera object."""
scene = context.scene
if context.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
before = {ob.as_pointer() for ob in bpy.data.objects}
bpy.ops.object.build_camera_rig(mode=config.SHOT_CAMERA_RIG_MODES["3D" if is_3d else "2D"])
# Found by difference: the add-on may also add widget objects for the bone shapes.
new = [ob for ob in bpy.data.objects if ob.as_pointer() not in before]
camera = next((ob for ob in new if ob.type == "CAMERA"), None)
if camera is None:
raise RuntimeError("build_camera_rig created no camera")
camera.name = camera.data.name = camera_name(scene, shot)
rig = camera.parent
if rig is not None and rig in new:
rig.name = rig.data.name = config.SHOT_CAMERA_RIG_NAME_FORMAT.format(
prefix=config.OBJECT_PREFIXES.get("ARMATURE", ""), sequence=sequence_name(scene), shot=shot
)
return camera
@@ -158,7 +165,15 @@ def rig_groups(view_layer):
patterns = [(label, re.compile(pattern)) for label, pattern in config.SHOT_VIEWER_RIG_GROUPS.items()]
groups = {label: [] for label, _pattern in patterns}
groups[OTHER_GROUP] = []
rigs = sorted((ob for ob in view_layer.objects if ob.type == "ARMATURE"), key=lambda ob: ob.name.lower())
# Camera rigs (armatures carrying a camera) are left out: one per shot.
rigs = sorted(
(
ob
for ob in view_layer.objects
if ob.type == "ARMATURE" and not any(child.type == "CAMERA" for child in ob.children)
),
key=lambda ob: ob.name.lower(),
)
for ob in rigs:
names = _rig_names(ob)
label = next(
@@ -225,9 +240,13 @@ class WTD_OT_shot_viewer_new_shot(bpy.types.Operator):
duration: IntProperty(name="Duration", description="Length of the shot in frames", min=1, default=48)
new_camera: BoolProperty(
name="New Camera",
description="Add a camera for this shot, copying the current one. Otherwise pick an existing camera",
description="Build a camera rig for this shot (Add Camera Rigs add-on). Otherwise pick an existing camera",
default=True,
)
camera_3d: BoolProperty(
name="3D Camera",
description="Build a 3D crane rig. Otherwise a 2D camera rig",
)
camera_name: StringProperty(name="Camera")
def invoke(self, context, event):
@@ -243,8 +262,11 @@ class WTD_OT_shot_viewer_new_shot(bpy.types.Operator):
layout.prop(self, "duration")
layout.prop(self, "new_camera")
if self.new_camera:
layout.prop(self, "camera_3d")
name = self.name.strip() or next_shot_name(context.scene)
layout.label(text=camera_name(context.scene, name), icon="CAMERA_DATA")
if not camera_rig_available():
layout.label(text="Enable the Add Camera Rigs add-on", icon="ERROR")
else:
layout.prop_search(self, "camera_name", context.scene, "objects", icon="CAMERA_DATA")
@@ -252,7 +274,10 @@ class WTD_OT_shot_viewer_new_shot(bpy.types.Operator):
scene = context.scene
name = self.name.strip() or next_shot_name(scene)
if self.new_camera:
camera = _new_camera(scene, name, scene.camera)
if not camera_rig_available():
self.report({"WARNING"}, "Enable the Add Camera Rigs add-on to build the shot camera")
return {"CANCELLED"}
camera = _new_camera_rig(context, name, self.camera_3d)
else:
camera = scene.objects.get(self.camera_name)
if camera is None or camera.type != "CAMERA":