Merge branch 'develop' into feature/OP-4245Data_Exchange_Geometry

This commit is contained in:
Kayla Man 2023-03-03 13:04:07 +08:00
commit 24b492dc49
14 changed files with 311 additions and 121 deletions

View file

@ -4,7 +4,6 @@ import os
import sys
import platform
import uuid
import math
import re
import json
@ -2064,13 +2063,8 @@ def set_scene_resolution(width, height, pixelAspect):
cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect)
def reset_frame_range():
"""Set frame range to current asset"""
fps = convert_to_maya_fps(
float(legacy_io.Session.get("AVALON_FPS", 25))
)
set_scene_fps(fps)
def get_frame_range():
"""Get the current assets frame range and handles."""
# Set frame start/end
project_name = legacy_io.active_project()
@ -2097,8 +2091,26 @@ def reset_frame_range():
if handle_end is None:
handle_end = handles
frame_start -= int(handle_start)
frame_end += int(handle_end)
return {
"frameStart": frame_start,
"frameEnd": frame_end,
"handleStart": handle_start,
"handleEnd": handle_end
}
def reset_frame_range():
"""Set frame range to current asset"""
fps = convert_to_maya_fps(
float(legacy_io.Session.get("AVALON_FPS", 25))
)
set_scene_fps(fps)
frame_range = get_frame_range()
frame_start = frame_range["frameStart"] - int(frame_range["handleStart"])
frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"])
cmds.playbackOptions(minTime=frame_start)
cmds.playbackOptions(maxTime=frame_end)
@ -3562,3 +3574,34 @@ def get_color_management_output_transform():
if preferences["output_transform_enabled"]:
colorspace = preferences["output_transform"]
return colorspace
def len_flattened(components):
"""Return the length of the list as if it was flattened.
Maya will return consecutive components as a single entry
when requesting with `maya.cmds.ls` without the `flatten`
flag. Though enabling `flatten` on a large list (e.g. millions)
will result in a slow result. This command will return the amount
of entries in a non-flattened list by parsing the result with
regex.
Args:
components (list): The non-flattened components.
Returns:
int: The amount of entries.
"""
assert isinstance(components, (list, tuple))
n = 0
pattern = re.compile(r"\[(\d+):(\d+)\]")
for c in components:
match = pattern.search(c)
if match:
start, end = match.groups()
n += int(end) - int(start) + 1
else:
n += 1
return n

View file

@ -25,16 +25,20 @@ class CreateReview(plugin.Creator):
"depth peeling",
"alpha cut"
]
useMayaTimeline = True
def __init__(self, *args, **kwargs):
super(CreateReview, self).__init__(*args, **kwargs)
# get basic animation data : start / end / handles / steps
data = OrderedDict(**self.data)
animation_data = lib.collect_animation_data(fps=True)
for key, value in animation_data.items():
# Option for using Maya or asset frame range in settings.
frame_range = lib.get_frame_range()
if self.useMayaTimeline:
frame_range = lib.collect_animation_data(fps=True)
for key, value in frame_range.items():
data[key] = value
data["fps"] = lib.collect_animation_data(fps=True)["fps"]
data["review_width"] = self.Width
data["review_height"] = self.Height
data["isolate"] = self.isolate

View file

@ -137,6 +137,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
# Create the instance
instance = context.create_instance(objset)
instance[:] = members_hierarchy
instance.data["objset"] = objset
# Store the exact members of the object set
instance.data["setMembers"] = members

View file

