Merge branch 'develop' into enhancement/OP-3807_playblast_profiles

# Conflicts:
#	openpype/hosts/maya/plugins/create/create_review.py
This commit is contained in:
Toke Stuart Jepsen 2023-04-14 15:09:44 +01:00
commit 281f6186e4
30 changed files with 943 additions and 172 deletions

View file

@ -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
)

View file

@ -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)

View file

@ -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"""

View file

@ -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,

View 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")

View file

@ -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"

View file

@ -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

View file

@ -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 = [

View file

@ -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:

View file

@ -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:

View file

@ -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)

View file

@ -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):

View file

@ -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)
)

View file

@ -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"]

View file

@ -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"])

View file

@ -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`
"""

View file

@ -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

View file

@ -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,)

View file

@ -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

View file

@ -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

View file

@ -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,)

View file

@ -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,

View file

@ -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": {

View file

@ -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",

View file

@ -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"
}
]
}
]
},

View file

@ -178,7 +178,7 @@
{ "all": "All Lights"},
{ "selected": "Selected Lights"},
{ "flat": "Flat Lighting"},
{ "nolights": "No Lights"}
{ "none": "No Lights"}
]
},
{

View 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"

View file

@ -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()

View file

@ -274,3 +274,14 @@ Fill in the necessary fields (the optional fields are regex filters)
- Build your workfile
![maya build template](assets/maya-build_workfile_from_template.png)
## 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.
:::

View file

@ -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: