Merge branch 'develop' into feature/next_task_update_with_settings

This commit is contained in:
iLLiCiTiT 2020-12-22 12:32:33 +01:00
commit a794043b7c
61 changed files with 707 additions and 378 deletions

View file

@ -32,6 +32,9 @@ Attributes:
ImagePrefixes (dict): Mapping between renderers and their respective
image prefix atrribute names.
Todo:
Determine `multipart` from render instance.
"""
import types
@ -94,6 +97,10 @@ class ExpectedFiles:
multipart = False
def __init__(self, render_instance):
"""Constructor."""
self._render_instance = render_instance
def get(self, renderer, layer):
"""Get expected files for given renderer and render layer.
@ -114,15 +121,20 @@ class ExpectedFiles:
renderSetup.instance().switchToLayerUsingLegacyName(layer)
if renderer.lower() == "arnold":
return self._get_files(ExpectedFilesArnold(layer))
return self._get_files(ExpectedFilesArnold(layer,
self._render_instance))
elif renderer.lower() == "vray":
return self._get_files(ExpectedFilesVray(layer))
return self._get_files(ExpectedFilesVray(
layer, self._render_instance))
elif renderer.lower() == "redshift":
return self._get_files(ExpectedFilesRedshift(layer))
return self._get_files(ExpectedFilesRedshift(
layer, self._render_instance))
elif renderer.lower() == "mentalray":
return self._get_files(ExpectedFilesMentalray(layer))
return self._get_files(ExpectedFilesMentalray(
layer, self._render_instance))
elif renderer.lower() == "renderman":
return self._get_files(ExpectedFilesRenderman(layer))
return self._get_files(ExpectedFilesRenderman(
layer, self._render_instance))
else:
raise UnsupportedRendererException(
"unsupported {}".format(renderer)
@ -149,9 +161,10 @@ class AExpectedFiles:
layer = None
multipart = False
def __init__(self, layer):
def __init__(self, layer, render_instance):
"""Constructor."""
self.layer = layer
self.render_instance = render_instance
@abstractmethod
def get_aovs(self):
@ -460,9 +473,9 @@ class ExpectedFilesArnold(AExpectedFiles):
"maya": "",
}
def __init__(self, layer):
def __init__(self, layer, render_instance):
"""Constructor."""
super(ExpectedFilesArnold, self).__init__(layer)
super(ExpectedFilesArnold, self).__init__(layer, render_instance)
self.renderer = "arnold"
def get_aovs(self):
@ -531,9 +544,9 @@ class ExpectedFilesArnold(AExpectedFiles):
class ExpectedFilesVray(AExpectedFiles):
"""Expected files for V-Ray renderer."""
def __init__(self, layer):
def __init__(self, layer, render_instance):
"""Constructor."""
super(ExpectedFilesVray, self).__init__(layer)
super(ExpectedFilesVray, self).__init__(layer, render_instance)
self.renderer = "vray"
def get_renderer_prefix(self):
@ -614,24 +627,25 @@ class ExpectedFilesVray(AExpectedFiles):
if default_ext == "exr (multichannel)" or default_ext == "exr (deep)":
default_ext = "exr"
# add beauty as default
enabled_aovs.append(
(u"beauty", default_ext)
)
if not self.maya_is_true(
cmds.getAttr("vraySettings.relements_enableall")
):
return enabled_aovs
# handle aovs from references
use_ref_aovs = self.render_instance.data.get(
"vrayUseReferencedAovs", False) or False
# filter all namespace prefixed AOVs - they are pulled in from
# references and are not rendered.
vr_aovs = [
n
for n in cmds.ls(
type=["VRayRenderElement", "VRayRenderElementSet"]
)
if len(n.split(":")) == 1
]
# this will have list of all aovs no matter if they are coming from
# reference or not.
vr_aovs = cmds.ls(
type=["VRayRenderElement", "VRayRenderElementSet"]) or []
if not use_ref_aovs:
ref_aovs = cmds.ls(
type=["VRayRenderElement", "VRayRenderElementSet"],
referencedNodes=True) or []
# get difference
vr_aovs = list(set(vr_aovs) - set(ref_aovs))
for aov in vr_aovs:
enabled = self.maya_is_true(cmds.getAttr("{}.enabled".format(aov)))
@ -703,9 +717,9 @@ class ExpectedFilesRedshift(AExpectedFiles):
ext_mapping = ["iff", "exr", "tif", "png", "tga", "jpg"]
def __init__(self, layer):
def __init__(self, layer, render_instance):
"""Construtor."""
super(ExpectedFilesRedshift, self).__init__(layer)
super(ExpectedFilesRedshift, self).__init__(layer, render_instance)
self.renderer = "redshift"
def get_renderer_prefix(self):
@ -822,9 +836,9 @@ class ExpectedFilesRenderman(AExpectedFiles):
This is very rudimentary and needs more love and testing.
"""
def __init__(self, layer):
def __init__(self, layer, render_instance):
"""Constructor."""
super(ExpectedFilesRenderman, self).__init__(layer)
super(ExpectedFilesRenderman, self).__init__(layer, render_instance)
self.renderer = "renderman"
def get_aovs(self):
@ -887,7 +901,7 @@ class ExpectedFilesRenderman(AExpectedFiles):
class ExpectedFilesMentalray(AExpectedFiles):
"""Skeleton unimplemented class for Mentalray renderer."""
def __init__(self, layer):
def __init__(self, layer, render_instance):
"""Constructor.
Raises:

View file