@ -57,6 +57,10 @@ class ValidateFrameRange(pyblish.api.InstancePlugin):
inst_start = int(instance.data.get("frameStartHandle"))
inst_end = int(instance.data.get("frameEndHandle"))
inst_frame_start = int(instance.data.get("frameStart"))
inst_frame_end = int(instance.data.get("frameEnd"))
inst_handle_start = int(instance.data.get("handleStart"))
inst_handle_end = int(instance.data.get("handleEnd"))
# basic sanity checks
assert frame_start_handle <= frame_end_handle, (
@ -69,24 +73,37 @@ class ValidateFrameRange(pyblish.api.InstancePlugin):
if [ef for ef in self.exclude_families
if instance.data["family"] in ef]:
return
if(inst_start != frame_start_handle):
if (inst_start != frame_start_handle):
errors.append("Instance start frame [ {} ] doesn't "
"match the one set on instance [ {} ]: "
"match the one set on asset [ {} ]: "
"{}/{}/{}/{} (handle/start/end/handle)".format(
inst_start,
frame_start_handle,
handle_start, frame_start, frame_end, handle_end
))
if(inst_end != frame_end_handle):
if (inst_end != frame_end_handle):
errors.append("Instance end frame [ {} ] doesn't "
"match the one set on instance [ {} ]: "
"match the one set on asset [ {} ]: "
"{}/{}/{}/{} (handle/start/end/handle)".format(
inst_end,
frame_end_handle,
handle_start, frame_start, frame_end, handle_end
))
checks = {
"frame start": (frame_start, inst_frame_start),
"frame end": (frame_end, inst_frame_end),
"handle start": (handle_start, inst_handle_start),
"handle end": (handle_end, inst_handle_end)
}
for label, values in checks.items():
if values[0] != values[1]:
errors.append(
"{} on instance ({}) does not match with the asset "
"({}).".format(label.title(), values[1], values[0])
)
for e in errors:
self.log.error(e)

View file

@ -0,0 +1,60 @@
from maya import cmds
import pyblish.api
from openpype.pipeline.publish import (
ValidateContentsOrder, PublishValidationError, RepairAction
)
from openpype.pipeline import discover_legacy_creator_plugins
from openpype.hosts.maya.api.lib import imprint
class ValidateInstanceAttributes(pyblish.api.InstancePlugin):
"""Validate Instance Attributes.
New attributes can be introduced as new features come in. Old instances
will need to be updated with these attributes for the documentation to make
sense, and users do not have to recreate the instances.
"""
order = ValidateContentsOrder
hosts = ["maya"]
families = ["*"]
label = "Instance Attributes"
plugins_by_family = {
p.family: p for p in discover_legacy_creator_plugins()
}
actions = [RepairAction]
@classmethod
def get_missing_attributes(self, instance):
plugin = self.plugins_by_family[instance.data["family"]]
subset = instance.data["subset"]
asset = instance.data["asset"]
objset = instance.data["objset"]
missing_attributes = {}
for key, value in plugin(subset, asset).data.items():
if not cmds.objExists("{}.{}".format(objset, key)):
missing_attributes[key] = value
return missing_attributes
def process(self, instance):
objset = instance.data.get("objset")
if objset is None:
self.log.debug(
"Skipping {} because no objectset found.".format(instance)
)
return
missing_attributes = self.get_missing_attributes(instance)
if missing_attributes:
raise PublishValidationError(
"Missing attributes on {}:\n{}".format(
objset, missing_attributes
)
)
@classmethod
def repair(cls, instance):
imprint(instance.data["objset"], cls.get_missing_attributes(instance))

View file

@ -0,0 +1,54 @@
from maya import cmds
import pyblish.api
import openpype.hosts.maya.api.action
from openpype.pipeline.publish import (
RepairAction,
ValidateMeshOrder
)
class ValidateMeshEmpty(pyblish.api.InstancePlugin):
"""Validate meshes have some vertices.
Its possible to have meshes without any vertices. To replicate
this issue, delete all faces/polygons then all edges.
"""
order = ValidateMeshOrder
hosts = ["maya"]
families = ["model"]
label = "Mesh Empty"
actions = [
openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction
]
@classmethod
def repair(cls, instance):
invalid = cls.get_invalid(instance)
for node in invalid:
cmds.delete(node)
@classmethod
def get_invalid(cls, instance):
invalid = []
meshes = cmds.ls(instance, type="mesh", long=True)
for mesh in meshes:
num_vertices = cmds.polyEvaluate(mesh, vertex=True)
if num_vertices == 0:
cls.log.warning(
"\"{}\" does not have any vertices.".format(mesh)
)
invalid.append(mesh)
return invalid
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError(
"Meshes found in instance without any vertices: %s" % invalid
)

View file

@ -1,39 +1,9 @@
import re
from maya import cmds
import pyblish.api
import openpype.hosts.maya.api.action
from openpype.pipeline.publish import ValidateMeshOrder
def len_flattened(components):
"""Return the length of the list as if it was flattened.
Maya will return consecutive components as a single entry
when requesting with `maya.cmds.ls` without the `flatten`
flag. Though enabling `flatten` on a large list (e.g. millions)
will result in a slow result. This command will return the amount
of entries in a non-flattened list by parsing the result with
regex.
Args:
components (list): The non-flattened components.
Returns:
int: The amount of entries.
"""
assert isinstance(components, (list, tuple))
n = 0
for c in components:
match = re.search("\[([0-9]+):([0-9]+)\]", c)
if match:
start, end = match.groups()
n += int(end) - int(start) + 1
else:
n += 1
return n
from openpype.hosts.maya.api.lib import len_flattened
class ValidateMeshHasUVs(pyblish.api.InstancePlugin):
@ -57,6 +27,15 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin):
invalid = []
for node in cmds.ls(instance, type='mesh'):
num_vertices = cmds.polyEvaluate(node, vertex=True)
if num_vertices == 0:
cls.log.warning(
"Skipping \"{}\", cause it does not have any "
"vertices.".format(node)
)
continue
uv = cmds.polyEvaluate(node, uv=True)
if uv == 0:

