mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #2495 from pypeclub/feature/OP-1536_Flame-Create-publishable-clips
Flame - create publishable clips
This commit is contained in:
commit
f5c0c64533
18 changed files with 1706 additions and 302 deletions
|
|
@ -1,107 +1,5 @@
|
|||
from .api.utils import (
|
||||
setup
|
||||
)
|
||||
|
||||
from .api.pipeline import (
|
||||
install,
|
||||
uninstall,
|
||||
ls,
|
||||
containerise,
|
||||
update_container,
|
||||
maintained_selection,
|
||||
remove_instance,
|
||||
list_instances,
|
||||
imprint
|
||||
)
|
||||
|
||||
from .api.lib import (
|
||||
FlameAppFramework,
|
||||
maintain_current_timeline,
|
||||
get_project_manager,
|
||||
get_current_project,
|
||||
get_current_sequence,
|
||||
create_bin,
|
||||
)
|
||||
|
||||
from .api.menu import (
|
||||
FlameMenuProjectConnect,
|
||||
FlameMenuTimeline
|
||||
)
|
||||
|
||||
from .api.workio import (
|
||||
open_file,
|
||||
save_file,
|
||||
current_file,
|
||||
has_unsaved_changes,
|
||||
file_extensions,
|
||||
work_root
|
||||
)
|
||||
|
||||
import os
|
||||
|
||||
HOST_DIR = os.path.dirname(
|
||||
os.path.abspath(__file__)
|
||||
)
|
||||
API_DIR = os.path.join(HOST_DIR, "api")
|
||||
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
||||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
|
||||
|
||||
app_framework = None
|
||||
apps = []
|
||||
selection = None
|
||||
|
||||
|
||||
__all__ = [
|
||||
"HOST_DIR",
|
||||
"API_DIR",
|
||||
"PLUGINS_DIR",
|
||||
"PUBLISH_PATH",
|
||||
"LOAD_PATH",
|
||||
"CREATE_PATH",
|
||||
"INVENTORY_PATH",
|
||||
"INVENTORY_PATH",
|
||||
|
||||
"app_framework",
|
||||
"apps",
|
||||
"selection",
|
||||
|
||||
# pipeline
|
||||
"install",
|
||||
"uninstall",
|
||||
"ls",
|
||||
"containerise",
|
||||
"update_container",
|
||||
"reload_pipeline",
|
||||
"maintained_selection",
|
||||
"remove_instance",
|
||||
"list_instances",
|
||||
"imprint",
|
||||
|
||||
# utils
|
||||
"setup",
|
||||
|
||||
# lib
|
||||
"FlameAppFramework",
|
||||
"maintain_current_timeline",
|
||||
"get_project_manager",
|
||||
"get_current_project",
|
||||
"get_current_sequence",
|
||||
"create_bin",
|
||||
|
||||
# menu
|
||||
"FlameMenuProjectConnect",
|
||||
"FlameMenuTimeline",
|
||||
|
||||
# plugin
|
||||
|
||||
# workio
|
||||
"open_file",
|
||||
"save_file",
|
||||
"current_file",
|
||||
"has_unsaved_changes",
|
||||
"file_extensions",
|
||||
"work_root"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,115 @@
|
|||
"""
|
||||
OpenPype Autodesk Flame api
|
||||
"""
|
||||
from .constants import (
|
||||
COLOR_MAP,
|
||||
MARKER_NAME,
|
||||
MARKER_COLOR,
|
||||
MARKER_DURATION,
|
||||
MARKER_PUBLISH_DEFAULT
|
||||
)
|
||||
from .lib import (
|
||||
CTX,
|
||||
FlameAppFramework,
|
||||
get_project_manager,
|
||||
get_current_project,
|
||||
get_current_sequence,
|
||||
create_bin,
|
||||
create_segment_data_marker,
|
||||
get_segment_data_marker,
|
||||
set_segment_data_marker,
|
||||
set_publish_attribute,
|
||||
get_publish_attribute,
|
||||
get_sequence_segments,
|
||||
maintained_segment_selection,
|
||||
reset_segment_selection,
|
||||
get_segment_attributes
|
||||
)
|
||||
from .utils import (
|
||||
setup
|
||||
)
|
||||
from .pipeline import (
|
||||
install,
|
||||
uninstall,
|
||||
ls,
|
||||
containerise,
|
||||
update_container,
|
||||
remove_instance,
|
||||
list_instances,
|
||||
imprint,
|
||||
maintained_selection
|
||||
)
|
||||
from .menu import (
|
||||
FlameMenuProjectConnect,
|
||||
FlameMenuTimeline
|
||||
)
|
||||
from .plugin import (
|
||||
Creator,
|
||||
PublishableClip
|
||||
)
|
||||
from .workio import (
|
||||
open_file,
|
||||
save_file,
|
||||
current_file,
|
||||
has_unsaved_changes,
|
||||
file_extensions,
|
||||
work_root
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# constants
|
||||
"COLOR_MAP",
|
||||
"MARKER_NAME",
|
||||
"MARKER_COLOR",
|
||||
"MARKER_DURATION",
|
||||
"MARKER_PUBLISH_DEFAULT",
|
||||
|
||||
# lib
|
||||
"CTX",
|
||||
"FlameAppFramework",
|
||||
"get_project_manager",
|
||||
"get_current_project",
|
||||
"get_current_sequence",
|
||||
"create_bin",
|
||||
"create_segment_data_marker",
|
||||
"get_segment_data_marker",
|
||||
"set_segment_data_marker",
|
||||
"set_publish_attribute",
|
||||
"get_publish_attribute",
|
||||
"get_sequence_segments",
|
||||
"maintained_segment_selection",
|
||||
"reset_segment_selection",
|
||||
"get_segment_attributes",
|
||||
|
||||
# pipeline
|
||||
"install",
|
||||
"uninstall",
|
||||
"ls",
|
||||
"containerise",
|
||||
"update_container",
|
||||
"reload_pipeline",
|
||||
"maintained_selection",
|
||||
"remove_instance",
|
||||
"list_instances",
|
||||
"imprint",
|
||||
"maintained_selection",
|
||||
|
||||
# utils
|
||||
"setup",
|
||||
|
||||
# menu
|
||||
"FlameMenuProjectConnect",
|
||||
"FlameMenuTimeline",
|
||||
|
||||
# plugin
|
||||
"Creator",
|
||||
"PublishableClip",
|
||||
|
||||
# workio
|
||||
"open_file",
|
||||
"save_file",
|
||||
"current_file",
|
||||
"has_unsaved_changes",
|
||||
"file_extensions",
|
||||
"work_root"
|
||||
]
|
||||
|
|
|
|||
24
openpype/hosts/flame/api/constants.py
Normal file
24
openpype/hosts/flame/api/constants.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
"""
|
||||
OpenPype Flame api constances
|
||||
"""
|
||||
# OpenPype marker workflow variables
|
||||
MARKER_NAME = "OpenPypeData"
|
||||
MARKER_DURATION = 0
|
||||
MARKER_COLOR = "cyan"
|
||||
MARKER_PUBLISH_DEFAULT = False
|
||||
|
||||
# OpenPype color definitions
|
||||
COLOR_MAP = {
|
||||
"red": (1.0, 0.0, 0.0),
|
||||
"orange": (1.0, 0.5, 0.0),
|
||||
"yellow": (1.0, 1.0, 0.0),
|
||||
"pink": (1.0, 0.5, 1.0),
|
||||
"white": (1.0, 1.0, 1.0),
|
||||
"green": (0.0, 1.0, 0.0),
|
||||
"cyan": (0.0, 1.0, 1.0),
|
||||
"blue": (0.0, 0.0, 1.0),
|
||||
"purple": (0.5, 0.0, 0.5),
|
||||
"magenta": (0.5, 0.0, 1.0),
|
||||
"black": (0.0, 0.0, 0.0)
|
||||
}
|
||||
|
|
@ -1,12 +1,27 @@
|
|||
import sys
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import pickle
|
||||
import contextlib
|
||||
from pprint import pformat
|
||||
|
||||
from .constants import (
|
||||
MARKER_COLOR,
|
||||
MARKER_DURATION,
|
||||
MARKER_NAME,
|
||||
COLOR_MAP,
|
||||
MARKER_PUBLISH_DEFAULT
|
||||
)
|
||||
from openpype.api import Logger
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
class CTX:
|
||||
# singleton used for passing data between api modules
|
||||
app_framework = None
|
||||
flame_apps = []
|
||||
selection = None
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
@ -115,10 +130,13 @@ class FlameAppFramework(object):
|
|||
)
|
||||
|
||||
self.log.info("[{}] waking up".format(self.__class__.__name__))
|
||||
self.load_prefs()
|
||||
|
||||
try:
|
||||
self.load_prefs()
|
||||
except RuntimeError:
|
||||
self.save_prefs()
|
||||
|
||||
# menu auto-refresh defaults
|
||||
|
||||
if not self.prefs_global.get("menu_auto_refresh"):
|
||||
self.prefs_global["menu_auto_refresh"] = {
|
||||
"media_panel": True,
|
||||
|
|
@ -207,40 +225,6 @@ class FlameAppFramework(object):
|
|||
return True
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def maintain_current_timeline(to_timeline, from_timeline=None):
|
||||
"""Maintain current timeline selection during context
|
||||
|
||||
Attributes:
|
||||
from_timeline (resolve.Timeline)[optional]:
|
||||
Example:
|
||||
>>> print(from_timeline.GetName())
|
||||
timeline1
|
||||
>>> print(to_timeline.GetName())
|
||||
timeline2
|
||||
|
||||
>>> with maintain_current_timeline(to_timeline):
|
||||
... print(get_current_sequence().GetName())
|
||||
timeline2
|
||||
|
||||
>>> print(get_current_sequence().GetName())
|
||||
timeline1
|
||||
"""
|
||||
# todo: this is still Resolve's implementation
|
||||
project = get_current_project()
|
||||
working_timeline = from_timeline or project.GetCurrentTimeline()
|
||||
|
||||
# swith to the input timeline
|
||||
project.SetCurrentTimeline(to_timeline)
|
||||
|
||||
try:
|
||||
# do a work
|
||||
yield
|
||||
finally:
|
||||
# put the original working timeline to context
|
||||
project.SetCurrentTimeline(working_timeline)
|
||||
|
||||
|
||||
def get_project_manager():
|
||||
# TODO: get_project_manager
|
||||
return
|
||||
|
|
@ -252,8 +236,8 @@ def get_media_storage():
|
|||
|
||||
|
||||
def get_current_project():
|
||||
# TODO: get_current_project
|
||||
return
|
||||
import flame
|
||||
return flame.project.current_project
|
||||
|
||||
|
||||
def get_current_sequence(selection):
|
||||
|
|
@ -334,3 +318,244 @@ def get_metadata(project_name, _log=None):
|
|||
|
||||
policy_wiretap = GetProjectColorPolicy(_log=_log)
|
||||
return policy_wiretap.process(project_name)
|
||||
|
||||
|
||||
def get_segment_data_marker(segment, with_marker=None):
|
||||
"""
|
||||
Get openpype track item tag created by creator or loader plugin.
|
||||
|
||||
Attributes:
|
||||
segment (flame.PySegment): flame api object
|
||||
with_marker (bool)[optional]: if true it will return also marker object
|
||||
|
||||
Returns:
|
||||
dict: openpype tag data
|
||||
|
||||
Returns(with_marker=True):
|
||||
flame.PyMarker, dict
|
||||
"""
|
||||
for marker in segment.markers:
|
||||
comment = marker.comment.get_value()
|
||||
color = marker.colour.get_value()
|
||||
name = marker.name.get_value()
|
||||
|
||||
if (name == MARKER_NAME) and (
|
||||
color == COLOR_MAP[MARKER_COLOR]):
|
||||
if not with_marker:
|
||||
return json.loads(comment)
|
||||
else:
|
||||
return marker, json.loads(comment)
|
||||
|
||||
|
||||
def set_segment_data_marker(segment, data=None):
|
||||
"""
|
||||
Set openpype track item tag to input segment.
|
||||
|
||||
Attributes:
|
||||
segment (flame.PySegment): flame api object
|
||||
|
||||
Returns:
|
||||
dict: json loaded data
|
||||
"""
|
||||
data = data or dict()
|
||||
|
||||
marker_data = get_segment_data_marker(segment, True)
|
||||
|
||||
if marker_data:
|
||||
# get available openpype tag if any
|
||||
marker, tag_data = marker_data
|
||||
# update tag data with new data
|
||||
tag_data.update(data)
|
||||
# update marker with tag data
|
||||
marker.comment = json.dumps(tag_data)
|
||||
else:
|
||||
# update tag data with new data
|
||||
marker = create_segment_data_marker(segment)
|
||||
# add tag data to marker's comment
|
||||
marker.comment = json.dumps(data)
|
||||
|
||||
|
||||
def set_publish_attribute(segment, value):
|
||||
""" Set Publish attribute in input Tag object
|
||||
|
||||
Attribute:
|
||||
segment (flame.PySegment)): flame api object
|
||||
value (bool): True or False
|
||||
"""
|
||||
tag_data = get_segment_data_marker(segment)
|
||||
tag_data["publish"] = value
|
||||
|
||||
# set data to the publish attribute
|
||||
set_segment_data_marker(segment, tag_data)
|
||||
|
||||
|
||||
def get_publish_attribute(segment):
|
||||
""" Get Publish attribute from input Tag object
|
||||
|
||||
Attribute:
|
||||
segment (flame.PySegment)): flame api object
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
tag_data = get_segment_data_marker(segment)
|
||||
|
||||
if not tag_data:
|
||||
set_publish_attribute(segment, MARKER_PUBLISH_DEFAULT)
|
||||
return MARKER_PUBLISH_DEFAULT
|
||||
|
||||
return tag_data["publish"]
|
||||
|
||||
|
||||
def create_segment_data_marker(segment):
|
||||
""" Create openpype marker on a segment.
|
||||
|
||||
Attributes:
|
||||
segment (flame.PySegment): flame api object
|
||||
|
||||
Returns:
|
||||
flame.PyMarker: flame api object
|
||||
"""
|
||||
# get duration of segment
|
||||
duration = segment.record_duration.relative_frame
|
||||
# calculate start frame of the new marker
|
||||
start_frame = int(segment.record_in.relative_frame) + int(duration / 2)
|
||||
# create marker
|
||||
marker = segment.create_marker(start_frame)
|
||||
# set marker name
|
||||
marker.name = MARKER_NAME
|
||||
# set duration
|
||||
marker.duration = MARKER_DURATION
|
||||
# set colour
|
||||
marker.colour = COLOR_MAP[MARKER_COLOR] # Red
|
||||
|
||||
return marker
|
||||
|
||||
|
||||
def get_sequence_segments(sequence, selected=False):
|
||||
segments = []
|
||||
# loop versions in sequence
|
||||
for ver in sequence.versions:
|
||||
# loop track in versions
|
||||
for track in ver.tracks:
|
||||
# ignore all empty tracks and hidden too
|
||||
if len(track.segments) == 0 and track.hidden:
|
||||
continue
|
||||
# loop all segment in remaining tracks
|
||||
for segment in track.segments:
|
||||
if segment.name.get_value() == "":
|
||||
continue
|
||||
if (
|
||||
selected is True
|
||||
and segment.selected.get_value() is not True
|
||||
):
|
||||
continue
|
||||
# add it to original selection
|
||||
segments.append(segment)
|
||||
return segments
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def maintained_segment_selection(sequence):
|
||||
"""Maintain selection during context
|
||||
|
||||
Attributes:
|
||||
sequence (flame.PySequence): python api object
|
||||
|
||||
Yield:
|
||||
list of flame.PySegment
|
||||
|
||||
Example:
|
||||
>>> with maintained_segment_selection(sequence) as selected_segments:
|
||||
... for segment in selected_segments:
|
||||
... segment.selected = False
|
||||
>>> print(segment.selected)
|
||||
True
|
||||
"""
|
||||
selected_segments = get_sequence_segments(sequence, True)
|
||||
try:
|
||||
# do the operation on selected segments
|
||||
yield selected_segments
|
||||
finally:
|
||||
# reset all selected clips
|
||||
reset_segment_selection(sequence)
|
||||
# select only original selection of segments
|
||||
for segment in selected_segments:
|
||||
segment.selected = True
|
||||
|
||||
|
||||
def reset_segment_selection(sequence):
|
||||
"""Deselect all selected nodes
|
||||
"""
|
||||
for ver in sequence.versions:
|
||||
for track in ver.tracks:
|
||||
if len(track.segments) == 0 and track.hidden:
|
||||
continue
|
||||
for segment in track.segments:
|
||||
segment.selected = False
|
||||
|
||||
|
||||
def _get_shot_tokens_values(clip, tokens):
|
||||
old_value = None
|
||||
output = {}
|
||||
|
||||
if not clip.shot_name:
|
||||
return output
|
||||
|
||||
old_value = clip.shot_name.get_value()
|
||||
|
||||
for token in tokens:
|
||||
clip.shot_name.set_value(token)
|
||||
_key = str(re.sub("[<>]", "", token)).replace(" ", "_")
|
||||
|
||||
try:
|
||||
output[_key] = int(clip.shot_name.get_value())
|
||||
except ValueError:
|
||||
output[_key] = clip.shot_name.get_value()
|
||||
|
||||
clip.shot_name.set_value(old_value)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def get_segment_attributes(segment):
|
||||
if str(segment.name)[1:-1] == "":
|
||||
return None
|
||||
|
||||
# Add timeline segment to tree
|
||||
clip_data = {
|
||||
"segment_name": segment.name.get_value(),
|
||||
"segment_comment": segment.comment.get_value(),
|
||||
"tape_name": segment.tape_name,
|
||||
"source_name": segment.source_name,
|
||||
"fpath": segment.file_path,
|
||||
"PySegment": segment
|
||||
}
|
||||
|
||||
# add all available shot tokens
|
||||
shot_tokens = _get_shot_tokens_values(segment, [
|
||||
"<colour space>", "<width>", "<height>", "<depth>", "<segment>",
|
||||
"<track>", "<track name>"
|
||||
])
|
||||
clip_data.update(shot_tokens)
|
||||
|
||||
# populate shot source metadata
|
||||
segment_attrs = [
|
||||
"record_duration", "record_in", "record_out",
|
||||
"source_duration", "source_in", "source_out"
|
||||
]
|
||||
segment_attrs_data = {}
|
||||
for attr_name in segment_attrs:
|
||||
if not hasattr(segment, attr_name):
|
||||
continue
|
||||
attr = getattr(segment, attr_name)
|
||||
segment_attrs_data[attr] = str(attr).replace("+", ":")
|
||||
|
||||
if attr in ["record_in", "record_out"]:
|
||||
clip_data[attr_name] = attr.relative_frame
|
||||
else:
|
||||
clip_data[attr_name] = attr.frame
|
||||
|
||||
clip_data["segment_timecodes"] = segment_attrs_data
|
||||
|
||||
return clip_data
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
from Qt import QtWidgets
|
||||
from copy import deepcopy
|
||||
|
||||
from pprint import pformat
|
||||
from openpype.tools.utils.host_tools import HostToolsHelper
|
||||
|
||||
menu_group_name = 'OpenPype'
|
||||
|
|
@ -26,9 +26,13 @@ default_flame_export_presets = {
|
|||
|
||||
|
||||
def callback_selection(selection, function):
|
||||
import openpype.hosts.flame as opflame
|
||||
opflame.selection = selection
|
||||
print(opflame.selection)
|
||||
import openpype.hosts.flame.api as opfapi
|
||||
opfapi.CTX.selection = selection
|
||||
print("Hook Selection: \n\t{}".format(
|
||||
pformat({
|
||||
index: (type(item), item.name)
|
||||
for index, item in enumerate(opfapi.CTX.selection)})
|
||||
))
|
||||
function()
|
||||
|
||||
|
||||
|
|
@ -109,16 +113,6 @@ class FlameMenuProjectConnect(_FlameMenuApp):
|
|||
"name": "Workfiles ...",
|
||||
"execute": lambda x: self.tools_helper.show_workfiles()
|
||||
})
|
||||
menu['actions'].append({
|
||||
"name": "Create ...",
|
||||
"execute": lambda x: callback_selection(
|
||||
x, self.tools_helper.show_creator)
|
||||
})
|
||||
menu['actions'].append({
|
||||
"name": "Publish ...",
|
||||
"execute": lambda x: callback_selection(
|
||||
x, self.tools_helper.show_publish)
|
||||
})
|
||||
menu['actions'].append({
|
||||
"name": "Load ...",
|
||||
"execute": lambda x: self.tools_helper.show_loader()
|
||||
|
|
|
|||
|
|
@ -1,25 +1,33 @@
|
|||
"""
|
||||
Basic avalon integration
|
||||
"""
|
||||
import os
|
||||
import contextlib
|
||||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
from openpype.api import Logger
|
||||
from .lib import (
|
||||
set_segment_data_marker,
|
||||
set_publish_attribute,
|
||||
maintained_segment_selection,
|
||||
get_current_sequence,
|
||||
reset_segment_selection
|
||||
)
|
||||
from .. import HOST_DIR
|
||||
|
||||
API_DIR = os.path.join(HOST_DIR, "api")
|
||||
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
||||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
|
||||
|
||||
AVALON_CONTAINERS = "AVALON_CONTAINERS"
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
def install():
|
||||
from .. import (
|
||||
PUBLISH_PATH,
|
||||
LOAD_PATH,
|
||||
CREATE_PATH,
|
||||
INVENTORY_PATH
|
||||
)
|
||||
# TODO: install
|
||||
|
||||
# Disable all families except for the ones we explicitly want to see
|
||||
family_states = [
|
||||
"imagesequence",
|
||||
|
|
@ -32,33 +40,24 @@ def install():
|
|||
avalon.data["familiesStateDefault"] = False
|
||||
avalon.data["familiesStateToggled"] = family_states
|
||||
|
||||
log.info("openpype.hosts.flame installed")
|
||||
|
||||
pyblish.register_host("flame")
|
||||
pyblish.register_plugin_path(PUBLISH_PATH)
|
||||
log.info("Registering Flame plug-ins..")
|
||||
|
||||
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
|
||||
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
|
||||
log.info("OpenPype Flame plug-ins registred ...")
|
||||
|
||||
# register callback for switching publishable
|
||||
pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||
|
||||
log.info("OpenPype Flame host installed ...")
|
||||
|
||||
def uninstall():
|
||||
from .. import (
|
||||
PUBLISH_PATH,
|
||||
LOAD_PATH,
|
||||
CREATE_PATH,
|
||||
INVENTORY_PATH
|
||||
)
|
||||
|
||||
# TODO: uninstall
|
||||
pyblish.deregister_host("flame")
|
||||
pyblish.deregister_plugin_path(PUBLISH_PATH)
|
||||
log.info("Deregistering DaVinci Resovle plug-ins..")
|
||||
|
||||
log.info("Deregistering Flame plug-ins..")
|
||||
pyblish.deregister_plugin_path(PUBLISH_PATH)
|
||||
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
|
||||
avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
|
||||
|
|
@ -66,6 +65,8 @@ def uninstall():
|
|||
# register callback for switching publishable
|
||||
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||
|
||||
log.info("OpenPype Flame host uninstalled ...")
|
||||
|
||||
|
||||
def containerise(tl_segment,
|
||||
name,
|
||||
|
|
@ -97,32 +98,6 @@ def update_container(tl_segment, data=None):
|
|||
# TODO: update_container
|
||||
pass
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def maintained_selection():
|
||||
"""Maintain selection during context
|
||||
|
||||
Example:
|
||||
>>> with maintained_selection():
|
||||
... node['selected'].setValue(True)
|
||||
>>> print(node['selected'].value())
|
||||
False
|
||||
"""
|
||||
# TODO: maintained_selection + remove undo steps
|
||||
|
||||
try:
|
||||
# do the operation
|
||||
yield
|
||||
finally:
|
||||
pass
|
||||
|
||||
|
||||
def reset_selection():
|
||||
"""Deselect all selected nodes
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def on_pyblish_instance_toggled(instance, old_value, new_value):
|
||||
"""Toggle node passthrough states on instance toggles."""
|
||||
|
||||
|
|
@ -150,6 +125,46 @@ def list_instances():
|
|||
pass
|
||||
|
||||
|
||||
def imprint(item, data=None):
|
||||
# TODO: imprint
|
||||
pass
|
||||
def imprint(segment, data=None):
|
||||
"""
|
||||
Adding openpype data to Flame timeline segment.
|
||||
|
||||
Also including publish attribute into tag.
|
||||
|
||||
Arguments:
|
||||
segment (flame.PySegment)): flame api object
|
||||
data (dict): Any data which needst to be imprinted
|
||||
|
||||
Examples:
|
||||
data = {
|
||||
'asset': 'sq020sh0280',
|
||||
'family': 'render',
|
||||
'subset': 'subsetMain'
|
||||
}
|
||||
"""
|
||||
data = data or {}
|
||||
|
||||
set_segment_data_marker(segment, data)
|
||||
|
||||
# add publish attribute
|
||||
set_publish_attribute(segment, True)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def maintained_selection():
|
||||
import flame
|
||||
from .lib import CTX
|
||||
|
||||
# check if segment is selected
|
||||
if isinstance(CTX.selection[0], flame.PySegment):
|
||||
sequence = get_current_sequence(CTX.selection)
|
||||
|
||||
try:
|
||||
with maintained_segment_selection(sequence) as selected:
|
||||
yield
|
||||
finally:
|
||||
# reset all selected clips
|
||||
reset_segment_selection(sequence)
|
||||
# select only original selection of segments
|
||||
for segment in selected:
|
||||
segment.selected = True
|
||||
|
|
|
|||
|
|
@ -1,3 +1,646 @@
|
|||
# Creator plugin functions
|
||||
import re
|
||||
from Qt import QtWidgets, QtCore
|
||||
import openpype.api as openpype
|
||||
from openpype import style
|
||||
from . import (
|
||||
lib as flib,
|
||||
pipeline as fpipeline,
|
||||
constants
|
||||
)
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
log = openpype.Logger.get_logger(__name__)
|
||||
|
||||
|
||||
class CreatorWidget(QtWidgets.QDialog):
|
||||
|
||||
# output items
|
||||
items = dict()
|
||||
_results_back = None
|
||||
|
||||
def __init__(self, name, info, ui_inputs, 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")
|
||||
self.resize(500, 700)
|
||||
|
||||
# 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))
|
||||
|
||||
# main dynamic layout
|
||||
self.scroll_area = QtWidgets.QScrollArea(self, widgetResizable=True)
|
||||
self.scroll_area.setVerticalScrollBarPolicy(
|
||||
QtCore.Qt.ScrollBarAsNeeded)
|
||||
self.scroll_area.setVerticalScrollBarPolicy(
|
||||
QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.scroll_area.setHorizontalScrollBarPolicy(
|
||||
QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
|
||||
self.content_widget.append(self.scroll_area)
|
||||
|
||||
scroll_widget = QtWidgets.QWidget(self)
|
||||
in_scroll_area = QtWidgets.QVBoxLayout(scroll_widget)
|
||||
self.content_layout = [in_scroll_area]
|
||||
|
||||
# add preset data into input widget layout
|
||||
self.items = self.populate_widgets(ui_inputs)
|
||||
self.scroll_area.setWidget(scroll_widget)
|
||||
|
||||
# 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)
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
@classmethod
|
||||
def set_results_back(cls, value):
|
||||
cls._results_back = value
|
||||
|
||||
@classmethod
|
||||
def get_results_back(cls):
|
||||
return cls._results_back
|
||||
|
||||
def _on_ok_clicked(self):
|
||||
log.debug("ok is clicked: {}".format(self.items))
|
||||
results_back = self._values(self.items)
|
||||
self.set_results_back(results_back)
|
||||
self.close()
|
||||
|
||||
def _on_cancel_clicked(self):
|
||||
self.set_results_back(None)
|
||||
self.close()
|
||||
|
||||
def showEvent(self, event):
|
||||
self.set_results_back(None)
|
||||
super(CreatorWidget, self).showEvent(event)
|
||||
|
||||
def _values(self, data, new_data=None):
|
||||
new_data = new_data or dict()
|
||||
for k, v in data.items():
|
||||
new_data[k] = {
|
||||
"target": None,
|
||||
"value": None
|
||||
}
|
||||
if v["type"] == "dict":
|
||||
new_data[k]["target"] = v["target"]
|
||||
new_data[k]["value"] = self._values(v["value"])
|
||||
if v["type"] == "section":
|
||||
new_data.pop(k)
|
||||
new_data = self._values(v["value"], new_data)
|
||||
elif getattr(v["value"], "currentText", None):
|
||||
new_data[k]["target"] = v["target"]
|
||||
new_data[k]["value"] = v["value"].currentText()
|
||||
elif getattr(v["value"], "isChecked", None):
|
||||
new_data[k]["target"] = v["target"]
|
||||
new_data[k]["value"] = v["value"].isChecked()
|
||||
elif getattr(v["value"], "value", None):
|
||||
new_data[k]["target"] = v["target"]
|
||||
new_data[k]["value"] = v["value"].value()
|
||||
elif getattr(v["value"], "text", None):
|
||||
new_data[k]["target"] = v["target"]
|
||||
new_data[k]["value"] = v["value"].text()
|
||||
|
||||
return new_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_name, text, **kwargs):
|
||||
# get type attribute from qwidgets
|
||||
attr = getattr(QtWidgets, type_name)
|
||||
|
||||
# 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 populate_widgets(self, data, content_layout=None):
|
||||
"""
|
||||
Populate widget from input dict.
|
||||
|
||||
Each plugin has its own set of widget rows defined in dictionary
|
||||
each row values should have following keys: `type`, `target`,
|
||||
`label`, `order`, `value` and optionally also `toolTip`.
|
||||
|
||||
Args:
|
||||
data (dict): widget rows or organized groups defined
|
||||
by types `dict` or `section`
|
||||
content_layout (QtWidgets.QFormLayout)[optional]: used when nesting
|
||||
|
||||
Returns:
|
||||
dict: redefined data dict updated with created widgets
|
||||
|
||||
"""
|
||||
|
||||
content_layout = content_layout or self.content_layout[-1]
|
||||
# fix order of process by defined order value
|
||||
ordered_keys = list(data.keys())
|
||||
for k, v in data.items():
|
||||
try:
|
||||
# try removing a key from index which should
|
||||
# be filled with new
|
||||
ordered_keys.pop(v["order"])
|
||||
except IndexError:
|
||||
pass
|
||||
# add key into correct order
|
||||
ordered_keys.insert(v["order"], k)
|
||||
|
||||
# process ordered
|
||||
for k in ordered_keys:
|
||||
v = data[k]
|
||||
tool_tip = v.get("toolTip", "")
|
||||
if v["type"] == "dict":
|
||||
self.content_layout.append(QtWidgets.QWidget(self))
|
||||
content_layout.addWidget(self.content_layout[-1])
|
||||
self.content_layout[-1].setObjectName("sectionHeadline")
|
||||
|
||||
headline = QtWidgets.QVBoxLayout(self.content_layout[-1])
|
||||
headline.addWidget(Spacer(20, self))
|
||||
headline.addWidget(QtWidgets.QLabel(v["label"]))
|
||||
|
||||
# adding nested layout with label
|
||||
self.content_layout.append(QtWidgets.QWidget(self))
|
||||
self.content_layout[-1].setObjectName("sectionContent")
|
||||
|
||||
nested_content_layout = QtWidgets.QFormLayout(
|
||||
self.content_layout[-1])
|
||||
nested_content_layout.setObjectName("NestedContentLayout")
|
||||
content_layout.addWidget(self.content_layout[-1])
|
||||
|
||||
# add nested key as label
|
||||
data[k]["value"] = self.populate_widgets(
|
||||
v["value"], nested_content_layout)
|
||||
|
||||
if v["type"] == "section":
|
||||
self.content_layout.append(QtWidgets.QWidget(self))
|
||||
content_layout.addWidget(self.content_layout[-1])
|
||||
self.content_layout[-1].setObjectName("sectionHeadline")
|
||||
|
||||
headline = QtWidgets.QVBoxLayout(self.content_layout[-1])
|
||||
headline.addWidget(Spacer(20, self))
|
||||
headline.addWidget(QtWidgets.QLabel(v["label"]))
|
||||
|
||||
# adding nested layout with label
|
||||
self.content_layout.append(QtWidgets.QWidget(self))
|
||||
self.content_layout[-1].setObjectName("sectionContent")
|
||||
|
||||
nested_content_layout = QtWidgets.QFormLayout(
|
||||
self.content_layout[-1])
|
||||
nested_content_layout.setObjectName("NestedContentLayout")
|
||||
content_layout.addWidget(self.content_layout[-1])
|
||||
|
||||
# add nested key as label
|
||||
data[k]["value"] = self.populate_widgets(
|
||||
v["value"], nested_content_layout)
|
||||
|
||||
elif v["type"] == "QLineEdit":
|
||||
data[k]["value"] = self.create_row(
|
||||
content_layout, "QLineEdit", v["label"],
|
||||
setText=v["value"], setToolTip=tool_tip)
|
||||
elif v["type"] == "QComboBox":
|
||||
data[k]["value"] = self.create_row(
|
||||
content_layout, "QComboBox", v["label"],
|
||||
addItems=v["value"], setToolTip=tool_tip)
|
||||
elif v["type"] == "QCheckBox":
|
||||
data[k]["value"] = self.create_row(
|
||||
content_layout, "QCheckBox", v["label"],
|
||||
setChecked=v["value"], setToolTip=tool_tip)
|
||||
elif v["type"] == "QSpinBox":
|
||||
data[k]["value"] = self.create_row(
|
||||
content_layout, "QSpinBox", v["label"],
|
||||
setValue=v["value"], setMinimum=0,
|
||||
setMaximum=100000, setToolTip=tool_tip)
|
||||
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)
|
||||
|
||||
|
||||
class Creator(openpype.Creator):
|
||||
"""Creator class wrapper
|
||||
"""
|
||||
clip_color = constants.COLOR_MAP["purple"]
|
||||
rename_index = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Creator, self).__init__(*args, **kwargs)
|
||||
self.presets = openpype.get_current_project_settings()[
|
||||
"flame"]["create"].get(self.__class__.__name__, {})
|
||||
|
||||
# adding basic current context flame objects
|
||||
self.project = flib.get_current_project()
|
||||
self.sequence = flib.get_current_sequence(flib.CTX.selection)
|
||||
|
||||
if (self.options or {}).get("useSelection"):
|
||||
self.selected = flib.get_sequence_segments(self.sequence, True)
|
||||
else:
|
||||
self.selected = flib.get_sequence_segments(self.sequence)
|
||||
|
||||
def create_widget(self, *args, **kwargs):
|
||||
widget = CreatorWidget(*args, **kwargs)
|
||||
widget.exec_()
|
||||
return widget.get_results_back()
|
||||
|
||||
|
||||
class PublishableClip:
|
||||
"""
|
||||
Convert a segment to publishable instance
|
||||
|
||||
Args:
|
||||
segment (flame.PySegment): flame api object
|
||||
kwargs (optional): additional data needed for rename=True (presets)
|
||||
|
||||
Returns:
|
||||
flame.PySegment: flame api object
|
||||
"""
|
||||
vertical_clip_match = {}
|
||||
marker_data = {}
|
||||
types = {
|
||||
"shot": "shot",
|
||||
"folder": "folder",
|
||||
"episode": "episode",
|
||||
"sequence": "sequence",
|
||||
"track": "sequence",
|
||||
}
|
||||
|
||||
# parents search patern
|
||||
parents_search_patern = r"\{([a-z]*?)\}"
|
||||
|
||||
# default templates for non-ui use
|
||||
rename_default = False
|
||||
hierarchy_default = "{_folder_}/{_sequence_}/{_track_}"
|
||||
clip_name_default = "shot_{_trackIndex_:0>3}_{_clipIndex_:0>4}"
|
||||
subset_name_default = "[ track name ]"
|
||||
review_track_default = "[ none ]"
|
||||
subset_family_default = "plate"
|
||||
count_from_default = 10
|
||||
count_steps_default = 10
|
||||
vertical_sync_default = False
|
||||
driving_layer_default = ""
|
||||
index_from_segment_default = False
|
||||
|
||||
def __init__(self, segment, **kwargs):
|
||||
self.rename_index = kwargs["rename_index"]
|
||||
self.family = kwargs["family"]
|
||||
self.log = kwargs["log"]
|
||||
|
||||
# get main parent objects
|
||||
self.current_segment = segment
|
||||
sequence_name = flib.get_current_sequence([segment]).name.get_value()
|
||||
self.sequence_name = str(sequence_name).replace(" ", "_")
|
||||
|
||||
self.clip_data = flib.get_segment_attributes(segment)
|
||||
# segment (clip) main attributes
|
||||
self.cs_name = self.clip_data["segment_name"]
|
||||
self.cs_index = int(self.clip_data["segment"])
|
||||
|
||||
# get track name and index
|
||||
self.track_index = int(self.clip_data["track"])
|
||||
track_name = self.clip_data["track_name"]
|
||||
self.track_name = str(track_name).replace(" ", "_").replace(
|
||||
"*", "noname{}".format(self.track_index))
|
||||
|
||||
# adding tag.family into tag
|
||||
if kwargs.get("avalon"):
|
||||
self.marker_data.update(kwargs["avalon"])
|
||||
|
||||
# add publish attribute to marker data
|
||||
self.marker_data.update({"publish": True})
|
||||
|
||||
# adding ui inputs if any
|
||||
self.ui_inputs = kwargs.get("ui_inputs", {})
|
||||
|
||||
self.log.info("Inside of plugin: {}".format(
|
||||
self.marker_data
|
||||
))
|
||||
# populate default data before we get other attributes
|
||||
self._populate_segment_default_data()
|
||||
|
||||
# use all populated default data to create all important attributes
|
||||
self._populate_attributes()
|
||||
|
||||
# create parents with correct types
|
||||
self._create_parents()
|
||||
|
||||
def convert(self):
|
||||
|
||||
# solve segment data and add them to marker data
|
||||
self._convert_to_marker_data()
|
||||
|
||||
# if track name is in review track name and also if driving track name
|
||||
# is not in review track name: skip tag creation
|
||||
if (self.track_name in self.review_layer) and (
|
||||
self.driving_layer not in self.review_layer):
|
||||
return
|
||||
|
||||
# deal with clip name
|
||||
new_name = self.marker_data.pop("newClipName")
|
||||
|
||||
if self.rename:
|
||||
# rename segment
|
||||
self.current_segment.name = str(new_name)
|
||||
self.marker_data["asset"] = str(new_name)
|
||||
else:
|
||||
self.marker_data["asset"] = self.cs_name
|
||||
self.marker_data["hierarchyData"]["shot"] = self.cs_name
|
||||
|
||||
if self.marker_data["heroTrack"] and self.review_layer:
|
||||
self.marker_data.update({"reviewTrack": self.review_layer})
|
||||
else:
|
||||
self.marker_data.update({"reviewTrack": None})
|
||||
|
||||
# create pype tag on track_item and add data
|
||||
fpipeline.imprint(self.current_segment, self.marker_data)
|
||||
|
||||
return self.current_segment
|
||||
|
||||
def _populate_segment_default_data(self):
|
||||
""" Populate default formating data from segment. """
|
||||
|
||||
self.current_segment_default_data = {
|
||||
"_folder_": "shots",
|
||||
"_sequence_": self.sequence_name,
|
||||
"_track_": self.track_name,
|
||||
"_clip_": self.cs_name,
|
||||
"_trackIndex_": self.track_index,
|
||||
"_clipIndex_": self.cs_index
|
||||
}
|
||||
|
||||
def _populate_attributes(self):
|
||||
""" Populate main object attributes. """
|
||||
# segment frame range and parent track name for vertical sync check
|
||||
self.clip_in = int(self.clip_data["record_in"])
|
||||
self.clip_out = int(self.clip_data["record_out"])
|
||||
|
||||
# define ui inputs if non gui mode was used
|
||||
self.shot_num = self.cs_index
|
||||
self.log.debug(
|
||||
"____ self.shot_num: {}".format(self.shot_num))
|
||||
|
||||
# ui_inputs data or default values if gui was not used
|
||||
self.rename = self.ui_inputs.get(
|
||||
"clipRename", {}).get("value") or self.rename_default
|
||||
self.clip_name = self.ui_inputs.get(
|
||||
"clipName", {}).get("value") or self.clip_name_default
|
||||
self.hierarchy = self.ui_inputs.get(
|
||||
"hierarchy", {}).get("value") or self.hierarchy_default
|
||||
self.hierarchy_data = self.ui_inputs.get(
|
||||
"hierarchyData", {}).get("value") or \
|
||||
self.current_segment_default_data.copy()
|
||||
self.index_from_segment = self.ui_inputs.get(
|
||||
"segmentIndex", {}).get("value") or self.index_from_segment_default
|
||||
self.count_from = self.ui_inputs.get(
|
||||
"countFrom", {}).get("value") or self.count_from_default
|
||||
self.count_steps = self.ui_inputs.get(
|
||||
"countSteps", {}).get("value") or self.count_steps_default
|
||||
self.subset_name = self.ui_inputs.get(
|
||||
"subsetName", {}).get("value") or self.subset_name_default
|
||||
self.subset_family = self.ui_inputs.get(
|
||||
"subsetFamily", {}).get("value") or self.subset_family_default
|
||||
self.vertical_sync = self.ui_inputs.get(
|
||||
"vSyncOn", {}).get("value") or self.vertical_sync_default
|
||||
self.driving_layer = self.ui_inputs.get(
|
||||
"vSyncTrack", {}).get("value") or self.driving_layer_default
|
||||
self.review_track = self.ui_inputs.get(
|
||||
"reviewTrack", {}).get("value") or self.review_track_default
|
||||
self.audio = self.ui_inputs.get(
|
||||
"audio", {}).get("value") or False
|
||||
|
||||
# build subset name from layer name
|
||||
if self.subset_name == "[ track name ]":
|
||||
self.subset_name = self.track_name
|
||||
|
||||
# create subset for publishing
|
||||
self.subset = self.subset_family + self.subset_name.capitalize()
|
||||
|
||||
def _replace_hash_to_expression(self, name, text):
|
||||
""" Replace hash with number in correct padding. """
|
||||
_spl = text.split("#")
|
||||
_len = (len(_spl) - 1)
|
||||
_repl = "{{{0}:0>{1}}}".format(name, _len)
|
||||
return text.replace(("#" * _len), _repl)
|
||||
|
||||
def _convert_to_marker_data(self):
|
||||
""" Convert internal data to marker data.
|
||||
|
||||
Populating the marker data into internal variable self.marker_data
|
||||
"""
|
||||
# define vertical sync attributes
|
||||
hero_track = True
|
||||
self.review_layer = ""
|
||||
if self.vertical_sync and self.track_name not in self.driving_layer:
|
||||
# if it is not then define vertical sync as None
|
||||
hero_track = False
|
||||
|
||||
# increasing steps by index of rename iteration
|
||||
if not self.index_from_segment:
|
||||
self.count_steps *= self.rename_index
|
||||
|
||||
hierarchy_formating_data = {}
|
||||
hierarchy_data = deepcopy(self.hierarchy_data)
|
||||
_data = self.current_segment_default_data.copy()
|
||||
if self.ui_inputs:
|
||||
# adding tag metadata from ui
|
||||
for _k, _v in self.ui_inputs.items():
|
||||
if _v["target"] == "tag":
|
||||
self.marker_data[_k] = _v["value"]
|
||||
|
||||
# driving layer is set as positive match
|
||||
if hero_track or self.vertical_sync:
|
||||
# mark review layer
|
||||
if self.review_track and (
|
||||
self.review_track not in self.review_track_default):
|
||||
# if review layer is defined and not the same as defalut
|
||||
self.review_layer = self.review_track
|
||||
|
||||
# shot num calculate
|
||||
if self.index_from_segment:
|
||||
# use clip index from timeline
|
||||
self.shot_num = self.count_steps * self.cs_index
|
||||
else:
|
||||
if self.rename_index == 0:
|
||||
self.shot_num = self.count_from
|
||||
else:
|
||||
self.shot_num = self.count_from + self.count_steps
|
||||
|
||||
# clip name sequence number
|
||||
_data.update({"shot": self.shot_num})
|
||||
|
||||
# solve # in test to pythonic expression
|
||||
for _k, _v in hierarchy_data.items():
|
||||
if "#" not in _v["value"]:
|
||||
continue
|
||||
hierarchy_data[
|
||||
_k]["value"] = self._replace_hash_to_expression(
|
||||
_k, _v["value"])
|
||||
|
||||
# fill up pythonic expresisons in hierarchy data
|
||||
for k, _v in hierarchy_data.items():
|
||||
hierarchy_formating_data[k] = _v["value"].format(**_data)
|
||||
else:
|
||||
# if no gui mode then just pass default data
|
||||
hierarchy_formating_data = hierarchy_data
|
||||
|
||||
tag_hierarchy_data = self._solve_tag_hierarchy_data(
|
||||
hierarchy_formating_data
|
||||
)
|
||||
|
||||
tag_hierarchy_data.update({"heroTrack": True})
|
||||
if hero_track and self.vertical_sync:
|
||||
self.vertical_clip_match.update({
|
||||
(self.clip_in, self.clip_out): tag_hierarchy_data
|
||||
})
|
||||
|
||||
if not hero_track and self.vertical_sync:
|
||||
# driving layer is set as negative match
|
||||
for (_in, _out), hero_data in self.vertical_clip_match.items():
|
||||
hero_data.update({"heroTrack": False})
|
||||
if _in == self.clip_in and _out == self.clip_out:
|
||||
data_subset = hero_data["subset"]
|
||||
# add track index in case duplicity of names in hero data
|
||||
if self.subset in data_subset:
|
||||
hero_data["subset"] = self.subset + str(
|
||||
self.track_index)
|
||||
# in case track name and subset name is the same then add
|
||||
if self.subset_name == self.track_name:
|
||||
hero_data["subset"] = self.subset
|
||||
# assing data to return hierarchy data to tag
|
||||
tag_hierarchy_data = hero_data
|
||||
|
||||
# add data to return data dict
|
||||
self.marker_data.update(tag_hierarchy_data)
|
||||
|
||||
def _solve_tag_hierarchy_data(self, hierarchy_formating_data):
|
||||
""" Solve marker data from hierarchy data and templates. """
|
||||
# fill up clip name and hierarchy keys
|
||||
hierarchy_filled = self.hierarchy.format(**hierarchy_formating_data)
|
||||
clip_name_filled = self.clip_name.format(**hierarchy_formating_data)
|
||||
|
||||
# remove shot from hierarchy data: is not needed anymore
|
||||
hierarchy_formating_data.pop("shot")
|
||||
|
||||
return {
|
||||
"newClipName": clip_name_filled,
|
||||
"hierarchy": hierarchy_filled,
|
||||
"parents": self.parents,
|
||||
"hierarchyData": hierarchy_formating_data,
|
||||
"subset": self.subset,
|
||||
"family": self.subset_family,
|
||||
"families": [self.family]
|
||||
}
|
||||
|
||||
def _convert_to_entity(self, type, template):
|
||||
""" Converting input key to key with type. """
|
||||
# convert to entity type
|
||||
entity_type = self.types.get(type, None)
|
||||
|
||||
assert entity_type, "Missing entity type for `{}`".format(
|
||||
type
|
||||
)
|
||||
|
||||
# first collect formating data to use for formating template
|
||||
formating_data = {}
|
||||
for _k, _v in self.hierarchy_data.items():
|
||||
value = _v["value"].format(
|
||||
**self.current_segment_default_data)
|
||||
formating_data[_k] = value
|
||||
|
||||
return {
|
||||
"entity_type": entity_type,
|
||||
"entity_name": template.format(
|
||||
**formating_data
|
||||
)
|
||||
}
|
||||
|
||||
def _create_parents(self):
|
||||
""" Create parents and return it in list. """
|
||||
self.parents = []
|
||||
|
||||
patern = re.compile(self.parents_search_patern)
|
||||
|
||||
par_split = [(patern.findall(t).pop(), t)
|
||||
for t in self.hierarchy.split("/")]
|
||||
|
||||
for type, template in par_split:
|
||||
parent = self._convert_to_entity(type, template)
|
||||
self.parents.append(parent)
|
||||
|
||||
|
||||
# Publishing plugin functions
|
||||
# Loader plugin functions
|
||||
|
|
|
|||
|
|
@ -9,26 +9,14 @@ import json
|
|||
import xml.dom.minidom as minidom
|
||||
from copy import deepcopy
|
||||
import datetime
|
||||
|
||||
try:
|
||||
from libwiretapPythonClientAPI import (
|
||||
WireTapClientInit)
|
||||
except ImportError:
|
||||
flame_python_path = "/opt/Autodesk/flame_2021/python"
|
||||
flame_exe_path = (
|
||||
"/opt/Autodesk/flame_2021/bin/flame.app"
|
||||
"/Contents/MacOS/startApp")
|
||||
|
||||
sys.path.append(flame_python_path)
|
||||
|
||||
from libwiretapPythonClientAPI import (
|
||||
WireTapClientInit,
|
||||
WireTapClientUninit,
|
||||
WireTapNodeHandle,
|
||||
WireTapServerHandle,
|
||||
WireTapInt,
|
||||
WireTapStr
|
||||
)
|
||||
from libwiretapPythonClientAPI import ( # noqa
|
||||
WireTapClientInit,
|
||||
WireTapClientUninit,
|
||||
WireTapNodeHandle,
|
||||
WireTapServerHandle,
|
||||
WireTapInt,
|
||||
WireTapStr
|
||||
)
|
||||
|
||||
|
||||
class WireTapCom(object):
|
||||
|
|
@ -55,6 +43,9 @@ class WireTapCom(object):
|
|||
self.volume_name = volume_name or "stonefs"
|
||||
self.group_name = group_name or "staff"
|
||||
|
||||
# wiretap tools dir path
|
||||
self.wiretap_tools_dir = os.getenv("OPENPYPE_WIRETAP_TOOLS")
|
||||
|
||||
# initialize WireTap client
|
||||
WireTapClientInit()
|
||||
|
||||
|
|
@ -84,9 +75,11 @@ class WireTapCom(object):
|
|||
workspace_name = kwargs.get("workspace_name")
|
||||
color_policy = kwargs.get("color_policy")
|
||||
|
||||
self._project_prep(project_name)
|
||||
self._set_project_settings(project_name, project_data)
|
||||
self._set_project_colorspace(project_name, color_policy)
|
||||
project_exists = self._project_prep(project_name)
|
||||
if not project_exists:
|
||||
self._set_project_settings(project_name, project_data)
|
||||
self._set_project_colorspace(project_name, color_policy)
|
||||
|
||||
user_name = self._user_prep(user_name)
|
||||
|
||||
if workspace_name is None:
|
||||
|
|
@ -169,18 +162,15 @@ class WireTapCom(object):
|
|||
# check if volumes exists
|
||||
if self.volume_name not in volumes:
|
||||
raise AttributeError(
|
||||
("Volume '{}' does not exist '{}'").format(
|
||||
("Volume '{}' does not exist in '{}'").format(
|
||||
self.volume_name, volumes)
|
||||
)
|
||||
|
||||
# form cmd arguments
|
||||
project_create_cmd = [
|
||||
os.path.join(
|
||||
"/opt/Autodesk/",
|
||||
"wiretap",
|
||||
"tools",
|
||||
"2021",
|
||||
"wiretap_create_node",
|
||||
self.wiretap_tools_dir,
|
||||
"wiretap_create_node"
|
||||
),
|
||||
'-n',
|
||||
os.path.join("/volumes", self.volume_name),
|
||||
|
|
@ -202,6 +192,7 @@ class WireTapCom(object):
|
|||
|
||||
print(
|
||||
"A new project '{}' is created.".format(project_name))
|
||||
return project_exists
|
||||
|
||||
def _get_all_volumes(self):
|
||||
"""Request all available volumens from WireTap
|
||||
|
|
@ -431,11 +422,8 @@ class WireTapCom(object):
|
|||
color_policy = color_policy or "Legacy"
|
||||
project_colorspace_cmd = [
|
||||
os.path.join(
|
||||
"/opt/Autodesk/",
|
||||
"wiretap",
|
||||
"tools",
|
||||
"2021",
|
||||
"wiretap_duplicate_node",
|
||||
self.wiretap_tools_dir,
|
||||
"wiretap_duplicate_node"
|
||||
),
|
||||
"-s",
|
||||
"/syncolor/policies/Autodesk/{}".format(color_policy),
|
||||
|
|
|
|||
|
|
@ -5,17 +5,14 @@ from pprint import pformat
|
|||
import atexit
|
||||
import openpype
|
||||
import avalon
|
||||
import openpype.hosts.flame as opflame
|
||||
|
||||
flh = sys.modules[__name__]
|
||||
flh._project = None
|
||||
import openpype.hosts.flame.api as opfapi
|
||||
|
||||
|
||||
def openpype_install():
|
||||
"""Registering OpenPype in context
|
||||
"""
|
||||
openpype.install()
|
||||
avalon.api.install(opflame)
|
||||
avalon.api.install(opfapi)
|
||||
print("Avalon registred hosts: {}".format(
|
||||
avalon.api.registered_host()))
|
||||
|
||||
|
|
@ -48,30 +45,34 @@ sys.excepthook = exeption_handler
|
|||
def cleanup():
|
||||
"""Cleaning up Flame framework context
|
||||
"""
|
||||
if opflame.apps:
|
||||
print('`{}` cleaning up apps:\n {}\n'.format(
|
||||
__file__, pformat(opflame.apps)))
|
||||
while len(opflame.apps):
|
||||
app = opflame.apps.pop()
|
||||
if opfapi.CTX.flame_apps:
|
||||
print('`{}` cleaning up flame_apps:\n {}\n'.format(
|
||||
__file__, pformat(opfapi.CTX.flame_apps)))
|
||||
while len(opfapi.CTX.flame_apps):
|
||||
app = opfapi.CTX.flame_apps.pop()
|
||||
print('`{}` removing : {}'.format(__file__, app.name))
|
||||
del app
|
||||
opflame.apps = []
|
||||
opfapi.CTX.flame_apps = []
|
||||
|
||||
if opflame.app_framework:
|
||||
print('PYTHON\t: %s cleaning up' % opflame.app_framework.bundle_name)
|
||||
opflame.app_framework.save_prefs()
|
||||
opflame.app_framework = None
|
||||
if opfapi.CTX.app_framework:
|
||||
print('openpype\t: {} cleaning up'.format(
|
||||
opfapi.CTX.app_framework.bundle_name)
|
||||
)
|
||||
opfapi.CTX.app_framework.save_prefs()
|
||||
opfapi.CTX.app_framework = None
|
||||
|
||||
|
||||
atexit.register(cleanup)
|
||||
|
||||
|
||||
def load_apps():
|
||||
"""Load available apps into Flame framework
|
||||
"""Load available flame_apps into Flame framework
|
||||
"""
|
||||
opflame.apps.append(opflame.FlameMenuProjectConnect(opflame.app_framework))
|
||||
opflame.apps.append(opflame.FlameMenuTimeline(opflame.app_framework))
|
||||
opflame.app_framework.log.info("Apps are loaded")
|
||||
opfapi.CTX.flame_apps.append(
|
||||
opfapi.FlameMenuProjectConnect(opfapi.CTX.app_framework))
|
||||
opfapi.CTX.flame_apps.append(
|
||||
opfapi.FlameMenuTimeline(opfapi.CTX.app_framework))
|
||||
opfapi.CTX.app_framework.log.info("Apps are loaded")
|
||||
|
||||
|
||||
def project_changed_dict(info):
|
||||
|
|
@ -89,10 +90,10 @@ def app_initialized(parent=None):
|
|||
Args:
|
||||
parent (obj, optional): Parent object. Defaults to None.
|
||||
"""
|
||||
opflame.app_framework = opflame.FlameAppFramework()
|
||||
opfapi.CTX.app_framework = opfapi.FlameAppFramework()
|
||||
|
||||
print("{} initializing".format(
|
||||
opflame.app_framework.bundle_name))
|
||||
opfapi.CTX.app_framework.bundle_name))
|
||||
|
||||
load_apps()
|
||||
|
||||
|
|
@ -103,7 +104,7 @@ Initialisation of the hook is starting from here
|
|||
First it needs to test if it can import the flame modul.
|
||||
This will happen only in case a project has been loaded.
|
||||
Then `app_initialized` will load main Framework which will load
|
||||
all menu objects as apps.
|
||||
all menu objects as flame_apps.
|
||||
"""
|
||||
|
||||
try:
|
||||
|
|
@ -131,15 +132,15 @@ def _build_app_menu(app_name):
|
|||
|
||||
# first find the relative appname
|
||||
app = None
|
||||
for _app in opflame.apps:
|
||||
for _app in opfapi.CTX.flame_apps:
|
||||
if _app.__class__.__name__ == app_name:
|
||||
app = _app
|
||||
|
||||
if app:
|
||||
menu.append(app.build_menu())
|
||||
|
||||
if opflame.app_framework:
|
||||
menu_auto_refresh = opflame.app_framework.prefs_global.get(
|
||||
if opfapi.CTX.app_framework:
|
||||
menu_auto_refresh = opfapi.CTX.app_framework.prefs_global.get(
|
||||
'menu_auto_refresh', {})
|
||||
if menu_auto_refresh.get('timeline_menu', True):
|
||||
try:
|
||||
|
|
@ -163,8 +164,8 @@ def project_saved(project_name, save_time, is_auto_save):
|
|||
save_time (str): time when it was saved
|
||||
is_auto_save (bool): autosave is on or off
|
||||
"""
|
||||
if opflame.app_framework:
|
||||
opflame.app_framework.save_prefs()
|
||||
if opfapi.CTX.app_framework:
|
||||
opfapi.CTX.app_framework.save_prefs()
|
||||
|
||||
|
||||
def get_main_menu_custom_ui_actions():
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Flame utils for syncing scripts
|
|||
import os
|
||||
import shutil
|
||||
from openpype.api import Logger
|
||||
log = Logger().get_logger(__name__)
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
def _sync_utility_scripts(env=None):
|
||||
|
|
@ -75,10 +75,19 @@ def _sync_utility_scripts(env=None):
|
|||
|
||||
path = os.path.join(flame_shared_dir, _itm)
|
||||
log.info("Removing `{path}`...".format(**locals()))
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path, onerror=None)
|
||||
else:
|
||||
os.remove(path)
|
||||
|
||||
try:
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path, onerror=None)
|
||||
else:
|
||||
os.remove(path)
|
||||
except PermissionError as msg:
|
||||
log.warning(
|
||||
"Not able to remove: `{}`, Problem with: `{}`".format(
|
||||
path,
|
||||
msg
|
||||
)
|
||||
)
|
||||
|
||||
# copy scripts into Resolve's utility scripts dir
|
||||
for dirpath, scriptlist in scripts.items():
|
||||
|
|
@ -88,13 +97,22 @@ def _sync_utility_scripts(env=None):
|
|||
src = os.path.join(dirpath, _script)
|
||||
dst = os.path.join(flame_shared_dir, _script)
|
||||
log.info("Copying `{src}` to `{dst}`...".format(**locals()))
|
||||
if os.path.isdir(src):
|
||||
shutil.copytree(
|
||||
src, dst, symlinks=False,
|
||||
ignore=None, ignore_dangling_symlinks=False
|
||||
|
||||
try:
|
||||
if os.path.isdir(src):
|
||||
shutil.copytree(
|
||||
src, dst, symlinks=False,
|
||||
ignore=None, ignore_dangling_symlinks=False
|
||||
)
|
||||
else:
|
||||
shutil.copy2(src, dst)
|
||||
except (PermissionError, FileExistsError) as msg:
|
||||
log.warning(
|
||||
"Not able to coppy to: `{}`, Problem with: `{}`".format(
|
||||
dst,
|
||||
msg
|
||||
)
|
||||
)
|
||||
else:
|
||||
shutil.copy2(src, dst)
|
||||
|
||||
|
||||
def setup(env=None):
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from openpype.api import Logger
|
|||
# )
|
||||
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
exported_projet_ext = ".otoc"
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import socket
|
|||
from openpype.lib import (
|
||||
PreLaunchHook, get_openpype_username)
|
||||
from openpype.hosts import flame as opflame
|
||||
import openpype.hosts.flame.api as opfapi
|
||||
import openpype
|
||||
from pprint import pformat
|
||||
|
||||
|
|
@ -18,18 +19,18 @@ class FlamePrelaunch(PreLaunchHook):
|
|||
"""
|
||||
app_groups = ["flame"]
|
||||
|
||||
# todo: replace version number with avalon launch app version
|
||||
flame_python_exe = "/opt/Autodesk/python/2021/bin/python2.7"
|
||||
|
||||
wtc_script_path = os.path.join(
|
||||
opflame.HOST_DIR, "api", "scripts", "wiretap_com.py")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.signature = "( {} )".format(self.__class__.__name__)
|
||||
|
||||
def execute(self):
|
||||
_env = self.launch_context.env
|
||||
self.flame_python_exe = _env["OPENPYPE_FLAME_PYTHON_EXEC"]
|
||||
self.flame_pythonpath = _env["OPENPYPE_FLAME_PYTHONPATH"]
|
||||
|
||||
"""Hook entry method."""
|
||||
project_doc = self.data["project_doc"]
|
||||
user_name = get_openpype_username()
|
||||
|
|
@ -55,12 +56,11 @@ class FlamePrelaunch(PreLaunchHook):
|
|||
"FieldDominance": "PROGRESSIVE"
|
||||
}
|
||||
|
||||
|
||||
data_to_script = {
|
||||
# from settings
|
||||
"host_name": os.getenv("FLAME_WIRETAP_HOSTNAME") or hostname,
|
||||
"volume_name": os.getenv("FLAME_WIRETAP_VOLUME"),
|
||||
"group_name": os.getenv("FLAME_WIRETAP_GROUP"),
|
||||
"host_name": _env.get("FLAME_WIRETAP_HOSTNAME") or hostname,
|
||||
"volume_name": _env.get("FLAME_WIRETAP_VOLUME"),
|
||||
"group_name": _env.get("FLAME_WIRETAP_GROUP"),
|
||||
"color_policy": "ACES 1.1",
|
||||
|
||||
# from project
|
||||
|
|
@ -68,14 +68,28 @@ class FlamePrelaunch(PreLaunchHook):
|
|||
"user_name": user_name,
|
||||
"project_data": project_data
|
||||
}
|
||||
|
||||
self.log.info(pformat(dict(_env)))
|
||||
self.log.info(pformat(data_to_script))
|
||||
|
||||
# add to python path from settings
|
||||
self._add_pythonpath()
|
||||
|
||||
app_arguments = self._get_launch_arguments(data_to_script)
|
||||
|
||||
self.log.info(pformat(dict(self.launch_context.env)))
|
||||
|
||||
opflame.setup(self.launch_context.env)
|
||||
opfapi.setup(self.launch_context.env)
|
||||
|
||||
self.launch_context.launch_args.extend(app_arguments)
|
||||
|
||||
def _add_pythonpath(self):
|
||||
pythonpath = self.launch_context.env.get("PYTHONPATH")
|
||||
|
||||
# separate it explicity by `;` that is what we use in settings
|
||||
new_pythonpath = self.flame_pythonpath.split(os.pathsep)
|
||||
new_pythonpath += pythonpath.split(os.pathsep)
|
||||
|
||||
self.launch_context.env["PYTHONPATH"] = os.pathsep.join(new_pythonpath)
|
||||
|
||||
def _get_launch_arguments(self, script_data):
|
||||
# Dump data to string
|
||||
dumped_script_data = json.dumps(script_data)
|
||||
|
|
@ -83,7 +97,9 @@ class FlamePrelaunch(PreLaunchHook):
|
|||
with make_temp_file(dumped_script_data) as tmp_json_path:
|
||||
# Prepare subprocess arguments
|
||||
args = [
|
||||
self.flame_python_exe,
|
||||
self.flame_python_exe.format(
|
||||
**self.launch_context.env
|
||||
),
|
||||
self.wtc_script_path,
|
||||
tmp_json_path
|
||||
]
|
||||
|
|
@ -91,7 +107,7 @@ class FlamePrelaunch(PreLaunchHook):
|
|||
|
||||
process_kwargs = {
|
||||
"logger": self.log,
|
||||
"env": {}
|
||||
"env": self.launch_context.env
|
||||
}
|
||||
|
||||
openpype.api.run_subprocess(args, **process_kwargs)
|
||||
|
|
|
|||
275
openpype/hosts/flame/plugins/create/create_shot_clip.py
Normal file
275
openpype/hosts/flame/plugins/create/create_shot_clip.py
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
from copy import deepcopy
|
||||
import openpype.hosts.flame.api as opfapi
|
||||
|
||||
|
||||
class CreateShotClip(opfapi.Creator):
|
||||
"""Publishable clip"""
|
||||
|
||||
label = "Create Publishable Clip"
|
||||
family = "clip"
|
||||
icon = "film"
|
||||
defaults = ["Main"]
|
||||
|
||||
presets = None
|
||||
|
||||
def process(self):
|
||||
# Creator copy of object attributes that are modified during `process`
|
||||
presets = deepcopy(self.presets)
|
||||
gui_inputs = self.get_gui_inputs()
|
||||
|
||||
# get key pares from presets and match it on ui inputs
|
||||
for k, v in gui_inputs.items():
|
||||
if v["type"] in ("dict", "section"):
|
||||
# nested dictionary (only one level allowed
|
||||
# for sections and dict)
|
||||
for _k, _v in v["value"].items():
|
||||
if presets.get(_k):
|
||||
gui_inputs[k][
|
||||
"value"][_k]["value"] = presets[_k]
|
||||
if presets.get(k):
|
||||
gui_inputs[k]["value"] = presets[k]
|
||||
|
||||
# open widget for plugins inputs
|
||||
results_back = self.create_widget(
|
||||
"Pype publish attributes creator",
|
||||
"Define sequential rename and fill hierarchy data.",
|
||||
gui_inputs
|
||||
)
|
||||
|
||||
if len(self.selected) < 1:
|
||||
return
|
||||
|
||||
if not results_back:
|
||||
print("Operation aborted")
|
||||
return
|
||||
|
||||
# get ui output for track name for vertical sync
|
||||
v_sync_track = results_back["vSyncTrack"]["value"]
|
||||
|
||||
# sort selected trackItems by
|
||||
sorted_selected_segments = []
|
||||
unsorted_selected_segments = []
|
||||
for _segment in self.selected:
|
||||
if _segment.parent.name.get_value() in v_sync_track:
|
||||
sorted_selected_segments.append(_segment)
|
||||
else:
|
||||
unsorted_selected_segments.append(_segment)
|
||||
|
||||
sorted_selected_segments.extend(unsorted_selected_segments)
|
||||
|
||||
kwargs = {
|
||||
"log": self.log,
|
||||
"ui_inputs": results_back,
|
||||
"avalon": self.data,
|
||||
"family": self.data["family"]
|
||||
}
|
||||
|
||||
for i, segment in enumerate(sorted_selected_segments):
|
||||
kwargs["rename_index"] = i
|
||||
# convert track item to timeline media pool item
|
||||
opfapi.PublishableClip(segment, **kwargs).convert()
|
||||
|
||||
def get_gui_inputs(self):
|
||||
gui_tracks = self._get_video_track_names(
|
||||
opfapi.get_current_sequence(opfapi.CTX.selection)
|
||||
)
|
||||
return deepcopy({
|
||||
"renameHierarchy": {
|
||||
"type": "section",
|
||||
"label": "Shot Hierarchy And Rename Settings",
|
||||
"target": "ui",
|
||||
"order": 0,
|
||||
"value": {
|
||||
"hierarchy": {
|
||||
"value": "{folder}/{sequence}",
|
||||
"type": "QLineEdit",
|
||||
"label": "Shot Parent Hierarchy",
|
||||
"target": "tag",
|
||||
"toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa
|
||||
"order": 0},
|
||||
"clipRename": {
|
||||
"value": False,
|
||||
"type": "QCheckBox",
|
||||
"label": "Rename clips",
|
||||
"target": "ui",
|
||||
"toolTip": "Renaming selected clips on fly", # noqa
|
||||
"order": 1},
|
||||
"clipName": {
|
||||
"value": "{sequence}{shot}",
|
||||
"type": "QLineEdit",
|
||||
"label": "Clip Name Template",
|
||||
"target": "ui",
|
||||
"toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa
|
||||
"order": 2},
|
||||
"segmentIndex": {
|
||||
"value": True,
|
||||
"type": "QCheckBox",
|
||||
"label": "Segment index",
|
||||
"target": "ui",
|
||||
"toolTip": "Take number from segment index", # noqa
|
||||
"order": 3},
|
||||
"countFrom": {
|
||||
"value": 10,
|
||||
"type": "QSpinBox",
|
||||
"label": "Count sequence from",
|
||||
"target": "ui",
|
||||
"toolTip": "Set when the sequence number stafrom", # noqa
|
||||
"order": 4},
|
||||
"countSteps": {
|
||||
"value": 10,
|
||||
"type": "QSpinBox",
|
||||
"label": "Stepping number",
|
||||
"target": "ui",
|
||||
"toolTip": "What number is adding every new step", # noqa
|
||||
"order": 5},
|
||||
}
|
||||
},
|
||||
"hierarchyData": {
|
||||
"type": "dict",
|
||||
"label": "Shot Template Keywords",
|
||||
"target": "tag",
|
||||
"order": 1,
|
||||
"value": {
|
||||
"folder": {
|
||||
"value": "shots",
|
||||
"type": "QLineEdit",
|
||||
"label": "{folder}",
|
||||
"target": "tag",
|
||||
"toolTip": "Name of folder used for root of generated shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa
|
||||
"order": 0},
|
||||
"episode": {
|
||||
"value": "ep01",
|
||||
"type": "QLineEdit",
|
||||
"label": "{episode}",
|
||||
"target": "tag",
|
||||
"toolTip": "Name of episode.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa
|
||||
"order": 1},
|
||||
"sequence": {
|
||||
"value": "sq01",
|
||||
"type": "QLineEdit",
|
||||
"label": "{sequence}",
|
||||
"target": "tag",
|
||||
"toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa
|
||||
"order": 2},
|
||||
"track": {
|
||||
"value": "{_track_}",
|
||||
"type": "QLineEdit",
|
||||
"label": "{track}",
|
||||
"target": "tag",
|
||||
"toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa
|
||||
"order": 3},
|
||||
"shot": {
|
||||
"value": "sh###",
|
||||
"type": "QLineEdit",
|
||||
"label": "{shot}",
|
||||
"target": "tag",
|
||||
"toolTip": "Name of shot. `#` is converted to paded number. \nAlso could be used with usable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa
|
||||
"order": 4}
|
||||
}
|
||||
},
|
||||
"verticalSync": {
|
||||
"type": "section",
|
||||
"label": "Vertical Synchronization Of Attributes",
|
||||
"target": "ui",
|
||||
"order": 2,
|
||||
"value": {
|
||||
"vSyncOn": {
|
||||
"value": True,
|
||||
"type": "QCheckBox",
|
||||
"label": "Enable Vertical Sync",
|
||||
"target": "ui",
|
||||
"toolTip": "Switch on if you want clips above each other to share its attributes", # noqa
|
||||
"order": 0},
|
||||
"vSyncTrack": {
|
||||
"value": gui_tracks, # noqa
|
||||
"type": "QComboBox",
|
||||
"label": "Hero track",
|
||||
"target": "ui",
|
||||
"toolTip": "Select driving track name which should be hero for all others", # noqa
|
||||
"order": 1}
|
||||
}
|
||||
},
|
||||
"publishSettings": {
|
||||
"type": "section",
|
||||
"label": "Publish Settings",
|
||||
"target": "ui",
|
||||
"order": 3,
|
||||
"value": {
|
||||
"subsetName": {
|
||||
"value": ["[ track name ]", "main", "bg", "fg", "bg",
|
||||
"animatic"],
|
||||
"type": "QComboBox",
|
||||
"label": "Subset Name",
|
||||
"target": "ui",
|
||||
"toolTip": "chose subset name patern, if [ track name ] is selected, name of track layer will be used", # noqa
|
||||
"order": 0},
|
||||
"subsetFamily": {
|
||||
"value": ["plate", "take"],
|
||||
"type": "QComboBox",
|
||||
"label": "Subset Family",
|
||||
"target": "ui", "toolTip": "What use of this subset is for", # noqa
|
||||
"order": 1},
|
||||
"reviewTrack": {
|
||||
"value": ["< none >"] + gui_tracks,
|
||||
"type": "QComboBox",
|
||||
"label": "Use Review Track",
|
||||
"target": "ui",
|
||||
"toolTip": "Generate preview videos on fly, if `< none >` is defined nothing will be generated.", # noqa
|
||||
"order": 2},
|
||||
"audio": {
|
||||
"value": False,
|
||||
"type": "QCheckBox",
|
||||
"label": "Include audio",
|
||||
"target": "tag",
|
||||
"toolTip": "Process subsets with corresponding audio", # noqa
|
||||
"order": 3},
|
||||
"sourceResolution": {
|
||||
"value": False,
|
||||
"type": "QCheckBox",
|
||||
"label": "Source resolution",
|
||||
"target": "tag",
|
||||
"toolTip": "Is resloution taken from timeline or source?", # noqa
|
||||
"order": 4},
|
||||
}
|
||||
},
|
||||
"frameRangeAttr": {
|
||||
"type": "section",
|
||||
"label": "Shot Attributes",
|
||||
"target": "ui",
|
||||
"order": 4,
|
||||
"value": {
|
||||
"workfileFrameStart": {
|
||||
"value": 1001,
|
||||
"type": "QSpinBox",
|
||||
"label": "Workfiles Start Frame",
|
||||
"target": "tag",
|
||||
"toolTip": "Set workfile starting frame number", # noqa
|
||||
"order": 0
|
||||
},
|
||||
"handleStart": {
|
||||
"value": 0,
|
||||
"type": "QSpinBox",
|
||||
"label": "Handle Start",
|
||||
"target": "tag",
|
||||
"toolTip": "Handle at start of clip", # noqa
|
||||
"order": 1
|
||||
},
|
||||
"handleEnd": {
|
||||
"value": 0,
|
||||
"type": "QSpinBox",
|
||||
"label": "Handle End",
|
||||
"target": "tag",
|
||||
"toolTip": "Handle at end of clip", # noqa
|
||||
"order": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
def _get_video_track_names(self, sequence):
|
||||
track_names = []
|
||||
for ver in sequence.versions:
|
||||
for track in ver.tracks:
|
||||
track_names.append(track.name.get_value())
|
||||
|
||||
return track_names
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
import openpype.hosts.flame as opflame
|
||||
import tempfile
|
||||
import openpype.hosts.flame.api as opfapi
|
||||
from openpype.hosts.flame.otio import flame_export as otio_export
|
||||
from openpype.hosts.flame.api import lib
|
||||
import opentimelineio as otio
|
||||
from pprint import pformat
|
||||
reload(lib) # noqa
|
||||
reload(otio_export) # noqa
|
||||
|
||||
|
||||
|
|
@ -17,10 +18,46 @@ class CollectTestSelection(pyblish.api.ContextPlugin):
|
|||
hosts = ["flame"]
|
||||
|
||||
def process(self, context):
|
||||
self.log.info(opflame.selection)
|
||||
self.log.info(
|
||||
"Active Selection: {}".format(opfapi.CTX.selection))
|
||||
|
||||
sequence = lib.get_current_sequence(opflame.selection)
|
||||
sequence = opfapi.get_current_sequence(opfapi.CTX.selection)
|
||||
|
||||
self.test_imprint_data(sequence)
|
||||
self.test_otio_export(sequence)
|
||||
|
||||
def test_otio_export(self, sequence):
|
||||
test_dir = os.path.normpath(
|
||||
tempfile.mkdtemp(prefix="test_pyblish_tmp_")
|
||||
)
|
||||
export_path = os.path.normpath(
|
||||
os.path.join(
|
||||
test_dir, "otio_timeline_export.otio"
|
||||
)
|
||||
)
|
||||
otio_timeline = otio_export.create_otio_timeline(sequence)
|
||||
otio_export.write_to_file(
|
||||
otio_timeline, export_path
|
||||
)
|
||||
read_timeline_otio = otio.adapters.read_from_file(export_path)
|
||||
|
||||
if otio_timeline != read_timeline_otio:
|
||||
raise Exception("Exported timeline is different from original")
|
||||
|
||||
self.log.info(pformat(otio_timeline))
|
||||
self.log.info("Otio exported to: {}".format(export_path))
|
||||
|
||||
def test_imprint_data(self, sequence):
|
||||
with opfapi.maintained_segment_selection(sequence) as sel_segments:
|
||||
for segment in sel_segments:
|
||||
if str(segment.name)[1:-1] == "":
|
||||
continue
|
||||
|
||||
self.log.debug("Segment with OpenPypeData: {}".format(
|
||||
segment.name))
|
||||
|
||||
opfapi.imprint(segment, {
|
||||
'asset': segment.name.get_value(),
|
||||
'family': 'render',
|
||||
'subset': 'subsetMain'
|
||||
})
|
||||
|
|
|
|||
20
openpype/settings/defaults/project_settings/flame.json
Normal file
20
openpype/settings/defaults/project_settings/flame.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"create": {
|
||||
"CreateShotClip": {
|
||||
"hierarchy": "{folder}/{sequence}",
|
||||
"clipRename": true,
|
||||
"clipName": "{track}{sequence}{shot}",
|
||||
"countFrom": 10,
|
||||
"countSteps": 10,
|
||||
"folder": "shots",
|
||||
"episode": "ep01",
|
||||
"sequence": "sq01",
|
||||
"track": "{_track_}",
|
||||
"shot": "sh###",
|
||||
"vSyncOn": false,
|
||||
"workfileFrameStart": 1001,
|
||||
"handleStart": 10,
|
||||
"handleEnd": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -129,7 +129,11 @@
|
|||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"environment": {}
|
||||
"environment": {
|
||||
"OPENPYPE_FLAME_PYTHON_EXEC": "/opt/Autodesk/python/2021/bin/python2.7",
|
||||
"OPENPYPE_FLAME_PYTHONPATH": "/opt/Autodesk/flame_2021/python",
|
||||
"OPENPYPE_WIRETAP_TOOLS": "/opt/Autodesk/wiretap/tools/2021"
|
||||
}
|
||||
},
|
||||
"__dynamic_keys_labels__": {
|
||||
"2021": "2021 (Testing Only)"
|
||||
|
|
@ -142,7 +146,10 @@
|
|||
"icon": "{}/app_icons/nuke.png",
|
||||
"host_name": "nuke",
|
||||
"environment": {
|
||||
"NUKE_PATH": ["{NUKE_PATH}", "{OPENPYPE_STUDIO_PLUGINS}/nuke"]
|
||||
"NUKE_PATH": [
|
||||
"{NUKE_PATH}",
|
||||
"{OPENPYPE_STUDIO_PLUGINS}/nuke"
|
||||
]
|
||||
},
|
||||
"variants": {
|
||||
"13-0": {
|
||||
|
|
@ -248,7 +255,10 @@
|
|||
"icon": "{}/app_icons/nuke.png",
|
||||
"host_name": "nuke",
|
||||
"environment": {
|
||||
"NUKE_PATH": ["{NUKE_PATH}", "{OPENPYPE_STUDIO_PLUGINS}/nuke"]
|
||||
"NUKE_PATH": [
|
||||
"{NUKE_PATH}",
|
||||
"{OPENPYPE_STUDIO_PLUGINS}/nuke"
|
||||
]
|
||||
},
|
||||
"variants": {
|
||||
"13-0": {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,10 @@
|
|||
"type": "schema",
|
||||
"name": "schema_project_celaction"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_project_flame"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_project_resolve"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "flame",
|
||||
"label": "Flame",
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "create",
|
||||
"label": "Create plugins",
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "CreateShotClip",
|
||||
"label": "Create Shot Clip",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "collapsible-wrap",
|
||||
"label": "Shot Hierarchy And Rename Settings",
|
||||
"collapsible": false,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "hierarchy",
|
||||
"label": "Shot parent hierarchy"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "clipRename",
|
||||
"label": "Rename clips"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "clipName",
|
||||
"label": "Clip name template"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "countFrom",
|
||||
"label": "Count sequence from"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "countSteps",
|
||||
"label": "Stepping number"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "collapsible-wrap",
|
||||
"label": "Shot Template Keywords",
|
||||
"collapsible": false,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "folder",
|
||||
"label": "{folder}"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "episode",
|
||||
"label": "{episode}"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "sequence",
|
||||
"label": "{sequence}"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "track",
|
||||
"label": "{track}"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "shot",
|
||||
"label": "{shot}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "collapsible-wrap",
|
||||
"label": "Vertical Synchronization Of Attributes",
|
||||
"collapsible": false,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "vSyncOn",
|
||||
"label": "Enable Vertical Sync"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "collapsible-wrap",
|
||||
"label": "Shot Attributes",
|
||||
"collapsible": false,
|
||||
"children": [
|
||||
{
|
||||
"type": "number",
|
||||
"key": "workfileFrameStart",
|
||||
"label": "Workfiles Start Frame"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "handleStart",
|
||||
"label": "Handle start (head)"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "handleEnd",
|
||||
"label": "Handle end (tail)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue