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:
1 parent
00788bb3bf
commit
14408b2dbf
3 files changed
+57
-25
No files matched your search
@@ -29,6 +29,8 @@ Most values the tools rely on (prefixes, shot naming, frame rate…) live in `co
|
|||||||
|
|
||||||
Tested on **Blender 5.2**.
|
Tested on **Blender 5.2**.
|
||||||
|
|
||||||
|
The Shot Viewer builds its cameras with the **Add Camera Rigs** extension (*Edit > Preferences > Get Extensions*, search "Add Camera Rigs"). Install and enable it next to Melvile Tools.
|
||||||
|
|
||||||
**As an extension (artists):** build the package, then in Blender go to *Edit > Preferences > Get Extensions > Install from Disk…* and pick the `.zip`.
|
**As an extension (artists):** build the package, then in Blender go to *Edit > Preferences > Get Extensions > Install from Disk…* and pick the `.zip`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -279,11 +281,12 @@ Click **+** next to the shot list:
|
|||||||
|---|---|
|
|---|---|
|
||||||
| **Name** | Pre-filled with the next free shot number (`sh0010`, `sh0020`…). This is the marker name. |
|
| **Name** | Pre-filled with the next free shot number (`sh0010`, `sh0020`…). This is the marker name. |
|
||||||
| **Duration** | Length of the shot in frames. |
|
| **Duration** | Length of the shot in frames. |
|
||||||
| **New Camera** | On: creates the camera `CAM_<sequence>_<shot>` (its name is shown in the dialog), copying the lens and position of the current camera, so you start from the current framing. Off: pick an existing camera of the scene. |
|
| **New Camera** | On: builds a camera rig with the **Add Camera Rigs** add-on. The camera is named `CAM_<sequence>_<shot>` (shown in the dialog) and its rig `RIG_<sequence>_<shot>`. Off: pick an existing camera of the scene. |
|
||||||
|
| **3D Camera** | On: a 3D **crane** rig. Off: a **2D** camera rig. |
|
||||||
|
|
||||||
The shot is added at the end of the sequence and the scene's **End** frame grows to include it. The first shot starts at frame 1001, and the scene **Start** moves there too. The playhead and the 3D view then jump to the new shot.
|
The shot is added at the end of the sequence and the scene's **End** frame grows to include it. The first shot starts at frame 1001, and the scene **Start** moves there too. The playhead and the 3D view then jump to the new shot.
|
||||||
|
|
||||||
New cameras go in the scene's top collection: move them into your camera collection if you have one.
|
The rig is built at the **3D cursor**, in the active collection: place the cursor and pick the collection first if you have a camera collection. The add-on keeps the rig's bone shapes in a hidden **Widgets** collection. If the add-on is not enabled, the dialog says so and no shot is added.
|
||||||
|
|
||||||
### Deleting a shot
|
### Deleting a shot
|
||||||
|
|
||||||
@@ -304,7 +307,7 @@ The bottom of the panel lists the armatures of the scene, grouped into **Charact
|
|||||||
- **Shift+click**: adds it to the selection, to pose several rigs at once.
|
- **Shift+click**: adds it to the selection, to pose several rigs at once.
|
||||||
- The rig(s) currently selected are highlighted.
|
- The rig(s) currently selected are highlighted.
|
||||||
|
|
||||||
A rig counts as a character or a prop when its name, one of its collections, or the library file it comes from starts with `chr_` / `prp_` (see `SHOT_VIEWER_RIG_GROUPS` in [Configuration](#configuration)). Hidden rigs cannot be selected.
|
A rig counts as a character or a prop when its name, one of its collections, or the library file it comes from starts with `chr_` / `prp_` (see `SHOT_VIEWER_RIG_GROUPS` in [Configuration](#configuration)). Hidden rigs cannot be selected. Camera rigs (armatures carrying a camera) are not listed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -329,7 +332,7 @@ To change them **without editing the code**, write a JSON file with the keys to
|
|||||||
|
|
||||||
Unknown keys are ignored and reported in the system console.
|
Unknown keys are ignored and reported in the system console.
|
||||||
|
|
||||||
Name formats use Python's `str.format`: `"sq{:04d}"` turns `10` into `sq0010`. `SHOT_CAMERA_NAME_FORMAT` takes the named tokens `{prefix}` (the camera prefix of `OBJECT_PREFIXES`, `CAM_`), `{sequence}` and `{shot}`. The default `"{prefix}{sequence}_{shot}"` gives `CAM_sq0010_sh0030`.
|
Name formats use Python's `str.format`: `"sq{:04d}"` turns `10` into `sq0010`. `SHOT_CAMERA_NAME_FORMAT` takes the named tokens `{prefix}` (the camera prefix of `OBJECT_PREFIXES`, `CAM_`), `{sequence}` and `{shot}`. The default `"{prefix}{sequence}_{shot}"` gives `CAM_sq0010_sh0030`. `SHOT_CAMERA_RIG_NAME_FORMAT` names the rig the same way, with `{prefix}` = `RIG_`. `SHOT_CAMERA_RIG_MODES` picks the Add Camera Rigs mode for 3D and 2D cameras (`CRANE` and `2D`; `DOLLY` is also available).
|
||||||
|
|
||||||
### Keys by tool
|
### Keys by tool
|
||||||
|
|
||||||
@@ -343,7 +346,7 @@ Name formats use Python's `str.format`: `"sq{:04d}"` turns `10` into `sq0010`. `
|
|||||||
| Sanity Check: layout | `SET_COLLECTION_PATTERN` |
|
| Sanity Check: layout | `SET_COLLECTION_PATTERN` |
|
||||||
| Sanity Check: panel | `CATEGORY_ORDER`, `MAX_ISSUES_DRAWN` |
|
| Sanity Check: panel | `CATEGORY_ORDER`, `MAX_ISSUES_DRAWN` |
|
||||||
| Rename | `RENAME_SEPARATOR`, `RENAME_PADDING`, `RENAME_SIDES`, `RENAME_TYPE_SUFFIXES` (plus `OBJECT_PREFIXES` / `DATABLOCK_PREFIXES` for *By Type* prefixes) |
|
| Rename | `RENAME_SEPARATOR`, `RENAME_PADDING`, `RENAME_SIDES`, `RENAME_TYPE_SUFFIXES` (plus `OBJECT_PREFIXES` / `DATABLOCK_PREFIXES` for *By Type* prefixes) |
|
||||||
| Shot Viewer | `SHOT_VIEWER_RIG_GROUPS`, `SHOT_NAME_FORMAT`, `SHOT_NUMBER_STEP`, `SHOT_FRAME_START`, `SHOT_DEFAULT_DURATION`, `SEQUENCE_NAME_FORMAT`, `SHOT_CAMERA_NAME_FORMAT` |
|
| Shot Viewer | `SHOT_VIEWER_RIG_GROUPS`, `SHOT_NAME_FORMAT`, `SHOT_NUMBER_STEP`, `SHOT_FRAME_START`, `SHOT_DEFAULT_DURATION`, `SEQUENCE_NAME_FORMAT`, `SHOT_CAMERA_NAME_FORMAT`, `SHOT_CAMERA_RIG_NAME_FORMAT`, `SHOT_CAMERA_RIG_MODES` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -251,6 +251,10 @@ SEQUENCE_NAME_FORMAT = "sq{:04d}"
|
|||||||
# Cameras added by the shot viewer. Tokens: {prefix} (OBJECT_PREFIXES["CAMERA"]),
|
# Cameras added by the shot viewer. Tokens: {prefix} (OBJECT_PREFIXES["CAMERA"]),
|
||||||
# {sequence} (SEQUENCE_NAME_FORMAT) and {shot} (the shot name).
|
# {sequence} (SEQUENCE_NAME_FORMAT) and {shot} (the shot name).
|
||||||
SHOT_CAMERA_NAME_FORMAT = "{prefix}{sequence}_{shot}"
|
SHOT_CAMERA_NAME_FORMAT = "{prefix}{sequence}_{shot}"
|
||||||
|
# Their rig, same tokens with {prefix} = OBJECT_PREFIXES["ARMATURE"].
|
||||||
|
SHOT_CAMERA_RIG_NAME_FORMAT = "{prefix}{sequence}_{shot}"
|
||||||
|
# Mode of bpy.ops.object.build_camera_rig (Add Camera Rigs add-on) for 3D and 2D cameras.
|
||||||
|
SHOT_CAMERA_RIG_MODES = {"3D": "CRANE", "2D": "2D"}
|
||||||
SHOT_NUMBER_STEP = 10
|
SHOT_NUMBER_STEP = 10
|
||||||
SHOT_FRAME_START = 1001
|
SHOT_FRAME_START = 1001
|
||||||
SHOT_DEFAULT_DURATION = 48
|
SHOT_DEFAULT_DURATION = 48
|
||||||
|
|||||||
+45
-20
@@ -12,7 +12,6 @@ import re
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.app.handlers import persistent
|
from bpy.app.handlers import persistent
|
||||||
from bpy.props import BoolProperty, IntProperty, PointerProperty, StringProperty
|
from bpy.props import BoolProperty, IntProperty, PointerProperty, StringProperty
|
||||||
from mathutils import Euler, Vector
|
|
||||||
|
|
||||||
from . import config
|
from . import config
|
||||||
|
|
||||||
@@ -121,22 +120,30 @@ def camera_name(scene, shot):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _new_camera(scene, shot, source=None):
|
def camera_rig_available():
|
||||||
"""New camera in the scene collection, copying ``source`` (lens and placement) when given."""
|
"""True when the Add Camera Rigs add-on (bpy.ops.object.build_camera_rig) is enabled."""
|
||||||
name = camera_name(scene, shot)
|
return hasattr(bpy.types, "OBJECT_OT_build_camera_rig")
|
||||||
if source is not None and source.type == "CAMERA":
|
|
||||||
data = source.data.copy()
|
|
||||||
data.name = name
|
def _new_camera_rig(context, shot, is_3d):
|
||||||
else:
|
"""Camera rig built by the Add Camera Rigs add-on at the 3D cursor, named for the shot.
|
||||||
source = None
|
Returns the camera object."""
|
||||||
data = bpy.data.cameras.new(name)
|
scene = context.scene
|
||||||
camera = bpy.data.objects.new(name, data)
|
if context.mode != "OBJECT":
|
||||||
if source is not None:
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
camera.matrix_world = source.matrix_world.copy()
|
before = {ob.as_pointer() for ob in bpy.data.objects}
|
||||||
else:
|
bpy.ops.object.build_camera_rig(mode=config.SHOT_CAMERA_RIG_MODES["3D" if is_3d else "2D"])
|
||||||
camera.location = Vector((0.0, -10.0, 1.6))
|
# Found by difference: the add-on may also add widget objects for the bone shapes.
|
||||||
camera.rotation_euler = Euler((1.5708, 0.0, 0.0))
|
new = [ob for ob in bpy.data.objects if ob.as_pointer() not in before]
|
||||||
scene.collection.objects.link(camera)
|
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
|
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()]
|
patterns = [(label, re.compile(pattern)) for label, pattern in config.SHOT_VIEWER_RIG_GROUPS.items()]
|
||||||
groups = {label: [] for label, _pattern in patterns}
|
groups = {label: [] for label, _pattern in patterns}
|
||||||
groups[OTHER_GROUP] = []
|
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:
|
for ob in rigs:
|
||||||
names = _rig_names(ob)
|
names = _rig_names(ob)
|
||||||
label = next(
|
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)
|
duration: IntProperty(name="Duration", description="Length of the shot in frames", min=1, default=48)
|
||||||
new_camera: BoolProperty(
|
new_camera: BoolProperty(
|
||||||
name="New Camera",
|
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,
|
default=True,
|
||||||
)
|
)
|
||||||
|
camera_3d: BoolProperty(
|
||||||
|
name="3D Camera",
|
||||||
|
description="Build a 3D crane rig. Otherwise a 2D camera rig",
|
||||||
|
)
|
||||||
camera_name: StringProperty(name="Camera")
|
camera_name: StringProperty(name="Camera")
|
||||||
|
|
||||||
def invoke(self, context, event):
|
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, "duration")
|
||||||
layout.prop(self, "new_camera")
|
layout.prop(self, "new_camera")
|
||||||
if self.new_camera:
|
if self.new_camera:
|
||||||
|
layout.prop(self, "camera_3d")
|
||||||
name = self.name.strip() or next_shot_name(context.scene)
|
name = self.name.strip() or next_shot_name(context.scene)
|
||||||
layout.label(text=camera_name(context.scene, name), icon="CAMERA_DATA")
|
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:
|
else:
|
||||||
layout.prop_search(self, "camera_name", context.scene, "objects", icon="CAMERA_DATA")
|
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
|
scene = context.scene
|
||||||
name = self.name.strip() or next_shot_name(scene)
|
name = self.name.strip() or next_shot_name(scene)
|
||||||
if self.new_camera:
|
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:
|
else:
|
||||||
camera = scene.objects.get(self.camera_name)
|
camera = scene.objects.get(self.camera_name)
|
||||||
if camera is None or camera.type != "CAMERA":
|
if camera is None or camera.type != "CAMERA":
|
||||||
|
|||||||
Reference in new issue
Block a user