View file

@ -28,7 +28,10 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin):
@classmethod
def get_invalid(cls, instance):
"""Return the invalid edges.
Also see: http://help.autodesk.com/view/MAYAUL/2015/ENU/?guid=Mesh__Cleanup
Also see:
http://help.autodesk.com/view/MAYAUL/2015/ENU/?guid=Mesh__Cleanup
"""
@ -36,8 +39,21 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin):
if not meshes:
return list()
valid_meshes = []
for mesh in meshes:
num_vertices = cmds.polyEvaluate(mesh, vertex=True)
if num_vertices == 0:
cls.log.warning(
"Skipping \"{}\", cause it does not have any "
"vertices.".format(mesh)
)
continue
valid_meshes.append(mesh)
# Get all edges
edges = ['{0}.e[*]'.format(node) for node in meshes]
edges = ['{0}.e[*]'.format(node) for node in valid_meshes]
# Filter by constraint on edge length
invalid = lib.polyConstraint(edges,

View file

@ -1,5 +1,3 @@
import re
from maya import cmds
import pyblish.api
@ -8,37 +6,7 @@ from openpype.pipeline.publish import (
RepairAction,
ValidateMeshOrder,
)
def len_flattened(components):
"""Return the length of the list as if it was flattened.
Maya will return consecutive components as a single entry
when requesting with `maya.cmds.ls` without the `flatten`
flag. Though enabling `flatten` on a large list (e.g. millions)
will result in a slow result. This command will return the amount
of entries in a non-flattened list by parsing the result with
regex.
Args:
components (list): The non-flattened components.
Returns:
int: The amount of entries.
"""
assert isinstance(components, (list, tuple))
n = 0
pattern = re.compile(r"\[(\d+):(\d+)\]")
for c in components:
match = pattern.search(c)
if match:
start, end = match.groups()
n += int(end) - int(start) + 1
else:
n += 1
return n
from openpype.hosts.maya.api.lib import len_flattened
class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin):
@ -87,6 +55,13 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin):
for mesh in meshes:
num_vertices = cmds.polyEvaluate(mesh, vertex=True)
if num_vertices == 0:
cls.log.warning(
"Skipping \"{}\", cause it does not have any "
"vertices.".format(mesh)
)
continue
# Vertices from all edges
edges = "%s.e[*]" % mesh
vertices = cmds.polyListComponentConversion(edges, toVertex=True)

