mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into enhancement/OP-3807_playblast_profiles
# Conflicts: # openpype/hosts/maya/plugins/create/create_review.py
This commit is contained in:
commit
281f6186e4
30 changed files with 943 additions and 172 deletions
|
|
@ -1216,7 +1216,7 @@ def get_representations(
|
|||
version_ids=version_ids,
|
||||
context_filters=context_filters,
|
||||
names_by_version_ids=names_by_version_ids,
|
||||
standard=True,
|
||||
standard=standard,
|
||||
archived=archived,
|
||||
fields=fields
|
||||
)
|
||||
|
|
|
|||
|
|
@ -42,13 +42,5 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook):
|
|||
self.log.info("Current context does not have any workfile yet.")
|
||||
return
|
||||
|
||||
# Determine whether to open workfile post initialization.
|
||||
if self.host_name == "maya":
|
||||
key = "open_workfile_post_initialization"
|
||||
if self.data["project_settings"]["maya"][key]:
|
||||
self.log.debug("Opening workfile post initialization.")
|
||||
self.data["env"]["OPENPYPE_" + key.upper()] = "1"
|
||||
return
|
||||
|
||||
# Add path to workfile to arguments
|
||||
self.launch_context.launch_args.append(last_workfile)
|
||||
|
|
|
|||
|
|
@ -118,6 +118,18 @@ FLOAT_FPS = {23.98, 23.976, 29.97, 47.952, 59.94}
|
|||
|
||||
RENDERLIKE_INSTANCE_FAMILIES = ["rendering", "vrayscene"]
|
||||
|
||||
DISPLAY_LIGHTS_VALUES = [
|
||||
"project_settings", "default", "all", "selected", "flat", "none"
|
||||
]
|
||||
DISPLAY_LIGHTS_LABELS = [
|
||||
"Use Project Settings",
|
||||
"Default Lighting",
|
||||
"All Lights",
|
||||
"Selected Lights",
|
||||
"Flat Lighting",
|
||||
"No Lights"
|
||||
]
|
||||
|
||||
|
||||
def get_main_window():
|
||||
"""Acquire Maya's main window"""
|
||||
|
|
|
|||
|
|
@ -234,26 +234,10 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin):
|
|||
return self.get_load_plugin_options(options)
|
||||
|
||||
def cleanup_placeholder(self, placeholder, failed):
|
||||
"""Hide placeholder, parent them to root
|
||||
add them to placeholder set and register placeholder's parent
|
||||
to keep placeholder info available for future use
|
||||
"""Hide placeholder, add them to placeholder set
|
||||
"""
|
||||
|
||||
node = placeholder._scene_identifier
|
||||
node_parent = placeholder.data["parent"]
|
||||
if node_parent:
|
||||
cmds.setAttr(node + ".parent", node_parent, type="string")
|
||||
|
||||
if cmds.getAttr(node + ".index") < 0:
|
||||
cmds.setAttr(node + ".index", placeholder.data["index"])
|
||||
|
||||
holding_sets = cmds.listSets(object=node)
|
||||
if holding_sets:
|
||||
for set in holding_sets:
|
||||
cmds.sets(node, remove=set)
|
||||
|
||||
if cmds.listRelatives(node, p=True):
|
||||
node = cmds.parent(node, world=True)[0]
|
||||
cmds.sets(node, addElement=PLACEHOLDER_SET)
|
||||
cmds.hide(node)
|
||||
cmds.setAttr(node + ".hiddenInOutliner", True)
|
||||
|
|
@ -286,8 +270,6 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin):
|
|||
elif not cmds.sets(root, q=True):
|
||||
return
|
||||
|
||||
if placeholder.data["parent"]:
|
||||
cmds.parent(nodes_to_parent, placeholder.data["parent"])
|
||||
# Move loaded nodes to correct index in outliner hierarchy
|
||||
placeholder_form = cmds.xform(
|
||||
placeholder.scene_identifier,
|
||||
|
|
|
|||
29
openpype/hosts/maya/hooks/pre_auto_load_plugins.py
Normal file
29
openpype/hosts/maya/hooks/pre_auto_load_plugins.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
from openpype.lib import PreLaunchHook
|
||||
|
||||
|
||||
class MayaPreAutoLoadPlugins(PreLaunchHook):
|
||||
"""Define -noAutoloadPlugins command flag."""
|
||||
|
||||
# Before AddLastWorkfileToLaunchArgs
|
||||
order = 9
|
||||
app_groups = ["maya"]
|
||||
|
||||
def execute(self):
|
||||
|
||||
# Ignore if there's no last workfile to start.
|
||||
if not self.data.get("start_last_workfile"):
|
||||
return
|
||||
|
||||
maya_settings = self.data["project_settings"]["maya"]
|
||||
enabled = maya_settings["explicit_plugins_loading"]["enabled"]
|
||||
if enabled:
|
||||
# Force disable the `AddLastWorkfileToLaunchArgs`.
|
||||
self.data.pop("start_last_workfile")
|
||||
|
||||
# Force post initialization so our dedicated plug-in load can run
|
||||
# prior to Maya opening a scene file.
|
||||
key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION"
|
||||
self.launch_context.env[key] = "1"
|
||||
|
||||
self.log.debug("Explicit plugins loading.")
|
||||
self.launch_context.launch_args.append("-noAutoloadPlugins")
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
from openpype.lib import PreLaunchHook
|
||||
|
||||
|
||||
class MayaPreOpenWorkfilePostInitialization(PreLaunchHook):
|
||||
"""Define whether open last workfile should run post initialize."""
|
||||
|
||||
# Before AddLastWorkfileToLaunchArgs.
|
||||
order = 9
|
||||
app_groups = ["maya"]
|
||||
|
||||
def execute(self):
|
||||
|
||||
# Ignore if there's no last workfile to start.
|
||||
if not self.data.get("start_last_workfile"):
|
||||
return
|
||||
|
||||
maya_settings = self.data["project_settings"]["maya"]
|
||||
enabled = maya_settings["open_workfile_post_initialization"]
|
||||
if enabled:
|
||||
# Force disable the `AddLastWorkfileToLaunchArgs`.
|
||||
self.data.pop("start_last_workfile")
|
||||
|
||||
self.log.debug("Opening workfile post initialization.")
|
||||
key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION"
|
||||
self.launch_context.env[key] = "1"
|
||||
|
|
@ -71,5 +71,6 @@ class CreateReview(plugin.Creator):
|
|||
data["isolate"] = preset["Generic"]["isolate_view"]
|
||||
data["imagePlane"] = preset["Viewport Options"]["imagePlane"]
|
||||
data["panZoom"] = preset["Generic"]["pan_zoom"]
|
||||
data["displayLights"] = lib.DISPLAY_LIGHTS_LABELS
|
||||
|
||||
self.data = data
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import pyblish.api
|
|||
|
||||
from openpype.client import get_subset_by_name
|
||||
from openpype.pipeline import legacy_io, KnownPublishError
|
||||
from openpype.hosts.maya.api.lib import get_attribute_input
|
||||
from openpype.hosts.maya.api import lib
|
||||
|
||||
|
||||
class CollectReview(pyblish.api.InstancePlugin):
|
||||
|
|
@ -145,12 +145,22 @@ class CollectReview(pyblish.api.InstancePlugin):
|
|||
|
||||
instance.data["audio"] = audio_data
|
||||
|
||||
# Convert enum attribute index to string.
|
||||
index = instance.data.get("displayLights", 0)
|
||||
display_lights = lib.DISPLAY_LIGHTS_VALUES[index]
|
||||
if display_lights == "project_settings":
|
||||
settings = instance.context.data["project_settings"]
|
||||
settings = settings["maya"]["publish"]["ExtractPlayblast"]
|
||||
settings = settings["capture_preset"]["Viewport Options"]
|
||||
display_lights = settings["displayLights"]
|
||||
instance.data["displayLights"] = display_lights
|
||||
|
||||
# Collect focal length.
|
||||
if camera is None:
|
||||
return
|
||||
|
||||
attr = camera + ".focalLength"
|
||||
if get_attribute_input(attr):
|
||||
if lib.get_attribute_input(attr):
|
||||
start = instance.data["frameStart"]
|
||||
end = instance.data["frameEnd"] + 1
|
||||
focal_length = [
|
||||
|
|
|
|||
|
|
@ -130,6 +130,10 @@ class ExtractPlayblast(publish.Extractor):
|
|||
cmds.currentTime(refreshFrameInt - 1, edit=True)
|
||||
cmds.currentTime(refreshFrameInt, edit=True)
|
||||
|
||||
# Use displayLights setting from instance
|
||||
key = "displayLights"
|
||||
preset["viewport_options"][key] = instance.data[key]
|
||||
|
||||
# Override transparency if requested.
|
||||
transparency = instance.data.get("transparency", 0)
|
||||
if transparency != 0:
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ class ExtractThumbnail(publish.Extractor):
|
|||
cmds.currentTime(refreshFrameInt - 1, edit=True)
|
||||
cmds.currentTime(refreshFrameInt, edit=True)
|
||||
|
||||
# Use displayLights setting from instance
|
||||
key = "displayLights"
|
||||
preset["viewport_options"][key] = instance.data[key]
|
||||
|
||||
# Override transparency if requested.
|
||||
transparency = instance.data.get("transparency", 0)
|
||||
if transparency != 0:
|
||||
|
|
|
|||
|
|
@ -65,9 +65,10 @@ class ExtractXgen(publish.Extractor):
|
|||
)
|
||||
cmds.delete(set(children) - set(shapes))
|
||||
|
||||
duplicate_transform = cmds.parent(
|
||||
duplicate_transform, world=True
|
||||
)[0]
|
||||
if cmds.listRelatives(duplicate_transform, parent=True):
|
||||
duplicate_transform = cmds.parent(
|
||||
duplicate_transform, world=True
|
||||
)[0]
|
||||
|
||||
duplicate_nodes.append(duplicate_transform)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class ValidateSingleAssembly(pyblish.api.InstancePlugin):
|
|||
|
||||
order = ValidateContentsOrder
|
||||
hosts = ['maya']
|
||||
families = ['rig', 'animation']
|
||||
families = ['rig']
|
||||
label = 'Single Assembly'
|
||||
|
||||
def process(self, instance):
|
||||
|
|
|
|||
|
|
@ -57,3 +57,16 @@ class ValidateXgen(pyblish.api.InstancePlugin):
|
|||
json.dumps(inactive_modifiers, indent=4, sort_keys=True)
|
||||
)
|
||||
)
|
||||
|
||||
# We need a namespace else there will be a naming conflict when
|
||||
# extracting because of stripping namespaces and parenting to world.
|
||||
node_names = [instance.data["xgmPalette"]]
|
||||
for _, connections in instance.data["xgenConnections"].items():
|
||||
node_names.append(connections["transform"].split(".")[0])
|
||||
|
||||
non_namespaced_nodes = [n for n in node_names if ":" not in n]
|
||||
if non_namespaced_nodes:
|
||||
raise PublishValidationError(
|
||||
"Could not find namespace on {}. Namespace is required for"
|
||||
" xgen publishing.".format(non_namespaced_nodes)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import os
|
||||
from functools import partial
|
||||
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.pipeline import install_host
|
||||
|
|
@ -13,24 +12,41 @@ install_host(host)
|
|||
|
||||
print("Starting OpenPype usersetup...")
|
||||
|
||||
project_settings = get_project_settings(os.environ['AVALON_PROJECT'])
|
||||
|
||||
# Loading plugins explicitly.
|
||||
explicit_plugins_loading = project_settings["maya"]["explicit_plugins_loading"]
|
||||
if explicit_plugins_loading["enabled"]:
|
||||
def _explicit_load_plugins():
|
||||
for plugin in explicit_plugins_loading["plugins_to_load"]:
|
||||
if plugin["enabled"]:
|
||||
print("Loading plug-in: " + plugin["name"])
|
||||
try:
|
||||
cmds.loadPlugin(plugin["name"], quiet=True)
|
||||
except RuntimeError as e:
|
||||
print(e)
|
||||
|
||||
# We need to load plugins deferred as loading them directly does not work
|
||||
# correctly due to Maya's initialization.
|
||||
cmds.evalDeferred(
|
||||
_explicit_load_plugins,
|
||||
lowestPriority=True
|
||||
)
|
||||
|
||||
# Open Workfile Post Initialization.
|
||||
key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION"
|
||||
if bool(int(os.environ.get(key, "0"))):
|
||||
def _log_and_open():
|
||||
path = os.environ["AVALON_LAST_WORKFILE"]
|
||||
print("Opening \"{}\"".format(path))
|
||||
cmds.file(path, open=True, force=True)
|
||||
cmds.evalDeferred(
|
||||
partial(
|
||||
cmds.file,
|
||||
os.environ["AVALON_LAST_WORKFILE"],
|
||||
open=True,
|
||||
force=True
|
||||
),
|
||||
_log_and_open,
|
||||
lowestPriority=True
|
||||
)
|
||||
|
||||
|
||||
# Build a shelf.
|
||||
settings = get_project_settings(os.environ['AVALON_PROJECT'])
|
||||
shelf_preset = settings['maya'].get('project_shelf')
|
||||
shelf_preset = project_settings['maya'].get('project_shelf')
|
||||
|
||||
if shelf_preset:
|
||||
project = os.environ["AVALON_PROJECT"]
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ class MayaLookAssignerWindow(QtWidgets.QWidget):
|
|||
if vp in nodes:
|
||||
vrayproxy_assign_look(vp, subset_name)
|
||||
|
||||
nodes = list(set(item["nodes"]).difference(vray_proxies))
|
||||
nodes = list(set(nodes).difference(vray_proxies))
|
||||
else:
|
||||
self.echo(
|
||||
"Could not assign to VRayProxy because vrayformaya plugin "
|
||||
|
|
@ -260,17 +260,18 @@ class MayaLookAssignerWindow(QtWidgets.QWidget):
|
|||
# Assign Arnold Standin look.
|
||||
if cmds.pluginInfo("mtoa", query=True, loaded=True):
|
||||
arnold_standins = set(cmds.ls(type="aiStandIn", long=True))
|
||||
|
||||
for standin in arnold_standins:
|
||||
if standin in nodes:
|
||||
arnold_standin.assign_look(standin, subset_name)
|
||||
|
||||
nodes = list(set(nodes).difference(arnold_standins))
|
||||
else:
|
||||
self.echo(
|
||||
"Could not assign to aiStandIn because mtoa plugin is not "
|
||||
"loaded."
|
||||
)
|
||||
|
||||
nodes = list(set(item["nodes"]).difference(arnold_standins))
|
||||
|
||||
# Assign look
|
||||
if nodes:
|
||||
assign_look_by_version(nodes, version_id=version["_id"])
|
||||
|
|
|
|||
|
|
@ -36,11 +36,9 @@ class BatchMovieCreator(TrayPublishCreator):
|
|||
# Position batch creator after simple creators
|
||||
order = 110
|
||||
|
||||
def __init__(self, project_settings, *args, **kwargs):
|
||||
super(BatchMovieCreator, self).__init__(project_settings,
|
||||
*args, **kwargs)
|
||||
def apply_settings(self, project_settings, system_settings):
|
||||
creator_settings = (
|
||||
project_settings["traypublisher"]["BatchMovieCreator"]
|
||||
project_settings["traypublisher"]["create"]["BatchMovieCreator"]
|
||||
)
|
||||
self.default_variants = creator_settings["default_variants"]
|
||||
self.default_tasks = creator_settings["default_tasks"]
|
||||
|
|
@ -151,4 +149,3 @@ class BatchMovieCreator(TrayPublishCreator):
|
|||
File names must then contain only asset name, or asset name + version.
|
||||
(eg. 'chair.mov', 'chair_v001.mov', not really safe `my_chair_v001.mov`
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
|
||||
# Fill tags and new families from project settings
|
||||
tags = []
|
||||
if family_lowered == "review":
|
||||
if "review" in instance.data["families"]:
|
||||
tags.append("review")
|
||||
|
||||
# Sequence of one frame
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
import clique
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class ValidateSequenceFrames(pyblish.api.InstancePlugin):
|
||||
"""Ensure the sequence of frames is complete
|
||||
|
||||
The files found in the folder are checked against the frameStart and
|
||||
frameEnd of the instance. If the first or last file is not
|
||||
corresponding with the first or last frame it is flagged as invalid.
|
||||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Validate Sequence Frames"
|
||||
families = ["render"]
|
||||
hosts = ["unreal"]
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
representations = instance.data.get("representations")
|
||||
for repr in representations:
|
||||
patterns = [clique.PATTERNS["frames"]]
|
||||
collections, remainder = clique.assemble(
|
||||
repr["files"], minimum_items=1, patterns=patterns)
|
||||
|
||||
assert not remainder, "Must not have remainder"
|
||||
assert len(collections) == 1, "Must detect single collection"
|
||||
collection = collections[0]
|
||||
frames = list(collection.indexes)
|
||||
|
||||
current_range = (frames[0], frames[-1])
|
||||
required_range = (instance.data["frameStart"],
|
||||
instance.data["frameEnd"])
|
||||
|
||||
if current_range != required_range:
|
||||
raise ValueError(f"Invalid frame range: {current_range} - "
|
||||
f"expected: {required_range}")
|
||||
|
||||
missing = collection.holes().indexes
|
||||
assert not missing, "Missing frames: %s" % (missing,)
|
||||
|
|
@ -325,6 +325,11 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
|
|||
job_info = copy.deepcopy(payload_job_info)
|
||||
plugin_info = copy.deepcopy(payload_plugin_info)
|
||||
|
||||
# Force plugin reload for vray cause the region does not get flushed
|
||||
# between tile renders.
|
||||
if plugin_info["Renderer"] == "vray":
|
||||
job_info.ForceReloadPlugin = True
|
||||
|
||||
# if we have sequence of files, we need to create tile job for
|
||||
# every frame
|
||||
job_info.TileJob = True
|
||||
|
|
@ -434,6 +439,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
|
|||
|
||||
assembly_payloads = []
|
||||
output_dir = self.job_info.OutputDirectory[0]
|
||||
config_files = []
|
||||
for file in assembly_files:
|
||||
frame = re.search(R_FRAME_NUMBER, file).group("frame")
|
||||
|
||||
|
|
@ -459,6 +465,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
|
|||
datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
|
||||
)
|
||||
)
|
||||
config_files.append(config_file)
|
||||
try:
|
||||
if not os.path.isdir(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
|
@ -467,8 +474,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
|
|||
self.log.warning("Path is unreachable: "
|
||||
"`{}`".format(output_dir))
|
||||
|
||||
assembly_plugin_info["ConfigFile"] = config_file
|
||||
|
||||
with open(config_file, "w") as cf:
|
||||
print("TileCount={}".format(tiles_count), file=cf)
|
||||
print("ImageFileName={}".format(file), file=cf)
|
||||
|
|
@ -477,6 +482,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
|
|||
print("ImageHeight={}".format(
|
||||
instance.data.get("resolutionHeight")), file=cf)
|
||||
|
||||
reversed_y = False
|
||||
if plugin_info["Renderer"] == "arnold":
|
||||
reversed_y = True
|
||||
|
||||
with open(config_file, "a") as cf:
|
||||
# Need to reverse the order of the y tiles, because image
|
||||
# coordinates are calculated from bottom left corner.
|
||||
|
|
@ -487,7 +496,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
|
|||
instance.data.get("resolutionWidth"),
|
||||
instance.data.get("resolutionHeight"),
|
||||
payload_plugin_info["OutputFilePrefix"],
|
||||
reversed_y=True
|
||||
reversed_y=reversed_y
|
||||
)[1]
|
||||
for k, v in sorted(tiles.items()):
|
||||
print("{}={}".format(k, v), file=cf)
|
||||
|
|
@ -516,6 +525,11 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
|
|||
|
||||
instance.data["assemblySubmissionJobs"] = assembly_job_ids
|
||||
|
||||
# Remove config files to avoid confusion about where data is coming
|
||||
# from in Deadline.
|
||||
for config_file in config_files:
|
||||
os.remove(config_file)
|
||||
|
||||
def _get_maya_payload(self, data):
|
||||
|
||||
job_info = copy.deepcopy(self.job_info)
|
||||
|
|
@ -876,8 +890,6 @@ def _format_tiles(
|
|||
out["PluginInfo"]["RegionRight{}".format(tile)] = right
|
||||
|
||||
# Tile config
|
||||
cfg["Tile{}".format(tile)] = new_filename
|
||||
cfg["Tile{}Tile".format(tile)] = new_filename
|
||||
cfg["Tile{}FileName".format(tile)] = new_filename
|
||||
cfg["Tile{}X".format(tile)] = left
|
||||
cfg["Tile{}Y".format(tile)] = top
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ class AbstractTemplateBuilder(object):
|
|||
def linked_asset_docs(self):
|
||||
if self._linked_asset_docs is None:
|
||||
self._linked_asset_docs = get_linked_assets(
|
||||
self.current_asset_doc
|
||||
self.project_name, self.current_asset_doc
|
||||
)
|
||||
return self._linked_asset_docs
|
||||
|
||||
|
|
@ -1151,13 +1151,10 @@ class PlaceholderItem(object):
|
|||
return self._log
|
||||
|
||||
def __repr__(self):
|
||||
name = None
|
||||
if hasattr("name", self):
|
||||
name = self.name
|
||||
if hasattr("_scene_identifier ", self):
|
||||
name = self._scene_identifier
|
||||
|
||||
return "< {} {} >".format(self.__class__.__name__, name)
|
||||
return "< {} {} >".format(
|
||||
self.__class__.__name__,
|
||||
self._scene_identifier
|
||||
)
|
||||
|
||||
@property
|
||||
def order(self):
|
||||
|
|
@ -1419,16 +1416,7 @@ class PlaceholderLoadMixin(object):
|
|||
"family": [placeholder.data["family"]]
|
||||
}
|
||||
|
||||
elif builder_type != "linked_asset":
|
||||
context_filters = {
|
||||
"asset": [re.compile(placeholder.data["asset"])],
|
||||
"subset": [re.compile(placeholder.data["subset"])],
|
||||
"hierarchy": [re.compile(placeholder.data["hierarchy"])],
|
||||
"representation": [placeholder.data["representation"]],
|
||||
"family": [placeholder.data["family"]]
|
||||
}
|
||||
|
||||
else:
|
||||
elif builder_type == "linked_asset":
|
||||
asset_regex = re.compile(placeholder.data["asset"])
|
||||
linked_asset_names = []
|
||||
for asset_doc in linked_asset_docs:
|
||||
|
|
@ -1444,6 +1432,15 @@ class PlaceholderLoadMixin(object):
|
|||
"family": [placeholder.data["family"]],
|
||||
}
|
||||
|
||||
else:
|
||||
context_filters = {
|
||||
"asset": [re.compile(placeholder.data["asset"])],
|
||||
"subset": [re.compile(placeholder.data["subset"])],
|
||||
"hierarchy": [re.compile(placeholder.data["hierarchy"])],
|
||||
"representation": [placeholder.data["representation"]],
|
||||
"family": [placeholder.data["family"]]
|
||||
}
|
||||
|
||||
return list(get_representations(
|
||||
project_name,
|
||||
context_filters=context_filters
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
import clique
|
||||
import pyblish.api
|
||||
|
||||
|
||||
|
|
@ -7,28 +11,51 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin):
|
|||
The files found in the folder are checked against the startFrame and
|
||||
endFrame of the instance. If the first or last file is not
|
||||
corresponding with the first or last frame it is flagged as invalid.
|
||||
|
||||
Used regular expression pattern handles numbers in the file names
|
||||
(eg "Main_beauty.v001.1001.exr", "Main_beauty_v001.1001.exr",
|
||||
"Main_beauty.1001.1001.exr") but not numbers behind frames (eg.
|
||||
"Main_beauty.1001.v001.exr")
|
||||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Validate Sequence Frames"
|
||||
families = ["imagesequence"]
|
||||
hosts = ["shell"]
|
||||
families = ["imagesequence", "render"]
|
||||
hosts = ["shell", "unreal"]
|
||||
|
||||
def process(self, instance):
|
||||
representations = instance.data.get("representations")
|
||||
if not representations:
|
||||
return
|
||||
for repr in representations:
|
||||
repr_files = repr["files"]
|
||||
if isinstance(repr_files, str):
|
||||
continue
|
||||
|
||||
collection = instance[0]
|
||||
self.log.info(collection)
|
||||
ext = repr.get("ext")
|
||||
if not ext:
|
||||
_, ext = os.path.splitext(repr_files[0])
|
||||
elif not ext.startswith("."):
|
||||
ext = ".{}".format(ext)
|
||||
pattern = r"\D?(?P<index>(?P<padding>0*)\d+){}$".format(
|
||||
re.escape(ext))
|
||||
patterns = [pattern]
|
||||
|
||||
frames = list(collection.indexes)
|
||||
collections, remainder = clique.assemble(
|
||||
repr_files, minimum_items=1, patterns=patterns)
|
||||
|
||||
current_range = (frames[0], frames[-1])
|
||||
required_range = (instance.data["frameStart"],
|
||||
instance.data["frameEnd"])
|
||||
assert not remainder, "Must not have remainder"
|
||||
assert len(collections) == 1, "Must detect single collection"
|
||||
collection = collections[0]
|
||||
frames = list(collection.indexes)
|
||||
|
||||
if current_range != required_range:
|
||||
raise ValueError("Invalid frame range: {0} - "
|
||||
"expected: {1}".format(current_range,
|
||||
required_range))
|
||||
current_range = (frames[0], frames[-1])
|
||||
required_range = (instance.data["frameStart"],
|
||||
instance.data["frameEnd"])
|
||||
|
||||
missing = collection.holes().indexes
|
||||
assert not missing, "Missing frames: %s" % (missing,)
|
||||
if current_range != required_range:
|
||||
raise ValueError(f"Invalid frame range: {current_range} - "
|
||||
f"expected: {required_range}")
|
||||
|
||||
missing = collection.holes().indexes
|
||||
assert not missing, "Missing frames: %s" % (missing,)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,414 @@
|
|||
{
|
||||
"open_workfile_post_initialization": false,
|
||||
"explicit_plugins_loading": {
|
||||
"enabled": false,
|
||||
"plugins_to_load": [
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "AbcBullet"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "AbcExport"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "AbcImport"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "animImportExport"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "ArubaTessellator"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "ATFPlugin"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "atomImportExport"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "AutodeskPacketFile"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "autoLoader"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "bifmeshio"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "bifrostGraph"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "bifrostshellnode"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "bifrostvisplugin"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "blast2Cmd"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "bluePencil"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "Boss"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "bullet"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "cacheEvaluator"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "cgfxShader"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "cleanPerFaceAssignment"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "clearcoat"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "convertToComponentTags"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "curveWarp"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "ddsFloatReader"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "deformerEvaluator"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "dgProfiler"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "drawUfe"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "dx11Shader"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "fbxmaya"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "fltTranslator"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "freeze"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "Fur"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "gameFbxExporter"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "gameInputDevice"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "GamePipeline"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "gameVertexCount"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "geometryReport"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "geometryTools"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "glslShader"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "GPUBuiltInDeformer"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "gpuCache"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "hairPhysicalShader"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "ik2Bsolver"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "ikSpringSolver"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "invertShape"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "lges"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "lookdevKit"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "MASH"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "matrixNodes"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "mayaCharacterization"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "mayaHIK"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "MayaMuscle"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "mayaUsdPlugin"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "mayaVnnPlugin"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "melProfiler"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "meshReorder"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "modelingToolkit"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "mtoa"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "mtoh"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "nearestPointOnMesh"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "objExport"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "OneClick"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "OpenEXRLoader"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "pgYetiMaya"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "pgyetiVrayMaya"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "polyBoolean"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "poseInterpolator"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "quatNodes"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "randomizerDevice"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "redshift4maya"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "renderSetup"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "retargeterNodes"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "RokokoMotionLibrary"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "rotateHelper"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "sceneAssembly"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "shaderFXPlugin"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "shotCamera"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "snapTransform"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "stage"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "stereoCamera"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "stlTranslator"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "studioImport"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "Substance"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "substancelink"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "substancemaya"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "substanceworkflow"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "svgFileTranslator"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "sweep"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "testify"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "tiffFloatReader"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "timeSliderBookmark"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "Turtle"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "Type"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "udpDevice"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "ufeSupport"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "Unfold3D"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "VectorRender"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "vrayformaya"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "vrayvolumegrid"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "xgenToolkit"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "xgenVray"
|
||||
}
|
||||
]
|
||||
},
|
||||
"imageio": {
|
||||
"ocio_config": {
|
||||
"enabled": false,
|
||||
|
|
|
|||
|
|
@ -303,16 +303,18 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"BatchMovieCreator": {
|
||||
"default_variants": [
|
||||
"Main"
|
||||
],
|
||||
"default_tasks": [
|
||||
"Compositing"
|
||||
],
|
||||
"extensions": [
|
||||
".mov"
|
||||
]
|
||||
"create": {
|
||||
"BatchMovieCreator": {
|
||||
"default_variants": [
|
||||
"Main"
|
||||
],
|
||||
"default_tasks": [
|
||||
"Compositing"
|
||||
],
|
||||
"extensions": [
|
||||
".mov"
|
||||
]
|
||||
}
|
||||
},
|
||||
"publish": {
|
||||
"ValidateFrameRange": {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,41 @@
|
|||
"key": "open_workfile_post_initialization",
|
||||
"label": "Open Workfile Post Initialization"
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "explicit_plugins_loading",
|
||||
"label": "Explicit Plugins Loading",
|
||||
"collapsible": true,
|
||||
"is_group": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "plugins_to_load",
|
||||
"label": "Plugins To Load",
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "imageio",
|
||||
"type": "dict",
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
"type": "list",
|
||||
"collapsible": true,
|
||||
"key": "simple_creators",
|
||||
"label": "Creator plugins",
|
||||
"label": "Simple Create Plugins",
|
||||
"use_label_wrap": true,
|
||||
"collapsible_key": true,
|
||||
"object_type": {
|
||||
|
|
@ -292,40 +292,48 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"key": "create",
|
||||
"label": "Create plugins",
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "BatchMovieCreator",
|
||||
"label": "Batch Movie Creator",
|
||||
"collapsible_key": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Allows to publish multiple video files in one go. <br />Name of matching asset is parsed from file names ('asset.mov', 'asset_v001.mov', 'my_asset_to_publish.mov')"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default variants",
|
||||
"object_type": {
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "default_tasks",
|
||||
"label": "Default tasks",
|
||||
"object_type": {
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "extensions",
|
||||
"label": "Extensions",
|
||||
"use_label_wrap": true,
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "BatchMovieCreator",
|
||||
"label": "Batch Movie Creator",
|
||||
"collapsible_key": true,
|
||||
"collapsed": false,
|
||||
"object_type": "text"
|
||||
"children": [
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Allows to publish multiple video files in one go. <br />Name of matching asset is parsed from file names ('asset.mov', 'asset_v001.mov', 'my_asset_to_publish.mov')"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default variants",
|
||||
"object_type": {
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "default_tasks",
|
||||
"label": "Default tasks",
|
||||
"object_type": {
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "extensions",
|
||||
"label": "Extensions",
|
||||
"use_label_wrap": true,
|
||||
"collapsible_key": true,
|
||||
"collapsed": false,
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@
|
|||
{ "all": "All Lights"},
|
||||
{ "selected": "Selected Lights"},
|
||||
{ "flat": "Flat Lighting"},
|
||||
{ "nolights": "No Lights"}
|
||||
{ "none": "No Lights"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
36
tests/unit/openpype/conftest.py
Normal file
36
tests/unit/openpype/conftest.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""Dummy environment that allows importing Openpype modules and run
|
||||
tests in parent folder and all subfolders manually from IDE.
|
||||
|
||||
This should not get triggered if the tests are running from `runtests` as it
|
||||
is expected there that environment is handled by OP itself.
|
||||
|
||||
This environment should be enough to run simple `BaseTest` where no
|
||||
external preparation is necessary (eg. no prepared DB, no source files).
|
||||
These tests might be enough to import and run simple pyblish plugins to
|
||||
validate logic.
|
||||
|
||||
Please be aware that these tests might use values in real databases, so use
|
||||
`BaseTest` only for logic without side effects or special configuration. For
|
||||
these there is `tests.lib.testing_classes.ModuleUnitTest` which would setup
|
||||
proper test DB (but it requires `mongorestore` on the sys.path)
|
||||
|
||||
If pyblish plugins require any host dependent communication, it would need
|
||||
to be mocked.
|
||||
|
||||
This setting of env vars is necessary to run before any imports of OP code!
|
||||
(This is why it is in `conftest.py` file.)
|
||||
If your test requires any additional env var, copy this file to folder of your
|
||||
test, it should only that folder.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
|
||||
if not os.environ.get("IS_TEST"): # running tests from cmd or CI
|
||||
os.environ["OPENPYPE_MONGO"] = "mongodb://localhost:27017"
|
||||
os.environ["AVALON_DB"] = "avalon"
|
||||
os.environ["OPENPYPE_DATABASE_NAME"] = "openpype"
|
||||
os.environ["AVALON_TIMEOUT"] = '3000'
|
||||
os.environ["OPENPYPE_DEBUG"] = "1"
|
||||
os.environ["AVALON_ASSET"] = "test_asset"
|
||||
os.environ["AVALON_PROJECT"] = "test_project"
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
|
||||
|
||||
"""Test Publish_plugins pipeline publish modul, tests API methods
|
||||
|
||||
File:
|
||||
creates temporary directory and downloads .zip file from GDrive
|
||||
unzips .zip file
|
||||
uses content of .zip file (MongoDB's dumps) to import to new databases
|
||||
with use of 'monkeypatch_session' modifies required env vars
|
||||
temporarily
|
||||
runs battery of tests checking that site operation for Sync Server
|
||||
module are working
|
||||
removes temporary folder
|
||||
removes temporary databases (?)
|
||||
"""
|
||||
import pytest
|
||||
import logging
|
||||
|
||||
from pyblish.api import Instance as PyblishInstance
|
||||
|
||||
from tests.lib.testing_classes import BaseTest
|
||||
from openpype.plugins.publish.validate_sequence_frames import (
|
||||
ValidateSequenceFrames
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestValidateSequenceFrames(BaseTest):
|
||||
""" Testing ValidateSequenceFrames plugin
|
||||
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def instance(self):
|
||||
|
||||
class Instance(PyblishInstance):
|
||||
data = {
|
||||
"frameStart": 1001,
|
||||
"frameEnd": 1002,
|
||||
"representations": []
|
||||
}
|
||||
yield Instance
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def plugin(self):
|
||||
plugin = ValidateSequenceFrames()
|
||||
plugin.log = log
|
||||
|
||||
yield plugin
|
||||
|
||||
def test_validate_sequence_frames_single_frame(self, instance, plugin):
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": "Main_beauty.1001.exr",
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
instance.data["frameEnd"] = 1001
|
||||
|
||||
plugin.process(instance)
|
||||
|
||||
@pytest.mark.parametrize("files",
|
||||
[
|
||||
["Main_beauty.v001.1001.exr",
|
||||
"Main_beauty.v001.1002.exr"],
|
||||
["Main_beauty_v001.1001.exr",
|
||||
"Main_beauty_v001.1002.exr"],
|
||||
["Main_beauty.1001.1001.exr",
|
||||
"Main_beauty.1001.1002.exr"],
|
||||
["Main_beauty_v001_1001.exr",
|
||||
"Main_beauty_v001_1002.exr"]])
|
||||
def test_validate_sequence_frames_name(self, instance,
|
||||
plugin, files):
|
||||
# tests for names with number inside, caused clique failure before
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": files,
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
|
||||
plugin.process(instance)
|
||||
|
||||
@pytest.mark.parametrize("files",
|
||||
[["Main_beauty.1001.v001.exr",
|
||||
"Main_beauty.1002.v001.exr"]])
|
||||
def test_validate_sequence_frames_wrong_name(self, instance,
|
||||
plugin, files):
|
||||
# tests for names with number inside, caused clique failure before
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": files,
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
plugin.process(instance)
|
||||
assert ("Must detect single collection" in
|
||||
str(excinfo.value))
|
||||
|
||||
@pytest.mark.parametrize("files",
|
||||
[["Main_beauty.v001.1001.ass.gz",
|
||||
"Main_beauty.v001.1002.ass.gz"]])
|
||||
def test_validate_sequence_frames_possible_wrong_name(
|
||||
self, instance, plugin, files):
|
||||
# currently pattern fails on extensions with dots
|
||||
representations = [
|
||||
{
|
||||
"files": files,
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
plugin.process(instance)
|
||||
assert ("Must not have remainder" in
|
||||
str(excinfo.value))
|
||||
|
||||
@pytest.mark.parametrize("files",
|
||||
[["Main_beauty.v001.1001.ass.gz",
|
||||
"Main_beauty.v001.1002.ass.gz"]])
|
||||
def test_validate_sequence_frames__correct_ext(
|
||||
self, instance, plugin, files):
|
||||
# currently pattern fails on extensions with dots
|
||||
representations = [
|
||||
{
|
||||
"ext": "ass.gz",
|
||||
"files": files,
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
|
||||
plugin.process(instance)
|
||||
|
||||
def test_validate_sequence_frames_multi_frame(self, instance, plugin):
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": ["Main_beauty.1001.exr", "Main_beauty.1002.exr",
|
||||
"Main_beauty.1003.exr"]
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
instance.data["frameEnd"] = 1003
|
||||
|
||||
plugin.process(instance)
|
||||
|
||||
def test_validate_sequence_frames_multi_frame_missing(self, instance,
|
||||
plugin):
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": ["Main_beauty.1001.exr", "Main_beauty.1002.exr"]
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
instance.data["frameEnd"] = 1003
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
plugin.process(instance)
|
||||
assert ("Invalid frame range: (1001, 1002) - expected: (1001, 1003)" in
|
||||
str(excinfo.value))
|
||||
|
||||
def test_validate_sequence_frames_multi_frame_hole(self, instance, plugin):
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": ["Main_beauty.1001.exr", "Main_beauty.1003.exr"]
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
instance.data["frameEnd"] = 1003
|
||||
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
plugin.process(instance)
|
||||
assert ("Missing frames: [1002]" in str(excinfo.value))
|
||||
|
||||
|
||||
test_case = TestValidateSequenceFrames()
|
||||
|
|
@ -274,3 +274,14 @@ Fill in the necessary fields (the optional fields are regex filters)
|
|||
- Build your workfile
|
||||
|
||||

|
||||
|
||||
## Explicit Plugins Loading
|
||||
You can define which plugins to load on launch of Maya here; `project_settings/maya/explicit_plugins_loading`. This can help improve Maya's launch speed, if you know which plugins are needed.
|
||||
|
||||
By default only the required plugins are enabled. You can also add any plugin to the list to enable on launch.
|
||||
|
||||
:::note technical
|
||||
When enabling this feature, the workfile will be launched post initialization no matter the setting on `project_settings/maya/open_workfile_post_initialization`. This is to avoid any issues with references needing plugins.
|
||||
|
||||
Renderfarm integration is not supported for this feature.
|
||||
:::
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ Create an Xgen instance to publish. This needs to contain only **one Xgen collec
|
|||
|
||||
You can create multiple Xgen instances if you have multiple collections to publish.
|
||||
|
||||
:::note
|
||||
The Xgen publishing requires a namespace on the Xgen collection (palette) and the geometry used.
|
||||
:::
|
||||
|
||||
### Publish
|
||||
|
||||
The publishing process will grab geometry used for Xgen along with any external files used in the collection's descriptions. This creates an isolated Maya file with just the Xgen collection's dependencies, so you can use any nested geometry when creating the Xgen description. An Xgen version will consist of:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue