Shot Manager's Kitsu creator now allows the choice of 2D/3D camera.

This commit is contained in:
Nero Frame 13 committed 2026-10-28 13:49:10 +01:00
1 parent 8dcd4daad1
commit 644aa32014
2 files changed
+58 -18

No files matched your search

+2 -2
View File
@@ -300,7 +300,7 @@ The rig is built at the **3D cursor**, in the active collection: place the curso
**Shots from Kitsu...** builds every camera a sequence needs in one go, from the shots and casting in Kitsu. It needs Libreflow connected in the [Scene Manager](#connecting).
1. Click **Shots from Kitsu...** and pick the sequence. The sequence of the Scene Manager's shot is at the top of the list.
2. The dialog lists the Kitsu shots of the sequence in name order. Each line shows its frame count and what is cast in it:
2. The dialog lists the Kitsu shots of the sequence in name order. Each line shows its frame count, what is cast in it and, for a shot to build, its camera type (**3D** / **2D**):
- camera icon: to build;
- check mark: a shot of that name is in the scene already, left as it is;
- greyed out: nothing is cast in it (see **Only Cast Shots**).
@@ -309,7 +309,7 @@ The rig is built at the **3D cursor**, in the active collection: place the curso
| Setting | Use |
|---|---|
| **Only Cast Shots** | On (default): shots with nothing cast in Kitsu get no camera. |
| **3D Cameras** | On: 3D **crane** rigs. Off: **2D** camera rigs. |
| **All Cameras** | Sets the camera type of every shot at once (**3D**: a **crane** rig, **2D**: a 2D camera rig). Then change single shots in their line. The types are the keys of `SHOT_CAMERA_RIG_MODES`: add one there (`"Dolly": "DOLLY"`) and it shows up as a choice. |
| **Default Duration** | Length of the shots without a frame count in Kitsu. |
Each shot is built as with **+**: an Add Camera Rigs rig at the 3D cursor, `CAM_<sequence>_<shot>` / `RIG_<sequence>_<shot>`, named with the Kitsu sequence name, and a camera marker named after the Kitsu shot. The shots go one after the other after the shots of the scene, in Kitsu order. Each one is as long as its Kitsu frame count (**Nb Frames**). When the sequence name follows `SEQUENCE_NAME_FORMAT`, the **Sequence** field is set from it. Running it again only adds the shots that are still missing, so it also picks up shots added in Kitsu later. **Ctrl+Z** undoes it.
+56 -16
View File
@@ -12,7 +12,7 @@ import re
import bpy
from bpy.app.handlers import persistent
from bpy.props import BoolProperty, EnumProperty, IntProperty, PointerProperty, StringProperty
from bpy.props import BoolProperty, CollectionProperty, EnumProperty, IntProperty, PointerProperty, StringProperty
from . import config
from .scene_manager import ipc
@@ -135,14 +135,14 @@ def camera_rig_available():
return hasattr(bpy.types, "OBJECT_OT_build_camera_rig")
def _new_camera_rig(context, shot, is_3d, sequence=None):
def _new_camera_rig(context, shot, kind, sequence=None):
"""Camera rig built by the Add Camera Rigs add-on at the 3D cursor, named for the shot.
Returns the camera object."""
kind is a key of config SHOT_CAMERA_RIG_MODES ("3D", "2D"). 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"])
bpy.ops.object.build_camera_rig(mode=config.SHOT_CAMERA_RIG_MODES[kind])
# 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)
@@ -225,9 +225,10 @@ def kitsu_shot_state(scene, shot, only_cast):
return "NEW"
def create_kitsu_shots(context, data, is_3d, only_cast, duration):
def create_kitsu_shots(context, data, cameras, only_cast, duration, default_camera):
"""A camera rig and a shot for every NEW shot of the sequence, in Kitsu order, after the
shots of the scene. Kitsu's frame count is the shot length, else duration. Returns the markers."""
shots of the scene. The rig is cameras[shot name], else default_camera (keys of config
SHOT_CAMERA_RIG_MODES). Kitsu's frame count is the shot length, else duration. Returns the markers."""
scene = context.scene
number = sequence_number(data["sequence"])
if number is not None:
@@ -236,7 +237,8 @@ def create_kitsu_shots(context, data, is_3d, only_cast, duration):
for shot in data["shots"]:
if kitsu_shot_state(scene, shot, only_cast) != "NEW":
continue
camera = _new_camera_rig(context, shot["shot"], is_3d, data["sequence"])
kind = cameras.get(shot["shot"], default_camera)
camera = _new_camera_rig(context, shot["shot"], kind, data["sequence"])
markers.append(append_shot(context, shot["shot"], camera, shot.get("nb_frames") or duration))
return markers
@@ -381,7 +383,7 @@ class WTD_OT_shot_viewer_new_shot(bpy.types.Operator):
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)
camera = _new_camera_rig(context, name, "3D" if self.camera_3d else "2D")
else:
camera = scene.objects.get(self.camera_name)
if camera is None or camera.type != "CAMERA":
@@ -513,6 +515,22 @@ class WTD_OT_shot_viewer_kitsu_sequence(bpy.types.Operator):
KITSU_STATE_ICONS = {"NEW": "CAMERA_DATA", "EXISTS": "CHECKMARK", "UNCAST": "BLANK1"}
# Camera rig choices: the keys of config SHOT_CAMERA_RIG_MODES ("3D", "2D").
CAMERA_KIND_ITEMS = [
(kind, kind, f"{mode.title()} camera rig (Add Camera Rigs)") for kind, mode in config.SHOT_CAMERA_RIG_MODES.items()
]
DEFAULT_CAMERA_KIND = "2D" if "2D" in config.SHOT_CAMERA_RIG_MODES else CAMERA_KIND_ITEMS[0][0]
class WTD_KitsuShotCamera(bpy.types.PropertyGroup):
"""Camera rig of one Kitsu shot in Build Kitsu Shots (name: the shot)."""
camera: EnumProperty(name="Camera", items=CAMERA_KIND_ITEMS, default=DEFAULT_CAMERA_KIND)
def _all_cameras_update(self, context):
for item in self.cameras:
item.camera = self.camera
class WTD_OT_shot_viewer_kitsu_shots(bpy.types.Operator):
@@ -526,7 +544,14 @@ class WTD_OT_shot_viewer_kitsu_shots(bpy.types.Operator):
description="Leave out the shots with nothing cast in Kitsu",
default=True,
)
camera_3d: BoolProperty(name="3D Cameras", description="Build 3D crane rigs. Otherwise 2D camera rigs")
camera: EnumProperty(
name="All Cameras",
description="Camera rig of every shot. Each shot can then be changed in its line",
items=CAMERA_KIND_ITEMS,
default=DEFAULT_CAMERA_KIND,
update=_all_cameras_update,
)
cameras: CollectionProperty(type=WTD_KitsuShotCamera)
duration: IntProperty(
name="Default Duration",
description="Length of the shots without a frame count in Kitsu",
@@ -540,7 +565,12 @@ class WTD_OT_shot_viewer_kitsu_shots(bpy.types.Operator):
def invoke(self, context, event):
self.duration = config.SHOT_DEFAULT_DURATION
return context.window_manager.invoke_props_dialog(self, width=420)
self.cameras.clear()
for shot in KITSU["shots"]:
item = self.cameras.add()
item.name = shot["shot"]
item.camera = self.camera
return context.window_manager.invoke_props_dialog(self, width=560)
def draw(self, context):
layout = self.layout
@@ -550,16 +580,24 @@ class WTD_OT_shot_viewer_kitsu_shots(bpy.types.Operator):
for shot in KITSU["shots"]:
state = kitsu_shot_state(scene, shot, self.only_cast)
row = col.row()
row.active = state == "NEW"
row.label(text=shot["shot"], icon=KITSU_STATE_ICONS[state])
row.label(text=f"{shot['nb_frames']} fr" if shot.get("nb_frames") else "? fr")
info = row.row()
info.active = state == "NEW"
info.label(text=shot["shot"], icon=KITSU_STATE_ICONS[state])
info.label(text=f"{shot['nb_frames']} fr" if shot.get("nb_frames") else "? fr")
names = ", ".join(c["name"] for c in shot["casting"])
row.label(text=names or "nothing cast")
info.label(text=names or "nothing cast")
choice = row.row()
choice.ui_units_x = 5
item = self.cameras.get(shot["shot"])
if state == "NEW" and item is not None:
choice.prop(item, "camera", expand=True)
else:
choice.label(text="")
layout.separator()
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, "camera", expand=True)
layout.prop(self, "only_cast")
layout.prop(self, "camera_3d")
layout.prop(self, "duration")
new = [s for s in KITSU["shots"] if kitsu_shot_state(scene, s, self.only_cast) == "NEW"]
if not new:
@@ -574,7 +612,8 @@ class WTD_OT_shot_viewer_kitsu_shots(bpy.types.Operator):
if not camera_rig_available():
self.report({"WARNING"}, "Enable the Add Camera Rigs add-on to build the shot cameras")
return {"CANCELLED"}
markers = create_kitsu_shots(context, KITSU, self.camera_3d, self.only_cast, self.duration)
cameras = {item.name: item.camera for item in self.cameras}
markers = create_kitsu_shots(context, KITSU, cameras, self.only_cast, self.duration, self.camera)
if not markers:
self.report({"INFO"}, "Every shot is in the scene already")
return {"CANCELLED"}
@@ -622,6 +661,7 @@ classes = (
WTD_OT_shot_viewer_new_shot,
WTD_OT_shot_viewer_delete_shot,
WTD_OT_shot_viewer_kitsu_sequence,
WTD_KitsuShotCamera,
WTD_OT_shot_viewer_kitsu_shots,
WTD_OT_shot_viewer_select_rig,
)