2025-12-01
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# SPDX-FileCopyrightText: 2021 Blender Studio Tools Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import importlib
|
||||
from ..sqe import opsdata, checkstrip, pull, push, ops, ui, draw
|
||||
|
||||
|
||||
# ---------REGISTER ----------.
|
||||
|
||||
|
||||
def reload():
|
||||
global opsdata
|
||||
global checkstrip
|
||||
global pull
|
||||
global push
|
||||
global ops
|
||||
global ui
|
||||
global draw
|
||||
|
||||
opsdata = importlib.reload(opsdata)
|
||||
checkstrip = importlib.reload(checkstrip)
|
||||
pull = importlib.reload(pull)
|
||||
push = importlib.reload(push)
|
||||
ops = importlib.reload(ops)
|
||||
ui = importlib.reload(ui)
|
||||
draw = importlib.reload(draw)
|
||||
|
||||
|
||||
def register():
|
||||
ops.register()
|
||||
ui.register()
|
||||
draw.register()
|
||||
|
||||
|
||||
def unregister():
|
||||
ui.unregister()
|
||||
ops.unregister()
|
||||
draw.unregister()
|
||||
@@ -0,0 +1,72 @@
|
||||
# SPDX-FileCopyrightText: 2021 Blender Studio Tools Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
import bpy
|
||||
|
||||
from ..logger import LoggerFactory
|
||||
from ..sqe import checkstrip
|
||||
|
||||
logger = LoggerFactory.getLogger()
|
||||
|
||||
|
||||
def _is_range_in(range1: range, range2: range) -> bool:
|
||||
"""Whether range1 is a subset of range2"""
|
||||
# usual strip setup strip1(101, 120)|strip2(120, 130)|strip3(130, 140)
|
||||
# first and last frame can be the same for each strip
|
||||
range2 = range(range2.start + 1, range2.stop - 1)
|
||||
|
||||
if not range1:
|
||||
return True # empty range is subset of anything
|
||||
if not range2:
|
||||
return False # non-empty range can't be subset of empty range
|
||||
if len(range1) > 1 and range1.step % range2.step:
|
||||
return False # must have a single value or integer multiple step
|
||||
return range1.start in range2 or range1[-1] in range2
|
||||
|
||||
|
||||
def get_occupied_ranges(context: bpy.types.Context) -> Dict[str, List[range]]:
|
||||
"""
|
||||
Scans sequence editor and returns a dictionary. It contains a key for each channel
|
||||
and a list of ranges with the occupied frame ranges as values.
|
||||
"""
|
||||
# {'1': [(101, 213), (300, 320)]}.
|
||||
ranges: Dict[str, List[range]] = {}
|
||||
|
||||
# Populate ranges.
|
||||
for strip in context.scene.sequence_editor.sequences_all:
|
||||
ranges.setdefault(str(strip.channel), [])
|
||||
ranges[str(strip.channel)].append(
|
||||
range(strip.frame_final_start, strip.frame_final_end + 1)
|
||||
)
|
||||
|
||||
# Sort ranges tuple list.
|
||||
for channel in ranges:
|
||||
liste = ranges[channel]
|
||||
ranges[channel] = sorted(liste, key=lambda item: item.start)
|
||||
|
||||
return ranges
|
||||
|
||||
|
||||
def is_range_occupied(range_to_check: range, occupied_ranges: List[range]) -> bool:
|
||||
for r in occupied_ranges:
|
||||
# Range(101, 150).
|
||||
if _is_range_in(range_to_check, r):
|
||||
return True
|
||||
continue
|
||||
return False
|
||||
|
||||
|
||||
def get_shot_strips(context: bpy.types.Context) -> List[bpy.types.Strip]:
|
||||
shot_strips = []
|
||||
shot_strips.extend(
|
||||
[
|
||||
strip
|
||||
for strip in context.scene.sequence_editor.sequences_all
|
||||
if checkstrip.is_valid_type(strip, log=False)
|
||||
and checkstrip.is_linked(strip, log=False)
|
||||
]
|
||||
)
|
||||
return shot_strips
|
||||
@@ -0,0 +1,131 @@
|
||||
# SPDX-FileCopyrightText: 2021 Blender Studio Tools Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import bpy
|
||||
|
||||
import gazu
|
||||
from ..types import Sequence, Project, Shot, Cache
|
||||
from ..logger import LoggerFactory
|
||||
|
||||
logger = LoggerFactory.getLogger()
|
||||
|
||||
VALID_STRIP_TYPES = {"MOVIE", "COLOR"}
|
||||
|
||||
|
||||
def is_valid_type(strip: bpy.types.Strip, log: bool = True) -> bool:
|
||||
if not strip.type in VALID_STRIP_TYPES:
|
||||
if log:
|
||||
logger.info("Strip: %s. Invalid type", strip.type)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_initialized(strip: bpy.types.Strip) -> bool:
|
||||
"""Returns True if strip.kitsu.initialized is True else False"""
|
||||
if not strip.kitsu.initialized:
|
||||
logger.info("Strip: %s. Not initialized", strip.name)
|
||||
return False
|
||||
|
||||
logger.info("Strip: %s. Is initialized", strip.name)
|
||||
return True
|
||||
|
||||
|
||||
def is_linked(strip: bpy.types.Strip, log: bool = True) -> bool:
|
||||
"""Returns True if strip.kitsu.linked is True else False"""
|
||||
if not strip.kitsu.linked:
|
||||
if log:
|
||||
logger.info("Strip: %s. Not linked yet", strip.name)
|
||||
return False
|
||||
if log:
|
||||
logger.info("Strip: %s. Is linked to ID: %s", strip.name, strip.kitsu.shot_id)
|
||||
return True
|
||||
|
||||
|
||||
def has_meta(strip: bpy.types.Strip) -> bool:
|
||||
"""Returns True if strip.kitsu.shot_name and strip.kitsu.sequence_name is Truethy else False"""
|
||||
seq = strip.kitsu.sequence_name
|
||||
shot = strip.kitsu.shot_name or strip.kitsu.manual_shot_name
|
||||
|
||||
if not bool(seq and shot):
|
||||
logger.info("Strip: %s. Missing metadata", strip.name)
|
||||
return False
|
||||
|
||||
logger.info("Strip: %s. Has metadata (Sequence: %s, Shot: %s)", strip.name, seq, shot)
|
||||
return True
|
||||
|
||||
|
||||
def shot_exists_by_id(strip: bpy.types.Strip, clear_cache: bool = True) -> Optional[Shot]:
|
||||
"""Returns Shot instance if shot with strip.kitsu.shot_id exists else None"""
|
||||
|
||||
if clear_cache:
|
||||
Cache.clear_all()
|
||||
|
||||
try:
|
||||
shot = Shot.by_id(strip.kitsu.shot_id)
|
||||
except (gazu.exception.RouteNotFoundException, gazu.exception.ServerErrorException):
|
||||
logger.info(
|
||||
"Strip: %s No shot found on server with ID: %s",
|
||||
strip.name,
|
||||
strip.kitsu.shot_id,
|
||||
)
|
||||
return None
|
||||
|
||||
logger.info("Strip: %s Shot %s exists on server (ID: %s)", strip.name, shot.name, shot.id)
|
||||
return shot
|
||||
|
||||
|
||||
def seq_exists_by_id(
|
||||
strip: bpy.types.Strip, project: Project, clear_cache: bool = True
|
||||
) -> Optional[Sequence]:
|
||||
if clear_cache:
|
||||
Cache.clear_all()
|
||||
|
||||
zseq = project.get_sequence(strip.kitsu.sequence_id)
|
||||
|
||||
if not zseq:
|
||||
logger.info(
|
||||
"Strip: %s Sequence %s does not exist on server",
|
||||
strip.name,
|
||||
strip.kitsu.sequence_name,
|
||||
)
|
||||
return None
|
||||
|
||||
logger.info(
|
||||
"Strip: %s Sequence %s exists in on server (ID: %s)",
|
||||
strip.name,
|
||||
zseq.name,
|
||||
zseq.id,
|
||||
)
|
||||
return zseq
|
||||
|
||||
|
||||
def shot_exists_by_name(
|
||||
strip: bpy.types.Strip,
|
||||
project: Project,
|
||||
sequence: Sequence,
|
||||
clear_cache: bool = True,
|
||||
) -> Optional[Shot]:
|
||||
"""Returns Shot instance if strip.kitsu.shot_name exists on server, else None"""
|
||||
|
||||
if clear_cache:
|
||||
Cache.clear_all()
|
||||
|
||||
shot = project.get_shot_by_name(sequence, strip.kitsu.manual_shot_name)
|
||||
if not shot:
|
||||
logger.info(
|
||||
"Strip: %s Shot %s does not exist on server",
|
||||
strip.name,
|
||||
strip.kitsu.manual_shot_name,
|
||||
)
|
||||
return None
|
||||
|
||||
logger.info("Strip: %s Shot already existent on server (ID: %s)", strip.name, shot.id)
|
||||
return shot
|
||||
|
||||
|
||||
def contains(strip: bpy.types.Strip, framenr: int) -> bool:
|
||||
"""Returns True if the strip covers the given frame number"""
|
||||
return int(strip.frame_final_start) <= framenr <= int(strip.frame_final_end)
|
||||
@@ -0,0 +1,126 @@
|
||||
# SPDX-FileCopyrightText: 2021 Blender Studio Tools Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import typing
|
||||
|
||||
import bpy
|
||||
import gpu
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
|
||||
# Shaders and batches
|
||||
|
||||
rect_coords = ((0, 0), (1, 0), (1, 1), (0, 1))
|
||||
indices = ((0, 1, 2), (2, 3, 0))
|
||||
# Setup shaders only if Blender runs in the foreground.
|
||||
# If running in the background, no handles are registered, as drawing extra UI
|
||||
# elements does not make sense.
|
||||
# See register() and unregister().
|
||||
if not bpy.app.background:
|
||||
ucolor_2d_shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
ucolor_2d_rect_batch = batch_for_shader(
|
||||
ucolor_2d_shader, "TRIS", {"pos": rect_coords}, indices=indices
|
||||
)
|
||||
|
||||
|
||||
Float2 = typing.Tuple[float, float]
|
||||
Float3 = typing.Tuple[float, float, float]
|
||||
Float4 = typing.Tuple[float, float, float, float]
|
||||
|
||||
|
||||
def draw_line(position: Float2, size: Float2, color: Float4):
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
gpu.matrix.translate(position)
|
||||
gpu.matrix.scale(size)
|
||||
|
||||
ucolor_2d_shader.uniform_float("color", color)
|
||||
ucolor_2d_rect_batch.draw(ucolor_2d_shader)
|
||||
|
||||
gpu.state.blend_set("NONE")
|
||||
|
||||
|
||||
def get_strip_rectf(strip) -> Float4:
|
||||
# Get x and y in terms of the grid's frames and channels.
|
||||
x1 = strip.frame_final_start
|
||||
x2 = strip.frame_final_end
|
||||
# Seems to be a 5 % offset from channel top start of strip.
|
||||
y1 = strip.channel + 0.05
|
||||
y2 = strip.channel - 0.05 + 1
|
||||
|
||||
return x1, y1, x2, y2
|
||||
|
||||
|
||||
def draw_line_in_strip(strip_coords: Float4, height_factor: float, color: Float4):
|
||||
# Unpack strip coordinates.
|
||||
s_x1, channel, s_x2, _ = strip_coords
|
||||
|
||||
# Get the line's measures, as a percentage of channel height.
|
||||
# Note that the strip's height is 10% smaller than the channel (centered 5% top and bottom).
|
||||
# Note 2: offset the height slightly (0.005) to make room for the selection outline (channel coords).
|
||||
line_height_in_channel = (0.9 - 0.005 * 2) * height_factor + 0.005
|
||||
line_thickness = 0.04
|
||||
|
||||
# Offset the width slightly to make room for the selection outline (virtual grid horizontal coords).
|
||||
width_offset = 0.2
|
||||
width = (s_x2 - s_x1) - width_offset * 2
|
||||
|
||||
pos = (s_x1 + width_offset, channel + line_height_in_channel)
|
||||
scale = (width, line_thickness)
|
||||
draw_line(pos, scale, color)
|
||||
|
||||
|
||||
def draw_callback_px():
|
||||
context = bpy.context
|
||||
sqe = context.scene.sequence_editor
|
||||
if not sqe:
|
||||
return
|
||||
strips = sqe.sequences_all
|
||||
|
||||
for strip in strips:
|
||||
# Get corners of the strip rectangle in terms of the grid's frames and channels (virtual, not px).
|
||||
strip_coords = get_strip_rectf(strip)
|
||||
|
||||
if strip.kitsu.initialized or strip.kitsu.linked:
|
||||
try:
|
||||
color = tuple(
|
||||
context.scene.kitsu.sequence_colors[strip.kitsu.sequence_id].color
|
||||
)
|
||||
except KeyError:
|
||||
color = (1, 1, 1)
|
||||
|
||||
alpha = 0.75 if strip.kitsu.linked else 0.25
|
||||
|
||||
line_color = color + (alpha,)
|
||||
draw_line_in_strip(strip_coords, 0.0, line_color)
|
||||
|
||||
if strip.kitsu.media_outdated:
|
||||
line_color = (1.0, 0.05, 0.145, 0.75)
|
||||
draw_line_in_strip(strip_coords, 0.9, line_color)
|
||||
|
||||
|
||||
draw_handles = []
|
||||
|
||||
|
||||
def register():
|
||||
if bpy.app.background:
|
||||
# Do not register anything if Blender runs in the background (no UI needed).
|
||||
return
|
||||
draw_handles.append(
|
||||
bpy.types.SpaceSequenceEditor.draw_handler_add(
|
||||
draw_callback_px, (), "WINDOW", "POST_VIEW"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def unregister():
|
||||
if bpy.app.background:
|
||||
return
|
||||
for handle in reversed(draw_handles):
|
||||
try:
|
||||
bpy.types.SpaceSequenceEditor.draw_handler_remove(handle, "WINDOW")
|
||||
except ValueError:
|
||||
# Not sure why, but sometimes the handler seems to already be removed...??
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,265 @@
|
||||
# SPDX-FileCopyrightText: 2021 Blender Studio Tools Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||
from . import pull
|
||||
import bpy
|
||||
from .. import bkglobals, prefs
|
||||
from ..logger import LoggerFactory
|
||||
from ..types import Sequence, Task, TaskStatus, Shot, TaskType
|
||||
|
||||
logger = LoggerFactory.getLogger()
|
||||
|
||||
_sqe_shot_enum_list: List[Tuple[str, str, str]] = []
|
||||
_sqe_not_linked: List[Tuple[str, str, str]] = []
|
||||
_sqe_duplicates: List[Tuple[str, str, str]] = []
|
||||
_sqe_multi_project: List[Tuple[str, str, str]] = []
|
||||
|
||||
|
||||
def sqe_get_not_linked(self, context):
|
||||
return _sqe_not_linked
|
||||
|
||||
|
||||
def sqe_get_duplicates(self, context):
|
||||
return _sqe_duplicates
|
||||
|
||||
|
||||
def sqe_get_multi_project(self, context):
|
||||
return _sqe_multi_project
|
||||
|
||||
|
||||
def sqe_update_not_linked(context: bpy.types.Context) -> List[Tuple[str, str, str]]:
|
||||
"""get all strips that are initialized but not linked yet"""
|
||||
enum_list = []
|
||||
|
||||
if context.selected_sequences:
|
||||
strips = context.selected_sequences
|
||||
else:
|
||||
strips = context.scene.sequence_editor.sequences_all
|
||||
|
||||
for strip in strips:
|
||||
if strip.kitsu.initialized and not strip.kitsu.linked:
|
||||
enum_list.append((strip.name, strip.name, ""))
|
||||
|
||||
return enum_list
|
||||
|
||||
|
||||
def sqe_update_duplicates(context: bpy.types.Context) -> List[Tuple[str, str, str]]:
|
||||
"""get all strips that are initialized but not linked yet"""
|
||||
enum_list = []
|
||||
data_dict = {}
|
||||
if context.selected_sequences:
|
||||
strips = context.selected_sequences
|
||||
else:
|
||||
strips = context.scene.sequence_editor.sequences_all
|
||||
|
||||
# Create data dict that holds all shots ids and the corresponding strips that are linked to it.
|
||||
for i in range(len(strips)):
|
||||
if strips[i].kitsu.linked:
|
||||
# Get shot_id, shot_name, create entry in data_dict if id not existent.
|
||||
shot_id = strips[i].kitsu.shot_id
|
||||
shot_name = strips[i].kitsu.shot_name
|
||||
if shot_id not in data_dict:
|
||||
data_dict[shot_id] = {"name": shot_name, "strips": []}
|
||||
|
||||
# Append i to strips list.
|
||||
if strips[i] not in set(data_dict[shot_id]["strips"]):
|
||||
data_dict[shot_id]["strips"].append(strips[i])
|
||||
|
||||
# Comparet to all other strip.
|
||||
for j in range(i + 1, len(strips)):
|
||||
if shot_id == strips[j].kitsu.shot_id:
|
||||
data_dict[shot_id]["strips"].append(strips[j])
|
||||
|
||||
# Convert in data strucutre for enum property.
|
||||
for shot_id, data in data_dict.items():
|
||||
if len(data["strips"]) > 1:
|
||||
enum_list.append(("", data["name"], shot_id))
|
||||
for strip in data["strips"]:
|
||||
enum_list.append((strip.name, strip.name, ""))
|
||||
|
||||
return enum_list
|
||||
|
||||
|
||||
def sqe_update_multi_project(context: bpy.types.Context) -> List[Tuple[str, str, str]]:
|
||||
"""get all strips that are initialized but not linked yet"""
|
||||
enum_list: List[Tuple[str, str, str]] = []
|
||||
data_dict: Dict[str, Any] = {}
|
||||
|
||||
if context.selected_sequences:
|
||||
strips = context.selected_sequences
|
||||
else:
|
||||
strips = context.scene.sequence_editor.sequences_all
|
||||
|
||||
# Create data dict that holds project names as key and values the corresponding sequence strips.
|
||||
for strip in strips:
|
||||
if strip.kitsu.linked:
|
||||
project = strip.kitsu.project_name
|
||||
if project not in data_dict:
|
||||
data_dict[project] = []
|
||||
|
||||
# Append i to strips list.
|
||||
if strip not in set(data_dict[project]):
|
||||
data_dict[project].append(strip)
|
||||
|
||||
# Convert in data strucutre for enum property.
|
||||
for project, strips in data_dict.items():
|
||||
enum_list.append(("", project, ""))
|
||||
for strip in strips:
|
||||
enum_list.append((strip.name, strip.name, ""))
|
||||
|
||||
return enum_list
|
||||
|
||||
|
||||
def resolve_pattern(pattern: str, var_lookup_table: Dict[str, str]) -> str:
|
||||
matches = re.findall(r"\<(\w+)\>", pattern)
|
||||
matches = list(set(matches))
|
||||
# If no variable detected just return value.
|
||||
if len(matches) == 0:
|
||||
return pattern
|
||||
else:
|
||||
result = pattern
|
||||
for to_replace in matches:
|
||||
if to_replace in var_lookup_table:
|
||||
to_insert = var_lookup_table[to_replace]
|
||||
result = result.replace("<{}>".format(to_replace), to_insert)
|
||||
else:
|
||||
logger.warning(
|
||||
"Failed to resolve variable: %s not defined!", to_replace
|
||||
)
|
||||
return ""
|
||||
return result
|
||||
|
||||
|
||||
def get_shots_enum_for_link_shot_op(
|
||||
self: bpy.types.Operator, context: bpy.types.Context
|
||||
) -> List[Tuple[str, str, str]]:
|
||||
global _sqe_shot_enum_list
|
||||
|
||||
if not self.sequence_enum:
|
||||
return []
|
||||
|
||||
zseq_active = Sequence.by_id(self.sequence_enum)
|
||||
|
||||
_sqe_shot_enum_list.clear()
|
||||
_sqe_shot_enum_list.extend(
|
||||
[(s.id, s.name, s.description or "") for s in zseq_active.get_all_shots()]
|
||||
)
|
||||
return _sqe_shot_enum_list
|
||||
|
||||
|
||||
def upload_preview(
|
||||
context: bpy.types.Context, filepath: Path, task_type: TaskType, comment: str = ""
|
||||
) -> None:
|
||||
# Get shot by id which is in filename of thumbnail.
|
||||
shot_id = filepath.name.split(bkglobals.SPACE_REPLACER)[0]
|
||||
shot = Shot.by_id(shot_id)
|
||||
|
||||
# Find task from task type for that shot, ca be None of no task was added for that task type.
|
||||
task = Task.by_name(shot, task_type)
|
||||
|
||||
if not task:
|
||||
# Turns out a entity on the server can have 0 tasks even tough task types exist
|
||||
# you have to create a task first before being able to upload a thumbnail.
|
||||
task_status = TaskStatus.by_short_name("wip")
|
||||
task = Task.new_task(shot, task_type, task_status=task_status)
|
||||
else:
|
||||
task_status = TaskStatus.by_id(task.task_status_id)
|
||||
|
||||
# Create a comment, e.G 'Update thumbnail'.
|
||||
comment_obj = task.add_comment(task_status, comment=comment)
|
||||
|
||||
# Add_preview_to_comment.
|
||||
task.add_preview_to_comment(comment_obj, filepath.as_posix())
|
||||
|
||||
logger.info(f"Uploaded preview for shot: {shot.name} under: {task_type.name}")
|
||||
|
||||
|
||||
def init_start_frame_offset(strip: bpy.types.Strip) -> None:
|
||||
# Frame start offset.
|
||||
offset_start = strip.frame_final_start - strip.frame_start
|
||||
# Cast offset start to int, since after Blender 3.3 strip values are floats
|
||||
strip.kitsu.frame_start_offset = int(offset_start)
|
||||
|
||||
|
||||
def append_sequence_color(
|
||||
context: bpy.types.Context, seq: Sequence
|
||||
) -> Optional[Tuple[str, str, str]]:
|
||||
"""
|
||||
Extend scene.kitsu.sequence_colors property with seq.data['color'] value if it exists.
|
||||
"""
|
||||
# Pull sequencee color property.
|
||||
|
||||
if not seq.data:
|
||||
logger.info("%s failed to load sequence color. Missing 'data' key")
|
||||
return None
|
||||
if not "color" in seq.data:
|
||||
logger.info("%s failed to load sequence color. Missing data['color'] key")
|
||||
return None
|
||||
|
||||
try:
|
||||
item = context.scene.kitsu.sequence_colors[seq.id]
|
||||
except:
|
||||
item = context.scene.kitsu.sequence_colors.add()
|
||||
item.name = seq.id
|
||||
logger.info(
|
||||
"Added %s to scene.kitsu.seqeuence_colors",
|
||||
seq.name,
|
||||
)
|
||||
finally:
|
||||
item.color = tuple(seq.data["color"])
|
||||
|
||||
return tuple(seq.data["color"])
|
||||
|
||||
|
||||
def push_sequence_color(context: bpy.types.Context, sequence: Sequence) -> None:
|
||||
# Updates sequence color and logs.
|
||||
try:
|
||||
item = context.scene.kitsu.sequence_colors[sequence.id]
|
||||
except KeyError:
|
||||
logger.info(
|
||||
"%s failed to push sequence color. Does not exists in 'context.scene.kitsu.sequence_colors'",
|
||||
sequence.name,
|
||||
)
|
||||
else:
|
||||
sequence.update_data({"color": list(item.color)})
|
||||
logger.info("%s pushed sequence color", sequence.name)
|
||||
|
||||
|
||||
def create_metadata_strip(
|
||||
scene: bpy.types.Scene, name: str, channel, frame_start: int, frame_end: int
|
||||
) -> bpy.types.MovieStrip:
|
||||
|
||||
addon_prefs = prefs.addon_prefs_get(bpy.context)
|
||||
strip = scene.sequence_editor.sequences.new_movie(
|
||||
name,
|
||||
addon_prefs.metadatastrip_file,
|
||||
channel,
|
||||
frame_start,
|
||||
)
|
||||
|
||||
strip.frame_final_end = frame_end
|
||||
|
||||
# Set blend alpha.
|
||||
strip.blend_alpha = 0
|
||||
|
||||
init_start_frame_offset(strip)
|
||||
|
||||
return strip
|
||||
|
||||
|
||||
def link_metadata_strip(
|
||||
context, shot: Shot, seq: Sequence, strip: bpy.types.MovieStrip
|
||||
) -> bpy.types.MovieStrip:
|
||||
# Pull shot meta.
|
||||
pull.shot_meta(strip, shot)
|
||||
|
||||
# Rename strip.
|
||||
strip.name = shot.name
|
||||
|
||||
# Pull sequence color.
|
||||
append_sequence_color(context, seq)
|
||||
return strip
|
||||
@@ -0,0 +1,42 @@
|
||||
# SPDX-FileCopyrightText: 2021 Blender Studio Tools Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import bpy
|
||||
|
||||
from .. import bkglobals
|
||||
from ..types import Cache, Sequence, Project, Shot
|
||||
from ..logger import LoggerFactory
|
||||
|
||||
logger = LoggerFactory.getLogger()
|
||||
|
||||
|
||||
def shot_meta(strip: bpy.types.Strip, shot: Shot, clear_cache: bool = True) -> None:
|
||||
if clear_cache:
|
||||
# Clear cache before pulling.
|
||||
Cache.clear_all()
|
||||
|
||||
# Update sequence props.
|
||||
seq = Sequence.by_id(shot.parent_id)
|
||||
strip.kitsu.sequence_id = seq.id
|
||||
strip.kitsu.sequence_name = seq.name
|
||||
|
||||
# Update shot props.
|
||||
strip.kitsu.shot_id = shot.id
|
||||
strip.kitsu.shot_name = shot.name
|
||||
strip.kitsu.shot_description = shot.description if shot.description else ""
|
||||
|
||||
# Update project props.
|
||||
project = Project.by_id(shot.project_id)
|
||||
strip.kitsu.project_id = project.id
|
||||
strip.kitsu.project_name = project.name
|
||||
|
||||
# Update meta props.
|
||||
strip.kitsu.initialized = True
|
||||
strip.kitsu.linked = True
|
||||
|
||||
# Update strip name.
|
||||
strip.name = shot.name
|
||||
|
||||
# Log.
|
||||
logger.info("Pulled meta from shot: %s to strip: %s", shot.name, strip.name)
|
||||
@@ -0,0 +1,94 @@
|
||||
# SPDX-FileCopyrightText: 2021 Blender Studio Tools Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
import bpy
|
||||
|
||||
from .. import bkglobals
|
||||
from ..types import Sequence, Project, Shot
|
||||
from ..logger import LoggerFactory
|
||||
import gazu
|
||||
|
||||
logger = LoggerFactory.getLogger()
|
||||
|
||||
|
||||
def shot_meta(strip: bpy.types.Strip, shot: Shot) -> None:
|
||||
# Update shot info.
|
||||
|
||||
shot.name = strip.kitsu.shot_name
|
||||
shot.description = strip.kitsu.shot_description
|
||||
shot.data["frame_in"] = strip.frame_final_start
|
||||
shot.data["frame_out"] = strip.frame_final_end
|
||||
shot.data["3d_start"] = strip.kitsu_3d_start
|
||||
shot.nb_frames = strip.frame_final_duration
|
||||
shot.data["fps"] = bkglobals.FPS
|
||||
|
||||
# If user changed the sequence the shot belongs to
|
||||
# (can only be done by operator not by hand).
|
||||
if strip.kitsu.sequence_id != shot.sequence_id:
|
||||
sequence = Sequence.by_id(strip.kitsu.sequence_id)
|
||||
shot.sequence_id = sequence.id
|
||||
shot.parent_id = sequence.id
|
||||
shot.sequence_name = sequence.name
|
||||
|
||||
# Update on server.
|
||||
shot.update()
|
||||
logger.info("Pushed meta to shot: %s from strip: %s", shot.name, strip.name)
|
||||
|
||||
|
||||
def new_shot(
|
||||
strip: bpy.types.Strip, sequence: Sequence, project: Project, add_tasks=False
|
||||
) -> Shot:
|
||||
frame_range = (strip.frame_final_start, strip.frame_final_end)
|
||||
shot = project.create_shot(
|
||||
sequence,
|
||||
strip.kitsu.shot_name,
|
||||
nb_frames=strip.frame_final_duration,
|
||||
frame_in=frame_range[0],
|
||||
frame_out=frame_range[1],
|
||||
data={"fps": bkglobals.FPS, "3d_start": bkglobals.FRAME_START},
|
||||
)
|
||||
|
||||
if add_tasks:
|
||||
create_intial_tasks(shot, project)
|
||||
|
||||
# Update description, no option to pass that on create.
|
||||
if strip.kitsu.shot_description:
|
||||
shot.description = strip.kitsu.shot_description
|
||||
shot.update()
|
||||
|
||||
# Set project name locally, will be available on next pull.
|
||||
shot.project_name = project.name
|
||||
logger.info("Pushed create shot: %s for project: %s", shot.name, project.name)
|
||||
return shot
|
||||
|
||||
|
||||
def new_sequence(strip: bpy.types.Strip, project: Project) -> Sequence:
|
||||
sequence = project.create_sequence(
|
||||
strip.kitsu.sequence_name,
|
||||
strip.kitsu.episode_id
|
||||
)
|
||||
logger.info(
|
||||
"Pushed create sequence: %s for project: %s", sequence.name, project.name
|
||||
)
|
||||
return sequence
|
||||
|
||||
|
||||
def delete_shot(strip: bpy.types.Strip, shot: Shot) -> str:
|
||||
result = shot.remove()
|
||||
logger.info(
|
||||
"Pushed delete shot: %s for project: %s",
|
||||
shot.name,
|
||||
shot.project_name or "Unknown",
|
||||
)
|
||||
strip.kitsu.clear()
|
||||
return result
|
||||
|
||||
|
||||
def create_intial_tasks(shot: Shot, project: Project):
|
||||
shot_entity = gazu.shot.get_shot(shot.id)
|
||||
for task_type in gazu.task.all_task_types_for_project(project.id):
|
||||
if task_type["for_entity"] == "Shot":
|
||||
gazu.task.new_task(shot_entity, task_type)
|
||||
@@ -0,0 +1,770 @@
|
||||
# SPDX-FileCopyrightText: 2021 Blender Studio Tools Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import bpy
|
||||
|
||||
from .. import cache, prefs, ui, bkglobals
|
||||
from ..sqe import checkstrip
|
||||
from ..context import core as context_core
|
||||
from ..logger import LoggerFactory
|
||||
from ..sqe.ops import (
|
||||
KITSU_OT_sqe_push_new_sequence,
|
||||
KITSU_OT_sqe_push_new_shot,
|
||||
KITSU_OT_sqe_push_shot_meta,
|
||||
KITSU_OT_sqe_uninit_strip,
|
||||
KITSU_OT_sqe_unlink_shot,
|
||||
KITSU_OT_sqe_init_strip,
|
||||
KITSU_OT_sqe_link_shot,
|
||||
KITSU_OT_sqe_link_sequence,
|
||||
KITSU_OT_sqe_set_thumbnail_task_type,
|
||||
KITSU_OT_sqe_set_sqe_render_task_type,
|
||||
KITSU_OT_sqe_push_render_still,
|
||||
KITSU_OT_sqe_push_render,
|
||||
KITSU_OT_sqe_push_del_shot,
|
||||
KITSU_OT_sqe_pull_shot_meta,
|
||||
KITSU_OT_sqe_multi_edit_strip,
|
||||
KITSU_OT_sqe_debug_duplicates,
|
||||
KITSU_OT_sqe_debug_not_linked,
|
||||
KITSU_OT_sqe_debug_multi_project,
|
||||
KITSU_OT_sqe_pull_edit,
|
||||
KITSU_OT_sqe_init_strip_start_frame,
|
||||
KITSU_OT_sqe_create_metadata_strip,
|
||||
KITSU_OT_sqe_fix_metadata_strips,
|
||||
KITSU_OT_sqe_add_sequence_color,
|
||||
KITSU_OT_sqe_scan_for_media_updates,
|
||||
KITSU_OT_sqe_change_strip_source,
|
||||
KITSU_OT_sqe_clear_update_indicators,
|
||||
KITSU_OT_sqe_import_image_sequence,
|
||||
KITSU_OT_sqe_import_playblast,
|
||||
)
|
||||
from pathlib import Path
|
||||
|
||||
logger = LoggerFactory.getLogger()
|
||||
|
||||
|
||||
def get_selshots_noun(nr_of_shots: int, prefix: str = "Active") -> str:
|
||||
if not nr_of_shots:
|
||||
noun = "All"
|
||||
elif nr_of_shots == 1:
|
||||
noun = f"{prefix} Shot"
|
||||
else:
|
||||
noun = "%i Shots" % nr_of_shots
|
||||
return noun
|
||||
|
||||
|
||||
class KITSU_MT_sqe_advanced_delete(bpy.types.Menu):
|
||||
bl_label = "Advanced Delete"
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
selshots = context.selected_sequences
|
||||
strips_to_unlink = [s for s in selshots if s.kitsu.linked]
|
||||
|
||||
layout = self.layout
|
||||
layout.operator(
|
||||
KITSU_OT_sqe_push_del_shot.bl_idname,
|
||||
text=f"Unlink and Delete {len(strips_to_unlink)} Shots",
|
||||
icon="CANCEL",
|
||||
)
|
||||
|
||||
|
||||
class KITSU_PT_sqe_shot_tools(bpy.types.Panel):
|
||||
"""
|
||||
Panel in sequence editor that shows all kinds of tools related to Kitsu and sequence strips
|
||||
"""
|
||||
|
||||
# TODO: Because each draw function was previously a seperate Panel there might be a lot of
|
||||
# code duplication now, needs to be refactored at some point
|
||||
|
||||
bl_category = "Kitsu"
|
||||
bl_label = "Shot Tools"
|
||||
bl_space_type = "SEQUENCE_EDITOR"
|
||||
bl_region_type = "UI"
|
||||
bl_order = 20
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
if not context_core.is_edit_context():
|
||||
return False
|
||||
sqe = context.scene.sequence_editor
|
||||
return bool(prefs.session_auth(context) or (sqe and sqe.sequences_all))
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
active_project = cache.project_active_get()
|
||||
if active_project.production_type == bkglobals.KITSU_TV_PROJECT:
|
||||
if not cache.episode_active_get():
|
||||
self.layout.label(text="Please Set Active Episode", icon="ERROR")
|
||||
return
|
||||
if self.poll_error(context):
|
||||
self.draw_error(context)
|
||||
|
||||
if self.poll_setup(context):
|
||||
self.draw_setup(context)
|
||||
|
||||
if self.poll_metadata(context):
|
||||
self.draw_metadata(context)
|
||||
|
||||
if self.poll_offline_metadata(context):
|
||||
self.draw_offline_metadata(context)
|
||||
|
||||
if self.poll_multi_edit(context):
|
||||
self.draw_multi_edit(context)
|
||||
|
||||
if self.poll_push(context):
|
||||
self.draw_push(context)
|
||||
|
||||
if self.poll_pull(context):
|
||||
self.draw_pull(context)
|
||||
self.draw_media(context)
|
||||
|
||||
if self.poll_debug(context):
|
||||
self.draw_debug(context)
|
||||
|
||||
@classmethod
|
||||
def poll_error(cls, context: bpy.types.Context) -> bool:
|
||||
project_active = cache.project_active_get()
|
||||
addon_prefs = prefs.addon_prefs_get(context)
|
||||
|
||||
if not prefs.session_auth(context):
|
||||
return False
|
||||
|
||||
return bool(not project_active or not addon_prefs.is_project_root_valid)
|
||||
|
||||
def draw_error(self, context: bpy.types.Context) -> None:
|
||||
layout = self.layout
|
||||
project_active = cache.project_active_get()
|
||||
box = ui.draw_error_box(layout)
|
||||
addon_prefs = prefs.addon_prefs_get(context)
|
||||
|
||||
if not project_active:
|
||||
ui.draw_error_active_project_unset(box)
|
||||
|
||||
if not addon_prefs.is_project_root_valid:
|
||||
ui.draw_error_invalid_project_root_dir(box)
|
||||
|
||||
@classmethod
|
||||
def poll_setup(cls, context: bpy.types.Context) -> bool:
|
||||
return bool(context.selected_sequences)
|
||||
|
||||
def draw_setup(self, context: bpy.types.Context) -> None:
|
||||
"""
|
||||
Panel in SQE that shows operators to setup shots. That includes initialization,
|
||||
uninitizialization, linking and unlinking.
|
||||
"""
|
||||
|
||||
strip = context.scene.sequence_editor.active_strip
|
||||
selshots = context.selected_sequences
|
||||
nr_of_shots = len(selshots)
|
||||
noun = get_selshots_noun(nr_of_shots)
|
||||
project_active = cache.project_active_get()
|
||||
|
||||
strips_to_init = []
|
||||
strips_to_uninit = []
|
||||
strips_to_unlink = []
|
||||
|
||||
for s in selshots:
|
||||
if s.type not in checkstrip.VALID_STRIP_TYPES:
|
||||
continue
|
||||
if not s.kitsu.initialized:
|
||||
strips_to_init.append(s)
|
||||
elif s.kitsu.linked:
|
||||
strips_to_unlink.append(s)
|
||||
elif s.kitsu.initialized:
|
||||
strips_to_uninit.append(s)
|
||||
|
||||
# Create box.
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
box.label(text="Setup Shots", icon="TOOL_SETTINGS")
|
||||
|
||||
# Production.
|
||||
if prefs.session_auth(context):
|
||||
box.row().label(text=f"Production: {project_active.name}")
|
||||
|
||||
# Single Selection.
|
||||
if nr_of_shots == 1:
|
||||
row = box.row(align=True)
|
||||
|
||||
# Initialize.
|
||||
if strip.type not in checkstrip.VALID_STRIP_TYPES:
|
||||
row.label(text=f"Only sequence strips of types: {checkstrip.VALID_STRIP_TYPES }")
|
||||
return
|
||||
|
||||
if not strip.kitsu.initialized:
|
||||
# Init active.
|
||||
row.operator(KITSU_OT_sqe_init_strip.bl_idname, text=f"Init {noun}", icon="ADD")
|
||||
# Link active.
|
||||
row.operator(
|
||||
KITSU_OT_sqe_link_shot.bl_idname,
|
||||
text=f"Link {noun}",
|
||||
icon="LINKED",
|
||||
)
|
||||
# Create metadata strip from uninitialized strip.
|
||||
row = box.row(align=True)
|
||||
row.operator(
|
||||
KITSU_OT_sqe_create_metadata_strip.bl_idname,
|
||||
text=f"Create Metadata Strip from {noun}",
|
||||
)
|
||||
|
||||
# Unlink.
|
||||
elif strip.kitsu.linked:
|
||||
row = box.row(align=True)
|
||||
row.operator(
|
||||
KITSU_OT_sqe_unlink_shot.bl_idname,
|
||||
text=f"Unlink {noun}",
|
||||
icon="UNLINKED",
|
||||
)
|
||||
row.menu("KITSU_MT_sqe_advanced_delete", icon="DOWNARROW_HLT", text="")
|
||||
|
||||
# Uninitialize.
|
||||
else:
|
||||
row = box.row(align=True)
|
||||
# Unlink active.
|
||||
row.operator(
|
||||
KITSU_OT_sqe_uninit_strip.bl_idname,
|
||||
text=f"Uninitialize {noun}",
|
||||
icon="REMOVE",
|
||||
)
|
||||
|
||||
# Multiple Selection.
|
||||
elif nr_of_shots > 1:
|
||||
row = box.row(align=True)
|
||||
|
||||
# Init.
|
||||
if strips_to_init:
|
||||
row.operator(
|
||||
KITSU_OT_sqe_init_strip.bl_idname,
|
||||
text=f"Init {len(strips_to_init)} Shots",
|
||||
icon="ADD",
|
||||
)
|
||||
row = box.row(align=True)
|
||||
row.operator(
|
||||
KITSU_OT_sqe_create_metadata_strip.bl_idname,
|
||||
text=f"Create {len(strips_to_init)} Metadata Strips",
|
||||
)
|
||||
|
||||
# Make row.
|
||||
if strips_to_uninit or strips_to_unlink:
|
||||
row = box.row(align=True)
|
||||
|
||||
# Uninitialize.
|
||||
if strips_to_uninit:
|
||||
row.operator(
|
||||
KITSU_OT_sqe_uninit_strip.bl_idname,
|
||||
text=f"Uninitialize {len(strips_to_uninit)} Shots",
|
||||
icon="REMOVE",
|
||||
)
|
||||
|
||||
# Unlink all.
|
||||
if strips_to_unlink:
|
||||
row.operator(
|
||||
KITSU_OT_sqe_unlink_shot.bl_idname,
|
||||
text=f"Unlink {len(strips_to_unlink)} Shots",
|
||||
icon="UNLINKED",
|
||||
)
|
||||
row.menu("KITSU_MT_sqe_advanced_delete", icon="DOWNARROW_HLT", text="")
|
||||
|
||||
@classmethod
|
||||
def poll_metadata(cls, context: bpy.types.Context) -> bool:
|
||||
nr_of_shots = len(context.selected_sequences)
|
||||
strip = context.scene.sequence_editor.active_strip
|
||||
if nr_of_shots == 1:
|
||||
return strip.kitsu.initialized
|
||||
return False
|
||||
|
||||
def draw_metadata(self, context: bpy.types.Context) -> None:
|
||||
"""
|
||||
Panel in sequence editor that shows .kitsu properties of active strip. (shot, sequence)
|
||||
"""
|
||||
split_factor = 0.2
|
||||
|
||||
strip = context.scene.sequence_editor.active_strip
|
||||
|
||||
# Create box.
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
box.label(text="Metadata", icon="ALIGN_LEFT")
|
||||
|
||||
col = box.column(align=True)
|
||||
|
||||
# Sequence.
|
||||
split = col.split(factor=split_factor, align=True)
|
||||
split.label(text="Sequence")
|
||||
|
||||
if not strip.kitsu.sequence_id:
|
||||
sub_row = split.row(align=True)
|
||||
sub_row.prop(strip.kitsu, "sequence_name", text="")
|
||||
sub_row.operator(KITSU_OT_sqe_push_new_sequence.bl_idname, text="", icon="ADD")
|
||||
|
||||
else:
|
||||
# Lots of splitting because color prop is too big by default
|
||||
sub_split = split.split(factor=0.8, align=True)
|
||||
sub_split.prop(strip.kitsu, "sequence_name", text="") # TODO Use new dropdown here too
|
||||
|
||||
sub_sub_split = sub_split.split(factor=0.4, align=True)
|
||||
sub_sub_split.operator(KITSU_OT_sqe_push_new_sequence.bl_idname, text="", icon="ADD")
|
||||
|
||||
try:
|
||||
sequence_color_item = context.scene.kitsu.sequence_colors[strip.kitsu.sequence_id]
|
||||
except KeyError:
|
||||
sub_sub_split.operator(
|
||||
KITSU_OT_sqe_add_sequence_color.bl_idname, text="", icon="COLOR"
|
||||
)
|
||||
|
||||
else:
|
||||
sub_sub_split.prop(sequence_color_item, "color", text="")
|
||||
|
||||
# Shot.
|
||||
split = col.split(factor=split_factor, align=True)
|
||||
split.label(text="Shot")
|
||||
if not strip.kitsu.shot_id:
|
||||
placeholder = strip.kitsu.shot_name or ''
|
||||
split.prop(strip.kitsu, "manual_shot_name", text="", placeholder=placeholder)
|
||||
else:
|
||||
split.prop(strip.kitsu, "shot_name", text="")
|
||||
|
||||
# Description.
|
||||
split = col.split(factor=split_factor, align=True)
|
||||
split.label(text="Description")
|
||||
split.prop(strip.kitsu, "shot_description_display", text="")
|
||||
split.enabled = False if not strip.kitsu.initialized else True
|
||||
|
||||
# Frame range.
|
||||
split = col.split(factor=split_factor)
|
||||
split.label(text="Frame Range")
|
||||
row = split.row(align=False)
|
||||
row.prop(strip, "kitsu_3d_start", text="In")
|
||||
row.prop(strip, "kitsu_frame_end", text="Out")
|
||||
row.prop(strip, "kitsu_frame_duration", text="Duration")
|
||||
row.operator(KITSU_OT_sqe_init_strip_start_frame.bl_idname, text="", icon="FILE_REFRESH")
|
||||
|
||||
"""
|
||||
split = col.split(factor=split_factor)
|
||||
split.label(text="Offsets")
|
||||
row = split.row(align=False)
|
||||
row.prop(strip.kitsu, "frame_start_offset", text="In")
|
||||
"""
|
||||
|
||||
def poll_offline_metadata(cls, context: bpy.types.Context) -> bool:
|
||||
offline_metadata_strips = [
|
||||
strip
|
||||
for strip in context.scene.sequence_editor.sequences
|
||||
if strip.kitsu.shot_id != '' and not Path(strip.filepath).is_file()
|
||||
]
|
||||
|
||||
return len(offline_metadata_strips) > 0
|
||||
|
||||
def draw_offline_metadata(self, context: bpy.types.Context) -> None:
|
||||
# Create box.
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
box.label(text="Fix Metadata Strips", icon="ERROR")
|
||||
offline_metadata_strips = [
|
||||
strip
|
||||
for strip in context.selected_sequences
|
||||
if strip.kitsu.shot_id != '' and not Path(strip.filepath).is_file()
|
||||
]
|
||||
if len(offline_metadata_strips) == 0:
|
||||
text = "Fix All Missing Media"
|
||||
else:
|
||||
text = f"Fix {len(offline_metadata_strips)} Missing Media"
|
||||
box.operator(KITSU_OT_sqe_fix_metadata_strips.bl_idname, text=text)
|
||||
|
||||
@classmethod
|
||||
def poll_multi_edit(cls, context: bpy.types.Context) -> bool:
|
||||
if not prefs.session_auth(context):
|
||||
return False
|
||||
sel_shots = context.selected_sequences
|
||||
nr_of_shots = len(sel_shots)
|
||||
unvalid = [s for s in sel_shots if s.kitsu.linked or not s.kitsu.initialized]
|
||||
return bool(not unvalid and nr_of_shots > 1)
|
||||
|
||||
def draw_multi_edit(self, context: bpy.types.Context) -> None:
|
||||
"""
|
||||
Panel in sequence editor that can edit properties of multiple strips at one.
|
||||
Mostly used to quickly initialize lots of shots with an increasing counter.
|
||||
"""
|
||||
|
||||
addon_prefs = prefs.addon_prefs_get(context)
|
||||
|
||||
nr_of_shots = len(context.selected_sequences)
|
||||
noun = get_selshots_noun(nr_of_shots)
|
||||
|
||||
# Create box.
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
box.label(text="Multi Edit Metadata", icon="PROPERTIES")
|
||||
|
||||
# Sequence
|
||||
col = box.column()
|
||||
sub_row = col.row(align=True)
|
||||
sub_row.prop(context.window_manager, "selected_sequence_name", text="Sequence")
|
||||
sub_row.operator(KITSU_OT_sqe_push_new_sequence.bl_idname, text="", icon="ADD")
|
||||
|
||||
# Counter.
|
||||
row = box.row()
|
||||
row.prop(context.window_manager, "shot_counter_start", text="Shot Counter Start")
|
||||
row.prop(context.window_manager, "show_advanced", text="", icon="TOOL_SETTINGS")
|
||||
|
||||
if context.window_manager.show_advanced:
|
||||
# Counter.
|
||||
box.row().prop(addon_prefs, "shot_counter_digits", text="Shot Counter Digits")
|
||||
box.row().prop(addon_prefs, "shot_counter_increment", text="Shot Counter Increment")
|
||||
|
||||
# Variables.
|
||||
row = box.row(align=True)
|
||||
row.prop(
|
||||
context.window_manager,
|
||||
"var_use_custom_seq",
|
||||
text="Custom Sequence Variable",
|
||||
)
|
||||
if context.window_manager.var_use_custom_seq:
|
||||
row.prop(context.window_manager, "var_sequence_custom", text="")
|
||||
|
||||
# Project.
|
||||
row = box.row(align=True)
|
||||
row.prop(
|
||||
context.window_manager,
|
||||
"var_use_custom_project",
|
||||
text="Custom Project Variable",
|
||||
)
|
||||
if context.window_manager.var_use_custom_project:
|
||||
row.prop(context.window_manager, "var_project_custom", text="")
|
||||
|
||||
# Shot pattern.
|
||||
box.row().prop(addon_prefs, "shot_pattern", text="Shot Pattern")
|
||||
|
||||
# Preview.
|
||||
row = box.row()
|
||||
row.prop(context.window_manager, "shot_preview", text="Preview")
|
||||
|
||||
row = box.row(align=True)
|
||||
row.operator(
|
||||
KITSU_OT_sqe_multi_edit_strip.bl_idname,
|
||||
text=f"Set Metadata for {noun}",
|
||||
icon="ALIGN_LEFT",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll_push(cls, context: bpy.types.Context) -> bool:
|
||||
# If only one strip is selected and it is not init then hide panel.
|
||||
if not prefs.session_auth(context):
|
||||
return False
|
||||
|
||||
selshots = context.selected_sequences
|
||||
if not selshots:
|
||||
selshots = context.scene.sequence_editor.sequences_all
|
||||
|
||||
strips_to_meta = []
|
||||
strips_to_tb = []
|
||||
strips_to_submit = []
|
||||
|
||||
for s in selshots:
|
||||
if s.kitsu.linked:
|
||||
strips_to_tb.append(s)
|
||||
strips_to_meta.append(s)
|
||||
|
||||
elif s.kitsu.initialized and (s.kitsu.manual_shot_name != "" or s.kitsu.shot_name):
|
||||
strips_to_submit.append(s)
|
||||
|
||||
return bool(strips_to_meta or strips_to_tb or strips_to_submit)
|
||||
|
||||
def draw_push(self, context: bpy.types.Context) -> None:
|
||||
"""
|
||||
Panel that shows operator to sync sequence editor metadata with backend.
|
||||
"""
|
||||
nr_of_shots = len(context.selected_sequences)
|
||||
layout = self.layout
|
||||
strip = context.scene.sequence_editor.active_strip
|
||||
|
||||
selshots = context.selected_sequences
|
||||
if not selshots:
|
||||
selshots = context.scene.sequence_editor.sequences_all
|
||||
|
||||
strips_to_meta = []
|
||||
strips_to_tb = []
|
||||
strips_to_submit = []
|
||||
strips_to_delete = []
|
||||
|
||||
for s in selshots:
|
||||
if s.kitsu.linked:
|
||||
strips_to_tb.append(s)
|
||||
strips_to_meta.append(s)
|
||||
strips_to_delete.append(s)
|
||||
|
||||
elif s.kitsu.initialized:
|
||||
if s.kitsu.shot_name and s.kitsu.sequence_name:
|
||||
strips_to_submit.append(s)
|
||||
|
||||
# Create box.
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
box.label(text="Push", icon="EXPORT")
|
||||
# Special case if one shot is selected and it is init but not linked
|
||||
# shows the operator but it is not enabled until user types in required metadata.
|
||||
if nr_of_shots == 1 and not strip.kitsu.linked:
|
||||
# New operator.
|
||||
row = box.row()
|
||||
col = row.column(align=True)
|
||||
col.operator(
|
||||
KITSU_OT_sqe_push_new_shot.bl_idname,
|
||||
text="Submit New Shot",
|
||||
icon="ADD",
|
||||
)
|
||||
return
|
||||
|
||||
# Either way no selection one selection but linked or multiple.
|
||||
|
||||
# Metadata operator.
|
||||
row = box.row()
|
||||
if strips_to_meta:
|
||||
col = row.column(align=True)
|
||||
noun = get_selshots_noun(len(strips_to_meta), prefix=f"{len(strips_to_meta)}")
|
||||
col.operator(
|
||||
KITSU_OT_sqe_push_shot_meta.bl_idname,
|
||||
text=f"Metadata {noun}",
|
||||
icon="ALIGN_LEFT",
|
||||
)
|
||||
|
||||
# Thumbnail and seqeunce renderoperator.
|
||||
if strips_to_tb:
|
||||
# Upload thumbnail op.
|
||||
noun = get_selshots_noun(len(strips_to_tb), prefix=f"{len(strips_to_meta)}")
|
||||
split = col.split(factor=0.7, align=True)
|
||||
split.operator(
|
||||
KITSU_OT_sqe_push_render_still.bl_idname,
|
||||
text=f"Render Still {noun}",
|
||||
icon="IMAGE_DATA",
|
||||
)
|
||||
# Select task types op.
|
||||
noun = context.scene.kitsu.task_type_thumbnail_name or "Select Task Type"
|
||||
split.operator(
|
||||
KITSU_OT_sqe_set_thumbnail_task_type.bl_idname,
|
||||
text=noun,
|
||||
icon="DOWNARROW_HLT",
|
||||
)
|
||||
|
||||
# Sqe render op.
|
||||
noun = get_selshots_noun(len(strips_to_tb), prefix=f"{len(strips_to_meta)}")
|
||||
split = col.split(factor=0.7, align=True)
|
||||
split.operator(
|
||||
KITSU_OT_sqe_push_render.bl_idname,
|
||||
text=f"Render Movie {noun}",
|
||||
icon="IMAGE_DATA",
|
||||
)
|
||||
# Select task types op.
|
||||
noun = context.scene.kitsu.task_type_sqe_render_name or "Select Task Type"
|
||||
split.operator(
|
||||
KITSU_OT_sqe_set_sqe_render_task_type.bl_idname,
|
||||
text=noun,
|
||||
icon="DOWNARROW_HLT",
|
||||
)
|
||||
|
||||
# Submit operator.
|
||||
if nr_of_shots > 0:
|
||||
if strips_to_submit:
|
||||
noun = get_selshots_noun(len(strips_to_submit), prefix=f"{len(strips_to_submit)}")
|
||||
row = box.row()
|
||||
col = row.column(align=True)
|
||||
col.operator(
|
||||
KITSU_OT_sqe_push_new_shot.bl_idname,
|
||||
text=f"Submit {noun}",
|
||||
icon="ADD",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll_pull(cls, context: bpy.types.Context) -> bool:
|
||||
if not prefs.session_auth(context):
|
||||
return False
|
||||
|
||||
selshots = context.selected_sequences
|
||||
all_shots = context.scene.sequence_editor.sequences_all
|
||||
|
||||
if not selshots: # Pull entire edit.
|
||||
return True
|
||||
|
||||
strips_to_meta_sel = [s for s in selshots if s.kitsu.linked]
|
||||
strips_to_meta_all = [s for s in all_shots if s.kitsu.linked]
|
||||
|
||||
if not selshots:
|
||||
return bool(strips_to_meta_all)
|
||||
return bool(strips_to_meta_sel)
|
||||
|
||||
def draw_pull(self, context: bpy.types.Context) -> None:
|
||||
"""
|
||||
Panel that shows operator to sync sequence editor metadata with backend.
|
||||
"""
|
||||
|
||||
selshots = context.selected_sequences
|
||||
if not selshots:
|
||||
selshots = context.scene.sequence_editor.sequences_all
|
||||
|
||||
strips_to_meta = []
|
||||
|
||||
for s in selshots:
|
||||
if s.kitsu.linked:
|
||||
strips_to_meta.append(s)
|
||||
|
||||
# Create box.
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
box.label(text="Pull", icon="IMPORT")
|
||||
|
||||
layout = self.layout
|
||||
if strips_to_meta:
|
||||
noun = get_selshots_noun(len(strips_to_meta), prefix=f"{len(strips_to_meta)}")
|
||||
row = box.row()
|
||||
row.operator(
|
||||
KITSU_OT_sqe_pull_shot_meta.bl_idname,
|
||||
text=f"Metadata {noun}",
|
||||
icon="ALIGN_LEFT",
|
||||
)
|
||||
|
||||
if not context.selected_sequences:
|
||||
row = box.row()
|
||||
row.operator(
|
||||
KITSU_OT_sqe_pull_edit.bl_idname,
|
||||
text=f"Pull entire Edit",
|
||||
icon="FILE_MOVIE",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll_debug(cls, context: bpy.types.Context) -> bool:
|
||||
return prefs.addon_prefs_get(context).enable_debug
|
||||
|
||||
def draw_debug(self, context: bpy.types.Context) -> None:
|
||||
nr_of_shots = len(context.selected_sequences)
|
||||
noun = get_selshots_noun(nr_of_shots)
|
||||
|
||||
# Create box.
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
box.label(text="Debug", icon="MODIFIER_ON")
|
||||
|
||||
row = box.row()
|
||||
row.operator(
|
||||
KITSU_OT_sqe_debug_duplicates.bl_idname,
|
||||
text=f"Duplicates {noun}",
|
||||
icon="MODIFIER_ON",
|
||||
)
|
||||
row = box.row()
|
||||
row.operator(
|
||||
KITSU_OT_sqe_debug_not_linked.bl_idname,
|
||||
text=f"Not Linked {noun}",
|
||||
icon="MODIFIER_ON",
|
||||
)
|
||||
row = box.row()
|
||||
row.operator(
|
||||
KITSU_OT_sqe_debug_multi_project.bl_idname,
|
||||
text=f"Multi Projects {noun}",
|
||||
icon="MODIFIER_ON",
|
||||
)
|
||||
|
||||
def draw_media(self, context: bpy.types.Context) -> None:
|
||||
sel_metadata_strips = [strip for strip in context.selected_sequences if strip.kitsu.linked]
|
||||
|
||||
noun = get_selshots_noun(len(sel_metadata_strips), prefix=f"{len(sel_metadata_strips)}")
|
||||
playblast = "Playblast" if len(sel_metadata_strips) <= 1 else "Playblasts"
|
||||
|
||||
sequence = "Sequence" if len(sel_metadata_strips) <= 1 else "Sequences"
|
||||
|
||||
# Create box.
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
box.label(text="Media", icon="RENDER_ANIMATION")
|
||||
box.operator(
|
||||
KITSU_OT_sqe_import_playblast.bl_idname,
|
||||
text=f"Import {noun} {playblast}",
|
||||
icon="FILE_MOVIE",
|
||||
)
|
||||
box.operator(
|
||||
KITSU_OT_sqe_import_image_sequence.bl_idname,
|
||||
text=f"Import {noun} Image {sequence}",
|
||||
icon="RENDER_RESULT",
|
||||
)
|
||||
|
||||
|
||||
class KITSU_PT_sqe_general_tools(bpy.types.Panel):
|
||||
"""
|
||||
Panel in sequence editor that shows tools that don't relate directly to Kitsu
|
||||
"""
|
||||
|
||||
bl_category = "Kitsu"
|
||||
bl_label = "General Tools"
|
||||
bl_space_type = "SEQUENCE_EDITOR"
|
||||
bl_region_type = "UI"
|
||||
bl_order = 30
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
if not context_core.is_edit_context():
|
||||
return False
|
||||
selshots = context.selected_sequences
|
||||
|
||||
sqe = context.scene.sequence_editor
|
||||
if not sqe:
|
||||
return False
|
||||
|
||||
if not selshots:
|
||||
selshots = context.scene.sequence_editor.sequences_all
|
||||
movie_strips = [s for s in selshots if s.type == "MOVIE"]
|
||||
return bool(movie_strips)
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
active_strip = context.scene.sequence_editor.active_strip
|
||||
selshots = context.selected_sequences
|
||||
if not selshots:
|
||||
selshots = context.scene.sequence_editor.sequences_all
|
||||
|
||||
strips_to_update_media = []
|
||||
|
||||
for s in selshots:
|
||||
if s.type == "MOVIE":
|
||||
strips_to_update_media.append(s)
|
||||
|
||||
# Create box.
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
box.label(text="General", icon="MODIFIER")
|
||||
|
||||
# Scan for outdated media and reset operator.
|
||||
row = box.row(align=True)
|
||||
row.operator(
|
||||
KITSU_OT_sqe_scan_for_media_updates.bl_idname,
|
||||
text=f"Check media update for {len(strips_to_update_media)} {'strip' if len(strips_to_update_media) == 1 else 'strips'}",
|
||||
)
|
||||
row.operator(KITSU_OT_sqe_clear_update_indicators.bl_idname, text="", icon="X")
|
||||
|
||||
# Up down source operator. Check for to strips to accommodate linked strips
|
||||
if len(selshots) <= 2 and active_strip and active_strip.type == "MOVIE":
|
||||
row = box.row(align=True)
|
||||
row.prop(active_strip, "filepath_display", text="")
|
||||
row.operator(
|
||||
KITSU_OT_sqe_change_strip_source.bl_idname, text="", icon="TRIA_UP"
|
||||
).direction = "UP"
|
||||
row.operator(
|
||||
KITSU_OT_sqe_change_strip_source.bl_idname, text="", icon="TRIA_DOWN"
|
||||
).direction = "DOWN"
|
||||
row.operator(
|
||||
KITSU_OT_sqe_change_strip_source.bl_idname, text="", icon="FILE_PARENT"
|
||||
).go_latest = True
|
||||
|
||||
|
||||
# ---------REGISTER ----------.
|
||||
|
||||
classes = [
|
||||
KITSU_MT_sqe_advanced_delete,
|
||||
KITSU_PT_sqe_shot_tools,
|
||||
KITSU_PT_sqe_general_tools,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
Reference in New Issue
Block a user