View file

@ -419,8 +419,13 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
assembly_job_info.Name += " - Tile Assembly Job"
assembly_job_info.Frames = 1
assembly_job_info.MachineLimit = 1
assembly_job_info.Priority = instance.data.get("tile_priority",
self.tile_priority)
assembly_job_info.Priority = instance.data.get(
"tile_priority", self.tile_priority
)
pool = instance.context.data["project_settings"]["deadline"]
pool = pool["publish"]["ProcessSubmittedJobOnFarm"]["deadline_pool"]
assembly_job_info.Pool = pool or instance.data.get("primaryPool", "")
assembly_plugin_info = {
"CleanupTiles": 1,

View file

@ -284,6 +284,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
args.append("--automatic-tests")
# Generate the payload for Deadline submission
secondary_pool = (
self.deadline_pool_secondary or instance.data.get("secondaryPool")
)
payload = {
"JobInfo": {
"Plugin": self.deadline_plugin,
@ -297,8 +300,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
"Priority": priority,
"Group": self.deadline_group,
"Pool": instance.data.get("primaryPool"),
"SecondaryPool": instance.data.get("secondaryPool"),
"Pool": self.deadline_pool or instance.data.get("primaryPool"),
"SecondaryPool": secondary_pool,
# ensure the outputdirectory with correct slashes
"OutputDirectory0": output_dir.replace("\\", "/")
},
@ -588,7 +591,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
self.log.debug("instances:{}".format(instances))
return instances
def _get_representations(self, instance, exp_files, additional_data):
def _get_representations(self, instance, exp_files):
"""Create representations for file sequences.
This will return representations of expected files if they are not
@ -933,20 +936,21 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
self.log.info(data.get("expectedFiles"))
additional_data = {
"renderProducts": instance.data["renderProducts"],
"colorspaceConfig": instance.data["colorspaceConfig"],
"display": instance.data["colorspaceDisplay"],
"view": instance.data["colorspaceView"],
"colorspaceTemplate": instance.data["colorspaceConfig"].replace(
str(context.data["anatomy"].roots["work"]), "{root[work]}"
)
}
if isinstance(data.get("expectedFiles")[0], dict):
# we cannot attach AOVs to other subsets as we consider every
# AOV subset of its own.
config = instance.data["colorspaceConfig"]
additional_data = {
"renderProducts": instance.data["renderProducts"],
"colorspaceConfig": instance.data["colorspaceConfig"],
"display": instance.data["colorspaceDisplay"],
"view": instance.data["colorspaceView"],
"colorspaceTemplate": config.replace(
str(context.data["anatomy"].roots["work"]), "{root[work]}"
)
}
if len(data.get("attachTo")) > 0:
assert len(data.get("expectedFiles")[0].keys()) == 1, (
"attaching multiple AOVs or renderable cameras to "

View file

@ -179,6 +179,13 @@
"Main"
]
},
"CreateReview": {
"enabled": true,
"defaults": [
"Main"
],
"useMayaTimeline": true
},
"CreateAss": {
"enabled": true,
"defaults": [
@ -255,12 +262,6 @@
"Main"
]
},
"CreateReview": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateRig": {
"enabled": true,
"defaults": [

View file

@ -240,6 +240,31 @@
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "CreateReview",
"label": "Create Review",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "defaults",
"label": "Default Subsets",
"object_type": "text"
},
{
"type": "boolean",
"key": "useMayaTimeline",
"label": "Use Maya Timeline for Frame Range."
}
]
},
{
"type": "dict",
"collapsible": true,
@ -398,10 +423,6 @@
"key": "CreateRenderSetup",
"label": "Create Render Setup"
},
{
"key": "CreateReview",
"label": "Create Review"
},
{
"key": "CreateRig",
"label": "Create Rig"

View file

@ -28,16 +28,16 @@ For [AWS Thinkbox Deadline](https://www.awsthinkbox.com/deadline) support you ne
OpenPype integration for Deadline consists of two parts:
- The `OpenPype` Deadline Plug-in
- A `GlobalJobPreLoad` Deadline Script (this gets triggered for each deadline job)
- A `GlobalJobPreLoad` Deadline Script (this gets triggered for each deadline job)
The `GlobalJobPreLoad` handles populating render and publish jobs with proper environment variables using settings from the `OpenPype` Deadline Plug-in.
The `OpenPype` Deadline Plug-in must be configured to point to a valid OpenPype executable location. The executable need to be installed to
The `OpenPype` Deadline Plug-in must be configured to point to a valid OpenPype executable location. The executable need to be installed to
destinations accessible by DL process. Check permissions (must be executable and accessible by Deadline process)
- Enable `Tools > Super User Mode` in Deadline Monitor
- Go to `Tools > Configure Plugins...`, find `OpenPype` in the list on the left side, find location of OpenPype
- Go to `Tools > Configure Plugins...`, find `OpenPype` in the list on the left side, find location of OpenPype
executable. It is recommended to use the `openpype_console` executable as it provides a bit more logging.
- In case of multi OS farms, provide multiple locations, each Deadline Worker goes through the list and tries to find the first accessible
@ -45,12 +45,22 @@ executable. It is recommended to use the `openpype_console` executable as it pro
![Configure plugin](assets/deadline_configure_plugin.png)
### Pools
The main pools can be configured at `project_settings/deadline/publish/CollectDeadlinePools/primary_pool`, which is applied to the rendering jobs.
The dependent publishing job's pool uses `project_settings/deadline/publish/ProcessSubmittedJobOnFarm/deadline_pool`. If nothing is specified the pool will fallback to the primary pool above.
:::note maya tile rendering
The logic for publishing job pool assignment applies to tiling jobs.
:::
## Troubleshooting
#### Publishing jobs fail directly in DCCs
- Double check that all previously described steps were finished
- Check that `deadlinewebservice` is running on DL server
- Check that `deadlinewebservice` is running on DL server
- Check that user's machine has access to deadline server on configured port
#### Jobs are failing on DL side
@ -61,40 +71,40 @@ Each publishing from OpenPype consists of 2 jobs, first one is rendering, second
- Jobs are failing with `OpenPype executable was not found` error
Check if OpenPype is installed on the Worker handling this job and ensure `OpenPype` Deadline Plug-in is properly [configured](#configuration)
Check if OpenPype is installed on the Worker handling this job and ensure `OpenPype` Deadline Plug-in is properly [configured](#configuration)
- Publishing job is failing with `ffmpeg not installed` error
OpenPype executable has to have access to `ffmpeg` executable, check OpenPype `Setting > General`
![FFmpeg setting](assets/ffmpeg_path.png)
- Both jobs finished successfully, but there is no review on Ftrack
Make sure that you correctly set published family to be send to Ftrack.
Make sure that you correctly set published family to be send to Ftrack.
![Ftrack Family](assets/ftrack/ftrack-collect-main.png)
Example: I want send to Ftrack review of rendered images from Harmony :
- `Host names`: "harmony"
- `Families`: "render"
- `Families`: "render"
- `Add Ftrack Family` to "Enabled"
Make sure that you actually configured to create review for published subset in `project_settings/ftrack/publish/CollectFtrackFamily`
![Ftrack Family](assets/deadline_review.png)
Example: I want to create review for all reviewable subsets in Harmony :
Example: I want to create review for all reviewable subsets in Harmony :
- Add "harmony" as a new key an ".*" as a value.
- Rendering jobs are stuck in 'Queued' state or failing
Make sure that your Deadline is not limiting specific jobs to be run only on specific machines. (Eg. only some machines have installed particular application.)
Check `project_settings/deadline`
![Deadline group](assets/deadline_group.png)
Example: I have separated machines with "Harmony" installed into "harmony" group on Deadline. I want rendering jobs published from Harmony to run only on those machines.