@ -56,7 +56,11 @@ from .plugin_tools import (
filter_pyblish_plugins,
source_hash,
get_unique_layer_name,
get_background_layers
get_background_layers,
oiio_supported,
decompress,
get_decompress_dir,
should_decompress
)
from .user_settings import (
@ -108,6 +112,10 @@ __all__ = [
"source_hash",
"get_unique_layer_name",
"get_background_layers",
"oiio_supported",
"decompress",
"get_decompress_dir",
"should_decompress",
"version_up",
"get_version_from_path",

View file

@ -5,6 +5,9 @@ import inspect
import logging
import re
import json
import tempfile
from . import execute
from pype.settings import get_project_settings
@ -134,3 +137,115 @@ def get_background_layers(file_url):
layer.get("filename")).
replace("\\", "/"))
return layers
def oiio_supported():
"""
Checks if oiiotool is configured for this platform.
Expects full path to executable.
'should_decompress' will throw exception if configured,
but not present or not working.
Returns:
(bool)
"""
oiio_path = os.getenv("PYPE_OIIO_PATH", "")
if not oiio_path or not os.path.exists(oiio_path):
log.debug("OIIOTool is not configured or not present at {}".
format(oiio_path))
return False
return True
def decompress(target_dir, file_url,
input_frame_start=None, input_frame_end=None, log=None):
"""
Decompresses DWAA 'file_url' .exr to 'target_dir'.
Creates uncompressed files in 'target_dir', they need to be cleaned.
File url could be for single file or for a sequence, in that case
%0Xd will be as a placeholder for frame number AND input_frame* will
be filled.
In that case single oiio command with '--frames' will be triggered for
all frames, this should be faster then looping and running sequentially
Args:
target_dir (str): extended from stagingDir
file_url (str): full urls to source file (with or without %0Xd)
input_frame_start (int) (optional): first frame
input_frame_end (int) (optional): last frame
log (Logger) (optional): pype logger
"""
is_sequence = input_frame_start is not None and \
input_frame_end is not None and \
(int(input_frame_end) > int(input_frame_start))
oiio_cmd = []
oiio_cmd.append(os.getenv("PYPE_OIIO_PATH"))
oiio_cmd.append("--compression none")
base_file_name = os.path.basename(file_url)
oiio_cmd.append(file_url)
if is_sequence:
oiio_cmd.append("--frames {}-{}".format(input_frame_start,
input_frame_end))
oiio_cmd.append("-o")
oiio_cmd.append(os.path.join(target_dir, base_file_name))
subprocess_exr = " ".join(oiio_cmd)
if not log:
log = logging.getLogger(__name__)
log.debug("Decompressing {}".format(subprocess_exr))
execute.execute(
subprocess_exr, shell=True, logger=log
)
def get_decompress_dir():
"""
Creates temporary folder for decompressing.
Its local, in case of farm it is 'local' to the farm machine.
Should be much faster, needs to be cleaned up later.
"""
return os.path.normpath(
tempfile.mkdtemp(prefix="pyblish_tmp_")
)
def should_decompress(file_url):
"""
Tests that 'file_url' is compressed with DWAA.
Uses 'oiio_supported' to check that OIIO tool is available for this
platform.
Shouldn't throw exception as oiiotool is guarded by check function.
Currently implemented this way as there is no support for Mac and Linux
In the future, it should be more strict and throws exception on
misconfiguration.
Args:
file_url (str): path to rendered file (in sequence it would be
first file, if that compressed it is expected that whole seq
will be too)
Returns:
(bool): 'file_url' is DWAA compressed and should be decompressed
and we can decompress (oiiotool supported)
"""
if oiio_supported():
output = execute.execute([
os.getenv("PYPE_OIIO_PATH"),
"--info", "-v", file_url])
return "compression: \"dwaa\"" in output or \
"compression: \"dwab\"" in output
return False

View file

@ -32,7 +32,7 @@ from .ftrack import (
IFtrackEventHandlerPaths
)
from .clockify import ClockifyModule
from .logging import LoggingModule
from .log_viewer import LogViewModule
from .muster import MusterModule
from .standalonepublish_action import StandAlonePublishAction
from .websocket_server import WebsocketModule
@ -70,7 +70,7 @@ __all__ = (
"ClockifyModule",
"IdleManager",
"LoggingModule",
"LogViewModule",
"MusterModule",
"StandAlonePublishAction",

View file

@ -28,8 +28,8 @@ class AppplicationsAction(BaseAction):
identifier = "pype_app.{}.".format(str(uuid4()))
icon_url = os.environ.get("PYPE_STATICS_SERVER")
def __init__(self, session, plugins_presets=None):
super().__init__(session, plugins_presets)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.application_manager = ApplicationManager()
self.dbcon = AvalonMongoDB()
@ -210,6 +210,6 @@ class AppplicationsAction(BaseAction):
}
def register(session, plugins_presets=None):
def register(session):
"""Register action. Called when used as an event plugin."""
AppplicationsAction(session, plugins_presets).register()
AppplicationsAction(session).register()

View file

@ -158,7 +158,7 @@ class BatchTasksAction(BaseAction):
}
def register(session, plugins_presets=None):
def register(session):
'''Register action. Called when used as an event plugin.'''
BatchTasksAction(session, plugins_presets).register()
BatchTasksAction(session).register()

View file

@ -9,7 +9,6 @@ class CleanHierarchicalAttrsAction(BaseAction):
label = "Pype Admin"
variant = "- Clean hierarchical custom attributes"
description = "Unset empty hierarchical attribute values."
role_list = ["Pypeclub", "Administrator", "Project Manager"]
icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg")
all_project_entities_query = (
@ -20,12 +19,17 @@ class CleanHierarchicalAttrsAction(BaseAction):
"select value, entity_id from CustomAttributeValue "
"where entity_id in ({}) and configuration_id is \"{}\""
)
settings_key = "clean_hierarchical_attr"
def discover(self, session, entities, event):
"""Show only on project entity."""
if len(entities) == 1 and entities[0].entity_type.lower() == "project":
return True
return False
if (
len(entities) != 1
or entities[0].entity_type.lower() != "project"
):
return False
return self.valid_roles(session, entities, event)
def launch(self, session, entities, event):
project = entities[0]
@ -98,7 +102,7 @@ class CleanHierarchicalAttrsAction(BaseAction):
return True
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
CleanHierarchicalAttrsAction(session, plugins_presets).register()
CleanHierarchicalAttrsAction(session).register()

View file

@ -84,7 +84,7 @@ class ClientReviewSort(BaseAction):
}
def register(session, plugins_presets={}):
def register(session):
'''Register action. Called when used as an event plugin.'''
ClientReviewSort(session, plugins_presets).register()
ClientReviewSort(session).register()

View file

@ -60,7 +60,7 @@ class ComponentOpen(BaseAction):
}
def register(session, plugins_presets={}):
def register(session):
'''Register action. Called when used as an event plugin.'''
ComponentOpen(session, plugins_presets).register()
ComponentOpen(session).register()

View file

