mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-26 13:52:15 +01:00
Merge pull request #307 from pypeclub/feature/239-resolve_tagging_for_publish
[draft] Resolve: tagging and renaming for publish
This commit is contained in:
commit
c6e586f678
26 changed files with 1104 additions and 261 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import traceback
|
||||
import winreg
|
||||
from avalon import api, io, lib
|
||||
from pype.lib import PypeHook
|
||||
from pype.api import Logger, Anatomy
|
||||
|
|
@ -14,6 +15,12 @@ class PremierePrelaunch(PypeHook):
|
|||
shell script.
|
||||
"""
|
||||
project_code = None
|
||||
reg_string_value = [{
|
||||
"path": r"Software\Adobe\CSXS.9",
|
||||
"name": "PlayerDebugMode",
|
||||
"type": winreg.REG_SZ,
|
||||
"value": "1"
|
||||
}]
|
||||
|
||||
def __init__(self, logger=None):
|
||||
if not logger:
|
||||
|
|
@ -55,6 +62,10 @@ class PremierePrelaunch(PypeHook):
|
|||
# adding project code to env
|
||||
env["AVALON_PROJECT_CODE"] = self.project_code
|
||||
|
||||
# add keys to registry
|
||||
self.modify_registry()
|
||||
|
||||
# start avalon
|
||||
try:
|
||||
__import__("pype.hosts.premiere")
|
||||
__import__("pyblish")
|
||||
|
|
@ -69,6 +80,24 @@ class PremierePrelaunch(PypeHook):
|
|||
|
||||
return True
|
||||
|
||||
def modify_registry(self):
|
||||
# adding key to registry
|
||||
for key in self.reg_string_value:
|
||||
winreg.CreateKey(winreg.HKEY_CURRENT_USER, key["path"])
|
||||
rg_key = winreg.OpenKey(
|
||||
key=winreg.HKEY_CURRENT_USER,
|
||||
sub_key=key["path"],
|
||||
reserved=0,
|
||||
access=winreg.KEY_ALL_ACCESS)
|
||||
|
||||
winreg.SetValueEx(
|
||||
rg_key,
|
||||
key["name"],
|
||||
0,
|
||||
key["type"],
|
||||
key["value"]
|
||||
)
|
||||
|
||||
def get_anatomy_filled(self):
|
||||
root_path = api.registered_root()
|
||||
project_name = self._S["AVALON_PROJECT"]
|
||||
|
|
|
|||
|
|
@ -46,13 +46,14 @@ class ResolvePrelaunch(PypeHook):
|
|||
"`RESOLVE_UTILITY_SCRIPTS_DIR` or reinstall DaVinci Resolve. \n"
|
||||
f"RESOLVE_UTILITY_SCRIPTS_DIR: `{us_dir}`"
|
||||
)
|
||||
self.log.debug(f"-- us_dir: `{us_dir}`")
|
||||
|
||||
# correctly format path for pre python script
|
||||
pre_py_sc = os.path.normpath(env.get("PRE_PYTHON_SCRIPT", ""))
|
||||
env["PRE_PYTHON_SCRIPT"] = pre_py_sc
|
||||
|
||||
self.log.debug(f"-- pre_py_sc: `{pre_py_sc}`...")
|
||||
try:
|
||||
__import__("pype.resolve")
|
||||
__import__("pype.hosts.resolve")
|
||||
__import__("pyblish")
|
||||
|
||||
except ImportError as e:
|
||||
|
|
@ -62,6 +63,7 @@ class ResolvePrelaunch(PypeHook):
|
|||
else:
|
||||
# Resolve Setup integration
|
||||
importlib.reload(utils)
|
||||
self.log.debug(f"-- utils.__file__: `{utils.__file__}`")
|
||||
utils.setup(env)
|
||||
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -1,17 +1,34 @@
|
|||
from .utils import (
|
||||
setup,
|
||||
get_resolve_module
|
||||
)
|
||||
|
||||
from .pipeline import (
|
||||
install,
|
||||
uninstall,
|
||||
ls,
|
||||
containerise,
|
||||
publish,
|
||||
launch_workfiles_app
|
||||
launch_workfiles_app,
|
||||
maintained_selection
|
||||
)
|
||||
|
||||
from .utils import (
|
||||
setup,
|
||||
get_resolve_module
|
||||
from .lib import (
|
||||
get_project_manager,
|
||||
get_current_project,
|
||||
get_current_sequence,
|
||||
get_current_track_items,
|
||||
create_current_sequence_media_bin,
|
||||
create_compound_clip,
|
||||
swap_clips,
|
||||
get_pype_clip_metadata,
|
||||
set_project_manager_to_folder_name
|
||||
)
|
||||
|
||||
from .menu import launch_pype_menu
|
||||
|
||||
from .plugin import Creator
|
||||
|
||||
from .workio import (
|
||||
open_file,
|
||||
save_file,
|
||||
|
|
@ -21,12 +38,8 @@ from .workio import (
|
|||
work_root
|
||||
)
|
||||
|
||||
from .lib import (
|
||||
get_project_manager,
|
||||
set_project_manager_to_folder_name
|
||||
)
|
||||
|
||||
from .menu import launch_pype_menu
|
||||
bmdvr = None
|
||||
bmdvf = None
|
||||
|
||||
__all__ = [
|
||||
# pipeline
|
||||
|
|
@ -37,6 +50,7 @@ __all__ = [
|
|||
"reload_pipeline",
|
||||
"publish",
|
||||
"launch_workfiles_app",
|
||||
"maintained_selection",
|
||||
|
||||
# utils
|
||||
"setup",
|
||||
|
|
@ -44,16 +58,30 @@ __all__ = [
|
|||
|
||||
# lib
|
||||
"get_project_manager",
|
||||
"get_current_project",
|
||||
"get_current_sequence",
|
||||
"get_current_track_items",
|
||||
"create_current_sequence_media_bin",
|
||||
"create_compound_clip",
|
||||
"swap_clips",
|
||||
"get_pype_clip_metadata",
|
||||
"set_project_manager_to_folder_name",
|
||||
|
||||
# menu
|
||||
"launch_pype_menu",
|
||||
|
||||
# plugin
|
||||
"Creator",
|
||||
|
||||
# workio
|
||||
"open_file",
|
||||
"save_file",
|
||||
"current_file",
|
||||
"has_unsaved_changes",
|
||||
"file_extensions",
|
||||
"work_root"
|
||||
"work_root",
|
||||
|
||||
# singleton with black magic resolve module
|
||||
"bmdvr",
|
||||
"bmdvf"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ class SelectInvalidAction(pyblish.api.Action):
|
|||
def process(self, context, plugin):
|
||||
|
||||
try:
|
||||
from pype.hosts.resolve.utils import get_resolve_module
|
||||
resolve = get_resolve_module()
|
||||
self.log.debug(resolve)
|
||||
from . import get_project_manager
|
||||
pm = get_project_manager()
|
||||
self.log.debug(pm)
|
||||
except ImportError:
|
||||
raise ImportError("Current host is not Resolve")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,406 @@
|
|||
import sys
|
||||
from .utils import get_resolve_module
|
||||
from pypeapp import Logger
|
||||
import json
|
||||
from opentimelineio import opentime
|
||||
from pprint import pformat
|
||||
|
||||
from pype.api import Logger
|
||||
|
||||
log = Logger().get_logger(__name__, "resolve")
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self.pm = None
|
||||
self.rename_index = 0
|
||||
self.rename_add = 0
|
||||
self.pype_metadata_key = "VFX Notes"
|
||||
|
||||
|
||||
def get_project_manager():
|
||||
from . import bmdvr
|
||||
if not self.pm:
|
||||
resolve = get_resolve_module()
|
||||
self.pm = resolve.GetProjectManager()
|
||||
self.pm = bmdvr.GetProjectManager()
|
||||
return self.pm
|
||||
|
||||
|
||||
def get_current_project():
|
||||
# initialize project manager
|
||||
get_project_manager()
|
||||
|
||||
return self.pm.GetCurrentProject()
|
||||
|
||||
|
||||
def get_current_sequence():
|
||||
# get current project
|
||||
project = get_current_project()
|
||||
|
||||
return project.GetCurrentTimeline()
|
||||
|
||||
|
||||
def get_current_track_items(
|
||||
filter=False,
|
||||
track_type=None,
|
||||
selecting_color=None):
|
||||
""" Gets all available current timeline track items
|
||||
"""
|
||||
track_type = track_type or "video"
|
||||
selecting_color = selecting_color or "Chocolate"
|
||||
project = get_current_project()
|
||||
sequence = get_current_sequence()
|
||||
selected_clips = list()
|
||||
|
||||
# get all tracks count filtered by track type
|
||||
selected_track_count = sequence.GetTrackCount(track_type)
|
||||
|
||||
# loop all tracks and get items
|
||||
_clips = dict()
|
||||
for track_index in range(1, (int(selected_track_count) + 1)):
|
||||
track_name = sequence.GetTrackName(track_type, track_index)
|
||||
track_track_items = sequence.GetItemListInTrack(
|
||||
track_type, track_index)
|
||||
_clips[track_index] = track_track_items
|
||||
|
||||
_data = {
|
||||
"project": project,
|
||||
"sequence": sequence,
|
||||
"track": {
|
||||
"name": track_name,
|
||||
"index": track_index,
|
||||
"type": track_type}
|
||||
}
|
||||
# get track item object and its color
|
||||
for clip_index, ti in enumerate(_clips[track_index]):
|
||||
data = _data.copy()
|
||||
data["clip"] = {
|
||||
"item": ti,
|
||||
"index": clip_index
|
||||
}
|
||||
ti_color = ti.GetClipColor()
|
||||
if filter is True:
|
||||
if selecting_color in ti_color:
|
||||
selected_clips.append(data)
|
||||
# ti.ClearClipColor()
|
||||
else:
|
||||
selected_clips.append(data)
|
||||
|
||||
return selected_clips
|
||||
|
||||
|
||||
def create_current_sequence_media_bin(sequence):
|
||||
seq_name = sequence.GetName()
|
||||
media_pool = get_current_project().GetMediaPool()
|
||||
root_folder = media_pool.GetRootFolder()
|
||||
sub_folders = root_folder.GetSubFolderList()
|
||||
testing_names = list()
|
||||
|
||||
print(f"_ sub_folders: {sub_folders}")
|
||||
for subfolder in sub_folders:
|
||||
subf_name = subfolder.GetName()
|
||||
if seq_name in subf_name:
|
||||
testing_names.append(subfolder)
|
||||
else:
|
||||
testing_names.append(False)
|
||||
|
||||
matching = next((f for f in testing_names if f is not False), None)
|
||||
|
||||
if not matching:
|
||||
new_folder = media_pool.AddSubFolder(root_folder, seq_name)
|
||||
media_pool.SetCurrentFolder(new_folder)
|
||||
else:
|
||||
media_pool.SetCurrentFolder(matching)
|
||||
|
||||
return media_pool.GetCurrentFolder()
|
||||
|
||||
|
||||
def get_name_with_data(clip_data, presets):
|
||||
"""
|
||||
Take hierarchy data from presets and build name with parents data
|
||||
|
||||
Args:
|
||||
clip_data (dict): clip data from `get_current_track_items()`
|
||||
presets (dict): data from create plugin
|
||||
|
||||
Returns:
|
||||
list: name, data
|
||||
|
||||
"""
|
||||
def _replace_hash_to_expression(name, text):
|
||||
_spl = text.split("#")
|
||||
_len = (len(_spl) - 1)
|
||||
_repl = f"{{{name}:0>{_len}}}"
|
||||
new_text = text.replace(("#" * _len), _repl)
|
||||
return new_text
|
||||
|
||||
# presets data
|
||||
clip_name = presets["clipName"]
|
||||
hierarchy = presets["hierarchy"]
|
||||
hierarchy_data = presets["hierarchyData"].copy()
|
||||
count_from = presets["countFrom"]
|
||||
steps = presets["steps"]
|
||||
|
||||
# reset rename_add
|
||||
if self.rename_add < count_from:
|
||||
self.rename_add = count_from
|
||||
|
||||
# shot num calculate
|
||||
if self.rename_index == 0:
|
||||
shot_num = self.rename_add
|
||||
else:
|
||||
shot_num = self.rename_add + steps
|
||||
|
||||
print(f"shot_num: {shot_num}")
|
||||
|
||||
# clip data
|
||||
_data = {
|
||||
"sequence": clip_data["sequence"].GetName(),
|
||||
"track": clip_data["track"]["name"].replace(" ", "_"),
|
||||
"shot": shot_num
|
||||
}
|
||||
|
||||
# solve # in test to pythonic explression
|
||||
for k, v in hierarchy_data.items():
|
||||
if "#" not in v:
|
||||
continue
|
||||
hierarchy_data[k] = _replace_hash_to_expression(k, v)
|
||||
|
||||
# fill up pythonic expresisons
|
||||
for k, v in hierarchy_data.items():
|
||||
hierarchy_data[k] = v.format(**_data)
|
||||
|
||||
# fill up clip name and hierarchy keys
|
||||
hierarchy = hierarchy.format(**hierarchy_data)
|
||||
clip_name = clip_name.format(**hierarchy_data)
|
||||
|
||||
self.rename_add = shot_num
|
||||
print(f"shot_num: {shot_num}")
|
||||
|
||||
return (clip_name, {
|
||||
"hierarchy": hierarchy,
|
||||
"hierarchyData": hierarchy_data
|
||||
})
|
||||
|
||||
|
||||
def create_compound_clip(clip_data, folder, rename=False, **kwargs):
|
||||
"""
|
||||
Convert timeline object into nested timeline object
|
||||
|
||||
Args:
|
||||
clip_data (dict): timeline item object packed into dict
|
||||
with project, timeline (sequence)
|
||||
folder (resolve.MediaPool.Folder): media pool folder object,
|
||||
rename (bool)[optional]: renaming in sequence or not
|
||||
kwargs (optional): additional data needed for rename=True (presets)
|
||||
|
||||
Returns:
|
||||
resolve.MediaPoolItem: media pool item with compound clip timeline(cct)
|
||||
"""
|
||||
# get basic objects form data
|
||||
project = clip_data["project"]
|
||||
sequence = clip_data["sequence"]
|
||||
clip = clip_data["clip"]
|
||||
|
||||
# get details of objects
|
||||
clip_item = clip["item"]
|
||||
track = clip_data["track"]
|
||||
|
||||
mp = project.GetMediaPool()
|
||||
|
||||
# get clip attributes
|
||||
clip_attributes = get_clip_attributes(clip_item)
|
||||
print(f"_ clip_attributes: {pformat(clip_attributes)}")
|
||||
|
||||
if rename:
|
||||
presets = kwargs.get("presets")
|
||||
if presets:
|
||||
name, data = get_name_with_data(clip_data, presets)
|
||||
# add hirarchy data to clip attributes
|
||||
clip_attributes.update(data)
|
||||
else:
|
||||
name = "{:0>3}_{:0>4}".format(
|
||||
int(track["index"]), int(clip["index"]))
|
||||
else:
|
||||
# build name
|
||||
clip_name_split = clip_item.GetName().split(".")
|
||||
name = "_".join([
|
||||
track["name"],
|
||||
str(track["index"]),
|
||||
clip_name_split[0],
|
||||
str(clip["index"])]
|
||||
)
|
||||
|
||||
# get metadata
|
||||
mp_item = clip_item.GetMediaPoolItem()
|
||||
mp_props = mp_item.GetClipProperty()
|
||||
|
||||
mp_first_frame = int(mp_props["Start"])
|
||||
mp_last_frame = int(mp_props["End"])
|
||||
|
||||
# initialize basic source timing for otio
|
||||
ci_l_offset = clip_item.GetLeftOffset()
|
||||
ci_duration = clip_item.GetDuration()
|
||||
rate = float(mp_props["FPS"])
|
||||
|
||||
# source rational times
|
||||
mp_in_rc = opentime.RationalTime((ci_l_offset), rate)
|
||||
mp_out_rc = opentime.RationalTime((ci_l_offset + ci_duration - 1), rate)
|
||||
|
||||
# get frame in and out for clip swaping
|
||||
in_frame = opentime.to_frames(mp_in_rc)
|
||||
out_frame = opentime.to_frames(mp_out_rc)
|
||||
|
||||
# keep original sequence
|
||||
sq_origin = sequence
|
||||
|
||||
# Set current folder to input media_pool_folder:
|
||||
mp.SetCurrentFolder(folder)
|
||||
|
||||
# check if clip doesnt exist already:
|
||||
clips = folder.GetClipList()
|
||||
cct = next((c for c in clips
|
||||
if c.GetName() in name), None)
|
||||
|
||||
if cct:
|
||||
print(f"_ cct exists: {cct}")
|
||||
else:
|
||||
# Create empty timeline in current folder and give name:
|
||||
cct = mp.CreateEmptyTimeline(name)
|
||||
|
||||
# check if clip doesnt exist already:
|
||||
clips = folder.GetClipList()
|
||||
cct = next((c for c in clips
|
||||
if c.GetName() in name), None)
|
||||
print(f"_ cct created: {cct}")
|
||||
|
||||
# Set current timeline to created timeline:
|
||||
project.SetCurrentTimeline(cct)
|
||||
|
||||
# Add input clip to the current timeline:
|
||||
mp.AppendToTimeline([{
|
||||
"mediaPoolItem": mp_item,
|
||||
"startFrame": mp_first_frame,
|
||||
"endFrame": mp_last_frame
|
||||
}])
|
||||
|
||||
# Set current timeline to the working timeline:
|
||||
project.SetCurrentTimeline(sq_origin)
|
||||
|
||||
# Add collected metadata and attributes to the comound clip:
|
||||
if mp_item.GetMetadata(self.pype_metadata_key):
|
||||
clip_attributes[self.pype_metadata_key] = mp_item.GetMetadata(
|
||||
self.pype_metadata_key)[self.pype_metadata_key]
|
||||
|
||||
# stringify
|
||||
clip_attributes = json.dumps(clip_attributes)
|
||||
|
||||
# add attributes to metadata
|
||||
for k, v in mp_item.GetMetadata().items():
|
||||
cct.SetMetadata(k, v)
|
||||
|
||||
# add metadata to cct
|
||||
cct.SetMetadata(self.pype_metadata_key, clip_attributes)
|
||||
|
||||
# reset start timecode of the compound clip
|
||||
cct.SetClipProperty("Start TC", mp_props["Start TC"])
|
||||
|
||||
# swap clips on timeline
|
||||
swap_clips(clip_item, cct, name, in_frame, out_frame)
|
||||
|
||||
cct.SetClipColor("Pink")
|
||||
return cct
|
||||
|
||||
|
||||
def swap_clips(from_clip, to_clip, to_clip_name, to_in_frame, to_out_frame):
|
||||
"""
|
||||
Swaping clips on timeline in timelineItem
|
||||
|
||||
It will add take and activate it to the frame range which is inputted
|
||||
|
||||
Args:
|
||||
from_clip (resolve.mediaPoolItem)
|
||||
to_clip (resolve.mediaPoolItem)
|
||||
to_clip_name (str): name of to_clip
|
||||
to_in_frame (float): cut in frame, usually `GetLeftOffset()`
|
||||
to_out_frame (float): cut out frame, usually left offset plus duration
|
||||
|
||||
Returns:
|
||||
bool: True if successfully replaced
|
||||
|
||||
"""
|
||||
# add clip item as take to timeline
|
||||
take = from_clip.AddTake(
|
||||
to_clip,
|
||||
float(to_in_frame),
|
||||
float(to_out_frame)
|
||||
)
|
||||
|
||||
if not take:
|
||||
return False
|
||||
|
||||
for take_index in range(1, (int(from_clip.GetTakesCount()) + 1)):
|
||||
take_item = from_clip.GetTakeByIndex(take_index)
|
||||
take_mp_item = take_item["mediaPoolItem"]
|
||||
if to_clip_name in take_mp_item.GetName():
|
||||
from_clip.SelectTakeByIndex(take_index)
|
||||
from_clip.FinalizeTake()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def validate_tc(x):
|
||||
# Validate and reformat timecode string
|
||||
|
||||
if len(x) != 11:
|
||||
print('Invalid timecode. Try again.')
|
||||
|
||||
c = ':'
|
||||
colonized = x[:2] + c + x[3:5] + c + x[6:8] + c + x[9:]
|
||||
|
||||
if colonized.replace(':', '').isdigit():
|
||||
print(f"_ colonized: {colonized}")
|
||||
return colonized
|
||||
else:
|
||||
print('Invalid timecode. Try again.')
|
||||
|
||||
|
||||
def get_pype_clip_metadata(clip):
|
||||
"""
|
||||
Get pype metadata created by creator plugin
|
||||
|
||||
Attributes:
|
||||
clip (resolve.TimelineItem): resolve's object
|
||||
|
||||
Returns:
|
||||
dict: hierarchy, orig clip attributes
|
||||
"""
|
||||
mp_item = clip.GetMediaPoolItem()
|
||||
metadata = mp_item.GetMetadata()
|
||||
|
||||
return metadata.get(self.pype_metadata_key)
|
||||
|
||||
|
||||
def get_clip_attributes(clip):
|
||||
"""
|
||||
Collect basic atrributes from resolve timeline item
|
||||
|
||||
Args:
|
||||
clip (resolve.TimelineItem): timeline item object
|
||||
|
||||
Returns:
|
||||
dict: all collected attributres as key: values
|
||||
"""
|
||||
mp_item = clip.GetMediaPoolItem()
|
||||
|
||||
data = {
|
||||
"clipIn": clip.GetStart(),
|
||||
"clipOut": clip.GetEnd(),
|
||||
"clipLeftOffset": clip.GetLeftOffset(),
|
||||
"clipRightOffset": clip.GetRightOffset(),
|
||||
"clipMarkers": clip.GetMarkers(),
|
||||
"clipFlags": clip.GetFlagList(),
|
||||
"sourceId": mp_item.GetMediaId(),
|
||||
"sourceProperties": mp_item.GetClipProperty()
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
def set_project_manager_to_folder_name(folder_name):
|
||||
"""
|
||||
Sets context of Project manager to given folder by name.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
QWidget {
|
||||
background-color: #282828;
|
||||
border-radius: 3;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
QPushButton {
|
||||
|
|
@ -20,10 +21,38 @@ QPushButton:hover {
|
|||
color: #e64b3d;
|
||||
}
|
||||
|
||||
QSpinBox {
|
||||
border: 1px solid #090909;
|
||||
background-color: #201f1f;
|
||||
color: #ffffff;
|
||||
padding: 2;
|
||||
max-width: 8em;
|
||||
qproperty-alignment: AlignCenter;
|
||||
}
|
||||
|
||||
QLineEdit {
|
||||
border: 1px solid #090909;
|
||||
border-radius: 3px;
|
||||
background-color: #201f1f;
|
||||
color: #ffffff;
|
||||
padding: 2;
|
||||
min-width: 10em;
|
||||
qproperty-alignment: AlignCenter;
|
||||
}
|
||||
|
||||
#PypeMenu {
|
||||
border: 1px solid #fef9ef;
|
||||
}
|
||||
|
||||
#Spacer {
|
||||
QVBoxLayout {
|
||||
background-color: #282828;
|
||||
}
|
||||
|
||||
#Devider {
|
||||
border: 1px solid #090909;
|
||||
background-color: #585858;
|
||||
}
|
||||
|
||||
QLabel {
|
||||
color: #77776b;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,23 @@
|
|||
Basic avalon integration
|
||||
"""
|
||||
import os
|
||||
# import sys
|
||||
import contextlib
|
||||
from avalon.tools import workfiles
|
||||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
from pypeapp import Logger
|
||||
import pype
|
||||
from pype.api import Logger
|
||||
|
||||
log = Logger().get_logger(__name__, "resolve")
|
||||
|
||||
# self = sys.modules[__name__]
|
||||
|
||||
AVALON_CONFIG = os.environ["AVALON_CONFIG"]
|
||||
PARENT_DIR = os.path.dirname(__file__)
|
||||
PACKAGE_DIR = os.path.dirname(PARENT_DIR)
|
||||
PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins")
|
||||
|
||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "resolve", "load")
|
||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "resolve", "create")
|
||||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "resolve", "inventory")
|
||||
LOAD_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "load")
|
||||
CREATE_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "create")
|
||||
INVENTORY_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "inventory")
|
||||
|
||||
PUBLISH_PATH = os.path.join(
|
||||
PLUGINS_DIR, "resolve", "publish"
|
||||
pype.PLUGINS_DIR, "resolve", "publish"
|
||||
).replace("\\", "/")
|
||||
|
||||
AVALON_CONTAINERS = ":AVALON_CONTAINERS"
|
||||
|
|
@ -40,11 +36,13 @@ def install():
|
|||
See the Maya equivalent for inspiration on how to implement this.
|
||||
|
||||
"""
|
||||
from . import get_resolve_module
|
||||
|
||||
# Disable all families except for the ones we explicitly want to see
|
||||
family_states = [
|
||||
"imagesequence",
|
||||
"mov"
|
||||
"mov",
|
||||
"clip"
|
||||
]
|
||||
avalon.data["familiesStateDefault"] = False
|
||||
avalon.data["familiesStateToggled"] = family_states
|
||||
|
|
@ -59,6 +57,8 @@ def install():
|
|||
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
|
||||
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
|
||||
|
||||
get_resolve_module()
|
||||
|
||||
|
||||
def uninstall():
|
||||
"""Uninstall all tha was installed
|
||||
|
|
@ -140,3 +140,26 @@ def publish(parent):
|
|||
"""Shorthand to publish from within host"""
|
||||
from avalon.tools import publish
|
||||
return publish.show(parent)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def maintained_selection():
|
||||
"""Maintain selection during context
|
||||
|
||||
Example:
|
||||
>>> with maintained_selection():
|
||||
... node['selected'].setValue(True)
|
||||
>>> print(node['selected'].value())
|
||||
False
|
||||
"""
|
||||
try:
|
||||
# do the operation
|
||||
yield
|
||||
finally:
|
||||
pass
|
||||
|
||||
|
||||
def reset_selection():
|
||||
"""Deselect all selected nodes
|
||||
"""
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -1,6 +1,182 @@
|
|||
import re
|
||||
from avalon import api
|
||||
# from pype.hosts.resolve import lib as drlib
|
||||
from pype.hosts import resolve
|
||||
from avalon.vendor import qargparse
|
||||
from pype.api import config
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
|
||||
class CreatorWidget(QtWidgets.QDialog):
|
||||
|
||||
# output items
|
||||
items = dict()
|
||||
|
||||
def __init__(self, name, info, presets, parent=None):
|
||||
super(CreatorWidget, self).__init__(parent)
|
||||
|
||||
self.setObjectName(name)
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.Window
|
||||
| QtCore.Qt.CustomizeWindowHint
|
||||
| QtCore.Qt.WindowTitleHint
|
||||
| QtCore.Qt.WindowCloseButtonHint
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
)
|
||||
self.setWindowTitle(name or "Pype Creator Input")
|
||||
|
||||
# Where inputs and labels are set
|
||||
self.content_widget = [QtWidgets.QWidget(self)]
|
||||
top_layout = QtWidgets.QFormLayout(self.content_widget[0])
|
||||
top_layout.setObjectName("ContentLayout")
|
||||
top_layout.addWidget(Spacer(5, self))
|
||||
|
||||
# first add widget tag line
|
||||
top_layout.addWidget(QtWidgets.QLabel(info))
|
||||
|
||||
top_layout.addWidget(Spacer(5, self))
|
||||
|
||||
# main dynamic layout
|
||||
self.content_widget.append(QtWidgets.QWidget(self))
|
||||
content_layout = QtWidgets.QFormLayout(self.content_widget[-1])
|
||||
|
||||
# add preset data into input widget layout
|
||||
self.items = self.add_presets_to_layout(content_layout, presets)
|
||||
|
||||
# Confirmation buttons
|
||||
btns_widget = QtWidgets.QWidget(self)
|
||||
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
|
||||
|
||||
cancel_btn = QtWidgets.QPushButton("Cancel")
|
||||
btns_layout.addWidget(cancel_btn)
|
||||
|
||||
ok_btn = QtWidgets.QPushButton("Ok")
|
||||
btns_layout.addWidget(ok_btn)
|
||||
|
||||
# Main layout of the dialog
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(10, 10, 10, 10)
|
||||
main_layout.setSpacing(0)
|
||||
|
||||
# adding content widget
|
||||
for w in self.content_widget:
|
||||
main_layout.addWidget(w)
|
||||
|
||||
main_layout.addWidget(btns_widget)
|
||||
|
||||
ok_btn.clicked.connect(self._on_ok_clicked)
|
||||
cancel_btn.clicked.connect(self._on_cancel_clicked)
|
||||
|
||||
stylesheet = resolve.menu.load_stylesheet()
|
||||
self.setStyleSheet(stylesheet)
|
||||
|
||||
def _on_ok_clicked(self):
|
||||
self.result = self.value(self.items)
|
||||
self.close()
|
||||
|
||||
def _on_cancel_clicked(self):
|
||||
self.result = None
|
||||
self.close()
|
||||
|
||||
def value(self, data):
|
||||
for k, v in data.items():
|
||||
if isinstance(v, dict):
|
||||
print(f"nested: {k}")
|
||||
data[k] = self.value(v)
|
||||
elif getattr(v, "value", None):
|
||||
print(f"normal int: {k}")
|
||||
result = v.value()
|
||||
data[k] = result()
|
||||
else:
|
||||
print(f"normal text: {k}")
|
||||
result = v.text()
|
||||
data[k] = result()
|
||||
return data
|
||||
|
||||
def camel_case_split(self, text):
|
||||
matches = re.finditer(
|
||||
'.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text)
|
||||
return " ".join([str(m.group(0)).capitalize() for m in matches])
|
||||
|
||||
def create_row(self, layout, type, text, **kwargs):
|
||||
# get type attribute from qwidgets
|
||||
attr = getattr(QtWidgets, type)
|
||||
|
||||
# convert label text to normal capitalized text with spaces
|
||||
label_text = self.camel_case_split(text)
|
||||
|
||||
# assign the new text to lable widget
|
||||
label = QtWidgets.QLabel(label_text)
|
||||
label.setObjectName("LineLabel")
|
||||
|
||||
# create attribute name text strip of spaces
|
||||
attr_name = text.replace(" ", "")
|
||||
|
||||
# create attribute and assign default values
|
||||
setattr(
|
||||
self,
|
||||
attr_name,
|
||||
attr(parent=self))
|
||||
|
||||
# assign the created attribute to variable
|
||||
item = getattr(self, attr_name)
|
||||
for func, val in kwargs.items():
|
||||
if getattr(item, func):
|
||||
func_attr = getattr(item, func)
|
||||
func_attr(val)
|
||||
|
||||
# add to layout
|
||||
layout.addRow(label, item)
|
||||
|
||||
return item
|
||||
|
||||
def add_presets_to_layout(self, content_layout, data):
|
||||
for k, v in data.items():
|
||||
if isinstance(v, dict):
|
||||
# adding spacer between sections
|
||||
self.content_widget.append(QtWidgets.QWidget(self))
|
||||
devider = QtWidgets.QVBoxLayout(self.content_widget[-1])
|
||||
devider.addWidget(Spacer(5, self))
|
||||
devider.setObjectName("Devider")
|
||||
|
||||
# adding nested layout with label
|
||||
self.content_widget.append(QtWidgets.QWidget(self))
|
||||
nested_content_layout = QtWidgets.QFormLayout(
|
||||
self.content_widget[-1])
|
||||
nested_content_layout.setObjectName("NestedContentLayout")
|
||||
|
||||
# add nested key as label
|
||||
self.create_row(nested_content_layout, "QLabel", k)
|
||||
data[k] = self.add_presets_to_layout(nested_content_layout, v)
|
||||
elif isinstance(v, str):
|
||||
print(f"layout.str: {k}")
|
||||
print(f"content_layout: {content_layout}")
|
||||
data[k] = self.create_row(
|
||||
content_layout, "QLineEdit", k, setText=v)
|
||||
elif isinstance(v, int):
|
||||
print(f"layout.int: {k}")
|
||||
print(f"content_layout: {content_layout}")
|
||||
data[k] = self.create_row(
|
||||
content_layout, "QSpinBox", k, setValue=v)
|
||||
return data
|
||||
|
||||
|
||||
class Spacer(QtWidgets.QWidget):
|
||||
def __init__(self, height, *args, **kwargs):
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setFixedHeight(height)
|
||||
|
||||
real_spacer = QtWidgets.QWidget(self)
|
||||
real_spacer.setObjectName("Spacer")
|
||||
real_spacer.setFixedHeight(height)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(real_spacer)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
def get_reference_node_parents(ref):
|
||||
|
|
@ -73,3 +249,25 @@ class SequenceLoader(api.Loader):
|
|||
"""Remove an existing `container`
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Creator(api.Creator):
|
||||
"""Creator class wrapper
|
||||
"""
|
||||
marker_color = "Purple"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Creator, self).__init__(*args, **kwargs)
|
||||
self.presets = config.get_presets()['plugins']["resolve"][
|
||||
"create"].get(self.__class__.__name__, {})
|
||||
|
||||
# adding basic current context resolve objects
|
||||
self.project = resolve.get_current_project()
|
||||
self.sequence = resolve.get_current_sequence()
|
||||
|
||||
if (self.options or {}).get("useSelection"):
|
||||
self.selected = resolve.get_current_track_items(filter=True)
|
||||
else:
|
||||
self.selected = resolve.get_current_track_items(filter=False)
|
||||
|
||||
self.widget = CreatorWidget
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
import time
|
||||
from pype.hosts.resolve.utils import get_resolve_module
|
||||
from pypeapp import Logger
|
||||
from pype.api import Logger
|
||||
|
||||
log = Logger().get_logger(__name__, "resolve")
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import sys
|
|||
import avalon.api as avalon
|
||||
import pype
|
||||
|
||||
from pypeapp import Logger
|
||||
from pype.api import Logger
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
# convert clip def
|
||||
def convert_clip(timeline=None):
|
||||
"""Convert timeline item (clip) into compound clip pype container
|
||||
|
||||
Args:
|
||||
timeline (MediaPool.Timeline): Object of timeline
|
||||
|
||||
Returns:
|
||||
bool: `True` if success
|
||||
|
||||
Raises:
|
||||
Exception: description
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# decorator function create_current_timeline_media_bin()
|
||||
def create_current_timeline_media_bin(timeline=None):
|
||||
"""Convert timeline item (clip) into compound clip pype container
|
||||
|
||||
Args:
|
||||
timeline (MediaPool.Timeline): Object of timeline
|
||||
|
||||
Returns:
|
||||
bool: `True` if success
|
||||
|
||||
Raises:
|
||||
Exception: description
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# decorator function get_selected_track_items()
|
||||
def get_selected_track_items():
|
||||
"""Convert timeline item (clip) into compound clip pype container
|
||||
|
||||
Args:
|
||||
timeline (MediaPool.Timeline): Object of timeline
|
||||
|
||||
Returns:
|
||||
bool: `True` if success
|
||||
|
||||
Raises:
|
||||
Exception: description
|
||||
|
||||
"""
|
||||
print("testText")
|
||||
|
||||
|
||||
# PypeCompoundClip() class
|
||||
class PypeCompoundClip(object):
|
||||
"""docstring for ."""
|
||||
|
||||
def __init__(self, arg):
|
||||
super(self).__init__()
|
||||
self.arg = arg
|
||||
|
||||
def create_compound_clip(self):
|
||||
pass
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
import os
|
||||
import sys
|
||||
import pype
|
||||
import importlib
|
||||
import pyblish.api
|
||||
import pyblish.util
|
||||
import avalon.api
|
||||
from avalon.tools import publish
|
||||
from pypeapp import Logger
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
|
||||
def main(env):
|
||||
# Registers pype's Global pyblish plugins
|
||||
pype.install()
|
||||
|
||||
# Register Host (and it's pyblish plugins)
|
||||
host_name = env["AVALON_APP"]
|
||||
# TODO not sure if use "pype." or "avalon." for host import
|
||||
host_import_str = f"pype.{host_name}"
|
||||
|
||||
try:
|
||||
host_module = importlib.import_module(host_import_str)
|
||||
except ModuleNotFoundError:
|
||||
log.error((
|
||||
f"Host \"{host_name}\" can't be imported."
|
||||
f" Import string \"{host_import_str}\" failed."
|
||||
))
|
||||
return False
|
||||
|
||||
avalon.api.install(host_module)
|
||||
|
||||
# Register additional paths
|
||||
addition_paths_str = env.get("PUBLISH_PATHS") or ""
|
||||
addition_paths = addition_paths_str.split(os.pathsep)
|
||||
for path in addition_paths:
|
||||
path = os.path.normpath(path)
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
|
||||
pyblish.api.register_plugin_path(path)
|
||||
|
||||
# Register project specific plugins
|
||||
project_name = os.environ["AVALON_PROJECT"]
|
||||
project_plugins_paths = env.get("PYPE_PROJECT_PLUGINS") or ""
|
||||
for path in project_plugins_paths.split(os.pathsep):
|
||||
plugin_path = os.path.join(path, project_name, "plugins")
|
||||
if os.path.exists(plugin_path):
|
||||
pyblish.api.register_plugin_path(plugin_path)
|
||||
|
||||
return publish.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = main(os.environ)
|
||||
sys.exit(not bool(result))
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
from pypeapp import execute, Logger
|
||||
from pype.hosts.resolve.utils import get_resolve_module
|
||||
|
||||
log = Logger().get_logger("Resolve")
|
||||
|
||||
CURRENT_DIR = os.getenv("RESOLVE_UTILITY_SCRIPTS_DIR", "")
|
||||
python_dir = os.getenv("PYTHON36_RESOLVE")
|
||||
python_exe = os.path.normpath(
|
||||
os.path.join(python_dir, "python.exe")
|
||||
)
|
||||
|
||||
resolve = get_resolve_module()
|
||||
PM = resolve.GetProjectManager()
|
||||
P = PM.GetCurrentProject()
|
||||
|
||||
log.info(P.GetName())
|
||||
|
||||
|
||||
# ______________________________________________________
|
||||
# testing subprocessing Scripts
|
||||
testing_py = os.path.join(CURRENT_DIR, "ResolvePageSwitcher.py")
|
||||
testing_py = os.path.normpath(testing_py)
|
||||
log.info(f"Testing path to script: `{testing_py}`")
|
||||
|
||||
returncode = execute(
|
||||
[python_exe, os.path.normpath(testing_py)],
|
||||
env=dict(os.environ)
|
||||
)
|
||||
|
||||
# Check if output file exists
|
||||
if returncode != 0:
|
||||
log.error("Executing failed!")
|
||||
21
pype/hosts/resolve/utility_scripts/test.py
Normal file
21
pype/hosts/resolve/utility_scripts/test.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#! python3
|
||||
import sys
|
||||
from pype.api import Logger
|
||||
import DaVinciResolveScript as bmdvr
|
||||
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
import pype.hosts.resolve as bmdvr
|
||||
bm = bmdvr.utils.get_resolve_module()
|
||||
log.info(f"blackmagicmodule: {bm}")
|
||||
|
||||
|
||||
print(f"_>> bmdvr.scriptapp(Resolve): {bmdvr.scriptapp('Resolve')}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = main()
|
||||
sys.exit(not bool(result))
|
||||
|
|
@ -9,18 +9,16 @@ import os
|
|||
import shutil
|
||||
|
||||
from pypeapp import Logger
|
||||
|
||||
log = Logger().get_logger(__name__, "resolve")
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self.bmd = None
|
||||
|
||||
|
||||
def get_resolve_module():
|
||||
from pype.hosts import resolve
|
||||
# dont run if already loaded
|
||||
if self.bmd:
|
||||
return self.bmd
|
||||
|
||||
if resolve.bmdvr:
|
||||
log.info(("resolve module is assigned to "
|
||||
f"`pype.hosts.resolve.bmdvr`: {resolve.bmdvr}"))
|
||||
return resolve.bmdvr
|
||||
try:
|
||||
"""
|
||||
The PYTHONPATH needs to be set correctly for this import
|
||||
|
|
@ -71,8 +69,14 @@ def get_resolve_module():
|
|||
)
|
||||
sys.exit()
|
||||
# assign global var and return
|
||||
self.bmd = bmd.scriptapp("Resolve")
|
||||
return self.bmd
|
||||
bmdvr = bmd.scriptapp("Resolve")
|
||||
# bmdvf = bmd.scriptapp("Fusion")
|
||||
resolve.bmdvr = bmdvr
|
||||
resolve.bmdvf = bmdvr.Fusion()
|
||||
log.info(("Assigning resolve module to "
|
||||
f"`pype.hosts.resolve.bmdvr`: {resolve.bmdvr}"))
|
||||
log.info(("Assigning resolve module to "
|
||||
f"`pype.hosts.resolve.bmdvf`: {resolve.bmdvf}"))
|
||||
|
||||
|
||||
def _sync_utility_scripts(env=None):
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
import os
|
||||
from pypeapp import Logger
|
||||
from .lib import (
|
||||
from . import (
|
||||
get_project_manager,
|
||||
get_current_project,
|
||||
set_project_manager_to_folder_name
|
||||
)
|
||||
|
||||
|
|
@ -26,7 +27,7 @@ def save_file(filepath):
|
|||
pm = get_project_manager()
|
||||
file = os.path.basename(filepath)
|
||||
fname, _ = os.path.splitext(file)
|
||||
project = pm.GetCurrentProject()
|
||||
project = get_current_project()
|
||||
name = project.GetName()
|
||||
|
||||
if "Untitled Project" not in name:
|
||||
|
|
|
|||
|
|
@ -497,9 +497,8 @@ class DeleteAssetSubset(BaseAction):
|
|||
for entity in entities:
|
||||
ftrack_id = entity["id"]
|
||||
ftrack_id_name_map[ftrack_id] = entity["name"]
|
||||
if ftrack_id in ftrack_ids_to_delete:
|
||||
continue
|
||||
not_deleted_entities_id.append(ftrack_id)
|
||||
if ftrack_id not in ftrack_ids_to_delete:
|
||||
not_deleted_entities_id.append(ftrack_id)
|
||||
|
||||
mongo_proc_txt = "MongoProcessing: "
|
||||
ftrack_proc_txt = "Ftrack processing: "
|
||||
|
|
@ -534,25 +533,20 @@ class DeleteAssetSubset(BaseAction):
|
|||
ftrack_proc_txt, ", ".join(ftrack_ids_to_delete)
|
||||
))
|
||||
|
||||
joined_ids_to_delete = ", ".join(
|
||||
["\"{}\"".format(id) for id in ftrack_ids_to_delete]
|
||||
ftrack_ents_to_delete = (
|
||||
self._filter_entities_to_delete(ftrack_ids_to_delete, session)
|
||||
)
|
||||
ftrack_ents_to_delete = self.session.query(
|
||||
"select id, link from TypedContext where id in ({})".format(
|
||||
joined_ids_to_delete
|
||||
)
|
||||
).all()
|
||||
for entity in ftrack_ents_to_delete:
|
||||
self.session.delete(entity)
|
||||
session.delete(entity)
|
||||
try:
|
||||
self.session.commit()
|
||||
session.commit()
|
||||
except Exception:
|
||||
ent_path = "/".join(
|
||||
[ent["name"] for ent in entity["link"]]
|
||||
)
|
||||
msg = "Failed to delete entity"
|
||||
report_messages[msg].append(ent_path)
|
||||
self.session.rollback()
|
||||
session.rollback()
|
||||
self.log.warning(
|
||||
"{} <{}>".format(msg, ent_path),
|
||||
exc_info=True
|
||||
|
|
@ -568,7 +562,7 @@ class DeleteAssetSubset(BaseAction):
|
|||
for name in asset_names_to_delete
|
||||
])
|
||||
# Find assets of selected entities with names of checked subsets
|
||||
assets = self.session.query((
|
||||
assets = session.query((
|
||||
"select id from Asset where"
|
||||
" context_id in ({}) and name in ({})"
|
||||
).format(joined_not_deleted, joined_asset_names)).all()
|
||||
|
|
@ -578,20 +572,54 @@ class DeleteAssetSubset(BaseAction):
|
|||
", ".join([asset["id"] for asset in assets])
|
||||
))
|
||||
for asset in assets:
|
||||
self.session.delete(asset)
|
||||
session.delete(asset)
|
||||
try:
|
||||
self.session.commit()
|
||||
session.commit()
|
||||
except Exception:
|
||||
self.session.rollback()
|
||||
session.rollback()
|
||||
msg = "Failed to delete asset"
|
||||
report_messages[msg].append(asset["id"])
|
||||
self.log.warning(
|
||||
"{} <{}>".format(asset["id"]),
|
||||
"Asset: {} <{}>".format(asset["name"], asset["id"]),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
return self.report_handle(report_messages, project_name, event)
|
||||
|
||||
def _filter_entities_to_delete(self, ftrack_ids_to_delete, session):
|
||||
"""Filter children entities to avoid CircularDependencyError."""
|
||||
joined_ids_to_delete = ", ".join(
|
||||
["\"{}\"".format(id) for id in ftrack_ids_to_delete]
|
||||
)
|
||||
to_delete_entities = session.query(
|
||||
"select id, link from TypedContext where id in ({})".format(
|
||||
joined_ids_to_delete
|
||||
)
|
||||
).all()
|
||||
filtered = to_delete_entities[:]
|
||||
while True:
|
||||
changed = False
|
||||
_filtered = filtered[:]
|
||||
for entity in filtered:
|
||||
entity_id = entity["id"]
|
||||
|
||||
for _entity in tuple(_filtered):
|
||||
if entity_id == _entity["id"]:
|
||||
continue
|
||||
|
||||
for _link in _entity["link"]:
|
||||
if entity_id == _link["id"] and _entity in _filtered:
|
||||
_filtered.remove(_entity)
|
||||
changed = True
|
||||
break
|
||||
|
||||
filtered = _filtered
|
||||
|
||||
if not changed:
|
||||
break
|
||||
|
||||
return filtered
|
||||
|
||||
def report_handle(self, report_messages, project_name, event):
|
||||
if not report_messages:
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
chunk_size = self.deadline_chunk_size
|
||||
|
||||
priority = instance.data.get("deadlinePriority")
|
||||
if priority != 50:
|
||||
if not priority:
|
||||
priority = self.deadline_priority
|
||||
|
||||
payload = {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class CollectFrameranges(pyblish.api.InstancePlugin):
|
|||
"""
|
||||
|
||||
label = "Collect Clip Frameranges"
|
||||
order = pyblish.api.CollectorOrder
|
||||
order = pyblish.api.CollectorOrder - 0.01
|
||||
families = ['clip']
|
||||
|
||||
def process(self, instance):
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class CollectClipRepresentations(pyblish.api.InstancePlugin):
|
|||
"""
|
||||
|
||||
label = "Collect Clip Representations"
|
||||
order = pyblish.api.CollectorOrder
|
||||
order = pyblish.api.CollectorOrder + 0.1
|
||||
families = ['clip']
|
||||
|
||||
def process(self, instance):
|
||||
|
|
|
|||
|
|
@ -37,13 +37,7 @@ class ValidateAutoSyncOff(pyblish.api.ContextPlugin):
|
|||
query = 'Project where full_name is "{}"'.format(project_name)
|
||||
project = session.query(query).one()
|
||||
|
||||
invalid = None
|
||||
|
||||
if project.get('custom_attributes', {}).get(
|
||||
'avalon_auto_sync', False):
|
||||
invalid = project
|
||||
|
||||
return invalid
|
||||
return project
|
||||
|
||||
@classmethod
|
||||
def repair(cls, context):
|
||||
|
|
@ -55,4 +49,4 @@ class ValidateAutoSyncOff(pyblish.api.ContextPlugin):
|
|||
except Exception:
|
||||
tp, value, tb = sys.exc_info()
|
||||
session.rollback()
|
||||
six.reraise(tp, value, tb)
|
||||
raise
|
||||
|
|
|
|||
79
pype/plugins/resolve/create/create_shot_clip.py
Normal file
79
pype/plugins/resolve/create/create_shot_clip.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
from pprint import pformat
|
||||
from pype.hosts import resolve
|
||||
from pype.hosts.resolve import lib
|
||||
|
||||
|
||||
class CreateShotClip(resolve.Creator):
|
||||
"""Publishable clip"""
|
||||
|
||||
label = "Shot"
|
||||
family = "clip"
|
||||
icon = "film"
|
||||
defaults = ["Main"]
|
||||
|
||||
gui_name = "Pype sequencial rename with hirerarchy"
|
||||
gui_info = "Define sequencial rename and fill hierarchy data."
|
||||
gui_inputs = {
|
||||
"clipName": "{episode}{sequence}{shot}",
|
||||
"hierarchy": "{folder}/{sequence}/{shot}",
|
||||
"countFrom": 10,
|
||||
"steps": 10,
|
||||
"hierarchyData": {
|
||||
"folder": "shots",
|
||||
"shot": "sh####",
|
||||
"track": "{track}",
|
||||
"sequence": "sc010",
|
||||
"episode": "ep01"
|
||||
}
|
||||
}
|
||||
presets = None
|
||||
|
||||
def process(self):
|
||||
# solve gui inputs overwrites from presets
|
||||
# overwrite gui inputs from presets
|
||||
for k, v in self.gui_inputs.items():
|
||||
if isinstance(v, dict):
|
||||
# nested dictionary (only one level allowed)
|
||||
for _k, _v in v.items():
|
||||
if self.presets.get(_k):
|
||||
self.gui_inputs[k][_k] = self.presets[_k]
|
||||
if self.presets.get(k):
|
||||
self.gui_inputs[k] = self.presets[k]
|
||||
|
||||
# open widget for plugins inputs
|
||||
widget = self.widget(self.gui_name, self.gui_info, self.gui_inputs)
|
||||
widget.exec_()
|
||||
|
||||
print(f"__ selected_clips: {self.selected}")
|
||||
if len(self.selected) < 1:
|
||||
return
|
||||
|
||||
if not widget.result:
|
||||
print("Operation aborted")
|
||||
return
|
||||
|
||||
# sequence attrs
|
||||
sq_frame_start = self.sequence.GetStartFrame()
|
||||
sq_markers = self.sequence.GetMarkers()
|
||||
print(f"__ sq_frame_start: {pformat(sq_frame_start)}")
|
||||
print(f"__ seq_markers: {pformat(sq_markers)}")
|
||||
|
||||
# create media bin for compound clips (trackItems)
|
||||
mp_folder = resolve.create_current_sequence_media_bin(self.sequence)
|
||||
print(f"_ mp_folder: {mp_folder.GetName()}")
|
||||
|
||||
lib.rename_add = 0
|
||||
for i, t_data in enumerate(self.selected):
|
||||
lib.rename_index = i
|
||||
|
||||
# clear color after it is done
|
||||
t_data["clip"]["item"].ClearClipColor()
|
||||
|
||||
# convert track item to timeline media pool item
|
||||
resolve.create_compound_clip(
|
||||
t_data,
|
||||
mp_folder,
|
||||
rename=True,
|
||||
**dict(
|
||||
{"presets": widget.result})
|
||||
)
|
||||
162
pype/plugins/resolve/publish/collect_clips.py
Normal file
162
pype/plugins/resolve/publish/collect_clips.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import os
|
||||
from pyblish import api
|
||||
from pype.hosts import resolve
|
||||
import json
|
||||
|
||||
|
||||
class CollectClips(api.ContextPlugin):
|
||||
"""Collect all Track items selection."""
|
||||
|
||||
order = api.CollectorOrder + 0.01
|
||||
label = "Collect Clips"
|
||||
hosts = ["resolve"]
|
||||
|
||||
def process(self, context):
|
||||
# create asset_names conversion table
|
||||
if not context.data.get("assetsShared"):
|
||||
self.log.debug("Created `assetsShared` in context")
|
||||
context.data["assetsShared"] = dict()
|
||||
|
||||
projectdata = context.data["projectEntity"]["data"]
|
||||
selection = resolve.get_current_track_items(
|
||||
filter=True, selecting_color="Pink")
|
||||
|
||||
for clip_data in selection:
|
||||
data = dict()
|
||||
|
||||
# get basic objects form data
|
||||
project = clip_data["project"]
|
||||
sequence = clip_data["sequence"]
|
||||
clip = clip_data["clip"]
|
||||
|
||||
# sequence attrs
|
||||
sq_frame_start = sequence.GetStartFrame()
|
||||
self.log.debug(f"sq_frame_start: {sq_frame_start}")
|
||||
|
||||
sq_markers = sequence.GetMarkers()
|
||||
|
||||
# get details of objects
|
||||
clip_item = clip["item"]
|
||||
track = clip_data["track"]
|
||||
|
||||
mp = project.GetMediaPool()
|
||||
|
||||
# get clip attributes
|
||||
clip_metadata = resolve.get_pype_clip_metadata(clip_item)
|
||||
clip_metadata = json.loads(clip_metadata)
|
||||
self.log.debug(f"clip_metadata: {clip_metadata}")
|
||||
|
||||
compound_source_prop = clip_metadata["sourceProperties"]
|
||||
self.log.debug(f"compound_source_prop: {compound_source_prop}")
|
||||
|
||||
asset_name = clip_item.GetName()
|
||||
mp_item = clip_item.GetMediaPoolItem()
|
||||
mp_prop = mp_item.GetClipProperty()
|
||||
source_first = int(compound_source_prop["Start"])
|
||||
source_last = int(compound_source_prop["End"])
|
||||
source_duration = compound_source_prop["Frames"]
|
||||
fps = float(mp_prop["FPS"])
|
||||
self.log.debug(f"source_first: {source_first}")
|
||||
self.log.debug(f"source_last: {source_last}")
|
||||
self.log.debug(f"source_duration: {source_duration}")
|
||||
self.log.debug(f"fps: {fps}")
|
||||
|
||||
source_path = os.path.normpath(
|
||||
compound_source_prop["File Path"])
|
||||
source_name = compound_source_prop["File Name"]
|
||||
source_id = clip_metadata["sourceId"]
|
||||
self.log.debug(f"source_path: {source_path}")
|
||||
self.log.debug(f"source_name: {source_name}")
|
||||
self.log.debug(f"source_id: {source_id}")
|
||||
|
||||
clip_left_offset = int(clip_item.GetLeftOffset())
|
||||
clip_right_offset = int(clip_item.GetRightOffset())
|
||||
self.log.debug(f"clip_left_offset: {clip_left_offset}")
|
||||
self.log.debug(f"clip_right_offset: {clip_right_offset}")
|
||||
|
||||
# source in/out
|
||||
source_in = int(source_first + clip_left_offset)
|
||||
source_out = int(source_first + clip_right_offset)
|
||||
self.log.debug(f"source_in: {source_in}")
|
||||
self.log.debug(f"source_out: {source_out}")
|
||||
|
||||
clip_in = int(clip_item.GetStart() - sq_frame_start)
|
||||
clip_out = int(clip_item.GetEnd() - sq_frame_start)
|
||||
clip_duration = int(clip_item.GetDuration())
|
||||
self.log.debug(f"clip_in: {clip_in}")
|
||||
self.log.debug(f"clip_out: {clip_out}")
|
||||
self.log.debug(f"clip_duration: {clip_duration}")
|
||||
|
||||
is_sequence = False
|
||||
|
||||
self.log.debug(
|
||||
"__ assets_shared: {}".format(
|
||||
context.data["assetsShared"]))
|
||||
|
||||
# Check for clips with the same range
|
||||
# this is for testing if any vertically neighbouring
|
||||
# clips has been already processed
|
||||
clip_matching_with_range = next(
|
||||
(k for k, v in context.data["assetsShared"].items()
|
||||
if (v.get("_clipIn", 0) == clip_in)
|
||||
and (v.get("_clipOut", 0) == clip_out)
|
||||
), False)
|
||||
|
||||
# check if clip name is the same in matched
|
||||
# vertically neighbouring clip
|
||||
# if it is then it is correct and resent variable to False
|
||||
# not to be rised wrong name exception
|
||||
if asset_name in str(clip_matching_with_range):
|
||||
clip_matching_with_range = False
|
||||
|
||||
# rise wrong name exception if found one
|
||||
assert (not clip_matching_with_range), (
|
||||
"matching clip: {asset}"
|
||||
" timeline range ({clip_in}:{clip_out})"
|
||||
" conflicting with {clip_matching_with_range}"
|
||||
" >> rename any of clips to be the same as the other <<"
|
||||
).format(
|
||||
**locals())
|
||||
|
||||
if ("[" in source_name) and ("]" in source_name):
|
||||
is_sequence = True
|
||||
|
||||
data.update({
|
||||
"name": "_".join([
|
||||
track["name"], asset_name, source_name]),
|
||||
"item": clip_item,
|
||||
"source": mp_item,
|
||||
# "timecodeStart": str(source.timecodeStart()),
|
||||
"timelineStart": sq_frame_start,
|
||||
"sourcePath": source_path,
|
||||
"sourceFileHead": source_name,
|
||||
"isSequence": is_sequence,
|
||||
"track": track["name"],
|
||||
"trackIndex": track["index"],
|
||||
"sourceFirst": source_first,
|
||||
|
||||
"sourceIn": source_in,
|
||||
"sourceOut": source_out,
|
||||
"mediaDuration": source_duration,
|
||||
"clipIn": clip_in,
|
||||
"clipOut": clip_out,
|
||||
"clipDuration": clip_duration,
|
||||
"asset": asset_name,
|
||||
"subset": "plateMain",
|
||||
"family": "clip",
|
||||
"families": [],
|
||||
"handleStart": projectdata.get("handleStart", 0),
|
||||
"handleEnd": projectdata.get("handleEnd", 0)})
|
||||
|
||||
instance = context.create_instance(**data)
|
||||
|
||||
self.log.info("Created instance: {}".format(instance))
|
||||
self.log.info("Created instance.data: {}".format(instance.data))
|
||||
|
||||
context.data["assetsShared"][asset_name] = {
|
||||
"_clipIn": clip_in,
|
||||
"_clipOut": clip_out
|
||||
}
|
||||
self.log.info(
|
||||
"context.data[\"assetsShared\"]: {}".format(
|
||||
context.data["assetsShared"]))
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import pyblish.api
|
||||
from pype.hosts.resolve.utils import get_resolve_module
|
||||
|
||||
|
||||
class CollectProject(pyblish.api.ContextPlugin):
|
||||
"""Collect Project object"""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.1
|
||||
label = "Collect Project"
|
||||
hosts = ["resolve"]
|
||||
|
||||
def process(self, context):
|
||||
resolve = get_resolve_module()
|
||||
PM = resolve.GetProjectManager()
|
||||
P = PM.GetCurrentProject()
|
||||
|
||||
self.log.info(P.GetName())
|
||||
29
pype/plugins/resolve/publish/collect_project.py
Normal file
29
pype/plugins/resolve/publish/collect_project.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
from pype.hosts.resolve.utils import get_resolve_module
|
||||
|
||||
|
||||
class CollectProject(pyblish.api.ContextPlugin):
|
||||
"""Collect Project object"""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.1
|
||||
label = "Collect Project"
|
||||
hosts = ["resolve"]
|
||||
|
||||
def process(self, context):
|
||||
exported_projet_ext = ".drp"
|
||||
current_dir = os.getenv("AVALON_WORKDIR")
|
||||
resolve = get_resolve_module()
|
||||
PM = resolve.GetProjectManager()
|
||||
P = PM.GetCurrentProject()
|
||||
name = P.GetName()
|
||||
|
||||
fname = name + exported_projet_ext
|
||||
current_file = os.path.join(current_dir, fname)
|
||||
normalised = os.path.normpath(current_file)
|
||||
|
||||
context.data["project"] = P
|
||||
context.data["currentFile"] = normalised
|
||||
|
||||
self.log.info(name)
|
||||
self.log.debug(normalised)
|
||||
|
|
@ -30,12 +30,15 @@ class TrayManager:
|
|||
os.path.join(CURRENT_DIR, "modules_imports.json")
|
||||
)
|
||||
presets = config.get_presets(first_run=True)
|
||||
menu_items = presets["tray"]["menu_items"]
|
||||
try:
|
||||
self.modules_usage = presets["tray"]["menu_items"]["item_usage"]
|
||||
self.modules_usage = menu_items["item_usage"]
|
||||
except Exception:
|
||||
self.modules_usage = {}
|
||||
self.log.critical("Couldn't find modules usage data.")
|
||||
|
||||
self.module_attributes = menu_items.get("attributes") or {}
|
||||
|
||||
self.icon_run = QtGui.QIcon(
|
||||
resources.get_resource("icons", "circle_green.png")
|
||||
)
|
||||
|
|
@ -71,19 +74,20 @@ class TrayManager:
|
|||
if item_usage is None:
|
||||
item_usage = self.modules_usage.get(import_path, True)
|
||||
|
||||
if item_usage:
|
||||
_attributes = attributes.get(title)
|
||||
if _attributes is None:
|
||||
_attributes = attributes.get(import_path)
|
||||
|
||||
if _attributes:
|
||||
item["attributes"] = _attributes
|
||||
|
||||
items.append(item)
|
||||
else:
|
||||
if not item_usage:
|
||||
if not title:
|
||||
title = import_path
|
||||
self.log.info("{} - Module ignored".format(title))
|
||||
continue
|
||||
|
||||
_attributes = self.module_attributes.get(title)
|
||||
if _attributes is None:
|
||||
_attributes = self.module_attributes.get(import_path)
|
||||
|
||||
if _attributes:
|
||||
item["attributes"] = _attributes
|
||||
|
||||
items.append(item)
|
||||
|
||||
if items:
|
||||
self.process_items(items, self.tray_widget.menu)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue