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

+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,
)