@ -131,9 +131,8 @@ class CustomAttributes(BaseAction):
variant = '- Create/Update Avalon Attributes'
#: Action description.
description = 'Creates Avalon/Mongo ID for double check'
#: roles that are allowed to register this action
role_list = ['Pypeclub', 'Administrator']
icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg")
settings_key = "create_update_attributes"
required_keys = ("key", "label", "type")
@ -150,7 +149,7 @@ class CustomAttributes(BaseAction):
Validation
- action is only for Administrators
'''
return True
return self.valid_roles(session, entities, event)
def launch(self, session, entities, event):
# JOB SETTINGS
@ -814,7 +813,7 @@ class CustomAttributes(BaseAction):
}
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
CustomAttributes(session, plugins_presets).register()
CustomAttributes(session).register()

View file

@ -243,6 +243,6 @@ class CreateFolders(BaseAction):
return os.path.normpath(filled_template.split("{")[0])
def register(session, plugins_presets={}):
def register(session):
"""Register plugin. Called when used as an plugin."""
CreateFolders(session, plugins_presets).register()
CreateFolders(session).register()

View file

@ -238,5 +238,5 @@ class CreateProjectFolders(BaseAction):
os.makedirs(path.format(project_root=project_root))
def register(session, plugins_presets={}):
CreateProjectFolders(session, plugins_presets).register()
def register(session):
CreateProjectFolders(session).register()

View file

@ -18,8 +18,8 @@ class DeleteAssetSubset(BaseAction):
#: Action description.
description = "Removes from Avalon with all childs and asset from Ftrack"
icon = statics_icon("ftrack", "action_icons", "DeleteAsset.svg")
#: roles that are allowed to register this action
role_list = ["Pypeclub", "Administrator", "Project Manager"]
settings_key = "delete_asset_subset"
#: Db connection
dbcon = AvalonMongoDB()
@ -32,17 +32,21 @@ class DeleteAssetSubset(BaseAction):
""" Validation """
task_ids = []
for ent_info in event["data"]["selection"]:
entType = ent_info.get("entityType", "")
if entType == "task":
if ent_info.get("entityType") == "task":
task_ids.append(ent_info["entityId"])
is_valid = False
for entity in entities:
ftrack_id = entity["id"]
if ftrack_id not in task_ids:
continue
if entity.entity_type.lower() != "task":
return True
return False
if (
entity["id"] in task_ids
and entity.entity_type.lower() != "task"
):
is_valid = True
break
if is_valid:
is_valid = self.valid_roles(session, entities, event)
return is_valid
def _launch(self, event):
try:
@ -662,7 +666,7 @@ class DeleteAssetSubset(BaseAction):
}
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
DeleteAssetSubset(session, plugins_presets).register()
DeleteAssetSubset(session).register()

View file

@ -21,7 +21,6 @@ class DeleteOldVersions(BaseAction):
"Delete files from older publishes so project can be"
" archived with only lates versions."
)
role_list = ["Pypeclub", "Project Manager", "Administrator"]
icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg")
dbcon = AvalonMongoDB()
@ -31,13 +30,16 @@ class DeleteOldVersions(BaseAction):
sequence_splitter = "__sequence_splitter__"
def discover(self, session, entities, event):
''' Validation '''
selection = event["data"].get("selection") or []
for entity in selection:
entity_type = (entity.get("entityType") or "").lower()
if entity_type == "assetversion":
return True
return False
""" Validation. """
is_valid = False
for entity in entities:
if entity.entity_type.lower() == "assetversion":
is_valid = True
break
if is_valid:
is_valid = self.valid_roles(session, entities, event)
return is_valid
def interface(self, session, entities, event):
# TODO Add roots existence validation
@ -577,7 +579,7 @@ class DeleteOldVersions(BaseAction):
return (os.path.normpath(path), sequence_path)
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
DeleteOldVersions(session, plugins_presets).register()
DeleteOldVersions(session).register()

View file

@ -23,6 +23,7 @@ class Delivery(BaseAction):
description = "Deliver data to client"
role_list = ["Pypeclub", "Administrator", "Project manager"]
icon = statics_icon("ftrack", "action_icons", "Delivery.svg")
settings_key = "delivery_action"
def __init__(self, *args, **kwargs):
self.db_con = AvalonMongoDB()
@ -30,11 +31,15 @@ class Delivery(BaseAction):
super(Delivery, self).__init__(*args, **kwargs)
def discover(self, session, entities, event):
is_valid = False
for entity in entities:
if entity.entity_type.lower() == "assetversion":
return True
is_valid = True
break
return False
if is_valid:
is_valid = self.valid_roles(session, entities, event)
return is_valid
def interface(self, session, entities, event):
if event["data"].get("values", {}):
@ -692,7 +697,7 @@ class Delivery(BaseAction):
}
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
Delivery(session, plugins_presets).register()
Delivery(session).register()

View file

@ -20,9 +20,8 @@ class DJVViewAction(BaseAction):
"sgi", "rgba", "rgb", "bw", "tga", "tiff", "tif", "img"
]
def __init__(self, session, plugins_presets):
'''Expects a ftrack_api.Session instance'''
super().__init__(session, plugins_presets)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.djv_path = self.find_djv_path()
@ -208,7 +207,7 @@ class DJVViewAction(BaseAction):
return True
def register(session, plugins_presets={}):
def register(session):
"""Register hooks."""
DJVViewAction(session, plugins_presets).register()
DJVViewAction(session).register()

View file

@ -13,13 +13,12 @@ class JobKiller(BaseAction):
#: Action description.
description = 'Killing selected running jobs'
#: roles that are allowed to register this action
role_list = ['Pypeclub', 'Administrator']
icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg")
settings_key = "job_killer"
def discover(self, session, entities, event):
''' Validation '''
return True
return self.valid_roles(session, entities, event)
def interface(self, session, entities, event):
if not event['data'].get('values', {}):
@ -112,7 +111,7 @@ class JobKiller(BaseAction):
}
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
JobKiller(session, plugins_presets).register()
JobKiller(session).register()

View file

@ -104,7 +104,7 @@ class MultipleNotes(BaseAction):
return True
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
MultipleNotes(session, plugins_presets).register()
MultipleNotes(session).register()

View file

@ -16,22 +16,23 @@ class PrepareProject(BaseAction):
#: Action description.
description = 'Set basic attributes on the project'
#: roles that are allowed to register this action
role_list = ["Pypeclub", "Administrator", "Project manager"]
icon = statics_icon("ftrack", "action_icons", "PrepareProject.svg")
settings_key = "prepare_project"
# Key to store info about trigerring create folder structure
create_project_structure_key = "create_folder_structure"
item_splitter = {'type': 'label', 'value': '---'}
def discover(self, session, entities, event):
''' Validation '''
if len(entities) != 1:
if (
len(entities) != 1
or entities[0].entity_type.lower() != "project"
):
return False
if entities[0].entity_type.lower() != "project":
return False
return True
return self.valid_roles(session, entities, event)
def interface(self, session, entities, event):
if event['data'].get('values', {}):
@ -454,6 +455,6 @@ class PrepareProject(BaseAction):
self.log.debug("*** Creating project specifig configs Finished ***")
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
PrepareProject(session, plugins_presets).register()
PrepareProject(session).register()

View file

@ -19,13 +19,8 @@ class RVAction(BaseAction):
allowed_types = ["img", "mov", "exr", "mp4"]
def __init__(self, session, plugins_presets):
""" Constructor
:param session: ftrack Session
:type session: :class:`ftrack_api.Session`
"""
super().__init__(session, plugins_presets)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# QUESTION load RV application data from AppplicationManager?
rv_path = None
@ -317,7 +312,7 @@ class RVAction(BaseAction):
return paths
def register(session, plugins_presets={}):
def register(session):
"""Register hooks."""
RVAction(session, plugins_presets).register()
RVAction(session).register()

View file

@ -15,7 +15,6 @@ class SeedDebugProject(BaseAction):
#: priority
priority = 100
#: roles that are allowed to register this action
role_list = ["Pypeclub"]
icon = statics_icon("ftrack", "action_icons", "SeedProject.svg")
# Asset names which will be created in `Assets` entity
@ -58,9 +57,12 @@ class SeedDebugProject(BaseAction):
existing_projects = None
new_project_item = "< New Project >"
current_project_item = "< Current Project >"
settings_key = "seed_project"
def discover(self, session, entities, event):
''' Validation '''
if not self.valid_roles(session, entities, event):
return False
return True
def interface(self, session, entities, event):
@ -428,7 +430,7 @@ class SeedDebugProject(BaseAction):
return True
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
SeedDebugProject(session, plugins_presets).register()
SeedDebugProject(session).register()

View file

@ -21,8 +21,8 @@ class StoreThumbnailsToAvalon(BaseAction):
# Action description
description = 'Test action'
# roles that are allowed to register this action
role_list = ["Pypeclub", "Administrator", "Project Manager"]
icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg")
settings_key = "store_thubmnail_to_avalon"
thumbnail_key = "AVALON_THUMBNAIL_ROOT"
@ -31,10 +31,15 @@ class StoreThumbnailsToAvalon(BaseAction):
super(StoreThumbnailsToAvalon, self).__init__(*args, **kwargs)
def discover(self, session, entities, event):
is_valid = False
for entity in entities:
if entity.entity_type.lower() == "assetversion":
return True
return False
is_valid = True
break
if is_valid:
is_valid = self.valid_roles(session, entities, event)
return is_valid
def launch(self, session, entities, event):
user = session.query(
@ -457,5 +462,5 @@ class StoreThumbnailsToAvalon(BaseAction):
return output
def register(session, plugins_presets={}):
StoreThumbnailsToAvalon(session, plugins_presets).register()
def register(session):
StoreThumbnailsToAvalon(session).register()

View file

@ -41,20 +41,26 @@ class SyncToAvalonLocal(BaseAction):
#: priority
priority = 200
#: roles that are allowed to register this action
role_list = ["Pypeclub"]
icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg")
settings_key = "sync_to_avalon_local"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.entities_factory = SyncEntitiesFactory(self.log, self.session)
def discover(self, session, entities, event):
''' Validation '''
""" Validate selection. """
is_valid = False
for ent in event["data"]["selection"]:
# Ignore entities that are not tasks or projects
if ent["entityType"].lower() in ["show", "task"]:
return True
return False
is_valid = True
break
if is_valid:
is_valid = self.valid_roles(session, entities, event)
return is_valid
def launch(self, session, in_entities, event):
time_start = time.time()
@ -187,7 +193,7 @@ class SyncToAvalonLocal(BaseAction):
pass
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
SyncToAvalonLocal(session, plugins_presets).register()
SyncToAvalonLocal(session).register()

View file

@ -22,5 +22,5 @@ class TestAction(BaseAction):
return True
def register(session, plugins_presets={}):
TestAction(session, plugins_presets).register()
def register(session):
TestAction(session).register()

View file

@ -15,11 +15,9 @@ class ThumbToChildren(BaseAction):
icon = statics_icon("ftrack", "action_icons", "Thumbnail.svg")
def discover(self, session, entities, event):
''' Validation '''
if (len(entities) != 1 or entities[0].entity_type in ['Project']):
"""Show only on project."""
if (len(entities) != 1 or entities[0].entity_type in ["Project"]):
return False
return True
def launch(self, session, entities, event):
@ -59,7 +57,7 @@ class ThumbToChildren(BaseAction):
}
def register(session, plugins_presets={}):
def register(session):
'''Register action. Called when used as an event plugin.'''
ThumbToChildren(session, plugins_presets).register()
ThumbToChildren(session).register()

View file

@ -85,7 +85,7 @@ class ThumbToParent(BaseAction):
}
def register(session, plugins_presets={}):
def register(session):
'''Register action. Called when used as an event plugin.'''
ThumbToParent(session, plugins_presets).register()
ThumbToParent(session).register()

View file

@ -27,7 +27,7 @@ class ActionAskWhereIRun(BaseAction):
return True
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
ActionAskWhereIRun(session, plugins_presets).register()
ActionAskWhereIRun(session).register()

View file

@ -76,7 +76,7 @@ class ActionShowWhereIRun(BaseAction):
return True
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
ActionShowWhereIRun(session, plugins_presets).register()
ActionShowWhereIRun(session).register()

View file

@ -430,5 +430,5 @@ class PushHierValuesToNonHier(ServerAction):
session.commit()
def register(session, plugins_presets={}):
PushHierValuesToNonHier(session, plugins_presets).register()
def register(session):
PushHierValuesToNonHier(session).register()

View file

@ -182,6 +182,6 @@ class SyncToAvalonServer(ServerAction):
pass
def register(session, plugins_presets={}):
def register(session):
'''Register plugin. Called when used as an plugin.'''
SyncToAvalonServer(session, plugins_presets).register()
SyncToAvalonServer(session).register()

View file

@ -47,6 +47,6 @@ class DelAvalonIdFromNew(BaseEvent):
continue
def register(session, plugins_presets):
def register(session):
'''Register plugin. Called when used as an plugin.'''
DelAvalonIdFromNew(session, plugins_presets).register()
DelAvalonIdFromNew(session).register()

View file

@ -182,7 +182,7 @@ class FirstVersionStatus(BaseEvent):
return filtered_ents
def register(session, plugins_presets):
def register(session):
'''Register plugin. Called when used as an plugin.'''
FirstVersionStatus(session, plugins_presets).register()
FirstVersionStatus(session).register()

View file

@ -260,5 +260,5 @@ class NextTaskUpdate(BaseEvent):
return types
def register(session, plugins_presets):
NextTaskUpdate(session, plugins_presets).register()
def register(session):
NextTaskUpdate(session).register()

View file

@ -364,5 +364,5 @@ class PushFrameValuesToTaskEvent(BaseEvent):
return output, hiearchical
def register(session, plugins_presets):
PushFrameValuesToTaskEvent(session, plugins_presets).register()
def register(session):
PushFrameValuesToTaskEvent(session).register()

View file

@ -34,7 +34,7 @@ class RadioButtons(BaseEvent):
session.commit()
def register(session, plugins_presets):
def register(session):
'''Register plugin. Called when used as an plugin.'''
RadioButtons(session, plugins_presets).register()
RadioButtons(session).register()

View file

@ -53,7 +53,7 @@ class SyncToAvalonEvent(BaseEvent):
created_entities = []
report_splitter = {"type": "label", "value": "---"}
def __init__(self, session, plugins_presets={}):
def __init__(self, session):
'''Expects a ftrack_api.Session instance'''
# Debug settings
# - time expiration in seconds
@ -67,7 +67,7 @@ class SyncToAvalonEvent(BaseEvent):
self.dbcon = AvalonMongoDB()
# Set processing session to not use global
self.set_process_session(session)
super().__init__(session, plugins_presets)
super().__init__(session)
def debug_logs(self):
"""This is debug method for printing small debugs messages. """
@ -2513,6 +2513,6 @@ class SyncToAvalonEvent(BaseEvent):
return mongo_id_configuration_id
def register(session, plugins_presets):
def register(session):
'''Register plugin. Called when used as an plugin.'''
SyncToAvalonEvent(session, plugins_presets).register()
SyncToAvalonEvent(session).register()

View file

@ -61,8 +61,8 @@ class TaskStatusToParent(BaseEvent):
session, event, project_id
)
# Load settings
project_settings = self.get_settings_for_project(
session, event, project_entity=project_entity
project_settings = self.get_project_settings_from_event(
event, project_entity
)
# Prepare loaded settings and check if can be processed
@ -419,5 +419,5 @@ class TaskStatusToParent(BaseEvent):
return output
def register(session, plugins_presets):
TaskStatusToParent(session, plugins_presets).register()
def register(session):
TaskStatusToParent(session).register()

View file

@ -102,8 +102,8 @@ class TaskToVersionStatus(BaseEvent):
project_entity = self.get_project_entity_from_event(
session, event, project_id
)
project_settings = self.get_settings_for_project(
session, event, project_entity=project_entity
project_settings = self.get_project_settings_from_event(
event, project_entity
)
project_name = project_entity["full_name"]
@ -372,5 +372,5 @@ class TaskToVersionStatus(BaseEvent):
return last_asset_versions_by_task_id
def register(session, plugins_presets):
TaskToVersionStatus(session, plugins_presets).register()
def register(session):
TaskToVersionStatus(session).register()

View file

@ -1,7 +1,3 @@
import os
import sys
import re
import ftrack_api
from pype.modules.ftrack import BaseEvent
@ -20,7 +16,7 @@ class TestEvent(BaseEvent):
return True
def register(session, plugins_presets):
def register(session):
'''Register plugin. Called when used as an plugin.'''
TestEvent(session, plugins_presets).register()
TestEvent(session).register()

View file

@ -22,8 +22,8 @@ class ThumbnailEvents(BaseEvent):
project_entity = self.get_project_entity_from_event(
session, event, project_id
)
project_settings = self.get_settings_for_project(
session, event, project_entity=project_entity
project_settings = self.get_project_settings_from_event(
event, project_entity
)
project_name = project_entity["full_name"]
@ -151,5 +151,5 @@ class ThumbnailEvents(BaseEvent):
return filtered_entities_info
def register(session, plugins_presets):
ThumbnailEvents(session, plugins_presets).register()
def register(session):
ThumbnailEvents(session).register()

View file

@ -250,9 +250,9 @@ class UserAssigmentEvent(BaseEvent):
return True
def register(session, plugins_presets):
def register(session):
"""
Register plugin. Called when used as an plugin.
"""
UserAssigmentEvent(session, plugins_presets).register()
UserAssigmentEvent(session).register()

View file

@ -51,8 +51,8 @@ class VersionToTaskStatus(BaseEvent):
project_entity = self.get_project_entity_from_event(
session, event, project_id
)
project_settings = self.get_settings_for_project(
session, event, project_entity=project_entity
project_settings = self.get_project_settings_from_event(
event, project_entity
)
project_name = project_entity["full_name"]
@ -241,7 +241,7 @@ class VersionToTaskStatus(BaseEvent):
return output
def register(session, plugins_presets):
def register(session):
'''Register plugin. Called when used as an plugin.'''
VersionToTaskStatus(session, plugins_presets).register()
VersionToTaskStatus(session).register()

View file

@ -108,21 +108,10 @@ class FtrackServer:
" in registered paths: \"{}\""
).format("| ".join(paths)))
# TODO replace with settings or get rid of passing the dictionary
plugins_presets = {}
function_counter = 0
for function_dict in register_functions_dict:
register = function_dict["register"]
try:
if len(inspect.signature(register).parameters) == 1:
register(self.session)
else:
register(self.session, plugins_presets=plugins_presets)
if function_counter % 7 == 0:
time.sleep(0.1)
function_counter += 1
register(self.session)
except Exception as exc:
msg = '"{}" - register was not successful ({})'.format(
function_dict['name'], str(exc)

View file

@ -29,7 +29,9 @@ class BaseAction(BaseHandler):
icon = None
type = 'Action'
def __init__(self, session, plugins_presets={}):
settings_frack_subkey = "user_handlers"
def __init__(self, session):
'''Expects a ftrack_api.Session instance'''
if self.label is None:
raise ValueError('Action missing label.')
@ -37,7 +39,7 @@ class BaseAction(BaseHandler):
if self.identifier is None:
raise ValueError('Action missing identifier.')
super().__init__(session, plugins_presets)
super().__init__(session)
def register(self):
'''
@ -67,6 +69,9 @@ class BaseAction(BaseHandler):
def _discover(self, event):
entities = self._translate_event(event)
if not entities:
return
accepts = self.discover(self.session, entities, event)
if not accepts:
return
@ -146,21 +151,18 @@ class BaseAction(BaseHandler):
def _launch(self, event):
entities = self._translate_event(event)
if not entities:
return
preactions_launched = self._handle_preactions(self.session, event)
if preactions_launched is False:
return
interface = self._interface(
self.session, entities, event
)
interface = self._interface(self.session, entities, event)
if interface:
return interface
response = self.launch(
self.session, entities, event
)
response = self.launch(self.session, entities, event)
return self._handle_result(response)
@ -196,50 +198,29 @@ class BaseAction(BaseHandler):
return result
@staticmethod
def roles_check(settings_roles, user_roles, default=True):
"""Compare roles from setting and user's roles.
class ServerAction(BaseAction):
"""Action class meant to be used on event server.
Args:
settings_roles(list): List of role names from settings.
user_roles(list): User's lowered role names.
default(bool): If `settings_roles` is empty list.
Unlike the `BaseAction` roles are not checked on register but on discover.
For the same reason register is modified to not filter topics by username.
"""
Returns:
bool: `True` if user has at least one role from settings or
default if `settings_roles` is empty.
"""
if not settings_roles:
return default
def __init__(self, *args, **kwargs):
if not self.role_list:
self.role_list = set()
else:
self.role_list = set(
role_name.lower()
for role_name in self.role_list
)
super(ServerAction, self).__init__(*args, **kwargs)
def _register_role_check(self):
# Skip register role check.
return
def _discover(self, event):
"""Check user discover availability."""
if not self._check_user_discover(event):
return
return super(ServerAction, self)._discover(event)
def _check_user_discover(self, event):
"""Should be action discovered by user trying to show actions."""
if not self.role_list:
return True
user_entity = self._get_user_entity(event)
if not user_entity:
return False
for role in user_entity["user_security_roles"]:
lowered_role = role["security_role"]["name"].lower()
if lowered_role in self.role_list:
for role_name in settings_roles:
if role_name.lower() in user_roles:
return True
return False
def _get_user_entity(self, event):
@classmethod
def get_user_entity_from_event(cls, session, event):
"""Query user entity from event."""
not_set = object()
@ -251,17 +232,88 @@ class ServerAction(BaseAction):
user_id = user_info.get("id")
username = user_info.get("username")
if user_id:
user_entity = self.session.query(
user_entity = session.query(
"User where id is {}".format(user_id)
).first()
if not user_entity and username:
user_entity = self.session.query(
user_entity = session.query(
"User where username is {}".format(username)
).first()
event["data"]["user_entity"] = user_entity
return user_entity
@classmethod
def get_user_roles_from_event(cls, session, event):
"""Query user entity from event."""
not_set = object()
user_roles = event["data"].get("user_roles", not_set)
if user_roles is not_set:
user_roles = []
user_entity = cls.get_user_entity_from_event(session, event)
for role in user_entity["user_security_roles"]:
user_roles.append(role["security_role"]["name"].lower())
event["data"]["user_roles"] = user_roles
return user_roles
def get_project_entity_from_event(self, session, event, entities):
"""Load or query and fill project entity from/to event data.
Project data are stored by ftrack id because in most cases it is
easier to access project id than project name.
Args:
session (ftrack_api.Session): Current session.
event (ftrack_api.Event): Processed event by session.
entities (list): Ftrack entities of selection.
"""
# Try to get project entity from event
project_entity = event["data"].get("project_entity")
if not project_entity:
project_entity = self.get_project_from_entity(
entities[0], session
)
event["data"]["project_entity"] = project_entity
return project_entity
def get_ftrack_settings(self, session, event, entities):
project_entity = self.get_project_entity_from_event(
session, event, entities
)
project_settings = self.get_project_settings_from_event(
event, project_entity
)
return project_settings["ftrack"]
def valid_roles(self, session, entities, event):
"""Validate user roles by settings.
Method requires to have set `settings_key` attribute.
"""
ftrack_settings = self.get_ftrack_settings(session, event, entities)
settings = (
ftrack_settings[self.settings_frack_subkey][self.settings_key]
)
if not settings.get("enabled", True):
return False
user_role_list = self.get_user_roles_from_event(session, event)
if not self.roles_check(settings.get("role_list"), user_role_list):
return False
return True
class ServerAction(BaseAction):
"""Action class meant to be used on event server.
Unlike the `BaseAction` roles are not checked on register but on discover.
For the same reason register is modified to not filter topics by username.
"""
settings_frack_subkey = "events"
def register(self):
"""Register subcription to Ftrack event hub."""
self.session.event_hub.subscribe(

View file

@ -37,14 +37,13 @@ class BaseHandler(object):
type = 'No-type'
ignore_me = False
preactions = []
role_list = []
@staticmethod
def join_query_keys(keys):
"""Helper to join keys to query."""
return ",".join(["\"{}\"".format(key) for key in keys])
def __init__(self, session, plugins_presets=None):
def __init__(self, session):
'''Expects a ftrack_api.Session instance'''
self.log = Logger().get_logger(self.__class__.__name__)
if not(
@ -65,31 +64,19 @@ class BaseHandler(object):
# Using decorator
self.register = self.register_decorator(self.register)
self.launch = self.launch_log(self.launch)
if plugins_presets is None:
plugins_presets = {}
self.plugins_presets = plugins_presets
# Decorator
def register_decorator(self, func):
@functools.wraps(func)
def wrapper_register(*args, **kwargs):
presets_data = self.plugins_presets.get(self.__class__.__name__)
if presets_data:
for key, value in presets_data.items():
if not hasattr(self, key):
continue
setattr(self, key, value)
if self.ignore_me:
return
label = self.__class__.__name__
if hasattr(self, 'label'):
if self.variant is None:
label = self.label
else:
label = '{} {}'.format(self.label, self.variant)
label = getattr(self, "label", self.__class__.__name__)
variant = getattr(self, "variant", None)
if variant:
label = "{} {}".format(label, variant)
try:
self._preregister()
@ -126,12 +113,10 @@ class BaseHandler(object):
def launch_log(self, func):
@functools.wraps(func)
def wrapper_launch(*args, **kwargs):
label = self.__class__.__name__
if hasattr(self, 'label'):
label = self.label
if hasattr(self, 'variant'):
if self.variant is not None:
label = '{} {}'.format(self.label, self.variant)
label = getattr(self, "label", self.__class__.__name__)
variant = getattr(self, "variant", None)
if variant:
label = "{} {}".format(label, variant)
self.log.info(('{} "{}": Launched').format(self.type, label))
try:
@ -156,28 +141,7 @@ class BaseHandler(object):
def reset_session(self):
self.session.reset()
def _register_role_check(self):
if not self.role_list or not isinstance(self.role_list, (list, tuple)):
return
user_entity = self.session.query(
"User where username is \"{}\"".format(self.session.api_user)
).one()
available = False
lowercase_rolelist = [
role_name.lower()
for role_name in self.role_list
]
for role in user_entity["user_security_roles"]:
if role["security_role"]["name"].lower() in lowercase_rolelist:
available = True
break
if available is False:
raise MissingPermision
def _preregister(self):
self._register_role_check()
# Custom validations
result = self.preregister()
if result is None:
@ -564,7 +528,7 @@ class BaseHandler(object):
"Publishing event: {}"
).format(str(event.__dict__)))
def get_project_from_entity(self, entity):
def get_project_from_entity(self, entity, session=None):
low_entity_type = entity.entity_type.lower()
if low_entity_type == "project":
return entity
@ -585,61 +549,30 @@ class BaseHandler(object):
return parent["project"]
project_data = entity["link"][0]
return self.session.query(
if session is None:
session = self.session
return session.query(
"Project where id is {}".format(project_data["id"])
).one()
def get_project_entity_from_event(self, session, event, project_id):
"""Load or query and fill project entity from/to event data.
Project data are stored by ftrack id because in most cases it is
easier to access project id than project name.
Args:
session (ftrack_api.Session): Current session.
event (ftrack_api.Event): Processed event by session.
project_id (str): Ftrack project id.
"""
if not project_id:
raise ValueError(
"Entered `project_id` is not valid. {} ({})".format(
str(project_id), str(type(project_id))
)
)
# Try to get project entity from event
project_entities = event["data"].get("project_entities")
if not project_entities:
project_entities = {}
event["data"]["project_entities"] = project_entities
project_entity = project_entities.get(project_id)
if not project_entity:
# Get project entity from task and store to event
project_entity = session.get("Project", project_id)
event["data"]["project_entities"][project_id] = project_entity
return project_entity
def get_settings_for_project(
self, session, event, project_id=None, project_entity=None
):
def get_project_settings_from_event(self, event, project_entity):
"""Load or fill pype's project settings from event data.
Project data are stored by ftrack id because in most cases it is
easier to access project id than project name.
Args:
session (ftrack_api.Session): Current session.
event (ftrack_api.Event): Processed event by session.
project_id (str): Ftrack project id. Must be entered if
project_entity is not.
project_entity (ftrack_api.Entity): Project entity. Must be entered
if project_id is not.
project_entity (ftrack_api.Entity): Project entity.
"""
if not project_entity:
project_entity = self.get_project_entity_from_event(
session, event, project_id
)
raise AssertionError((
"Invalid arguments entered. Project entity or project id"
"must be entered."
))
project_id = project_entity["id"]
project_name = project_entity["full_name"]
project_settings_by_id = event["data"].get("project_settings")

View file

@ -15,10 +15,6 @@ class BaseEvent(BaseHandler):
type = 'Event'
def __init__(self, session, plugins_presets={}):
'''Expects a ftrack_api.Session instance'''
super().__init__(session, plugins_presets)
# Decorator
def launch_log(self, func):
@functools.wraps(func)
@ -50,3 +46,33 @@ class BaseEvent(BaseHandler):
session,
ignore=['socialfeed', 'socialnotification']
)
def get_project_entity_from_event(self, session, event, project_id):
"""Load or query and fill project entity from/to event data.
Project data are stored by ftrack id because in most cases it is
easier to access project id than project name.
Args:
session (ftrack_api.Session): Current session.
event (ftrack_api.Event): Processed event by session.
project_id (str): Ftrack project id.
"""
if not project_id:
raise ValueError(
"Entered `project_id` is not valid. {} ({})".format(
str(project_id), str(type(project_id))
)
)
# Try to get project entity from event
project_entities = event["data"].get("project_entities")
if not project_entities:
project_entities = {}
event["data"]["project_entities"] = project_entities
project_entity = project_entities.get(project_id)
if not project_entity:
# Get project entity from task and store to event
project_entity = session.get("Project", project_id)
event["data"]["project_entities"][project_id] = project_entity
return project_entity

View file

@ -0,0 +1,6 @@
from .log_view_module import LogViewModule
__all__ = (
"LogViewModule",
)

View file

@ -2,7 +2,7 @@ from pype.api import Logger
from .. import PypeModule, ITrayModule
class LoggingModule(PypeModule, ITrayModule):
class LogViewModule(PypeModule, ITrayModule):
name = "log_viewer"
def initialize(self, modules_settings):

View file

@ -1,6 +0,0 @@
from .logging_module import LoggingModule
__all__ = (
"LoggingModule",
)

View file

@ -6,6 +6,9 @@ import tempfile
import pype.api
import pyblish
from pype.lib import should_decompress, \
get_decompress_dir, decompress
import shutil
class ExtractBurnin(pype.api.Extractor):
@ -28,7 +31,8 @@ class ExtractBurnin(pype.api.Extractor):
"premiere",
"standalonepublisher",
"harmony",
"fusion"
"fusion",
"aftereffects"
]
optional = True
@ -204,6 +208,26 @@ class ExtractBurnin(pype.api.Extractor):
# Prepare paths and files for process.
self.input_output_paths(new_repre, temp_data, filename_suffix)
decompressed_dir = ''
full_input_path = temp_data["full_input_path"]
do_decompress = should_decompress(full_input_path)
if do_decompress:
decompressed_dir = get_decompress_dir()
decompress(
decompressed_dir,
full_input_path,
temp_data["frame_start"],
temp_data["frame_end"],
self.log
)
# input path changed, 'decompressed' added
input_file = os.path.basename(full_input_path)
temp_data["full_input_path"] = os.path.join(
decompressed_dir,
input_file)
# Data for burnin script
script_data = {
"input": temp_data["full_input_path"],
@ -263,6 +287,9 @@ class ExtractBurnin(pype.api.Extractor):
os.remove(filepath)
self.log.debug("Removed: \"{}\"".format(filepath))
if do_decompress and os.path.exists(decompressed_dir):
shutil.rmtree(decompressed_dir)
def prepare_basic_data(self, instance):
"""Pick data from instance for processing and for burnin strings.

View file

@ -3,6 +3,9 @@ import os
import pyblish.api
import pype.api
import pype.lib
from pype.lib import should_decompress, \
get_decompress_dir, decompress
import shutil
class ExtractJpegEXR(pyblish.api.InstancePlugin):
@ -22,7 +25,8 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin):
if 'crypto' in instance.data['subset']:
return
# ffmpeg doesn't support multipart exrs
do_decompress = False
# ffmpeg doesn't support multipart exrs, use oiiotool if available
if instance.data.get("multipartExr") is True:
return
@ -36,10 +40,6 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin):
# filter out mov and img sequences
representations_new = representations[:]
if instance.data.get("multipartExr"):
# ffmpeg doesn't support multipart exrs
return
for repre in representations:
tags = repre.get("tags", [])
self.log.debug(repre)
@ -60,6 +60,19 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin):
full_input_path = os.path.join(stagingdir, input_file)
self.log.info("input {}".format(full_input_path))
decompressed_dir = ''
do_decompress = should_decompress(full_input_path)
if do_decompress:
decompressed_dir = get_decompress_dir()
decompress(
decompressed_dir,
full_input_path)
# input path changed, 'decompressed' added
full_input_path = os.path.join(
decompressed_dir,
input_file)
filename = os.path.splitext(input_file)[0]
if not filename.endswith('.'):
filename += "."
@ -93,7 +106,14 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin):
# run subprocess
self.log.debug("{}".format(subprocess_jpeg))
pype.api.subprocess(subprocess_jpeg, shell=True)
try: # temporary until oiiotool is supported cross platform
pype.api.subprocess(subprocess_jpeg, shell=True)
except RuntimeError as exp:
if "Compression" in str(exp):
self.log.debug("Unsupported compression on input files. " +
"Skipping!!!")
return
raise
if "representations" not in instance.data:
instance.data["representations"] = []
@ -111,4 +131,7 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin):
self.log.debug("Adding: {}".format(representation))
representations_new.append(representation)
if do_decompress and os.path.exists(decompressed_dir):
shutil.rmtree(decompressed_dir)
instance.data["representations"] = representations_new

View file

@ -6,6 +6,8 @@ import pyblish.api
import clique
import pype.api
import pype.lib
from pype.lib import should_decompress, \
get_decompress_dir, decompress
class ExtractReview(pyblish.api.InstancePlugin):
@ -14,7 +16,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
Compulsory attribute of representation is tags list with "review",
otherwise the representation is ignored.
All new represetnations are created and encoded by ffmpeg following
All new representations are created and encoded by ffmpeg following
presets found in `pype-config/presets/plugins/global/
publish.json:ExtractReview:outputs`.
"""
@ -188,9 +190,17 @@ class ExtractReview(pyblish.api.InstancePlugin):
temp_data = self.prepare_temp_data(instance, repre, output_def)
ffmpeg_args = self._ffmpeg_arguments(
output_def, instance, new_repre, temp_data
)
try: # temporary until oiiotool is supported cross platform
ffmpeg_args = self._ffmpeg_arguments(
output_def, instance, new_repre, temp_data
)
except ZeroDivisionError:
if 'exr' in temp_data["origin_repre"]["ext"]:
self.log.debug("Unsupported compression on input " +
"files. Skipping!!!")
return
raise
subprcs_cmd = " ".join(ffmpeg_args)
# run subprocess
@ -318,9 +328,9 @@ class ExtractReview(pyblish.api.InstancePlugin):
Args:
output_def (dict): Currently processed output definition.
instance (Instance): Currently processed instance.
new_repre (dict): Reprensetation representing output of this
new_repre (dict): Representation representing output of this
process.
temp_data (dict): Base data for successfull process.
temp_data (dict): Base data for successful process.
"""
# Get FFmpeg arguments from profile presets
@ -331,9 +341,35 @@ class ExtractReview(pyblish.api.InstancePlugin):
ffmpeg_video_filters = out_def_ffmpeg_args.get("video_filters") or []
ffmpeg_audio_filters = out_def_ffmpeg_args.get("audio_filters") or []
if isinstance(new_repre['files'], list):
input_files_urls = [os.path.join(new_repre["stagingDir"], f) for f
in new_repre['files']]
test_path = input_files_urls[0]
else:
test_path = os.path.join(
new_repre["stagingDir"], new_repre['files'])
do_decompress = should_decompress(test_path)
if do_decompress:
# change stagingDir, decompress first
# calculate all paths with modified directory, used on too many
# places
# will be purged by cleanup.py automatically
orig_staging_dir = new_repre["stagingDir"]
new_repre["stagingDir"] = get_decompress_dir()
# Prepare input and output filepaths
self.input_output_paths(new_repre, output_def, temp_data)
if do_decompress:
input_file = temp_data["full_input_path"].\
replace(new_repre["stagingDir"], orig_staging_dir)
decompress(new_repre["stagingDir"], input_file,
temp_data["frame_start"],
temp_data["frame_end"],
self.log)
# Set output frames len to 1 when ouput is single image
if (
temp_data["output_ext_is_image"]
@ -930,7 +966,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
return regexes
def validate_value_by_regexes(self, value, in_list):
"""Validates in any regexe from list match entered value.
"""Validates in any regex from list match entered value.
Args:
in_list (list): List with regexes.
@ -955,9 +991,9 @@ class ExtractReview(pyblish.api.InstancePlugin):
def profile_exclusion(self, matching_profiles):
"""Find out most matching profile byt host, task and family match.
Profiles are selectivelly filtered. Each profile should have
Profiles are selectively filtered. Each profile should have
"__value__" key with list of booleans. Each boolean represents
existence of filter for specific key (host, taks, family).
existence of filter for specific key (host, tasks, family).
Profiles are looped in sequence. In each sequence are split into
true_list and false_list. For next sequence loop are used profiles in
true_list if there are any profiles else false_list is used.
@ -1036,7 +1072,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
highest_profile_points = -1
# Each profile get 1 point for each matching filter. Profile with most
# points is returnd. For cases when more than one profile will match
# points is returned. For cases when more than one profile will match
# are also stored ordered lists of matching values.
for profile in self.profiles:
profile_points = 0
@ -1648,7 +1684,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
def add_video_filter_args(self, args, inserting_arg):
"""
Fixing video filter argumets to be one long string
Fixing video filter arguments to be one long string
Args:
args (list): list of string arguments

View file

@ -15,7 +15,7 @@ from avalon import io
from avalon.vendor import filelink
import pype.api
from datetime import datetime
from pype.modules import ModulesManager
# from pype.modules import ModulesManager
# this is needed until speedcopy for linux is fixed
if sys.platform == "win32":
@ -933,15 +933,15 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
remote_site = None
sync_server_presets = None
manager = ModulesManager()
sync_server = manager.modules_by_name["sync_server"]
try:
if sync_server.enabled:
local_site, remote_site = sync_server.get_sites_for_project()
except ValueError:
log.debug(("There are not set presets for SyncServer."
" No credentials provided, no synching possible").
format(str(sync_server_presets)))
# manager = ModulesManager()
# sync_server = manager.modules_by_name["sync_server"]
# try:
# if sync_server.enabled:
# local_site, remote_site = sync_server.get_sites_for_project()
# except ValueError:
# log.debug(("There are not set presets for SyncServer."
# " No credentials provided, no synching possible").
# format(str(sync_server_presets)))
rec = {
"_id": io.ObjectId(),

View file

@ -193,6 +193,7 @@ class CreateRender(avalon.maya.Creator):
self.data["tilesX"] = 2
self.data["tilesY"] = 2
self.data["convertToScanline"] = False
self.data["vrayUseReferencedAovs"] = False
# Disable for now as this feature is not working yet
# self.data["assScene"] = False

View file

@ -149,7 +149,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
# return all expected files for all cameras and aovs in given
# frame range
ef = ExpectedFiles()
ef = ExpectedFiles(render_instance)
exp_files = ef.get(renderer, layer_name)
self.log.info("multipart: {}".format(ef.multipart))
assert exp_files, "no file names were generated, this is bug"
@ -248,7 +248,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
"tilesX": render_instance.data.get("tilesX") or 2,
"tilesY": render_instance.data.get("tilesY") or 2,
"priority": render_instance.data.get("priority"),
"convertToScanline": render_instance.data.get("convertToScanline") or False # noqa: E501
"convertToScanline": render_instance.data.get("convertToScanline") or False, # noqa: E501
"vrayUseReferencedAovs": render_instance.data.get("vrayUseReferencedAovs") or False # noqa: E501
}
if self.sync_workfile_version:

View file

@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
"""Validate if there are AOVs pulled from references."""
import pyblish.api
import types
from maya import cmds
import pype.hosts.maya.action
class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin):
"""Validate whether the V-Ray Render Elements (AOVs) include references.
This will check if there are AOVs pulled from references. If
`Vray Use Referenced Aovs` is checked on render instance, u must add those
manually to Render Elements as Pype will expect them to be rendered.
"""
order = pyblish.api.ValidatorOrder
label = 'VRay Referenced AOVs'
hosts = ['maya']
families = ['renderlayer']
actions = [pype.api.RepairContextAction]
def process(self, instance):
"""Plugin main entry point."""
if instance.data.get("renderer") != "vray":
# If not V-Ray ignore..
return
ref_aovs = cmds.ls(
type=["VRayRenderElement", "VRayRenderElementSet"],
referencedNodes=True)
ref_aovs_enabled = ValidateVrayReferencedAOVs.maya_is_true(
cmds.getAttr("vraySettings.relements_usereferenced"))
if not instance.data.get("vrayUseReferencedAovs"):
if ref_aovs_enabled and ref_aovs:
self.log.warning((
"Referenced AOVs are enabled in Vray "
"Render Settings and are detected in scene, but "
"Pype render instance option for referenced AOVs is "
"disabled. Those AOVs will be rendered but not published "
"by Pype."
))
self.log.warning(", ".join(ref_aovs))
else:
if not ref_aovs:
self.log.warning((
"Use of referenced AOVs enabled but there are none "
"in the scene."
))
if not ref_aovs_enabled:
self.log.error((
"'Use referenced' not enabled in Vray Render Settings."
))
raise AssertionError("Invalid render settings")
@classmethod
def repair(cls, context):
"""Repair action."""
vray_settings = cmds.ls(type="VRaySettingsNode")
if not vray_settings:
node = cmds.createNode("VRaySettingsNode")
else:
node = vray_settings[0]
cmds.setAttr("{}.relements_usereferenced".format(node), True)
@staticmethod
def maya_is_true(attr_val):
"""Whether a Maya attr evaluates to True.
When querying an attribute value from an ambiguous object the
Maya API will return a list of values, which need to be properly
handled to evaluate properly.
Args:
attr_val (mixed): Maya attribute to be evaluated as bool.
Returns:
bool: cast Maya attribute to Pythons boolean value.
"""
if isinstance(attr_val, types.BooleanType):
return attr_val
elif isinstance(attr_val, (types.ListType, types.GeneratorType)):
return any(attr_val)
else:
return bool(attr_val)

View file

@ -97,13 +97,11 @@
"ignored_statuses": [
"In Progress",
"Omitted",
"On hold"
"On hold",
"Approved"
],
"status_change": {
"In Progress": [],
"Ready": [
"Not Ready"
]
"In Progress": []
}
},
"create_update_attributes": {
@ -170,7 +168,8 @@
"sync_to_avalon_local": {
"enabled": true,
"role_list": [
"Pypeclub"
"Pypeclub",
"Administrator"
]
},
"seed_project": {