mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
[Automated] Merged develop into main
This commit is contained in:
commit
e9e4133820
30 changed files with 672 additions and 147 deletions
|
|
@ -1,3 +1,4 @@
|
|||
from copy import deepcopy
|
||||
import openpype.hosts.hiero.api as phiero
|
||||
# from openpype.hosts.hiero.api import plugin, lib
|
||||
# reload(lib)
|
||||
|
|
@ -206,20 +207,24 @@ class CreateShotClip(phiero.Creator):
|
|||
presets = None
|
||||
|
||||
def process(self):
|
||||
# Creator copy of object attributes that are modified during `process`
|
||||
presets = deepcopy(self.presets)
|
||||
gui_inputs = deepcopy(self.gui_inputs)
|
||||
|
||||
# get key pares from presets and match it on ui inputs
|
||||
for k, v in self.gui_inputs.items():
|
||||
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 self.presets.get(_k):
|
||||
self.gui_inputs[k][
|
||||
"value"][_k]["value"] = self.presets[_k]
|
||||
if self.presets.get(k):
|
||||
self.gui_inputs[k]["value"] = self.presets[k]
|
||||
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
|
||||
widget = self.widget(self.gui_name, self.gui_info, self.gui_inputs)
|
||||
widget = self.widget(self.gui_name, self.gui_info, gui_inputs)
|
||||
widget.exec_()
|
||||
|
||||
if len(self.selected) < 1:
|
||||
|
|
|
|||
|
|
@ -46,12 +46,6 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
source_clip = track_item.source()
|
||||
self.log.debug("clip_name: {}".format(clip_name))
|
||||
|
||||
# get clips subtracks and anotations
|
||||
annotations = self.clip_annotations(source_clip)
|
||||
subtracks = self.clip_subtrack(track_item)
|
||||
self.log.debug("Annotations: {}".format(annotations))
|
||||
self.log.debug(">> Subtracks: {}".format(subtracks))
|
||||
|
||||
# get openpype tag data
|
||||
tag_data = phiero.get_track_item_pype_data(track_item)
|
||||
self.log.debug("__ tag_data: {}".format(pformat(tag_data)))
|
||||
|
|
@ -62,6 +56,12 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
if tag_data.get("id") != "pyblish.avalon.instance":
|
||||
continue
|
||||
|
||||
# get clips subtracks and anotations
|
||||
annotations = self.clip_annotations(source_clip)
|
||||
subtracks = self.clip_subtrack(track_item)
|
||||
self.log.debug("Annotations: {}".format(annotations))
|
||||
self.log.debug(">> Subtracks: {}".format(subtracks))
|
||||
|
||||
# solve handles length
|
||||
tag_data["handleStart"] = min(
|
||||
tag_data["handleStart"], int(track_item.handleInLength()))
|
||||
|
|
|
|||
|
|
@ -128,5 +128,4 @@ class CreateWritePrerender(plugin.PypeCreator):
|
|||
w_node["first"].setValue(nuke.root()["first_frame"].value())
|
||||
w_node["last"].setValue(nuke.root()["last_frame"].value())
|
||||
|
||||
|
||||
return write_node
|
||||
|
|
|
|||
|
|
@ -100,6 +100,13 @@ class CreateWriteRender(plugin.PypeCreator):
|
|||
"/{subset}.{frame}.{ext}")})
|
||||
|
||||
# add crop node to cut off all outside of format bounding box
|
||||
# get width and height
|
||||
try:
|
||||
width, height = (selected_node.width(), selected_node.height())
|
||||
except AttributeError:
|
||||
actual_format = nuke.root().knob('format').value()
|
||||
width, height = (actual_format.width(), actual_format.height())
|
||||
|
||||
_prenodes = [
|
||||
{
|
||||
"name": "Crop01",
|
||||
|
|
@ -108,8 +115,8 @@ class CreateWriteRender(plugin.PypeCreator):
|
|||
("box", [
|
||||
0.0,
|
||||
0.0,
|
||||
selected_node.width(),
|
||||
selected_node.height()
|
||||
width,
|
||||
height
|
||||
])
|
||||
],
|
||||
"dependent": None
|
||||
|
|
|
|||
26
openpype/hosts/photoshop/plugins/lib.py
Normal file
26
openpype/hosts/photoshop/plugins/lib.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import re
|
||||
|
||||
|
||||
def get_unique_layer_name(layers, asset_name, subset_name):
|
||||
"""
|
||||
Gets all layer names and if 'asset_name_subset_name' is present, it
|
||||
increases suffix by 1 (eg. creates unique layer name - for Loader)
|
||||
Args:
|
||||
layers (list) of dict with layers info (name, id etc.)
|
||||
asset_name (string):
|
||||
subset_name (string):
|
||||
|
||||
Returns:
|
||||
(string): name_00X (without version)
|
||||
"""
|
||||
name = "{}_{}".format(asset_name, subset_name)
|
||||
names = {}
|
||||
for layer in layers:
|
||||
layer_name = re.sub(r'_\d{3}$', '', layer.name)
|
||||
if layer_name in names.keys():
|
||||
names[layer_name] = names[layer_name] + 1
|
||||
else:
|
||||
names[layer_name] = 1
|
||||
occurrences = names.get(name, 0)
|
||||
|
||||
return "{}_{:0>3d}".format(name, occurrences + 1)
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
from avalon import api, photoshop
|
||||
import os
|
||||
import re
|
||||
|
||||
from avalon import api, photoshop
|
||||
|
||||
from openpype.hosts.photoshop.plugins.lib import get_unique_layer_name
|
||||
|
||||
stub = photoshop.stub()
|
||||
|
||||
|
||||
|
|
@ -15,8 +17,9 @@ class ImageLoader(api.Loader):
|
|||
representations = ["*"]
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
layer_name = self._get_unique_layer_name(context["asset"]["name"],
|
||||
name)
|
||||
layer_name = get_unique_layer_name(stub.get_layers(),
|
||||
context["asset"]["name"],
|
||||
name)
|
||||
with photoshop.maintained_selection():
|
||||
layer = stub.import_smart_object(self.fname, layer_name)
|
||||
|
||||
|
|
@ -69,25 +72,3 @@ class ImageLoader(api.Loader):
|
|||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def _get_unique_layer_name(self, asset_name, subset_name):
|
||||
"""
|
||||
Gets all layer names and if 'name' is present in them, increases
|
||||
suffix by 1 (eg. creates unique layer name - for Loader)
|
||||
Args:
|
||||
name (string): in format asset_subset
|
||||
|
||||
Returns:
|
||||
(string): name_00X (without version)
|
||||
"""
|
||||
name = "{}_{}".format(asset_name, subset_name)
|
||||
names = {}
|
||||
for layer in stub.get_layers():
|
||||
layer_name = re.sub(r'_\d{3}$', '', layer.name)
|
||||
if layer_name in names.keys():
|
||||
names[layer_name] = names[layer_name] + 1
|
||||
else:
|
||||
names[layer_name] = 1
|
||||
occurrences = names.get(name, 0)
|
||||
|
||||
return "{}_{:0>3d}".format(name, occurrences + 1)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
import os
|
||||
|
||||
from avalon import api
|
||||
from avalon import photoshop
|
||||
from avalon.pipeline import get_representation_path_from_context
|
||||
from avalon.vendor import qargparse
|
||||
|
||||
from openpype.lib import Anatomy
|
||||
from openpype.hosts.photoshop.plugins.lib import get_unique_layer_name
|
||||
|
||||
stub = photoshop.stub()
|
||||
|
||||
|
||||
class ImageFromSequenceLoader(api.Loader):
|
||||
""" Load specifing image from sequence
|
||||
|
||||
Used only as quick load of reference file from a sequence.
|
||||
|
||||
Plain ImageLoader picks first frame from sequence.
|
||||
|
||||
Loads only existing files - currently not possible to limit loaders
|
||||
to single select - multiselect. If user selects multiple repres, list
|
||||
for all of them is provided, but selection is only single file.
|
||||
This loader will be triggered multiple times, but selected name will
|
||||
match only to proper path.
|
||||
|
||||
Loader doesnt do containerization as there is currently no data model
|
||||
of 'frame of rendered files' (only rendered sequence), update would be
|
||||
difficult.
|
||||
"""
|
||||
|
||||
families = ["render"]
|
||||
representations = ["*"]
|
||||
options = []
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
if data.get("frame"):
|
||||
self.fname = os.path.join(os.path.dirname(self.fname),
|
||||
data["frame"])
|
||||
if not os.path.exists(self.fname):
|
||||
return
|
||||
|
||||
stub = photoshop.stub()
|
||||
layer_name = get_unique_layer_name(stub.get_layers(),
|
||||
context["asset"]["name"],
|
||||
name)
|
||||
|
||||
with photoshop.maintained_selection():
|
||||
layer = stub.import_smart_object(self.fname, layer_name)
|
||||
|
||||
self[:] = [layer]
|
||||
namespace = namespace or layer_name
|
||||
|
||||
return namespace
|
||||
|
||||
@classmethod
|
||||
def get_options(cls, repre_contexts):
|
||||
"""
|
||||
Returns list of files for selected 'repre_contexts'.
|
||||
|
||||
It returns only files with same extension as in context as it is
|
||||
expected that context points to sequence of frames.
|
||||
|
||||
Returns:
|
||||
(list) of qargparse.Choice
|
||||
"""
|
||||
files = []
|
||||
for context in repre_contexts:
|
||||
fname = get_representation_path_from_context(context)
|
||||
_, file_extension = os.path.splitext(fname)
|
||||
|
||||
for file_name in os.listdir(os.path.dirname(fname)):
|
||||
if not file_name.endswith(file_extension):
|
||||
continue
|
||||
files.append(file_name)
|
||||
|
||||
# return selection only if there is something
|
||||
if not files or len(files) <= 1:
|
||||
return []
|
||||
|
||||
return [
|
||||
qargparse.Choice(
|
||||
"frame",
|
||||
label="Select specific file",
|
||||
items=files,
|
||||
default=0,
|
||||
help="Which frame should be loaded?"
|
||||
)
|
||||
]
|
||||
|
||||
def update(self, container, representation):
|
||||
"""No update possible, not containerized."""
|
||||
pass
|
||||
|
||||
def remove(self, container):
|
||||
"""No update possible, not containerized."""
|
||||
pass
|
||||
|
||||
|
|
@ -34,7 +34,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin):
|
|||
|
||||
# presets
|
||||
batch_extensions = ["edl", "xml", "psd"]
|
||||
default_families = ["ftrack"]
|
||||
|
||||
def process(self, context):
|
||||
# get json paths from os and load them
|
||||
|
|
@ -213,10 +212,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin):
|
|||
subset = in_data["subset"]
|
||||
# If instance data already contain families then use it
|
||||
instance_families = in_data.get("families") or []
|
||||
# Make sure default families are in instance
|
||||
for default_family in self.default_families or []:
|
||||
if default_family not in instance_families:
|
||||
instance_families.append(default_family)
|
||||
|
||||
instance = context.create_instance(subset)
|
||||
instance.data.update(
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ class CollectInstances(pyblish.api.InstancePlugin):
|
|||
subsets = {
|
||||
"referenceMain": {
|
||||
"family": "review",
|
||||
"families": ["clip", "ftrack"],
|
||||
"families": ["clip"],
|
||||
"extensions": [".mp4"]
|
||||
},
|
||||
"audioMain": {
|
||||
"family": "audio",
|
||||
"families": ["clip", "ftrack"],
|
||||
"families": ["clip"],
|
||||
"extensions": [".wav"],
|
||||
},
|
||||
"shotMain": {
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
"""
|
||||
Requires:
|
||||
Nothing
|
||||
|
||||
Provides:
|
||||
Instance
|
||||
"""
|
||||
|
||||
import pyblish.api
|
||||
import logging
|
||||
|
||||
|
||||
log = logging.getLogger("collector")
|
||||
|
||||
|
||||
class CollectMatchmovePublish(pyblish.api.InstancePlugin):
|
||||
"""
|
||||
Collector with only one reason for its existence - remove 'ftrack'
|
||||
family implicitly added by Standalone Publisher
|
||||
"""
|
||||
|
||||
label = "Collect Matchmove - SA Publish"
|
||||
order = pyblish.api.CollectorOrder
|
||||
families = ["matchmove"]
|
||||
hosts = ["standalonepublisher"]
|
||||
|
||||
def process(self, instance):
|
||||
if "ftrack" in instance.data["families"]:
|
||||
instance.data["families"].remove("ftrack")
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import json
|
||||
import tempfile
|
||||
|
||||
import pyblish.api
|
||||
import avalon.api
|
||||
|
|
@ -153,9 +154,45 @@ class CollectWorkfileData(pyblish.api.ContextPlugin):
|
|||
"sceneMarkIn": int(mark_in_frame),
|
||||
"sceneMarkInState": mark_in_state == "set",
|
||||
"sceneMarkOut": int(mark_out_frame),
|
||||
"sceneMarkOutState": mark_out_state == "set"
|
||||
"sceneMarkOutState": mark_out_state == "set",
|
||||
"sceneBgColor": self._get_bg_color()
|
||||
}
|
||||
self.log.debug(
|
||||
"Scene data: {}".format(json.dumps(scene_data, indent=4))
|
||||
)
|
||||
context.data.update(scene_data)
|
||||
|
||||
def _get_bg_color(self):
|
||||
"""Background color set on scene.
|
||||
|
||||
Is important for review exporting where scene bg color is used as
|
||||
background.
|
||||
"""
|
||||
output_file = tempfile.NamedTemporaryFile(
|
||||
mode="w", prefix="a_tvp_", suffix=".txt", delete=False
|
||||
)
|
||||
output_file.close()
|
||||
output_filepath = output_file.name.replace("\\", "/")
|
||||
george_script_lines = [
|
||||
# Variable containing full path to output file
|
||||
"output_path = \"{}\"".format(output_filepath),
|
||||
"tv_background",
|
||||
"bg_color = result",
|
||||
# Write data to output file
|
||||
(
|
||||
"tv_writetextfile"
|
||||
" \"strict\" \"append\" '\"'output_path'\"' bg_color"
|
||||
)
|
||||
]
|
||||
|
||||
george_script = "\n".join(george_script_lines)
|
||||
lib.execute_george_through_file(george_script)
|
||||
|
||||
with open(output_filepath, "r") as stream:
|
||||
data = stream.read()
|
||||
|
||||
os.remove(output_filepath)
|
||||
data = data.strip()
|
||||
if not data:
|
||||
return None
|
||||
return data.split(" ")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import shutil
|
||||
import copy
|
||||
import tempfile
|
||||
|
||||
import pyblish.api
|
||||
|
|
@ -13,6 +14,9 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
hosts = ["tvpaint"]
|
||||
families = ["review", "renderPass", "renderLayer"]
|
||||
|
||||
# Modifiable with settings
|
||||
review_bg = [255, 255, 255, 255]
|
||||
|
||||
def process(self, instance):
|
||||
self.log.info(
|
||||
"* Processing instance \"{}\"".format(instance.data["label"])
|
||||
|
|
@ -53,6 +57,8 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
handle_start = instance.context.data["handleStart"]
|
||||
handle_end = instance.context.data["handleEnd"]
|
||||
|
||||
scene_bg_color = instance.context.data["sceneBgColor"]
|
||||
|
||||
# --- Fallbacks ----------------------------------------------------
|
||||
# This is required if validations of ranges are ignored.
|
||||
# - all of this code won't change processing if range to render
|
||||
|
|
@ -120,7 +126,8 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
|
||||
if instance.data["family"] == "review":
|
||||
output_filenames, thumbnail_fullpath = self.render_review(
|
||||
filename_template, output_dir, mark_in, mark_out
|
||||
filename_template, output_dir, mark_in, mark_out,
|
||||
scene_bg_color
|
||||
)
|
||||
else:
|
||||
# Render output
|
||||
|
|
@ -241,7 +248,9 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
for path in repre_filepaths
|
||||
]
|
||||
|
||||
def render_review(self, filename_template, output_dir, mark_in, mark_out):
|
||||
def render_review(
|
||||
self, filename_template, output_dir, mark_in, mark_out, scene_bg_color
|
||||
):
|
||||
""" Export images from TVPaint using `tv_savesequence` command.
|
||||
|
||||
Args:
|
||||
|
|
@ -252,6 +261,8 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
output_dir (str): Directory where files will be stored.
|
||||
mark_in (int): Starting frame index from which export will begin.
|
||||
mark_out (int): On which frame index export will end.
|
||||
scene_bg_color (list): Bg color set in scene. Result of george
|
||||
script command `tv_background`.
|
||||
|
||||
Retruns:
|
||||
tuple: With 2 items first is list of filenames second is path to
|
||||
|
|
@ -263,7 +274,11 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
filename_template.format(frame=mark_in)
|
||||
)
|
||||
|
||||
bg_color = self._get_review_bg_color()
|
||||
|
||||
george_script_lines = [
|
||||
# Change bg color to color from settings
|
||||
"tv_background \"color\" {} {} {}".format(*bg_color),
|
||||
"tv_SaveMode \"PNG\"",
|
||||
"export_path = \"{}\"".format(
|
||||
first_frame_filepath.replace("\\", "/")
|
||||
|
|
@ -272,6 +287,18 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
mark_in, mark_out
|
||||
)
|
||||
]
|
||||
if scene_bg_color:
|
||||
# Change bg color back to previous scene bg color
|
||||
_scene_bg_color = copy.deepcopy(scene_bg_color)
|
||||
bg_type = _scene_bg_color.pop(0)
|
||||
orig_color_command = [
|
||||
"tv_background",
|
||||
"\"{}\"".format(bg_type)
|
||||
]
|
||||
orig_color_command.extend(_scene_bg_color)
|
||||
|
||||
george_script_lines.append(" ".join(orig_color_command))
|
||||
|
||||
lib.execute_george_through_file("\n".join(george_script_lines))
|
||||
|
||||
first_frame_filepath = None
|
||||
|
|
@ -291,12 +318,13 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
if first_frame_filepath is None:
|
||||
first_frame_filepath = filepath
|
||||
|
||||
thumbnail_filepath = os.path.join(output_dir, "thumbnail.jpg")
|
||||
thumbnail_filepath = None
|
||||
if first_frame_filepath and os.path.exists(first_frame_filepath):
|
||||
thumbnail_filepath = os.path.join(output_dir, "thumbnail.jpg")
|
||||
source_img = Image.open(first_frame_filepath)
|
||||
thumbnail_obj = Image.new("RGB", source_img.size, (255, 255, 255))
|
||||
thumbnail_obj.paste(source_img)
|
||||
thumbnail_obj.save(thumbnail_filepath)
|
||||
if source_img.mode.lower() != "rgb":
|
||||
source_img = source_img.convert("RGB")
|
||||
source_img.save(thumbnail_filepath)
|
||||
|
||||
return output_filenames, thumbnail_filepath
|
||||
|
||||
|
|
@ -392,12 +420,35 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
if thumbnail_src_filepath and os.path.exists(thumbnail_src_filepath):
|
||||
source_img = Image.open(thumbnail_src_filepath)
|
||||
thumbnail_filepath = os.path.join(output_dir, "thumbnail.jpg")
|
||||
thumbnail_obj = Image.new("RGB", source_img.size, (255, 255, 255))
|
||||
thumbnail_obj.paste(source_img)
|
||||
thumbnail_obj.save(thumbnail_filepath)
|
||||
# Composite background only on rgba images
|
||||
# - just making sure
|
||||
if source_img.mode.lower() == "rgba":
|
||||
bg_color = self._get_review_bg_color()
|
||||
self.log.debug("Adding thumbnail background color {}.".format(
|
||||
" ".join([str(val) for val in bg_color])
|
||||
))
|
||||
bg_image = Image.new("RGBA", source_img.size, bg_color)
|
||||
thumbnail_obj = Image.alpha_composite(bg_image, source_img)
|
||||
thumbnail_obj.convert("RGB").save(thumbnail_filepath)
|
||||
|
||||
else:
|
||||
self.log.info((
|
||||
"Source for thumbnail has mode \"{}\" (Expected: RGBA)."
|
||||
" Can't use thubmanail background color."
|
||||
).format(source_img.mode))
|
||||
source_img.save(thumbnail_filepath)
|
||||
|
||||
return output_filenames, thumbnail_filepath
|
||||
|
||||
def _get_review_bg_color(self):
|
||||
red = green = blue = 255
|
||||
if self.review_bg:
|
||||
if len(self.review_bg) == 4:
|
||||
red, green, blue, _ = self.review_bg
|
||||
elif len(self.review_bg) == 3:
|
||||
red, green, blue = self.review_bg
|
||||
return (red, green, blue)
|
||||
|
||||
def _render_layer(
|
||||
self,
|
||||
layer,
|
||||
|
|
|
|||
|
|
@ -1,29 +1,107 @@
|
|||
"""
|
||||
Requires:
|
||||
none
|
||||
|
||||
Provides:
|
||||
instance -> families ([])
|
||||
"""
|
||||
import pyblish.api
|
||||
import avalon.api
|
||||
|
||||
from openpype.lib.plugin_tools import filter_profiles
|
||||
|
||||
|
||||
class CollectFtrackFamilies(pyblish.api.InstancePlugin):
|
||||
"""Collect family for ftrack publishing
|
||||
|
||||
Add ftrack family to those instance that should be published to ftrack
|
||||
|
||||
class CollectFtrackFamily(pyblish.api.InstancePlugin):
|
||||
"""
|
||||
Adds explicitly 'ftrack' to families to upload instance to FTrack.
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.3
|
||||
label = 'Add ftrack family'
|
||||
families = ["model",
|
||||
"setdress",
|
||||
"model",
|
||||
"animation",
|
||||
"look",
|
||||
"rig",
|
||||
"camera"
|
||||
]
|
||||
hosts = ["maya"]
|
||||
Uses selection by combination of hosts/families/tasks names via
|
||||
profiles resolution.
|
||||
|
||||
Triggered everywhere, checks instance against configured.
|
||||
|
||||
Checks advanced filtering which works on 'families' not on main
|
||||
'family', as some variants dynamically resolves addition of ftrack
|
||||
based on 'families' (editorial drives it by presence of 'review')
|
||||
"""
|
||||
label = "Collect Ftrack Family"
|
||||
order = pyblish.api.CollectorOrder + 0.4998
|
||||
|
||||
profiles = None
|
||||
|
||||
def process(self, instance):
|
||||
if not self.profiles:
|
||||
self.log.warning("No profiles present for adding Ftrack family")
|
||||
return
|
||||
|
||||
# make ftrack publishable
|
||||
if instance.data.get('families'):
|
||||
instance.data['families'].append('ftrack')
|
||||
task_name = instance.data.get("task",
|
||||
avalon.api.Session["AVALON_TASK"])
|
||||
host_name = avalon.api.Session["AVALON_APP"]
|
||||
family = instance.data["family"]
|
||||
|
||||
filtering_criteria = {
|
||||
"hosts": host_name,
|
||||
"families": family,
|
||||
"tasks": task_name
|
||||
}
|
||||
profile = filter_profiles(self.profiles, filtering_criteria,
|
||||
logger=self.log)
|
||||
|
||||
if profile:
|
||||
families = instance.data.get("families")
|
||||
add_ftrack_family = profile["add_ftrack_family"]
|
||||
|
||||
additional_filters = profile.get("additional_filters")
|
||||
if additional_filters:
|
||||
add_ftrack_family = self._get_add_ftrack_f_from_addit_filters(
|
||||
additional_filters,
|
||||
families,
|
||||
add_ftrack_family
|
||||
)
|
||||
|
||||
if add_ftrack_family:
|
||||
self.log.debug("Adding ftrack family for '{}'".
|
||||
format(instance.data.get("family")))
|
||||
|
||||
if families and "ftrack" not in families:
|
||||
instance.data["families"].append("ftrack")
|
||||
else:
|
||||
instance.data["families"] = ["ftrack"]
|
||||
else:
|
||||
instance.data['families'] = ['ftrack']
|
||||
self.log.debug("Instance '{}' doesn't match any profile".format(
|
||||
instance.data.get("family")))
|
||||
|
||||
def _get_add_ftrack_f_from_addit_filters(self,
|
||||
additional_filters,
|
||||
families,
|
||||
add_ftrack_family):
|
||||
"""
|
||||
Compares additional filters - working on instance's families.
|
||||
|
||||
Triggered for more detailed filtering when main family matches,
|
||||
but content of 'families' actually matter.
|
||||
(For example 'review' in 'families' should result in adding to
|
||||
Ftrack)
|
||||
|
||||
Args:
|
||||
additional_filters (dict) - from Setting
|
||||
families (list) - subfamilies
|
||||
add_ftrack_family (bool) - add ftrack to families if True
|
||||
"""
|
||||
override_filter = None
|
||||
override_filter_value = -1
|
||||
for additional_filter in additional_filters:
|
||||
filter_families = set(additional_filter["families"])
|
||||
valid = filter_families <= set(families) # issubset
|
||||
if not valid:
|
||||
continue
|
||||
|
||||
value = len(filter_families)
|
||||
if value > override_filter_value:
|
||||
override_filter = additional_filter
|
||||
override_filter_value = value
|
||||
|
||||
if override_filter:
|
||||
add_ftrack_family = override_filter["add_ftrack_family"]
|
||||
|
||||
return add_ftrack_family
|
||||
|
|
|
|||
|
|
@ -200,6 +200,68 @@
|
|||
}
|
||||
},
|
||||
"publish": {
|
||||
"CollectFtrackFamily": {
|
||||
"enabled": true,
|
||||
"profiles": [
|
||||
{
|
||||
"hosts": [
|
||||
"standalonepublisher"
|
||||
],
|
||||
"families": [],
|
||||
"tasks": [],
|
||||
"add_ftrack_family": true,
|
||||
"advanced_filtering": []
|
||||
},
|
||||
{
|
||||
"hosts": [
|
||||
"standalonepublisher"
|
||||
],
|
||||
"families": [
|
||||
"matchmove",
|
||||
"shot"
|
||||
],
|
||||
"tasks": [],
|
||||
"add_ftrack_family": false,
|
||||
"advanced_filtering": []
|
||||
},
|
||||
{
|
||||
"hosts": [
|
||||
"standalonepublisher"
|
||||
],
|
||||
"families": [
|
||||
"review",
|
||||
"plate"
|
||||
],
|
||||
"tasks": [],
|
||||
"add_ftrack_family": false,
|
||||
"advanced_filtering": [
|
||||
{
|
||||
"families": [
|
||||
"clip",
|
||||
"review"
|
||||
],
|
||||
"add_ftrack_family": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hosts": [
|
||||
"maya"
|
||||
],
|
||||
"families": [
|
||||
"model",
|
||||
"setdress",
|
||||
"animation",
|
||||
"look",
|
||||
"rig",
|
||||
"camera"
|
||||
],
|
||||
"tasks": [],
|
||||
"add_ftrack_family": true,
|
||||
"advanced_filtering": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"IntegrateFtrackNote": {
|
||||
"enabled": true,
|
||||
"note_with_intent_template": "{intent}: {comment}",
|
||||
|
|
|
|||
|
|
@ -105,16 +105,23 @@
|
|||
"label": "Render",
|
||||
"family": "render",
|
||||
"icon": "image",
|
||||
"defaults": ["Animation", "Lighting", "Lookdev", "Compositing"],
|
||||
"defaults": [
|
||||
"Animation",
|
||||
"Lighting",
|
||||
"Lookdev",
|
||||
"Compositing"
|
||||
],
|
||||
"help": "Rendered images or video files"
|
||||
},
|
||||
"create_mov_batch": {
|
||||
"name": "mov_batch",
|
||||
"label": "Batch Mov",
|
||||
"family": "render_mov_batch",
|
||||
"icon": "image",
|
||||
"defaults": ["Main"],
|
||||
"help": "Process multiple Mov files and publish them for layout and comp."
|
||||
"name": "mov_batch",
|
||||
"label": "Batch Mov",
|
||||
"family": "render_mov_batch",
|
||||
"icon": "image",
|
||||
"defaults": [
|
||||
"Main"
|
||||
],
|
||||
"help": "Process multiple Mov files and publish them for layout and comp."
|
||||
},
|
||||
"__dynamic_keys_labels__": {
|
||||
"create_workfile": "Workfile",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
{
|
||||
"publish": {
|
||||
"ExtractSequence": {
|
||||
"review_bg": [
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255
|
||||
]
|
||||
},
|
||||
"ValidateProjectSettings": {
|
||||
"enabled": true,
|
||||
"optional": true,
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ from .color_entity import ColorEntity
|
|||
from .enum_entity import (
|
||||
BaseEnumEntity,
|
||||
EnumEntity,
|
||||
HostsEnumEntity,
|
||||
AppsEnumEntity,
|
||||
ToolsEnumEntity,
|
||||
TaskTypeEnumEntity,
|
||||
|
|
@ -153,6 +154,7 @@ __all__ = (
|
|||
|
||||
"BaseEnumEntity",
|
||||
"EnumEntity",
|
||||
"HostsEnumEntity",
|
||||
"AppsEnumEntity",
|
||||
"ToolsEnumEntity",
|
||||
"TaskTypeEnumEntity",
|
||||
|
|
|
|||
|
|
@ -101,6 +101,78 @@ class EnumEntity(BaseEnumEntity):
|
|||
super(EnumEntity, self).schema_validations()
|
||||
|
||||
|
||||
class HostsEnumEntity(BaseEnumEntity):
|
||||
"""Enumeration of host names.
|
||||
|
||||
Enum items are hardcoded in definition of the entity.
|
||||
|
||||
Hosts enum can have defined empty value as valid option which is
|
||||
represented by empty string. Schema key to set this option is
|
||||
`use_empty_value` (true/false). And to set label of empty value set
|
||||
`empty_label` (string).
|
||||
|
||||
Enum can have single and multiselection.
|
||||
|
||||
NOTE:
|
||||
Host name is not the same as application name. Host name defines
|
||||
implementation instead of application name.
|
||||
"""
|
||||
schema_types = ["hosts-enum"]
|
||||
|
||||
def _item_initalization(self):
|
||||
self.multiselection = self.schema_data.get("multiselection", True)
|
||||
self.use_empty_value = self.schema_data.get(
|
||||
"use_empty_value", not self.multiselection
|
||||
)
|
||||
custom_labels = self.schema_data.get("custom_labels") or {}
|
||||
|
||||
host_names = [
|
||||
"aftereffects",
|
||||
"blender",
|
||||
"celaction",
|
||||
"fusion",
|
||||
"harmony",
|
||||
"hiero",
|
||||
"houdini",
|
||||
"maya",
|
||||
"nuke",
|
||||
"photoshop",
|
||||
"resolve",
|
||||
"tvpaint",
|
||||
"unreal"
|
||||
]
|
||||
if self.use_empty_value:
|
||||
host_names.insert(0, "")
|
||||
# Add default label for empty value if not available
|
||||
if "" not in custom_labels:
|
||||
custom_labels[""] = "< without host >"
|
||||
|
||||
# These are hardcoded there is not list of available host in OpenPype
|
||||
enum_items = []
|
||||
valid_keys = set()
|
||||
for key in host_names:
|
||||
label = custom_labels.get(key, key)
|
||||
valid_keys.add(key)
|
||||
enum_items.append({key: label})
|
||||
|
||||
self.enum_items = enum_items
|
||||
self.valid_keys = valid_keys
|
||||
|
||||
if self.multiselection:
|
||||
self.valid_value_types = (list, )
|
||||
self.value_on_not_set = []
|
||||
else:
|
||||
for key in valid_keys:
|
||||
if self.value_on_not_set is NOT_SET:
|
||||
self.value_on_not_set = key
|
||||
break
|
||||
|
||||
self.valid_value_types = (STRING_TYPE, )
|
||||
|
||||
# GUI attribute
|
||||
self.placeholder = self.schema_data.get("placeholder")
|
||||
|
||||
|
||||
class AppsEnumEntity(BaseEnumEntity):
|
||||
schema_types = ["apps-enum"]
|
||||
|
||||
|
|
|
|||
|
|
@ -272,6 +272,25 @@
|
|||
}
|
||||
```
|
||||
|
||||
### hosts-enum
|
||||
- enumeration of available hosts
|
||||
- multiselection can be allowed with setting key `"multiselection"` to `True` (Default: `False`)
|
||||
- it is possible to add empty value (represented with empty string) with setting `"use_empty_value"` to `True` (Default: `False`)
|
||||
- it is possible to set `"custom_labels"` for host names where key `""` is empty value (Default: `{}`)
|
||||
```
|
||||
{
|
||||
"key": "host",
|
||||
"label": "Host name",
|
||||
"type": "hosts-enum",
|
||||
"multiselection": false,
|
||||
"use_empty_value": true,
|
||||
"custom_labels": {
|
||||
"": "N/A",
|
||||
"nuke": "Nuke"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Inputs for setting value using Pure inputs
|
||||
- these inputs also have required `"key"`
|
||||
- attribute `"label"` is required in few conditions
|
||||
|
|
|
|||
|
|
@ -604,6 +604,82 @@
|
|||
"key": "publish",
|
||||
"label": "Publish plugins",
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"key": "CollectFtrackFamily",
|
||||
"label": "Collect Ftrack Family",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"collapsible": true,
|
||||
"key": "profiles",
|
||||
"label": "Profiles",
|
||||
"use_label_wrap": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "hosts",
|
||||
"label": "Host names",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"key": "families",
|
||||
"label": "Families",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"key": "tasks",
|
||||
"label": "Task names",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"key": "add_ftrack_family",
|
||||
"label": "Add Ftrack Family",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"collapsible": true,
|
||||
"key": "advanced_filtering",
|
||||
"label": "Advanced adding if additional families present",
|
||||
"use_label_wrap": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "families",
|
||||
"label": "Additional Families",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"key": "add_ftrack_family",
|
||||
"label": "Add Ftrack Family",
|
||||
"type": "boolean"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
|
|
|
|||
|
|
@ -59,10 +59,10 @@
|
|||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "hosts-enum",
|
||||
"key": "hosts",
|
||||
"label": "Host names",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
"multiselection": true
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,25 @@
|
|||
"key": "publish",
|
||||
"label": "Publish plugins",
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "ExtractSequence",
|
||||
"label": "ExtractSequence",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "label",
|
||||
"label": "<b>Review BG color</b> is used for whole scene review and for thumbnails."
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"key": "review_bg",
|
||||
"label": "Review BG color",
|
||||
"use_alpha": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_publish_plugin",
|
||||
|
|
|
|||
|
|
@ -90,10 +90,10 @@
|
|||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "hosts-enum",
|
||||
"key": "hosts",
|
||||
"label": "Hosts",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
"multiselection": true
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
|
|
@ -358,10 +358,10 @@
|
|||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "hosts-enum",
|
||||
"key": "hosts",
|
||||
"label": "Hosts",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
"multiselection": true
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
|
|
@ -492,10 +492,10 @@
|
|||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "hosts-enum",
|
||||
"key": "hosts",
|
||||
"label": "Hosts",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
"multiselection": true
|
||||
},
|
||||
{
|
||||
"key": "tasks",
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@
|
|||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "hosts-enum",
|
||||
"key": "hosts",
|
||||
"label": "Hosts",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
"multiselection": true
|
||||
},
|
||||
{
|
||||
"key": "tasks",
|
||||
|
|
@ -75,10 +75,10 @@
|
|||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"type": "hosts-enum",
|
||||
"key": "hosts",
|
||||
"label": "Hosts",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
"multiselection": true
|
||||
},
|
||||
{
|
||||
"key": "tasks",
|
||||
|
|
|
|||
|
|
@ -14,25 +14,11 @@
|
|||
"roles": ["developer"]
|
||||
},
|
||||
{
|
||||
"type": "enum",
|
||||
"type": "hosts-enum",
|
||||
"key": "host_name",
|
||||
"label": "Host implementation",
|
||||
"enum_items": [
|
||||
{ "": "< without host >" },
|
||||
{ "aftereffects": "aftereffects" },
|
||||
{ "blender": "blender" },
|
||||
{ "celaction": "celaction" },
|
||||
{ "fusion": "fusion" },
|
||||
{ "harmony": "harmony" },
|
||||
{ "hiero": "hiero" },
|
||||
{ "houdini": "houdini" },
|
||||
{ "maya": "maya" },
|
||||
{ "nuke": "nuke" },
|
||||
{ "photoshop": "photoshop" },
|
||||
{ "resolve": "resolve" },
|
||||
{ "tvpaint": "tvpaint" },
|
||||
{ "unreal": "unreal" }
|
||||
],
|
||||
"multiselection": false,
|
||||
"use_empty_value": true,
|
||||
"roles": ["developer"]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
2
poetry.lock
generated
2
poetry.lock
generated
|
|
@ -11,7 +11,7 @@ develop = false
|
|||
type = "git"
|
||||
url = "https://github.com/pypeclub/acre.git"
|
||||
reference = "master"
|
||||
resolved_reference = "efc1b8faa8f84568538b936688ae6f7604dd194c"
|
||||
resolved_reference = "68784b7eb5b7bb5f409b61ab31d4403878a3e1b7"
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ All of them are Project based, eg. each project could have different configurati
|
|||
|
||||
Location: Settings > Project > AfterEffects
|
||||
|
||||

|
||||

|
||||
|
||||
## Publish plugins
|
||||
|
||||
|
|
|
|||
BIN
website/docs/assets/ftrack/ftrack-collect-advanced.png
Normal file
BIN
website/docs/assets/ftrack/ftrack-collect-advanced.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
website/docs/assets/ftrack/ftrack-collect-main.png
Normal file
BIN
website/docs/assets/ftrack/ftrack-collect-main.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
|
|
@ -196,7 +196,7 @@ Is used to remove value from `Avalon/Mongo Id` Custom Attribute when entity is c
|
|||
|
||||
### Sync status from Task to Parent
|
||||
|
||||
List of parent boject types where this is triggered ("Shot", "Asset build", etc. Skipped if it is empty)
|
||||
List of parent object types where this is triggered ("Shot", "Asset build", etc. Skipped if it is empty)
|
||||
|
||||
### Sync status from Version to Task
|
||||
|
||||
|
|
@ -214,3 +214,29 @@ This is usefull for example if first version publish doesn't contain any actual
|
|||
|
||||
### Update status on next task
|
||||
Change status on next task by task types order when task status state changed to "Done". All tasks with the same Task mapping of next task status changes From → To. Some status can be ignored.
|
||||
|
||||
## Publish plugins
|
||||
|
||||
### Collect Ftrack Family
|
||||
|
||||
Reviews uploads to Ftrack could be configured by combination of hosts, families and task names.
|
||||
(Currently implemented only in Standalone Publisher, Maya.)
|
||||
|
||||
#### Profiles
|
||||
|
||||
Profiles are used to select when to add Ftrack family to the instance. One or multiple profiles could be configured, Families, Task names (regex available), Host names combination is needed.
|
||||
|
||||
Eg. If I want review created and uploaded to Ftrack for render published from Maya , setting is:
|
||||
|
||||
Host names: 'Maya'
|
||||
Families: 'render'
|
||||
Add Ftrack Family: enabled
|
||||
|
||||

|
||||
|
||||
#### Advanced adding if additional families present
|
||||
|
||||
In special cases adding 'ftrack' based on main family ('Families' set higher) is not enough.
|
||||
(For example upload to Ftrack for 'plate' main family should only happen if 'review' is contained in instance 'families', not added in other cases. )
|
||||
|
||||

|
||||
Loading…
Add table
Add a link
Reference in a new issue