mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge branch 'develop' into bugfix/OP-6416_3dsmax-container-tab
This commit is contained in:
commit
edbecc50fb
98 changed files with 2186 additions and 615 deletions
|
|
@ -138,7 +138,6 @@ class CollectAERender(publish.AbstractCollectRender):
|
|||
fam = "render.farm"
|
||||
if fam not in instance.families:
|
||||
instance.families.append(fam)
|
||||
instance.toBeRenderedOn = "deadline"
|
||||
instance.renderer = "aerender"
|
||||
instance.farm = True # to skip integrate
|
||||
if "review" in instance.families:
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@ class CollectFusionRender(
|
|||
fam = "render.farm"
|
||||
if fam not in instance.families:
|
||||
instance.families.append(fam)
|
||||
instance.toBeRenderedOn = "deadline"
|
||||
instance.farm = True # to skip integrate
|
||||
if "review" in instance.families:
|
||||
# to skip ExtractReview locally
|
||||
|
|
|
|||
|
|
@ -147,13 +147,13 @@ class CollectFarmRender(publish.AbstractCollectRender):
|
|||
attachTo=False,
|
||||
setMembers=[node],
|
||||
publish=info[4],
|
||||
review=False,
|
||||
renderer=None,
|
||||
priority=50,
|
||||
name=node.split("/")[1],
|
||||
|
||||
family="render.farm",
|
||||
families=["render.farm"],
|
||||
farm=True,
|
||||
|
||||
resolutionWidth=context.data["resolutionWidth"],
|
||||
resolutionHeight=context.data["resolutionHeight"],
|
||||
|
|
@ -174,7 +174,6 @@ class CollectFarmRender(publish.AbstractCollectRender):
|
|||
outputFormat=info[1],
|
||||
outputStartFrame=info[3],
|
||||
leadingZeros=info[2],
|
||||
toBeRenderedOn='deadline',
|
||||
ignoreFrameHandleCheck=True
|
||||
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,9 +22,12 @@ log = logging.getLogger(__name__)
|
|||
JSON_PREFIX = "JSON:::"
|
||||
|
||||
|
||||
def get_asset_fps():
|
||||
def get_asset_fps(asset_doc=None):
|
||||
"""Return current asset fps."""
|
||||
return get_current_project_asset()["data"].get("fps")
|
||||
|
||||
if asset_doc is None:
|
||||
asset_doc = get_current_project_asset(fields=["data.fps"])
|
||||
return asset_doc["data"]["fps"]
|
||||
|
||||
|
||||
def set_id(node, unique_id, overwrite=False):
|
||||
|
|
@ -472,14 +475,19 @@ def maintained_selection():
|
|||
|
||||
|
||||
def reset_framerange():
|
||||
"""Set frame range to current asset"""
|
||||
"""Set frame range and FPS to current asset"""
|
||||
|
||||
# Get asset data
|
||||
project_name = get_current_project_name()
|
||||
asset_name = get_current_asset_name()
|
||||
# Get the asset ID from the database for the asset of current context
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
asset_data = asset_doc["data"]
|
||||
|
||||
# Get FPS
|
||||
fps = get_asset_fps(asset_doc)
|
||||
|
||||
# Get Start and End Frames
|
||||
frame_start = asset_data.get("frameStart")
|
||||
frame_end = asset_data.get("frameEnd")
|
||||
|
||||
|
|
@ -493,6 +501,9 @@ def reset_framerange():
|
|||
frame_start -= int(handle_start)
|
||||
frame_end += int(handle_end)
|
||||
|
||||
# Set frame range and FPS
|
||||
print("Setting scene FPS to {}".format(int(fps)))
|
||||
set_scene_fps(fps)
|
||||
hou.playbar.setFrameRange(frame_start, frame_end)
|
||||
hou.playbar.setPlaybackRange(frame_start, frame_end)
|
||||
hou.setFrame(frame_start)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ from openpype.lib import (
|
|||
emit_event,
|
||||
)
|
||||
|
||||
from .lib import get_asset_fps
|
||||
|
||||
log = logging.getLogger("openpype.hosts.houdini")
|
||||
|
||||
|
|
@ -385,11 +384,6 @@ def _set_context_settings():
|
|||
None
|
||||
"""
|
||||
|
||||
# Set new scene fps
|
||||
fps = get_asset_fps()
|
||||
print("Setting scene FPS to %i" % fps)
|
||||
lib.set_scene_fps(fps)
|
||||
|
||||
lib.reset_framerange()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class CreateVDBCache(plugin.HoudiniCreator):
|
|||
}
|
||||
|
||||
if self.selected_nodes:
|
||||
parms["soppath"] = self.selected_nodes[0].path()
|
||||
parms["soppath"] = self.get_sop_node_path(self.selected_nodes[0])
|
||||
|
||||
instance_node.setParms(parms)
|
||||
|
||||
|
|
@ -42,3 +42,63 @@ class CreateVDBCache(plugin.HoudiniCreator):
|
|||
hou.ropNodeTypeCategory(),
|
||||
hou.sopNodeTypeCategory()
|
||||
]
|
||||
|
||||
def get_sop_node_path(self, selected_node):
|
||||
"""Get Sop Path of the selected node.
|
||||
|
||||
Although Houdini allows ObjNode path on `sop_path` for the
|
||||
the ROP node, we prefer it set to the SopNode path explicitly.
|
||||
"""
|
||||
|
||||
# Allow sop level paths (e.g. /obj/geo1/box1)
|
||||
if isinstance(selected_node, hou.SopNode):
|
||||
self.log.debug(
|
||||
"Valid SopNode selection, 'SOP Path' in ROP will"
|
||||
" be set to '%s'.", selected_node.path()
|
||||
)
|
||||
return selected_node.path()
|
||||
|
||||
# Allow object level paths to Geometry nodes (e.g. /obj/geo1)
|
||||
# but do not allow other object level nodes types like cameras, etc.
|
||||
elif isinstance(selected_node, hou.ObjNode) and \
|
||||
selected_node.type().name() == "geo":
|
||||
|
||||
# Try to find output node.
|
||||
sop_node = self.get_obj_output(selected_node)
|
||||
if sop_node:
|
||||
self.log.debug(
|
||||
"Valid ObjNode selection, 'SOP Path' in ROP will "
|
||||
"be set to the child path '%s'.", sop_node.path()
|
||||
)
|
||||
return sop_node.path()
|
||||
|
||||
self.log.debug(
|
||||
"Selection isn't valid. 'SOP Path' in ROP will be empty."
|
||||
)
|
||||
return ""
|
||||
|
||||
def get_obj_output(self, obj_node):
|
||||
"""Try to find output node.
|
||||
|
||||
If any output nodes are present, return the output node with
|
||||
the minimum 'outputidx'
|
||||
If no output nodes are present, return the node with display flag
|
||||
If no nodes are present at all, return None
|
||||
"""
|
||||
|
||||
outputs = obj_node.subnetOutputs()
|
||||
|
||||
# if obj_node is empty
|
||||
if not outputs:
|
||||
return
|
||||
|
||||
# if obj_node has one output child whether its
|
||||
# sop output node or a node with the render flag
|
||||
elif len(outputs) == 1:
|
||||
return outputs[0]
|
||||
|
||||
# if there are more than one, then it has multiple output nodes
|
||||
# return the one with the minimum 'outputidx'
|
||||
else:
|
||||
return min(outputs,
|
||||
key=lambda node: node.evalParm('outputidx'))
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ class HdaLoader(load.LoaderPlugin):
|
|||
def_paths = [d.libraryFilePath() for d in defs]
|
||||
new = def_paths.index(file_path)
|
||||
defs[new].setIsPreferred(True)
|
||||
hda_node.setParms({
|
||||
"representation": str(representation["_id"])
|
||||
})
|
||||
|
||||
def remove(self, container):
|
||||
node = container["node"]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,19 @@
|
|||
<mainMenu>
|
||||
<menuBar>
|
||||
<subMenu id="openpype_menu">
|
||||
<label>OpenPype</label>
|
||||
<labelExpression><![CDATA[
|
||||
import os
|
||||
return os.environ.get("AVALON_LABEL") or "OpenPype"
|
||||
]]></labelExpression>
|
||||
<actionItem id="asset_name">
|
||||
<labelExpression><![CDATA[
|
||||
from openpype.pipeline import get_current_asset_name, get_current_task_name
|
||||
label = "{}, {}".format(get_current_asset_name(), get_current_task_name())
|
||||
return label
|
||||
]]></labelExpression>
|
||||
</actionItem>
|
||||
|
||||
<separatorItem/>
|
||||
|
||||
<scriptItem id="openpype_create">
|
||||
<label>Create...</label>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class RenderSettings(object):
|
|||
rt.viewport.setCamera(sel)
|
||||
break
|
||||
if not found:
|
||||
raise RuntimeError("Camera not found")
|
||||
raise RuntimeError("Active Camera not found")
|
||||
|
||||
def render_output(self, container):
|
||||
folder = rt.maxFilePath
|
||||
|
|
@ -113,7 +113,8 @@ class RenderSettings(object):
|
|||
# for setting up renderable camera
|
||||
arv = rt.MAXToAOps.ArnoldRenderView()
|
||||
render_camera = rt.viewport.GetCamera()
|
||||
arv.setOption("Camera", str(render_camera))
|
||||
if render_camera:
|
||||
arv.setOption("Camera", str(render_camera))
|
||||
|
||||
# TODO: add AOVs and extension
|
||||
img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ class CollectRender(pyblish.api.InstancePlugin):
|
|||
aovs = RenderProducts().get_aovs(instance.name)
|
||||
files_by_aov.update(aovs)
|
||||
|
||||
camera = rt.viewport.GetCamera()
|
||||
instance.data["cameras"] = [camera.name] if camera else None # noqa
|
||||
|
||||
if "expectedFiles" not in instance.data:
|
||||
instance.data["expectedFiles"] = list()
|
||||
instance.data["files"] = list()
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ class ValidateMaxContents(pyblish.api.InstancePlugin):
|
|||
order = pyblish.api.ValidatorOrder
|
||||
families = ["camera",
|
||||
"maxScene",
|
||||
"maxrender",
|
||||
"review"]
|
||||
hosts = ["max"]
|
||||
label = "Max Scene Contents"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import pyblish.api
|
||||
from openpype.pipeline import (
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin)
|
||||
from openpype.pipeline.publish import RepairAction
|
||||
from openpype.hosts.max.api.lib import get_current_renderer
|
||||
|
||||
from pymxs import runtime as rt
|
||||
|
||||
|
||||
class ValidateRenderableCamera(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validates Renderable Camera
|
||||
|
||||
Check if the renderable camera used for rendering
|
||||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
families = ["maxrender"]
|
||||
hosts = ["max"]
|
||||
label = "Renderable Camera"
|
||||
optional = True
|
||||
actions = [RepairAction]
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
if not instance.data["cameras"]:
|
||||
raise PublishValidationError(
|
||||
"No renderable Camera found in scene."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
|
||||
rt.viewport.setType(rt.Name("view_camera"))
|
||||
camera = rt.viewport.GetCamera()
|
||||
cls.log.info(f"Camera {camera} set as renderable camera")
|
||||
renderer_class = get_current_renderer()
|
||||
renderer = str(renderer_class).split(":")[0]
|
||||
if renderer == "Arnold":
|
||||
arv = rt.MAXToAOps.ArnoldRenderView()
|
||||
arv.setOption("Camera", str(camera))
|
||||
arv.close()
|
||||
instance.data["cameras"] = [camera.name]
|
||||
|
|
@ -10,7 +10,6 @@ class CollectCurrentFile(pyblish.api.ContextPlugin):
|
|||
order = pyblish.api.CollectorOrder - 0.4
|
||||
label = "Maya Current File"
|
||||
hosts = ['maya']
|
||||
families = ["workfile"]
|
||||
|
||||
def process(self, context):
|
||||
"""Inject the current working file"""
|
||||
|
|
|
|||
|
|
@ -249,7 +249,6 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin):
|
|||
Authenticate with Muster, collect all data, prepare path for post
|
||||
render publish job and submit job to farm.
|
||||
"""
|
||||
instance.data["toBeRenderedOn"] = "muster"
|
||||
# setup muster environment
|
||||
self.MUSTER_REST_URL = os.environ.get("MUSTER_REST_URL")
|
||||
|
||||
|
|
|
|||
|
|
@ -3,94 +3,19 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import pyblish.api
|
||||
import openpype.hosts.maya.api.action
|
||||
from openpype.pipeline.publish import (
|
||||
ValidateContentsOrder, PublishValidationError
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
from maya import cmds
|
||||
|
||||
|
||||
class SelectInvalidInstances(pyblish.api.Action):
|
||||
"""Select invalid instances in Outliner."""
|
||||
|
||||
label = "Select Instances"
|
||||
icon = "briefcase"
|
||||
on = "failed"
|
||||
|
||||
def process(self, context, plugin):
|
||||
"""Process invalid validators and select invalid instances."""
|
||||
# Get the errored instances
|
||||
failed = []
|
||||
for result in context.data["results"]:
|
||||
if (
|
||||
result["error"] is None
|
||||
or result["instance"] is None
|
||||
or result["instance"] in failed
|
||||
or result["plugin"] != plugin
|
||||
):
|
||||
continue
|
||||
|
||||
failed.append(result["instance"])
|
||||
|
||||
# Apply pyblish.logic to get the instances for the plug-in
|
||||
instances = pyblish.api.instances_by_plugin(failed, plugin)
|
||||
|
||||
if instances:
|
||||
self.log.info(
|
||||
"Selecting invalid nodes: %s" % ", ".join(
|
||||
[str(x) for x in instances]
|
||||
)
|
||||
)
|
||||
self.select(instances)
|
||||
else:
|
||||
self.log.info("No invalid nodes found.")
|
||||
self.deselect()
|
||||
|
||||
def select(self, instances):
|
||||
cmds.select(instances, replace=True, noExpand=True)
|
||||
|
||||
def deselect(self):
|
||||
cmds.select(deselect=True)
|
||||
|
||||
|
||||
class RepairSelectInvalidInstances(pyblish.api.Action):
|
||||
"""Repair the instance asset."""
|
||||
|
||||
label = "Repair"
|
||||
icon = "wrench"
|
||||
on = "failed"
|
||||
|
||||
def process(self, context, plugin):
|
||||
# Get the errored instances
|
||||
failed = []
|
||||
for result in context.data["results"]:
|
||||
if result["error"] is None:
|
||||
continue
|
||||
if result["instance"] is None:
|
||||
continue
|
||||
if result["instance"] in failed:
|
||||
continue
|
||||
if result["plugin"] != plugin:
|
||||
continue
|
||||
|
||||
failed.append(result["instance"])
|
||||
|
||||
# Apply pyblish.logic to get the instances for the plug-in
|
||||
instances = pyblish.api.instances_by_plugin(failed, plugin)
|
||||
|
||||
context_asset = context.data["assetEntity"]["name"]
|
||||
for instance in instances:
|
||||
self.set_attribute(instance, context_asset)
|
||||
|
||||
def set_attribute(self, instance, context_asset):
|
||||
cmds.setAttr(
|
||||
instance.data.get("name") + ".asset",
|
||||
context_asset,
|
||||
type="string"
|
||||
)
|
||||
|
||||
|
||||
class ValidateInstanceInContext(pyblish.api.InstancePlugin):
|
||||
class ValidateInstanceInContext(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validator to check if instance asset match context asset.
|
||||
|
||||
When working in per-shot style you always publish data in context of
|
||||
|
|
@ -104,11 +29,49 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin):
|
|||
label = "Instance in same Context"
|
||||
optional = True
|
||||
hosts = ["maya"]
|
||||
actions = [SelectInvalidInstances, RepairSelectInvalidInstances]
|
||||
actions = [
|
||||
openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction
|
||||
]
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
asset = instance.data.get("asset")
|
||||
context_asset = instance.context.data["assetEntity"]["name"]
|
||||
msg = "{} has asset {}".format(instance.name, asset)
|
||||
context_asset = self.get_context_asset(instance)
|
||||
if asset != context_asset:
|
||||
raise PublishValidationError(msg)
|
||||
raise PublishValidationError(
|
||||
message=(
|
||||
"Instance '{}' publishes to different asset than current "
|
||||
"context: {}. Current context: {}".format(
|
||||
instance.name, asset, context_asset
|
||||
)
|
||||
),
|
||||
description=(
|
||||
"## Publishing to a different asset\n"
|
||||
"There are publish instances present which are publishing "
|
||||
"into a different asset than your current context.\n\n"
|
||||
"Usually this is not what you want but there can be cases "
|
||||
"where you might want to publish into another asset or "
|
||||
"shot. If that's the case you can disable the validation "
|
||||
"on the instance to ignore it."
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
return [instance.data["instance_node"]]
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
context_asset = cls.get_context_asset(instance)
|
||||
instance_node = instance.data["instance_node"]
|
||||
cmds.setAttr(
|
||||
"{}.asset".format(instance_node),
|
||||
context_asset,
|
||||
type="string"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_context_asset(instance):
|
||||
return instance.context.data["assetEntity"]["name"]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ from maya import cmds
|
|||
|
||||
import pyblish.api
|
||||
|
||||
from openpype.hosts.maya.api.lib import pairwise
|
||||
from openpype.hosts.maya.api.action import SelectInvalidAction
|
||||
from openpype.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
|
|
@ -19,31 +21,33 @@ class ValidatePluginPathAttributes(pyblish.api.InstancePlugin):
|
|||
hosts = ['maya']
|
||||
families = ["workfile"]
|
||||
label = "Plug-in Path Attributes"
|
||||
actions = [SelectInvalidAction]
|
||||
|
||||
def get_invalid(self, instance):
|
||||
# Attributes are defined in project settings
|
||||
attribute = []
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
invalid = list()
|
||||
|
||||
# get the project setting
|
||||
validate_path = (
|
||||
instance.context.data["project_settings"]["maya"]["publish"]
|
||||
)
|
||||
file_attr = validate_path["ValidatePluginPathAttributes"]["attribute"]
|
||||
file_attr = cls.attribute
|
||||
if not file_attr:
|
||||
return invalid
|
||||
|
||||
# get the nodes and file attributes
|
||||
for node, attr in file_attr.items():
|
||||
# check the related nodes
|
||||
targets = cmds.ls(type=node)
|
||||
# Consider only valid node types to avoid "Unknown object type" warning
|
||||
all_node_types = set(cmds.allNodeTypes())
|
||||
node_types = [key for key in file_attr.keys() if key in all_node_types]
|
||||
|
||||
for target in targets:
|
||||
# get the filepath
|
||||
file_attr = "{}.{}".format(target, attr)
|
||||
filepath = cmds.getAttr(file_attr)
|
||||
for node, node_type in pairwise(cmds.ls(type=node_types,
|
||||
showType=True)):
|
||||
# get the filepath
|
||||
file_attr = "{}.{}".format(node, file_attr[node_type])
|
||||
filepath = cmds.getAttr(file_attr)
|
||||
|
||||
if filepath and not os.path.exists(filepath):
|
||||
self.log.error("File {0} not exists".format(filepath)) # noqa
|
||||
invalid.append(target)
|
||||
if filepath and not os.path.exists(filepath):
|
||||
cls.log.error("{} '{}' uses non-existing filepath: {}"
|
||||
.format(node_type, node, filepath))
|
||||
invalid.append(node)
|
||||
|
||||
return invalid
|
||||
|
||||
|
|
@ -51,5 +55,16 @@ class ValidatePluginPathAttributes(pyblish.api.InstancePlugin):
|
|||
"""Process all directories Set as Filenames in Non-Maya Nodes"""
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError("Non-existent Path "
|
||||
"found: {0}".format(invalid))
|
||||
raise PublishValidationError(
|
||||
title="Plug-in Path Attributes",
|
||||
message="Non-existent filepath found on nodes: {}".format(
|
||||
", ".join(invalid)
|
||||
),
|
||||
description=(
|
||||
"## Plug-in nodes use invalid filepaths\n"
|
||||
"The workfile contains nodes from plug-ins that use "
|
||||
"filepaths which do not exist.\n\n"
|
||||
"Please make sure their filepaths are correct and the "
|
||||
"files exist on disk."
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from openpype.hosts.maya.api import lib
|
|||
from openpype.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
RepairAction,
|
||||
PublishValidationError
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -67,5 +68,30 @@ class ValidateShapeZero(pyblish.api.Validator):
|
|||
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise ValueError("Shapes found with non-zero component tweaks: "
|
||||
"{0}".format(invalid))
|
||||
raise PublishValidationError(
|
||||
title="Shape Component Tweaks",
|
||||
message="Shapes found with non-zero component tweaks: '{}'"
|
||||
"".format(", ".join(invalid)),
|
||||
description=(
|
||||
"## Shapes found with component tweaks\n"
|
||||
"Shapes were detected that have component tweaks on their "
|
||||
"components. Please remove the component tweaks to "
|
||||
"continue.\n\n"
|
||||
"### Repair\n"
|
||||
"The repair action will try to *freeze* the component "
|
||||
"tweaks into the shapes, which is usually the correct fix "
|
||||
"if the mesh has no construction history (= has its "
|
||||
"history deleted)."),
|
||||
detail=(
|
||||
"Maya allows to store component tweaks within shape nodes "
|
||||
"which are applied between its `inMesh` and `outMesh` "
|
||||
"connections resulting in the output of a shape node "
|
||||
"differing from the input. We usually want to avoid this "
|
||||
"for published meshes (in particular for Maya scenes) as "
|
||||
"it can have unintended results when using these meshes "
|
||||
"as intermediate meshes since it applies positional "
|
||||
"differences without being visible edits in the node "
|
||||
"graph.\n\n"
|
||||
"These tweaks are traditionally stored in the `.pnts` "
|
||||
"attribute of shapes.")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2041,6 +2041,7 @@ class WorkfileSettings(object):
|
|||
)
|
||||
|
||||
workfile_settings = imageio_host["workfile"]
|
||||
viewer_process_settings = imageio_host["viewer"]["viewerProcess"]
|
||||
|
||||
if not config_data:
|
||||
# TODO: backward compatibility for old projects - remove later
|
||||
|
|
@ -2091,6 +2092,15 @@ class WorkfileSettings(object):
|
|||
workfile_settings.pop("colorManagement", None)
|
||||
workfile_settings.pop("OCIO_config", None)
|
||||
|
||||
# get monitor lut from settings respecting Nuke version differences
|
||||
monitor_lut = workfile_settings.pop("monitorLut", None)
|
||||
monitor_lut_data = self._get_monitor_settings(
|
||||
viewer_process_settings, monitor_lut)
|
||||
|
||||
# set monitor related knobs luts (MonitorOut, Thumbnails)
|
||||
for knob, value_ in monitor_lut_data.items():
|
||||
workfile_settings[knob] = value_
|
||||
|
||||
# then set the rest
|
||||
for knob, value_ in workfile_settings.items():
|
||||
# skip unfilled ocio config path
|
||||
|
|
@ -2107,8 +2117,9 @@ class WorkfileSettings(object):
|
|||
|
||||
# set ocio config path
|
||||
if config_data:
|
||||
config_path = config_data["path"].replace("\\", "/")
|
||||
log.info("OCIO config path found: `{}`".format(
|
||||
config_data["path"]))
|
||||
config_path))
|
||||
|
||||
# check if there's a mismatch between environment and settings
|
||||
correct_settings = self._is_settings_matching_environment(
|
||||
|
|
@ -2118,6 +2129,40 @@ class WorkfileSettings(object):
|
|||
if correct_settings:
|
||||
self._set_ocio_config_path_to_workfile(config_data)
|
||||
|
||||
def _get_monitor_settings(self, viewer_lut, monitor_lut):
|
||||
""" Get monitor settings from viewer and monitor lut
|
||||
|
||||
Args:
|
||||
viewer_lut (str): viewer lut string
|
||||
monitor_lut (str): monitor lut string
|
||||
|
||||
Returns:
|
||||
dict: monitor settings
|
||||
"""
|
||||
output_data = {}
|
||||
m_display, m_viewer = get_viewer_config_from_string(monitor_lut)
|
||||
v_display, v_viewer = get_viewer_config_from_string(viewer_lut)
|
||||
|
||||
# set monitor lut differently for nuke version 14
|
||||
if nuke.NUKE_VERSION_MAJOR >= 14:
|
||||
output_data["monitorOutLUT"] = create_viewer_profile_string(
|
||||
m_viewer, m_display, path_like=False)
|
||||
# monitorLut=thumbnails - viewerProcess makes more sense
|
||||
output_data["monitorLut"] = create_viewer_profile_string(
|
||||
v_viewer, v_display, path_like=False)
|
||||
|
||||
if nuke.NUKE_VERSION_MAJOR == 13:
|
||||
output_data["monitorOutLUT"] = create_viewer_profile_string(
|
||||
m_viewer, m_display, path_like=False)
|
||||
# monitorLut=thumbnails - viewerProcess makes more sense
|
||||
output_data["monitorLut"] = create_viewer_profile_string(
|
||||
v_viewer, v_display, path_like=True)
|
||||
if nuke.NUKE_VERSION_MAJOR <= 12:
|
||||
output_data["monitorLut"] = create_viewer_profile_string(
|
||||
m_viewer, m_display, path_like=True)
|
||||
|
||||
return output_data
|
||||
|
||||
def _is_settings_matching_environment(self, config_data):
|
||||
""" Check if OCIO config path is different from environment
|
||||
|
||||
|
|
@ -2177,6 +2222,7 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
|||
"""
|
||||
# replace path with env var if possible
|
||||
ocio_path = self._replace_ocio_path_with_env_var(config_data)
|
||||
ocio_path = ocio_path.replace("\\", "/")
|
||||
|
||||
log.info("Setting OCIO config path to: `{}`".format(
|
||||
ocio_path))
|
||||
|
|
@ -2232,7 +2278,7 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
|||
Returns:
|
||||
str: OCIO config path with environment variable TCL expression
|
||||
"""
|
||||
config_path = config_data["path"]
|
||||
config_path = config_data["path"].replace("\\", "/")
|
||||
config_template = config_data["template"]
|
||||
|
||||
included_vars = self._get_included_vars(config_template)
|
||||
|
|
@ -3320,11 +3366,11 @@ def get_viewer_config_from_string(input_string):
|
|||
display = split[0]
|
||||
elif "(" in viewer:
|
||||
pattern = r"([\w\d\s\.\-]+).*[(](.*)[)]"
|
||||
result = re.findall(pattern, viewer)
|
||||
result_ = re.findall(pattern, viewer)
|
||||
try:
|
||||
result = result.pop()
|
||||
display = str(result[1]).rstrip()
|
||||
viewer = str(result[0]).rstrip()
|
||||
result_ = result_.pop()
|
||||
display = str(result_[1]).rstrip()
|
||||
viewer = str(result_[0]).rstrip()
|
||||
except IndexError:
|
||||
raise IndexError((
|
||||
"Viewer Input string is not correct. "
|
||||
|
|
@ -3332,3 +3378,22 @@ def get_viewer_config_from_string(input_string):
|
|||
).format(input_string))
|
||||
|
||||
return (display, viewer)
|
||||
|
||||
|
||||
def create_viewer_profile_string(viewer, display=None, path_like=False):
|
||||
"""Convert viewer and display to string
|
||||
|
||||
Args:
|
||||
viewer (str): viewer name
|
||||
display (Optional[str]): display name
|
||||
path_like (Optional[bool]): if True, return path like string
|
||||
|
||||
Returns:
|
||||
str: viewer config string
|
||||
"""
|
||||
if not display:
|
||||
return viewer
|
||||
|
||||
if path_like:
|
||||
return "{}/{}".format(display, viewer)
|
||||
return "{} ({})".format(viewer, display)
|
||||
|
|
|
|||
|
|
@ -543,6 +543,9 @@ def list_instances(creator_id=None):
|
|||
|
||||
For SubsetManager
|
||||
|
||||
Args:
|
||||
creator_id (Optional[str]): creator identifier
|
||||
|
||||
Returns:
|
||||
(list) of dictionaries matching instances format
|
||||
"""
|
||||
|
|
@ -575,10 +578,13 @@ def list_instances(creator_id=None):
|
|||
if creator_id and instance_data["creator_identifier"] != creator_id:
|
||||
continue
|
||||
|
||||
if instance_data["instance_id"] in instance_ids:
|
||||
instance_id = instance_data.get("instance_id")
|
||||
if not instance_id:
|
||||
pass
|
||||
elif instance_id in instance_ids:
|
||||
instance_data.pop("instance_id")
|
||||
else:
|
||||
instance_ids.add(instance_data["instance_id"])
|
||||
instance_ids.add(instance_id)
|
||||
|
||||
# node name could change, so update subset name data
|
||||
_update_subset_name_data(instance_data, node)
|
||||
|
|
|
|||
|
|
@ -327,6 +327,7 @@ class NukeWriteCreator(NukeCreator):
|
|||
"frames": "Use existing frames"
|
||||
}
|
||||
if ("farm_rendering" in self.instance_attributes):
|
||||
rendering_targets["frames_farm"] = "Use existing frames - farm"
|
||||
rendering_targets["farm"] = "Farm rendering"
|
||||
|
||||
return EnumDef(
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ import nuke
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectInstanceData(pyblish.api.InstancePlugin):
|
||||
"""Collect all nodes with Avalon knob."""
|
||||
class CollectNukeInstanceData(pyblish.api.InstancePlugin):
|
||||
"""Collect Nuke instance data
|
||||
|
||||
"""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.49
|
||||
label = "Collect Instance Data"
|
||||
label = "Collect Nuke Instance Data"
|
||||
hosts = ["nuke", "nukeassist"]
|
||||
|
||||
# presets
|
||||
|
|
@ -40,5 +42,14 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
|
|||
"pixelAspect": pixel_aspect
|
||||
|
||||
})
|
||||
|
||||
# add creator attributes to instance
|
||||
creator_attributes = instance.data["creator_attributes"]
|
||||
instance.data.update(creator_attributes)
|
||||
|
||||
# add review family if review activated on instance
|
||||
if instance.data.get("review"):
|
||||
instance.data["families"].append("review")
|
||||
|
||||
self.log.debug("Collected instance: {}".format(
|
||||
instance.data))
|
||||
|
|
@ -5,7 +5,7 @@ import nuke
|
|||
class CollectSlate(pyblish.api.InstancePlugin):
|
||||
"""Check if SLATE node is in scene and connected to rendering tree"""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.09
|
||||
order = pyblish.api.CollectorOrder + 0.002
|
||||
label = "Collect Slate Node"
|
||||
hosts = ["nuke"]
|
||||
families = ["render"]
|
||||
|
|
@ -13,10 +13,14 @@ class CollectSlate(pyblish.api.InstancePlugin):
|
|||
def process(self, instance):
|
||||
node = instance.data["transientData"]["node"]
|
||||
|
||||
slate = next((n for n in nuke.allNodes()
|
||||
if "slate" in n.name().lower()
|
||||
if not n["disable"].getValue()),
|
||||
None)
|
||||
slate = next(
|
||||
(
|
||||
n_ for n_ in nuke.allNodes()
|
||||
if "slate" in n_.name().lower()
|
||||
if not n_["disable"].getValue()
|
||||
),
|
||||
None
|
||||
)
|
||||
|
||||
if slate:
|
||||
# check if slate node is connected to write node tree
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import os
|
||||
from pprint import pformat
|
||||
import nuke
|
||||
import pyblish.api
|
||||
from openpype.hosts.nuke import api as napi
|
||||
|
|
@ -15,30 +14,16 @@ class CollectNukeWrites(pyblish.api.InstancePlugin,
|
|||
hosts = ["nuke", "nukeassist"]
|
||||
families = ["render", "prerender", "image"]
|
||||
|
||||
# cache
|
||||
_write_nodes = {}
|
||||
_frame_ranges = {}
|
||||
|
||||
def process(self, instance):
|
||||
self.log.debug(pformat(instance.data))
|
||||
creator_attributes = instance.data["creator_attributes"]
|
||||
instance.data.update(creator_attributes)
|
||||
|
||||
group_node = instance.data["transientData"]["node"]
|
||||
render_target = instance.data["render_target"]
|
||||
family = instance.data["family"]
|
||||
families = instance.data["families"]
|
||||
|
||||
# add targeted family to families
|
||||
instance.data["families"].append(
|
||||
"{}.{}".format(family, render_target)
|
||||
)
|
||||
if instance.data.get("review"):
|
||||
instance.data["families"].append("review")
|
||||
|
||||
child_nodes = napi.get_instance_group_node_childs(instance)
|
||||
instance.data["transientData"]["childNodes"] = child_nodes
|
||||
|
||||
write_node = None
|
||||
for x in child_nodes:
|
||||
if x.Class() == "Write":
|
||||
write_node = x
|
||||
write_node = self._write_node_helper(instance)
|
||||
|
||||
if write_node is None:
|
||||
self.log.warning(
|
||||
|
|
@ -48,113 +33,134 @@ class CollectNukeWrites(pyblish.api.InstancePlugin,
|
|||
)
|
||||
return
|
||||
|
||||
instance.data["writeNode"] = write_node
|
||||
self.log.debug("checking instance: {}".format(instance))
|
||||
# get colorspace and add to version data
|
||||
colorspace = napi.get_colorspace_from_node(write_node)
|
||||
|
||||
# Determine defined file type
|
||||
ext = write_node["file_type"].value()
|
||||
if render_target == "frames":
|
||||
self._set_existing_files_data(instance, colorspace)
|
||||
|
||||
# Get frame range
|
||||
handle_start = instance.context.data["handleStart"]
|
||||
handle_end = instance.context.data["handleEnd"]
|
||||
first_frame = int(nuke.root()["first_frame"].getValue())
|
||||
last_frame = int(nuke.root()["last_frame"].getValue())
|
||||
frame_length = int(last_frame - first_frame + 1)
|
||||
elif render_target == "frames_farm":
|
||||
collected_frames = self._set_existing_files_data(
|
||||
instance, colorspace)
|
||||
|
||||
if write_node["use_limit"].getValue():
|
||||
first_frame = int(write_node["first"].getValue())
|
||||
last_frame = int(write_node["last"].getValue())
|
||||
self._set_expected_files(instance, collected_frames)
|
||||
|
||||
self._add_farm_instance_data(instance)
|
||||
|
||||
elif render_target == "farm":
|
||||
self._add_farm_instance_data(instance)
|
||||
|
||||
# set additional instance data
|
||||
self._set_additional_instance_data(instance, render_target, colorspace)
|
||||
|
||||
def _set_existing_files_data(self, instance, colorspace):
|
||||
"""Set existing files data to instance data.
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): pyblish instance
|
||||
colorspace (str): colorspace
|
||||
|
||||
Returns:
|
||||
list: collected frames
|
||||
"""
|
||||
collected_frames = self._get_collected_frames(instance)
|
||||
|
||||
representation = self._get_existing_frames_representation(
|
||||
instance, collected_frames
|
||||
)
|
||||
|
||||
# inject colorspace data
|
||||
self.set_representation_colorspace(
|
||||
representation, instance.context,
|
||||
colorspace=colorspace
|
||||
)
|
||||
|
||||
instance.data["representations"].append(representation)
|
||||
|
||||
return collected_frames
|
||||
|
||||
def _set_expected_files(self, instance, collected_frames):
|
||||
"""Set expected files to instance data.
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): pyblish instance
|
||||
collected_frames (list): collected frames
|
||||
"""
|
||||
write_node = self._write_node_helper(instance)
|
||||
|
||||
write_file_path = nuke.filename(write_node)
|
||||
output_dir = os.path.dirname(write_file_path)
|
||||
|
||||
# get colorspace and add to version data
|
||||
colorspace = napi.get_colorspace_from_node(write_node)
|
||||
instance.data["expectedFiles"] = [
|
||||
os.path.join(output_dir, source_file)
|
||||
for source_file in collected_frames
|
||||
]
|
||||
|
||||
self.log.debug('output dir: {}'.format(output_dir))
|
||||
def _get_frame_range_data(self, instance):
|
||||
"""Get frame range data from instance.
|
||||
|
||||
if render_target == "frames":
|
||||
representation = {
|
||||
'name': ext,
|
||||
'ext': ext,
|
||||
"stagingDir": output_dir,
|
||||
"tags": []
|
||||
}
|
||||
Args:
|
||||
instance (pyblish.api.Instance): pyblish instance
|
||||
|
||||
# get file path knob
|
||||
node_file_knob = write_node["file"]
|
||||
# list file paths based on input frames
|
||||
expected_paths = list(sorted({
|
||||
node_file_knob.evaluate(frame)
|
||||
for frame in range(first_frame, last_frame + 1)
|
||||
}))
|
||||
Returns:
|
||||
tuple: first_frame, last_frame
|
||||
"""
|
||||
|
||||
# convert only to base names
|
||||
expected_filenames = [
|
||||
os.path.basename(filepath)
|
||||
for filepath in expected_paths
|
||||
]
|
||||
instance_name = instance.data["name"]
|
||||
|
||||
# make sure files are existing at folder
|
||||
collected_frames = [
|
||||
filename
|
||||
for filename in os.listdir(output_dir)
|
||||
if filename in expected_filenames
|
||||
]
|
||||
if self._frame_ranges.get(instance_name):
|
||||
# return cashed write node
|
||||
return self._frame_ranges[instance_name]
|
||||
|
||||
if collected_frames:
|
||||
collected_frames_len = len(collected_frames)
|
||||
frame_start_str = "%0{}d".format(
|
||||
len(str(last_frame))) % first_frame
|
||||
representation['frameStart'] = frame_start_str
|
||||
write_node = self._write_node_helper(instance)
|
||||
|
||||
# in case slate is expected and not yet rendered
|
||||
self.log.debug("_ frame_length: {}".format(frame_length))
|
||||
self.log.debug("_ collected_frames_len: {}".format(
|
||||
collected_frames_len))
|
||||
# Get frame range from workfile
|
||||
first_frame = int(nuke.root()["first_frame"].getValue())
|
||||
last_frame = int(nuke.root()["last_frame"].getValue())
|
||||
|
||||
# this will only run if slate frame is not already
|
||||
# rendered from previews publishes
|
||||
if (
|
||||
"slate" in families
|
||||
and frame_length == collected_frames_len
|
||||
and family == "render"
|
||||
):
|
||||
frame_slate_str = (
|
||||
"{{:0{}d}}".format(len(str(last_frame)))
|
||||
).format(first_frame - 1)
|
||||
# Get frame range from write node if activated
|
||||
if write_node["use_limit"].getValue():
|
||||
first_frame = int(write_node["first"].getValue())
|
||||
last_frame = int(write_node["last"].getValue())
|
||||
|
||||
slate_frame = collected_frames[0].replace(
|
||||
frame_start_str, frame_slate_str)
|
||||
collected_frames.insert(0, slate_frame)
|
||||
# add to cache
|
||||
self._frame_ranges[instance_name] = (first_frame, last_frame)
|
||||
|
||||
if collected_frames_len == 1:
|
||||
representation['files'] = collected_frames.pop()
|
||||
else:
|
||||
representation['files'] = collected_frames
|
||||
return first_frame, last_frame
|
||||
|
||||
# inject colorspace data
|
||||
self.set_representation_colorspace(
|
||||
representation, instance.context,
|
||||
colorspace=colorspace
|
||||
)
|
||||
def _set_additional_instance_data(
|
||||
self, instance, render_target, colorspace
|
||||
):
|
||||
"""Set additional instance data.
|
||||
|
||||
instance.data["representations"].append(representation)
|
||||
self.log.info("Publishing rendered frames ...")
|
||||
Args:
|
||||
instance (pyblish.api.Instance): pyblish instance
|
||||
render_target (str): render target
|
||||
colorspace (str): colorspace
|
||||
"""
|
||||
family = instance.data["family"]
|
||||
|
||||
elif render_target == "farm":
|
||||
farm_keys = ["farm_chunk", "farm_priority", "farm_concurrency"]
|
||||
for key in farm_keys:
|
||||
# Skip if key is not in creator attributes
|
||||
if key not in creator_attributes:
|
||||
continue
|
||||
# Add farm attributes to instance
|
||||
instance.data[key] = creator_attributes[key]
|
||||
# add targeted family to families
|
||||
instance.data["families"].append(
|
||||
"{}.{}".format(family, render_target)
|
||||
)
|
||||
self.log.debug("Appending render target to families: {}.{}".format(
|
||||
family, render_target)
|
||||
)
|
||||
|
||||
# Farm rendering
|
||||
instance.data["transfer"] = False
|
||||
instance.data["farm"] = True
|
||||
self.log.info("Farm rendering ON ...")
|
||||
write_node = self._write_node_helper(instance)
|
||||
|
||||
# Determine defined file type
|
||||
ext = write_node["file_type"].value()
|
||||
|
||||
# get frame range data
|
||||
handle_start = instance.context.data["handleStart"]
|
||||
handle_end = instance.context.data["handleEnd"]
|
||||
first_frame, last_frame = self._get_frame_range_data(instance)
|
||||
|
||||
# get output paths
|
||||
write_file_path = nuke.filename(write_node)
|
||||
output_dir = os.path.dirname(write_file_path)
|
||||
|
||||
# TODO: remove this when we have proper colorspace support
|
||||
version_data = {
|
||||
|
|
@ -188,10 +194,6 @@ class CollectNukeWrites(pyblish.api.InstancePlugin,
|
|||
"frameEndHandle": last_frame,
|
||||
})
|
||||
|
||||
# make sure rendered sequence on farm will
|
||||
# be used for extract review
|
||||
if not instance.data.get("review"):
|
||||
instance.data["useSequenceForReview"] = False
|
||||
|
||||
# TODO temporarily set stagingDir as persistent for backward
|
||||
# compatibility. This is mainly focused on `renders`folders which
|
||||
|
|
@ -199,4 +201,201 @@ class CollectNukeWrites(pyblish.api.InstancePlugin,
|
|||
# this logic should be removed and replaced with custom staging dir
|
||||
instance.data["stagingDir_persistent"] = True
|
||||
|
||||
self.log.debug("instance.data: {}".format(pformat(instance.data)))
|
||||
def _write_node_helper(self, instance):
|
||||
"""Helper function to get write node from instance.
|
||||
|
||||
Also sets instance transient data with child nodes.
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): pyblish instance
|
||||
|
||||
Returns:
|
||||
nuke.Node: write node
|
||||
"""
|
||||
instance_name = instance.data["name"]
|
||||
|
||||
if self._write_nodes.get(instance_name):
|
||||
# return cashed write node
|
||||
return self._write_nodes[instance_name]
|
||||
|
||||
# get all child nodes from group node
|
||||
child_nodes = napi.get_instance_group_node_childs(instance)
|
||||
|
||||
# set child nodes to instance transient data
|
||||
instance.data["transientData"]["childNodes"] = child_nodes
|
||||
|
||||
write_node = None
|
||||
for node_ in child_nodes:
|
||||
if node_.Class() == "Write":
|
||||
write_node = node_
|
||||
|
||||
if write_node:
|
||||
# for slate frame extraction
|
||||
instance.data["transientData"]["writeNode"] = write_node
|
||||
# add to cache
|
||||
self._write_nodes[instance_name] = write_node
|
||||
|
||||
return self._write_nodes[instance_name]
|
||||
|
||||
def _get_existing_frames_representation(
|
||||
self,
|
||||
instance,
|
||||
collected_frames
|
||||
):
|
||||
"""Get existing frames representation.
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): pyblish instance
|
||||
collected_frames (list): collected frames
|
||||
|
||||
Returns:
|
||||
dict: representation
|
||||
"""
|
||||
|
||||
first_frame, last_frame = self._get_frame_range_data(instance)
|
||||
|
||||
write_node = self._write_node_helper(instance)
|
||||
|
||||
write_file_path = nuke.filename(write_node)
|
||||
output_dir = os.path.dirname(write_file_path)
|
||||
|
||||
# Determine defined file type
|
||||
ext = write_node["file_type"].value()
|
||||
|
||||
representation = {
|
||||
"name": ext,
|
||||
"ext": ext,
|
||||
"stagingDir": output_dir,
|
||||
"tags": []
|
||||
}
|
||||
|
||||
frame_start_str = self._get_frame_start_str(first_frame, last_frame)
|
||||
|
||||
representation['frameStart'] = frame_start_str
|
||||
|
||||
# set slate frame
|
||||
collected_frames = self._add_slate_frame_to_collected_frames(
|
||||
instance,
|
||||
collected_frames,
|
||||
first_frame,
|
||||
last_frame
|
||||
)
|
||||
|
||||
if len(collected_frames) == 1:
|
||||
representation['files'] = collected_frames.pop()
|
||||
else:
|
||||
representation['files'] = collected_frames
|
||||
|
||||
return representation
|
||||
|
||||
def _get_frame_start_str(self, first_frame, last_frame):
|
||||
"""Get frame start string.
|
||||
|
||||
Args:
|
||||
first_frame (int): first frame
|
||||
last_frame (int): last frame
|
||||
|
||||
Returns:
|
||||
str: frame start string
|
||||
"""
|
||||
# convert first frame to string with padding
|
||||
return (
|
||||
"{{:0{}d}}".format(len(str(last_frame)))
|
||||
).format(first_frame)
|
||||
|
||||
def _add_slate_frame_to_collected_frames(
|
||||
self,
|
||||
instance,
|
||||
collected_frames,
|
||||
first_frame,
|
||||
last_frame
|
||||
):
|
||||
"""Add slate frame to collected frames.
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): pyblish instance
|
||||
collected_frames (list): collected frames
|
||||
first_frame (int): first frame
|
||||
last_frame (int): last frame
|
||||
|
||||
Returns:
|
||||
list: collected frames
|
||||
"""
|
||||
frame_start_str = self._get_frame_start_str(first_frame, last_frame)
|
||||
frame_length = int(last_frame - first_frame + 1)
|
||||
|
||||
# this will only run if slate frame is not already
|
||||
# rendered from previews publishes
|
||||
if (
|
||||
"slate" in instance.data["families"]
|
||||
and frame_length == len(collected_frames)
|
||||
):
|
||||
frame_slate_str = self._get_frame_start_str(
|
||||
first_frame - 1,
|
||||
last_frame
|
||||
)
|
||||
|
||||
slate_frame = collected_frames[0].replace(
|
||||
frame_start_str, frame_slate_str)
|
||||
collected_frames.insert(0, slate_frame)
|
||||
|
||||
return collected_frames
|
||||
|
||||
def _add_farm_instance_data(self, instance):
|
||||
"""Add farm publishing related instance data.
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): pyblish instance
|
||||
"""
|
||||
|
||||
# make sure rendered sequence on farm will
|
||||
# be used for extract review
|
||||
if not instance.data.get("review"):
|
||||
instance.data["useSequenceForReview"] = False
|
||||
|
||||
# Farm rendering
|
||||
instance.data.update({
|
||||
"transfer": False,
|
||||
"farm": True # to skip integrate
|
||||
})
|
||||
self.log.info("Farm rendering ON ...")
|
||||
|
||||
def _get_collected_frames(self, instance):
|
||||
"""Get collected frames.
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): pyblish instance
|
||||
|
||||
Returns:
|
||||
list: collected frames
|
||||
"""
|
||||
|
||||
first_frame, last_frame = self._get_frame_range_data(instance)
|
||||
|
||||
write_node = self._write_node_helper(instance)
|
||||
|
||||
write_file_path = nuke.filename(write_node)
|
||||
output_dir = os.path.dirname(write_file_path)
|
||||
|
||||
# get file path knob
|
||||
node_file_knob = write_node["file"]
|
||||
# list file paths based on input frames
|
||||
expected_paths = list(sorted({
|
||||
node_file_knob.evaluate(frame)
|
||||
for frame in range(first_frame, last_frame + 1)
|
||||
}))
|
||||
|
||||
# convert only to base names
|
||||
expected_filenames = {
|
||||
os.path.basename(filepath)
|
||||
for filepath in expected_paths
|
||||
}
|
||||
|
||||
# make sure files are existing at folder
|
||||
collected_frames = [
|
||||
filename
|
||||
for filename in os.listdir(output_dir)
|
||||
if filename in expected_filenames
|
||||
]
|
||||
|
||||
return collected_frames
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ from openpype.hosts.nuke.api.lib import maintained_selection
|
|||
|
||||
|
||||
class ExtractCamera(publish.Extractor):
|
||||
""" 3D camera exctractor
|
||||
""" 3D camera extractor
|
||||
"""
|
||||
label = 'Exctract Camera'
|
||||
label = 'Extract Camera'
|
||||
order = pyblish.api.ExtractorOrder
|
||||
families = ["camera"]
|
||||
hosts = ["nuke"]
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ from openpype.hosts.nuke.api.lib import (
|
|||
|
||||
|
||||
class ExtractModel(publish.Extractor):
|
||||
""" 3D model exctractor
|
||||
""" 3D model extractor
|
||||
"""
|
||||
label = 'Exctract Model'
|
||||
label = 'Extract Model'
|
||||
order = pyblish.api.ExtractorOrder
|
||||
families = ["model"]
|
||||
hosts = ["nuke"]
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ class ExtractSlateFrame(publish.Extractor):
|
|||
|
||||
# Add file to representation files
|
||||
# - get write node
|
||||
write_node = instance.data["writeNode"]
|
||||
write_node = instance.data["transientData"]["writeNode"]
|
||||
# - evaluate filepaths for first frame and slate frame
|
||||
first_filename = os.path.basename(
|
||||
write_node["file"].evaluate(first_frame))
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class ExtractThumbnail(publish.Extractor):
|
|||
def render_thumbnail(self, instance, output_name=None, **kwargs):
|
||||
first_frame = instance.data["frameStartHandle"]
|
||||
last_frame = instance.data["frameEndHandle"]
|
||||
colorspace = instance.data["colorspace"]
|
||||
|
||||
# find frame range and define middle thumb frame
|
||||
mid_frame = int((last_frame - first_frame) / 2)
|
||||
|
|
@ -112,8 +113,8 @@ class ExtractThumbnail(publish.Extractor):
|
|||
if self.use_rendered and os.path.isfile(path_render):
|
||||
# check if file exist otherwise connect to write node
|
||||
rnode = nuke.createNode("Read")
|
||||
|
||||
rnode["file"].setValue(path_render)
|
||||
rnode["colorspace"].setValue(colorspace)
|
||||
|
||||
# turn it raw if none of baking is ON
|
||||
if all([
|
||||
|
|
|
|||
|
|
@ -14,27 +14,26 @@ class RepairActionBase(pyblish.api.Action):
|
|||
# Get the errored instances
|
||||
return get_errored_instances_from_context(context, plugin=plugin)
|
||||
|
||||
def repair_knob(self, instances, state):
|
||||
def repair_knob(self, context, instances, state):
|
||||
create_context = context.data["create_context"]
|
||||
for instance in instances:
|
||||
node = instance.data["transientData"]["node"]
|
||||
files_remove = [os.path.join(instance.data["outputDir"], f)
|
||||
for r in instance.data.get("representations", [])
|
||||
for f in r.get("files", [])
|
||||
]
|
||||
self.log.info("Files to be removed: {}".format(files_remove))
|
||||
for f in files_remove:
|
||||
os.remove(f)
|
||||
self.log.debug("removing file: {}".format(f))
|
||||
node["render"].setValue(state)
|
||||
# Reset the render knob
|
||||
instance_id = instance.data.get("instance_id")
|
||||
created_instance = create_context.get_instance_by_id(
|
||||
instance_id
|
||||
)
|
||||
created_instance.creator_attributes["render_target"] = state
|
||||
self.log.info("Rendering toggled to `{}`".format(state))
|
||||
|
||||
create_context.save_changes()
|
||||
|
||||
|
||||
class RepairCollectionActionToLocal(RepairActionBase):
|
||||
label = "Repair - rerender with \"Local\""
|
||||
|
||||
def process(self, context, plugin):
|
||||
instances = self.get_instance(context, plugin)
|
||||
self.repair_knob(instances, "Local")
|
||||
self.repair_knob(context, instances, "local")
|
||||
|
||||
|
||||
class RepairCollectionActionToFarm(RepairActionBase):
|
||||
|
|
@ -42,7 +41,7 @@ class RepairCollectionActionToFarm(RepairActionBase):
|
|||
|
||||
def process(self, context, plugin):
|
||||
instances = self.get_instance(context, plugin)
|
||||
self.repair_knob(instances, "On farm")
|
||||
self.repair_knob(context, instances, "farm")
|
||||
|
||||
|
||||
class ValidateRenderedFrames(pyblish.api.InstancePlugin):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from collections import defaultdict
|
||||
|
||||
import pyblish.api
|
||||
from openpype.pipeline.publish import get_errored_instances_from_context
|
||||
from openpype.hosts.nuke.api.lib import (
|
||||
|
|
@ -87,6 +89,11 @@ class ValidateNukeWriteNode(
|
|||
correct_data
|
||||
))
|
||||
|
||||
# Collect key values of same type in a list.
|
||||
values_by_name = defaultdict(list)
|
||||
for knob_data in correct_data["knobs"]:
|
||||
values_by_name[knob_data["name"]].append(knob_data["value"])
|
||||
|
||||
for knob_data in correct_data["knobs"]:
|
||||
knob_type = knob_data["type"]
|
||||
self.log.debug("__ knob_type: {}".format(
|
||||
|
|
@ -105,28 +112,33 @@ class ValidateNukeWriteNode(
|
|||
)
|
||||
|
||||
key = knob_data["name"]
|
||||
value = knob_data["value"]
|
||||
values = values_by_name[key]
|
||||
node_value = write_node[key].value()
|
||||
|
||||
# fix type differences
|
||||
if type(node_value) in (int, float):
|
||||
try:
|
||||
if isinstance(value, list):
|
||||
value = color_gui_to_int(value)
|
||||
else:
|
||||
value = float(value)
|
||||
node_value = float(node_value)
|
||||
except ValueError:
|
||||
value = str(value)
|
||||
else:
|
||||
value = str(value)
|
||||
node_value = str(node_value)
|
||||
fixed_values = []
|
||||
for value in values:
|
||||
if type(node_value) in (int, float):
|
||||
try:
|
||||
|
||||
self.log.debug("__ key: {} | value: {}".format(
|
||||
key, value
|
||||
if isinstance(value, list):
|
||||
value = color_gui_to_int(value)
|
||||
else:
|
||||
value = float(value)
|
||||
node_value = float(node_value)
|
||||
except ValueError:
|
||||
value = str(value)
|
||||
else:
|
||||
value = str(value)
|
||||
node_value = str(node_value)
|
||||
|
||||
fixed_values.append(value)
|
||||
|
||||
self.log.debug("__ key: {} | values: {}".format(
|
||||
key, fixed_values
|
||||
))
|
||||
if (
|
||||
node_value != value
|
||||
node_value not in fixed_values
|
||||
and key != "file"
|
||||
and key != "tile_color"
|
||||
):
|
||||
|
|
|
|||
|
|
@ -76,11 +76,16 @@ class AnimationAlembicLoader(plugin.Loader):
|
|||
asset_name = "{}_{}".format(asset, name)
|
||||
else:
|
||||
asset_name = "{}".format(name)
|
||||
version = context.get('version').get('name')
|
||||
version = context.get('version')
|
||||
# Check if version is hero version and use different name
|
||||
if not version.get("name") and version.get('type') == "hero_version":
|
||||
name_version = f"{name}_hero"
|
||||
else:
|
||||
name_version = f"{name}_v{version.get('name'):03d}"
|
||||
|
||||
tools = unreal.AssetToolsHelpers().get_asset_tools()
|
||||
asset_dir, container_name = tools.create_unique_asset_name(
|
||||
f"{root}/{asset}/{name}_v{version:03d}", suffix="")
|
||||
f"{root}/{asset}/{name_version}", suffix="")
|
||||
|
||||
container_name += suffix
|
||||
|
||||
|
|
|
|||
|
|
@ -78,11 +78,16 @@ class SkeletalMeshAlembicLoader(plugin.Loader):
|
|||
asset_name = "{}_{}".format(asset, name)
|
||||
else:
|
||||
asset_name = "{}".format(name)
|
||||
version = context.get('version').get('name')
|
||||
version = context.get('version')
|
||||
# Check if version is hero version and use different name
|
||||
if not version.get("name") and version.get('type') == "hero_version":
|
||||
name_version = f"{name}_hero"
|
||||
else:
|
||||
name_version = f"{name}_v{version.get('name'):03d}"
|
||||
|
||||
tools = unreal.AssetToolsHelpers().get_asset_tools()
|
||||
asset_dir, container_name = tools.create_unique_asset_name(
|
||||
f"{root}/{asset}/{name}_v{version:03d}", suffix="")
|
||||
f"{root}/{asset}/{name_version}", suffix="")
|
||||
|
||||
container_name += suffix
|
||||
|
||||
|
|
|
|||
|
|
@ -52,11 +52,16 @@ class SkeletalMeshFBXLoader(plugin.Loader):
|
|||
asset_name = "{}_{}".format(asset, name)
|
||||
else:
|
||||
asset_name = "{}".format(name)
|
||||
version = context.get('version').get('name')
|
||||
version = context.get('version')
|
||||
# Check if version is hero version and use different name
|
||||
if not version.get("name") and version.get('type') == "hero_version":
|
||||
name_version = f"{name}_hero"
|
||||
else:
|
||||
name_version = f"{name}_v{version.get('name'):03d}"
|
||||
|
||||
tools = unreal.AssetToolsHelpers().get_asset_tools()
|
||||
asset_dir, container_name = tools.create_unique_asset_name(
|
||||
f"{root}/{asset}/{name}_v{version:03d}", suffix="")
|
||||
f"{root}/{asset}/{name_version}", suffix="")
|
||||
|
||||
container_name += suffix
|
||||
|
||||
|
|
|
|||
|
|
@ -79,11 +79,13 @@ class StaticMeshAlembicLoader(plugin.Loader):
|
|||
root = "/Game/Ayon/Assets"
|
||||
asset = context.get('asset').get('name')
|
||||
suffix = "_CON"
|
||||
if asset:
|
||||
asset_name = "{}_{}".format(asset, name)
|
||||
asset_name = f"{asset}_{name}" if asset else f"{name}"
|
||||
version = context.get('version')
|
||||
# Check if version is hero version and use different name
|
||||
if not version.get("name") and version.get('type') == "hero_version":
|
||||
name_version = f"{name}_hero"
|
||||
else:
|
||||
asset_name = "{}".format(name)
|
||||
version = context.get('version').get('name')
|
||||
name_version = f"{name}_v{version.get('name'):03d}"
|
||||
|
||||
default_conversion = False
|
||||
if options.get("default_conversion"):
|
||||
|
|
@ -91,7 +93,7 @@ class StaticMeshAlembicLoader(plugin.Loader):
|
|||
|
||||
tools = unreal.AssetToolsHelpers().get_asset_tools()
|
||||
asset_dir, container_name = tools.create_unique_asset_name(
|
||||
f"{root}/{asset}/{name}_v{version:03d}", suffix="")
|
||||
f"{root}/{asset}/{name_version}", suffix="")
|
||||
|
||||
container_name += suffix
|
||||
|
||||
|
|
|
|||
|
|
@ -78,10 +78,16 @@ class StaticMeshFBXLoader(plugin.Loader):
|
|||
asset_name = "{}_{}".format(asset, name)
|
||||
else:
|
||||
asset_name = "{}".format(name)
|
||||
version = context.get('version')
|
||||
# Check if version is hero version and use different name
|
||||
if not version.get("name") and version.get('type') == "hero_version":
|
||||
name_version = f"{name}_hero"
|
||||
else:
|
||||
name_version = f"{name}_v{version.get('name'):03d}"
|
||||
|
||||
tools = unreal.AssetToolsHelpers().get_asset_tools()
|
||||
asset_dir, container_name = tools.create_unique_asset_name(
|
||||
f"{root}/{asset}/{name}", suffix=""
|
||||
f"{root}/{asset}/{name_version}", suffix=""
|
||||
)
|
||||
|
||||
container_name += suffix
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import clique
|
||||
import os
|
||||
import re
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
|
@ -21,7 +23,19 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin):
|
|||
representations = instance.data.get("representations")
|
||||
for repr in representations:
|
||||
data = instance.data.get("assetEntity", {}).get("data", {})
|
||||
patterns = [clique.PATTERNS["frames"]]
|
||||
repr_files = repr["files"]
|
||||
if isinstance(repr_files, str):
|
||||
continue
|
||||
|
||||
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]
|
||||
|
||||
collections, remainder = clique.assemble(
|
||||
repr["files"], minimum_items=1, patterns=patterns)
|
||||
|
||||
|
|
@ -30,6 +44,10 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin):
|
|||
collection = collections[0]
|
||||
frames = list(collection.indexes)
|
||||
|
||||
if instance.data.get("slate"):
|
||||
# Slate is not part of the frame range
|
||||
frames = frames[1:]
|
||||
|
||||
current_range = (frames[0], frames[-1])
|
||||
required_range = (data["clipIn"],
|
||||
data["clipOut"])
|
||||
|
|
|
|||
|
|
@ -280,13 +280,14 @@ class BatchPublishEndpoint(WebpublishApiEndpoint):
|
|||
|
||||
for key, value in add_args.items():
|
||||
# Skip key values where value is None
|
||||
if value is not None:
|
||||
args.append("--{}".format(key))
|
||||
# Extend list into arguments (targets can be a list)
|
||||
if isinstance(value, (tuple, list)):
|
||||
args.extend(value)
|
||||
else:
|
||||
args.append(value)
|
||||
if value is None:
|
||||
continue
|
||||
arg_key = "--{}".format(key)
|
||||
if not isinstance(value, (tuple, list)):
|
||||
value = [value]
|
||||
|
||||
for item in value:
|
||||
args += [arg_key, item]
|
||||
|
||||
log.info("args:: {}".format(args))
|
||||
if add_to_queue:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue