mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
resolve conflict
This commit is contained in:
commit
8b513463f5
25 changed files with 1186 additions and 104 deletions
|
|
@ -15,7 +15,7 @@ from math import ceil
|
|||
from six import string_types
|
||||
|
||||
from maya import cmds, mel
|
||||
import maya.api.OpenMaya as om
|
||||
from maya.api import OpenMaya
|
||||
|
||||
from openpype.client import (
|
||||
get_project,
|
||||
|
|
@ -3511,6 +3511,59 @@ def write_xgen_file(data, filepath):
|
|||
f.writelines(lines)
|
||||
|
||||
|
||||
def get_color_management_preferences():
|
||||
"""Get and resolve OCIO preferences."""
|
||||
data = {
|
||||
# Is color management enabled.
|
||||
"enabled": cmds.colorManagementPrefs(
|
||||
query=True, cmEnabled=True
|
||||
),
|
||||
"rendering_space": cmds.colorManagementPrefs(
|
||||
query=True, renderingSpaceName=True
|
||||
),
|
||||
"output_transform": cmds.colorManagementPrefs(
|
||||
query=True, outputTransformName=True
|
||||
),
|
||||
"output_transform_enabled": cmds.colorManagementPrefs(
|
||||
query=True, outputTransformEnabled=True
|
||||
),
|
||||
"view_transform": cmds.colorManagementPrefs(
|
||||
query=True, viewTransformName=True
|
||||
)
|
||||
}
|
||||
|
||||
# Split view and display from view_transform. view_transform comes in
|
||||
# format of "{view} ({display})".
|
||||
regex = re.compile(r"^(?P<view>.+) \((?P<display>.+)\)$")
|
||||
match = regex.match(data["view_transform"])
|
||||
data.update({
|
||||
"display": match.group("display"),
|
||||
"view": match.group("view")
|
||||
})
|
||||
|
||||
# Get config absolute path.
|
||||
path = cmds.colorManagementPrefs(
|
||||
query=True, configFilePath=True
|
||||
)
|
||||
|
||||
# The OCIO config supports a custom <MAYA_RESOURCES> token.
|
||||
maya_resources_token = "<MAYA_RESOURCES>"
|
||||
maya_resources_path = OpenMaya.MGlobal.getAbsolutePathToResources()
|
||||
path = path.replace(maya_resources_token, maya_resources_path)
|
||||
|
||||
data["config"] = path
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_color_management_output_transform():
|
||||
preferences = get_color_management_preferences()
|
||||
colorspace = preferences["rendering_space"]
|
||||
if preferences["output_transform_enabled"]:
|
||||
colorspace = preferences["output_transform"]
|
||||
return colorspace
|
||||
|
||||
|
||||
def image_info(file_path):
|
||||
# type: (str) -> dict
|
||||
"""Based on tha texture path, get its bit depth and format information.
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import attr
|
|||
|
||||
from . import lib
|
||||
from . import lib_rendersetup
|
||||
from openpype.pipeline.colorspace import get_ocio_config_views
|
||||
|
||||
from maya import cmds, mel
|
||||
|
||||
|
|
@ -127,6 +128,7 @@ class RenderProduct(object):
|
|||
"""
|
||||
productName = attr.ib()
|
||||
ext = attr.ib() # extension
|
||||
colorspace = attr.ib() # colorspace
|
||||
aov = attr.ib(default=None) # source aov
|
||||
driver = attr.ib(default=None) # source driver
|
||||
multipart = attr.ib(default=False) # multichannel file
|
||||
|
|
@ -562,6 +564,9 @@ class RenderProductsArnold(ARenderProducts):
|
|||
]
|
||||
|
||||
for ai_driver in ai_drivers:
|
||||
colorspace = self._get_colorspace(
|
||||
ai_driver + ".colorManagement"
|
||||
)
|
||||
# todo: check aiAOVDriver.prefix as it could have
|
||||
# a custom path prefix set for this driver
|
||||
|
||||
|
|
@ -599,12 +604,15 @@ class RenderProductsArnold(ARenderProducts):
|
|||
global_aov = self._get_attr(aov, "globalAov")
|
||||
if global_aov:
|
||||
for camera in cameras:
|
||||
product = RenderProduct(productName=name,
|
||||
ext=ext,
|
||||
aov=aov_name,
|
||||
driver=ai_driver,
|
||||
multipart=self.multipart,
|
||||
camera=camera)
|
||||
product = RenderProduct(
|
||||
productName=name,
|
||||
ext=ext,
|
||||
aov=aov_name,
|
||||
driver=ai_driver,
|
||||
multipart=self.multipart,
|
||||
camera=camera,
|
||||
colorspace=colorspace
|
||||
)
|
||||
products.append(product)
|
||||
|
||||
all_light_groups = self._get_attr(aov, "lightGroups")
|
||||
|
|
@ -612,13 +620,16 @@ class RenderProductsArnold(ARenderProducts):
|
|||
# All light groups is enabled. A single multipart
|
||||
# Render Product
|
||||
for camera in cameras:
|
||||
product = RenderProduct(productName=name + "_lgroups",
|
||||
ext=ext,
|
||||
aov=aov_name,
|
||||
driver=ai_driver,
|
||||
# Always multichannel output
|
||||
multipart=True,
|
||||
camera=camera)
|
||||
product = RenderProduct(
|
||||
productName=name + "_lgroups",
|
||||
ext=ext,
|
||||
aov=aov_name,
|
||||
driver=ai_driver,
|
||||
# Always multichannel output
|
||||
multipart=True,
|
||||
camera=camera,
|
||||
colorspace=colorspace
|
||||
)
|
||||
products.append(product)
|
||||
else:
|
||||
value = self._get_attr(aov, "lightGroupsList")
|
||||
|
|
@ -634,12 +645,36 @@ class RenderProductsArnold(ARenderProducts):
|
|||
aov=aov_name,
|
||||
driver=ai_driver,
|
||||
ext=ext,
|
||||
camera=camera
|
||||
camera=camera,
|
||||
colorspace=colorspace
|
||||
)
|
||||
products.append(product)
|
||||
|
||||
return products
|
||||
|
||||
def _get_colorspace(self, attribute):
|
||||
"""Resolve colorspace from Arnold settings."""
|
||||
|
||||
def _view_transform():
|
||||
preferences = lib.get_color_management_preferences()
|
||||
views_data = get_ocio_config_views(preferences["config"])
|
||||
view_data = views_data[
|
||||
"{}/{}".format(preferences["display"], preferences["view"])
|
||||
]
|
||||
return view_data["colorspace"]
|
||||
|
||||
def _raw():
|
||||
preferences = lib.get_color_management_preferences()
|
||||
return preferences["rendering_space"]
|
||||
|
||||
resolved_values = {
|
||||
"Raw": _raw,
|
||||
"Use View Transform": _view_transform,
|
||||
# Default. Same as Maya Preferences.
|
||||
"Use Output Transform": lib.get_color_management_output_transform
|
||||
}
|
||||
return resolved_values[self._get_attr(attribute)]()
|
||||
|
||||
def get_render_products(self):
|
||||
"""Get all AOVs.
|
||||
|
||||
|
|
@ -668,11 +703,19 @@ class RenderProductsArnold(ARenderProducts):
|
|||
]
|
||||
|
||||
default_ext = self._get_attr("defaultRenderGlobals.imfPluginKey")
|
||||
beauty_products = [RenderProduct(
|
||||
productName="beauty",
|
||||
ext=default_ext,
|
||||
driver="defaultArnoldDriver",
|
||||
camera=camera) for camera in cameras]
|
||||
colorspace = self._get_colorspace(
|
||||
"defaultArnoldDriver.colorManagement"
|
||||
)
|
||||
beauty_products = [
|
||||
RenderProduct(
|
||||
productName="beauty",
|
||||
ext=default_ext,
|
||||
driver="defaultArnoldDriver",
|
||||
camera=camera,
|
||||
colorspace=colorspace
|
||||
) for camera in cameras
|
||||
]
|
||||
|
||||
# AOVs > Legacy > Maya Render View > Mode
|
||||
aovs_enabled = bool(
|
||||
self._get_attr("defaultArnoldRenderOptions.aovMode")
|
||||
|
|
@ -825,6 +868,7 @@ class RenderProductsVray(ARenderProducts):
|
|||
productName="",
|
||||
ext=default_ext,
|
||||
camera=camera,
|
||||
colorspace=lib.get_color_management_output_transform(),
|
||||
multipart=self.multipart
|
||||
)
|
||||
)
|
||||
|
|
@ -880,10 +924,13 @@ class RenderProductsVray(ARenderProducts):
|
|||
|
||||
aov_name = self._get_vray_aov_name(aov)
|
||||
for camera in cameras:
|
||||
product = RenderProduct(productName=aov_name,
|
||||
ext=default_ext,
|
||||
aov=aov,
|
||||
camera=camera)
|
||||
product = RenderProduct(
|
||||
productName=aov_name,
|
||||
ext=default_ext,
|
||||
aov=aov,
|
||||
camera=camera,
|
||||
colorspace=lib.get_color_management_output_transform()
|
||||
)
|
||||
products.append(product)
|
||||
|
||||
return products
|
||||
|
|
@ -1367,7 +1414,12 @@ class RenderProductsMayaHardware(ARenderProducts):
|
|||
|
||||
products = []
|
||||
for cam in self.get_renderable_cameras():
|
||||
product = RenderProduct(productName="beauty", ext=ext, camera=cam)
|
||||
product = RenderProduct(
|
||||
productName="beauty",
|
||||
ext=ext,
|
||||
camera=cam,
|
||||
colorspace=lib.get_color_management_output_transform()
|
||||
)
|
||||
products.append(product)
|
||||
|
||||
return products
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ class RenderSettings(object):
|
|||
'vray': 'vraySettings.fileNamePrefix',
|
||||
'arnold': 'defaultRenderGlobals.imageFilePrefix',
|
||||
'renderman': 'rmanGlobals.imageFileFormat',
|
||||
'redshift': 'defaultRenderGlobals.imageFilePrefix'
|
||||
'redshift': 'defaultRenderGlobals.imageFilePrefix',
|
||||
'mayahardware2': 'defaultRenderGlobals.imageFilePrefix'
|
||||
}
|
||||
|
||||
_image_prefixes = {
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
|
|||
self.log.info(full_exp_files)
|
||||
self.log.info("collecting layer: {}".format(layer_name))
|
||||
# Get layer specific settings, might be overrides
|
||||
|
||||
colorspace_data = lib.get_color_management_preferences()
|
||||
data = {
|
||||
"subset": expected_layer_name,
|
||||
"attachTo": attach_to,
|
||||
|
|
@ -323,6 +323,9 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
|
|||
"renderSetupIncludeLights": render_instance.data.get(
|
||||
"renderSetupIncludeLights"
|
||||
),
|
||||
"colorspaceConfig": colorspace_data["config"],
|
||||
"colorspaceDisplay": colorspace_data["display"],
|
||||
"colorspaceView": colorspace_data["view"],
|
||||
"strict_error_checking": render_instance.data.get(
|
||||
"strict_error_checking", True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ class ExtractGLB(publish.Extractor):
|
|||
|
||||
self.log.info("Extracting GLB to: {}".format(path))
|
||||
|
||||
cmds.loadPlugin("maya2glTF", quiet=True)
|
||||
|
||||
nodes = instance[:]
|
||||
|
||||
self.log.info("Instance: {0}".format(nodes))
|
||||
|
|
@ -45,6 +47,7 @@ class ExtractGLB(publish.Extractor):
|
|||
"glb": True,
|
||||
"vno": True # visibleNodeOnly
|
||||
}
|
||||
|
||||
with lib.maintained_selection():
|
||||
cmds.select(nodes, hi=True, noExpand=True)
|
||||
extract_gltf(staging_dir,
|
||||
|
|
|
|||
207
openpype/hosts/maya/plugins/publish/validate_glsl_material.py
Normal file
207
openpype/hosts/maya/plugins/publish/validate_glsl_material.py
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
import os
|
||||
from maya import cmds
|
||||
|
||||
import pyblish.api
|
||||
from openpype.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder
|
||||
)
|
||||
from openpype.pipeline import PublishValidationError
|
||||
|
||||
|
||||
class ValidateGLSLMaterial(pyblish.api.InstancePlugin):
|
||||
"""
|
||||
Validate if the asset uses GLSL Shader
|
||||
"""
|
||||
|
||||
order = ValidateContentsOrder + 0.1
|
||||
families = ['gltf']
|
||||
hosts = ['maya']
|
||||
label = 'GLSL Shader for GLTF'
|
||||
actions = [RepairAction]
|
||||
optional = True
|
||||
active = True
|
||||
|
||||
def process(self, instance):
|
||||
shading_grp = self.get_material_from_shapes(instance)
|
||||
if not shading_grp:
|
||||
raise PublishValidationError("No shading group found")
|
||||
invalid = self.get_texture_shader_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError("Non GLSL Shader found: "
|
||||
"{0}".format(invalid))
|
||||
|
||||
def get_material_from_shapes(self, instance):
|
||||
shapes = cmds.ls(instance, type="mesh", long=True)
|
||||
for shape in shapes:
|
||||
shading_grp = cmds.listConnections(shape,
|
||||
destination=True,
|
||||
type="shadingEngine")
|
||||
|
||||
return shading_grp or []
|
||||
|
||||
def get_texture_shader_invalid(self, instance):
|
||||
|
||||
invalid = set()
|
||||
shading_grp = self.get_material_from_shapes(instance)
|
||||
for shading_group in shading_grp:
|
||||
material_name = "{}.surfaceShader".format(shading_group)
|
||||
material = cmds.listConnections(material_name,
|
||||
source=True,
|
||||
destination=False,
|
||||
type="GLSLShader")
|
||||
|
||||
if not material:
|
||||
# add material name
|
||||
material = cmds.listConnections(material_name)[0]
|
||||
invalid.add(material)
|
||||
|
||||
return list(invalid)
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
"""
|
||||
Repair instance by assigning GLSL Shader
|
||||
to the material
|
||||
"""
|
||||
cls.assign_glsl_shader(instance)
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def assign_glsl_shader(cls, instance):
|
||||
"""
|
||||
Converting StingrayPBS material to GLSL Shaders
|
||||
for the glb export through Maya2GLTF plugin
|
||||
"""
|
||||
|
||||
meshes = cmds.ls(instance, type="mesh", long=True)
|
||||
cls.log.info("meshes: {}".format(meshes))
|
||||
# load the glsl shader plugin
|
||||
cmds.loadPlugin("glslShader", quiet=True)
|
||||
|
||||
for mesh in meshes:
|
||||
# create glsl shader
|
||||
glsl = cmds.createNode('GLSLShader')
|
||||
glsl_shading_grp = cmds.sets(name=glsl + "SG", empty=True,
|
||||
renderable=True, noSurfaceShader=True)
|
||||
cmds.connectAttr(glsl + ".outColor",
|
||||
glsl_shading_grp + ".surfaceShader")
|
||||
|
||||
# load the maya2gltf shader
|
||||
ogsfx_path = instance.context.data["project_settings"]["maya"]["publish"]["ExtractGLB"]["ogsfx_path"] # noqa
|
||||
if not os.path.exists(ogsfx_path):
|
||||
if ogsfx_path:
|
||||
# if custom ogsfx path is not specified
|
||||
# the log below is the warning for the user
|
||||
cls.log.warning("ogsfx shader file "
|
||||
"not found in {}".format(ogsfx_path))
|
||||
|
||||
cls.log.info("Find the ogsfx shader file in "
|
||||
"default maya directory...")
|
||||
# re-direct to search the ogsfx path in maya_dir
|
||||
ogsfx_path = os.getenv("MAYA_APP_DIR") + ogsfx_path
|
||||
if not os.path.exists(ogsfx_path):
|
||||
raise PublishValidationError("The ogsfx shader file does not " # noqa
|
||||
"exist: {}".format(ogsfx_path)) # noqa
|
||||
|
||||
cmds.setAttr(glsl + ".shader", ogsfx_path, typ="string")
|
||||
# list the materials used for the assets
|
||||
shading_grp = cmds.listConnections(mesh,
|
||||
destination=True,
|
||||
type="shadingEngine")
|
||||
|
||||
# get the materials related to the selected assets
|
||||
for material in shading_grp:
|
||||
pbs_shader = cmds.listConnections(material,
|
||||
destination=True,
|
||||
type="StingrayPBS")
|
||||
if pbs_shader:
|
||||
cls.pbs_shader_conversion(pbs_shader, glsl)
|
||||
# setting up to relink the texture if
|
||||
# the mesh is with aiStandardSurface
|
||||
arnold_shader = cmds.listConnections(material,
|
||||
destination=True,
|
||||
type="aiStandardSurface")
|
||||
if arnold_shader:
|
||||
cls.arnold_shader_conversion(arnold_shader, glsl)
|
||||
|
||||
cmds.sets(mesh, forceElement=str(glsl_shading_grp))
|
||||
|
||||
@classmethod
|
||||
def pbs_shader_conversion(cls, main_shader, glsl):
|
||||
|
||||
cls.log.info("StringrayPBS detected "
|
||||
"-> Can do texture conversion")
|
||||
|
||||
for shader in main_shader:
|
||||
# get the file textures related to the PBS Shader
|
||||
albedo = cmds.listConnections(shader +
|
||||
".TEX_color_map")
|
||||
if albedo:
|
||||
dif_output = albedo[0] + ".outColor"
|
||||
# get the glsl_shader input
|
||||
# reconnect the file nodes to maya2gltf shader
|
||||
glsl_dif = glsl + ".u_BaseColorTexture"
|
||||
cmds.connectAttr(dif_output, glsl_dif)
|
||||
|
||||
# connect orm map if there is one
|
||||
orm_packed = cmds.listConnections(shader +
|
||||
".TEX_ao_map")
|
||||
if orm_packed:
|
||||
orm_output = orm_packed[0] + ".outColor"
|
||||
|
||||
mtl = glsl + ".u_MetallicTexture"
|
||||
ao = glsl + ".u_OcclusionTexture"
|
||||
rough = glsl + ".u_RoughnessTexture"
|
||||
|
||||
cmds.connectAttr(orm_output, mtl)
|
||||
cmds.connectAttr(orm_output, ao)
|
||||
cmds.connectAttr(orm_output, rough)
|
||||
|
||||
# connect nrm map if there is one
|
||||
nrm = cmds.listConnections(shader +
|
||||
".TEX_normal_map")
|
||||
if nrm:
|
||||
nrm_output = nrm[0] + ".outColor"
|
||||
glsl_nrm = glsl + ".u_NormalTexture"
|
||||
cmds.connectAttr(nrm_output, glsl_nrm)
|
||||
|
||||
@classmethod
|
||||
def arnold_shader_conversion(cls, main_shader, glsl):
|
||||
cls.log.info("aiStandardSurface detected "
|
||||
"-> Can do texture conversion")
|
||||
|
||||
for shader in main_shader:
|
||||
# get the file textures related to the PBS Shader
|
||||
albedo = cmds.listConnections(shader + ".baseColor")
|
||||
if albedo:
|
||||
dif_output = albedo[0] + ".outColor"
|
||||
# get the glsl_shader input
|
||||
# reconnect the file nodes to maya2gltf shader
|
||||
glsl_dif = glsl + ".u_BaseColorTexture"
|
||||
cmds.connectAttr(dif_output, glsl_dif)
|
||||
|
||||
orm_packed = cmds.listConnections(shader +
|
||||
".specularRoughness")
|
||||
if orm_packed:
|
||||
orm_output = orm_packed[0] + ".outColor"
|
||||
|
||||
mtl = glsl + ".u_MetallicTexture"
|
||||
ao = glsl + ".u_OcclusionTexture"
|
||||
rough = glsl + ".u_RoughnessTexture"
|
||||
|
||||
cmds.connectAttr(orm_output, mtl)
|
||||
cmds.connectAttr(orm_output, ao)
|
||||
cmds.connectAttr(orm_output, rough)
|
||||
|
||||
# connect nrm map if there is one
|
||||
bump_node = cmds.listConnections(shader +
|
||||
".normalCamera")
|
||||
if bump_node:
|
||||
for bump in bump_node:
|
||||
nrm = cmds.listConnections(bump +
|
||||
".bumpValue")
|
||||
if nrm:
|
||||
nrm_output = nrm[0] + ".outColor"
|
||||
glsl_nrm = glsl + ".u_NormalTexture"
|
||||
cmds.connectAttr(nrm_output, glsl_nrm)
|
||||
31
openpype/hosts/maya/plugins/publish/validate_glsl_plugin.py
Normal file
31
openpype/hosts/maya/plugins/publish/validate_glsl_plugin.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
from maya import cmds
|
||||
|
||||
import pyblish.api
|
||||
from openpype.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder
|
||||
)
|
||||
|
||||
|
||||
class ValidateGLSLPlugin(pyblish.api.InstancePlugin):
|
||||
"""
|
||||
Validate if the asset uses GLSL Shader
|
||||
"""
|
||||
|
||||
order = ValidateContentsOrder + 0.15
|
||||
families = ['gltf']
|
||||
hosts = ['maya']
|
||||
label = 'maya2glTF plugin'
|
||||
actions = [RepairAction]
|
||||
|
||||
def process(self, instance):
|
||||
if not cmds.pluginInfo("maya2glTF", query=True, loaded=True):
|
||||
raise RuntimeError("maya2glTF is not loaded")
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
"""
|
||||
Repair instance by enabling the plugin
|
||||
"""
|
||||
return cmds.loadPlugin("maya2glTF", quiet=True)
|
||||
|
|
@ -7,12 +7,13 @@ from openpype.hosts.maya.api import MayaHost
|
|||
|
||||
from maya import cmds
|
||||
|
||||
|
||||
host = MayaHost()
|
||||
install_host(host)
|
||||
|
||||
|
||||
print("Starting OpenPype usersetup...")
|
||||
|
||||
|
||||
# Open Workfile Post Initialization.
|
||||
key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION"
|
||||
if bool(int(os.environ.get(key, "0"))):
|
||||
|
|
|
|||
|
|
@ -43,14 +43,15 @@ def parse_layers_data(data):
|
|||
layer_id, group_id, visible, position, opacity, name,
|
||||
layer_type,
|
||||
frame_start, frame_end, prelighttable, postlighttable,
|
||||
selected, editable, sencil_state
|
||||
selected, editable, sencil_state, is_current
|
||||
) = layer_raw.split("|")
|
||||
layer = {
|
||||
"layer_id": int(layer_id),
|
||||
"group_id": int(group_id),
|
||||
"visible": visible == "ON",
|
||||
"position": int(position),
|
||||
"opacity": int(opacity),
|
||||
# Opacity from 'tv_layerinfo' is always set to '0' so it's unusable
|
||||
# "opacity": int(opacity),
|
||||
"name": name,
|
||||
"type": layer_type,
|
||||
"frame_start": int(frame_start),
|
||||
|
|
@ -59,7 +60,8 @@ def parse_layers_data(data):
|
|||
"postlighttable": postlighttable == "1",
|
||||
"selected": selected == "1",
|
||||
"editable": editable == "1",
|
||||
"sencil_state": sencil_state
|
||||
"sencil_state": sencil_state,
|
||||
"is_current": is_current == "1"
|
||||
}
|
||||
layers.append(layer)
|
||||
return layers
|
||||
|
|
@ -87,15 +89,17 @@ def get_layers_data_george_script(output_filepath, layer_ids=None):
|
|||
" selected editable sencilState"
|
||||
),
|
||||
# Check if layer ID match `tv_LayerCurrentID`
|
||||
"is_current=0",
|
||||
"IF CMP(current_layer_id, layer_id)==1",
|
||||
# - mark layer as selected if layer id match to current layer id
|
||||
"is_current=1",
|
||||
"selected=1",
|
||||
"END",
|
||||
# Prepare line with data separated by "|"
|
||||
(
|
||||
"line = layer_id'|'group_id'|'visible'|'position'|'opacity'|'"
|
||||
"name'|'type'|'startFrame'|'endFrame'|'prelighttable'|'"
|
||||
"postlighttable'|'selected'|'editable'|'sencilState"
|
||||
"postlighttable'|'selected'|'editable'|'sencilState'|'is_current"
|
||||
),
|
||||
# Write data to output file
|
||||
"tv_writetextfile \"strict\" \"append\" '\"'output_path'\"' line",
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ from openpype.client import get_asset_by_name
|
|||
from openpype.lib import (
|
||||
prepare_template_data,
|
||||
AbstractAttrDef,
|
||||
UILabelDef,
|
||||
UISeparatorDef,
|
||||
EnumDef,
|
||||
TextDef,
|
||||
|
|
@ -66,7 +67,7 @@ RENDER_LAYER_DETAILED_DESCRIPTIONS = (
|
|||
Be aware Render Layer <b>is not</b> TVPaint layer.
|
||||
|
||||
All TVPaint layers in the scene with the color group id are rendered in the
|
||||
beauty pass. To create sub passes use Render Layer creator which is
|
||||
beauty pass. To create sub passes use Render Pass creator which is
|
||||
dependent on existence of render layer instance.
|
||||
|
||||
The group can represent an asset (tree) or different part of scene that consist
|
||||
|
|
@ -82,8 +83,8 @@ could be Render Layer which has 'Arm', 'Head' and 'Body' as Render Passes.
|
|||
RENDER_PASS_DETAILED_DESCRIPTIONS = (
|
||||
"""Render Pass is sub part of Render Layer.
|
||||
|
||||
Render Pass can consist of one or more TVPaint layers. Render Layers must
|
||||
belong to a Render Layer. Marker TVPaint layers will change it's group color
|
||||
Render Pass can consist of one or more TVPaint layers. Render Pass must
|
||||
belong to a Render Layer. Marked TVPaint layers will change it's group color
|
||||
to match group color of Render Layer.
|
||||
"""
|
||||
)
|
||||
|
|
@ -461,7 +462,10 @@ class CreateRenderPass(TVPaintCreator):
|
|||
"render_layer_instance_id"
|
||||
)
|
||||
if not render_layer_instance_id:
|
||||
raise CreatorError("Missing RenderLayer instance")
|
||||
raise CreatorError((
|
||||
"You cannot create a Render Pass without a Render Layer."
|
||||
" Please select one first"
|
||||
))
|
||||
|
||||
render_layer_instance = self.create_context.instances_by_id.get(
|
||||
render_layer_instance_id
|
||||
|
|
@ -598,12 +602,45 @@ class CreateRenderPass(TVPaintCreator):
|
|||
]
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
# Find available Render Layers
|
||||
# - instances are created after creators reset
|
||||
current_instances = self.host.list_instances()
|
||||
render_layers = [
|
||||
{
|
||||
"value": instance["instance_id"],
|
||||
"label": instance["subset"]
|
||||
}
|
||||
for instance in current_instances
|
||||
if instance["creator_identifier"] == CreateRenderlayer.identifier
|
||||
]
|
||||
if not render_layers:
|
||||
render_layers.append({"value": None, "label": "N/A"})
|
||||
|
||||
return [
|
||||
EnumDef(
|
||||
"render_layer_instance_id",
|
||||
label="Render Layer",
|
||||
items=render_layers
|
||||
),
|
||||
UILabelDef(
|
||||
"NOTE: Try to hit refresh if you don't see a Render Layer"
|
||||
),
|
||||
BoolDef(
|
||||
"mark_for_review",
|
||||
label="Review",
|
||||
default=self.mark_for_review
|
||||
)
|
||||
]
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
# Find available Render Layers
|
||||
current_instances = self.create_context.instances
|
||||
render_layers = [
|
||||
{
|
||||
"value": instance.id,
|
||||
"label": instance.label
|
||||
}
|
||||
for instance in self.create_context.instances
|
||||
for instance in current_instances
|
||||
if instance.creator_identifier == CreateRenderlayer.identifier
|
||||
]
|
||||
if not render_layers:
|
||||
|
|
@ -615,6 +652,9 @@ class CreateRenderPass(TVPaintCreator):
|
|||
label="Render Layer",
|
||||
items=render_layers
|
||||
),
|
||||
UILabelDef(
|
||||
"NOTE: Try to hit refresh if you don't see a Render Layer"
|
||||
),
|
||||
BoolDef(
|
||||
"mark_for_review",
|
||||
label="Review",
|
||||
|
|
@ -622,9 +662,6 @@ class CreateRenderPass(TVPaintCreator):
|
|||
)
|
||||
]
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
return self.get_pre_create_attr_defs()
|
||||
|
||||
|
||||
class TVPaintAutoDetectRenderCreator(TVPaintCreator):
|
||||
"""Create Render Layer and Render Pass instances based on scene data.
|
||||
|
|
|
|||
|
|
@ -1045,3 +1045,90 @@ def convert_ffprobe_fps_to_float(value):
|
|||
if divisor == 0.0:
|
||||
return 0.0
|
||||
return dividend / divisor
|
||||
|
||||
|
||||
def convert_colorspace(
|
||||
input_path,
|
||||
output_path,
|
||||
config_path,
|
||||
source_colorspace,
|
||||
target_colorspace=None,
|
||||
view=None,
|
||||
display=None,
|
||||
additional_command_args=None,
|
||||
logger=None
|
||||
):
|
||||
"""Convert source file from one color space to another.
|
||||
|
||||
Args:
|
||||
input_path (str): Path that should be converted. It is expected that
|
||||
contains single file or image sequence of same type
|
||||
(sequence in format 'file.FRAMESTART-FRAMEEND#.ext', see oiio docs,
|
||||
eg `big.1-3#.tif`)
|
||||
output_path (str): Path to output filename.
|
||||
(must follow format of 'input_path', eg. single file or
|
||||
sequence in 'file.FRAMESTART-FRAMEEND#.ext', `output.1-3#.tif`)
|
||||
config_path (str): path to OCIO config file
|
||||
source_colorspace (str): ocio valid color space of source files
|
||||
target_colorspace (str): ocio valid target color space
|
||||
if filled, 'view' and 'display' must be empty
|
||||
view (str): name for viewer space (ocio valid)
|
||||
both 'view' and 'display' must be filled (if 'target_colorspace')
|
||||
display (str): name for display-referred reference space (ocio valid)
|
||||
additional_command_args (list): arguments for oiiotool (like binary
|
||||
depth for .dpx)
|
||||
logger (logging.Logger): Logger used for logging.
|
||||
Raises:
|
||||
ValueError: if misconfigured
|
||||
"""
|
||||
if logger is None:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
oiio_cmd = [
|
||||
get_oiio_tools_path(),
|
||||
input_path,
|
||||
# Don't add any additional attributes
|
||||
"--nosoftwareattrib",
|
||||
"--colorconfig", config_path
|
||||
]
|
||||
|
||||
if all([target_colorspace, view, display]):
|
||||
raise ValueError("Colorspace and both screen and display"
|
||||
" cannot be set together."
|
||||
"Choose colorspace or screen and display")
|
||||
if not target_colorspace and not all([view, display]):
|
||||
raise ValueError("Both screen and display must be set.")
|
||||
|
||||
if additional_command_args:
|
||||
oiio_cmd.extend(additional_command_args)
|
||||
|
||||
if target_colorspace:
|
||||
oiio_cmd.extend(["--colorconvert",
|
||||
source_colorspace,
|
||||
target_colorspace])
|
||||
if view and display:
|
||||
oiio_cmd.extend(["--iscolorspace", source_colorspace])
|
||||
oiio_cmd.extend(["--ociodisplay", display, view])
|
||||
|
||||
oiio_cmd.extend(["-o", output_path])
|
||||
|
||||
logger.debug("Conversion command: {}".format(" ".join(oiio_cmd)))
|
||||
run_subprocess(oiio_cmd, logger=logger)
|
||||
|
||||
|
||||
def split_cmd_args(in_args):
|
||||
"""Makes sure all entered arguments are separated in individual items.
|
||||
|
||||
Split each argument string with " -" to identify if string contains
|
||||
one or more arguments.
|
||||
Args:
|
||||
in_args (list): of arguments ['-n', '-d uint10']
|
||||
Returns
|
||||
(list): ['-n', '-d', 'unint10']
|
||||
"""
|
||||
splitted_args = []
|
||||
for arg in in_args:
|
||||
if not arg.strip():
|
||||
continue
|
||||
splitted_args.extend(arg.split(" "))
|
||||
return splitted_args
|
||||
|
|
|
|||
|
|
@ -434,7 +434,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
self.log.info(
|
||||
"Finished copying %i files" % len(resource_files))
|
||||
|
||||
def _create_instances_for_aov(self, instance_data, exp_files):
|
||||
def _create_instances_for_aov(
|
||||
self, instance_data, exp_files, additional_data
|
||||
):
|
||||
"""Create instance for each AOV found.
|
||||
|
||||
This will create new instance for every aov it can detect in expected
|
||||
|
|
@ -536,6 +538,14 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
else:
|
||||
files = os.path.basename(col)
|
||||
|
||||
# Copy render product "colorspace" data to representation.
|
||||
colorspace = ""
|
||||
products = additional_data["renderProducts"].layer_data.products
|
||||
for product in products:
|
||||
if product.productName == aov:
|
||||
colorspace = product.colorspace
|
||||
break
|
||||
|
||||
rep = {
|
||||
"name": ext,
|
||||
"ext": ext,
|
||||
|
|
@ -545,7 +555,16 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
# If expectedFile are absolute, we need only filenames
|
||||
"stagingDir": staging,
|
||||
"fps": new_instance.get("fps"),
|
||||
"tags": ["review"] if preview else []
|
||||
"tags": ["review"] if preview else [],
|
||||
"colorspaceData": {
|
||||
"colorspace": colorspace,
|
||||
"config": {
|
||||
"path": additional_data["colorspaceConfig"],
|
||||
"template": additional_data["colorspaceTemplate"]
|
||||
},
|
||||
"display": additional_data["display"],
|
||||
"view": additional_data["view"]
|
||||
}
|
||||
}
|
||||
|
||||
# support conversion from tiled to scanline
|
||||
|
|
@ -569,7 +588,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
self.log.debug("instances:{}".format(instances))
|
||||
return instances
|
||||
|
||||
def _get_representations(self, instance, exp_files):
|
||||
def _get_representations(self, instance, exp_files, additional_data):
|
||||
"""Create representations for file sequences.
|
||||
|
||||
This will return representations of expected files if they are not
|
||||
|
|
@ -914,6 +933,16 @@ 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.
|
||||
|
|
@ -928,7 +957,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
# there are multiple renderable cameras in scene)
|
||||
instances = self._create_instances_for_aov(
|
||||
instance_skeleton_data,
|
||||
data.get("expectedFiles"))
|
||||
data.get("expectedFiles"),
|
||||
additional_data
|
||||
)
|
||||
self.log.info("got {} instance{}".format(
|
||||
len(instances),
|
||||
"s" if len(instances) > 1 else ""))
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ oauth_config:
|
|||
- chat:write.public
|
||||
- files:write
|
||||
- channels:read
|
||||
- users:read
|
||||
- usergroups:read
|
||||
settings:
|
||||
org_deploy_enabled: false
|
||||
socket_mode_enabled: false
|
||||
|
|
|
|||
|
|
@ -198,41 +198,19 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name):
|
|||
return True
|
||||
|
||||
|
||||
def get_ocio_config_colorspaces(config_path):
|
||||
"""Get all colorspace data
|
||||
|
||||
Wrapper function for aggregating all names and its families.
|
||||
Families can be used for building menu and submenus in gui.
|
||||
|
||||
Args:
|
||||
config_path (str): path leading to config.ocio file
|
||||
|
||||
Returns:
|
||||
dict: colorspace and family in couple
|
||||
"""
|
||||
if sys.version_info[0] == 2:
|
||||
return get_colorspace_data_subprocess(config_path)
|
||||
|
||||
from ..scripts.ocio_wrapper import _get_colorspace_data
|
||||
return _get_colorspace_data(config_path)
|
||||
|
||||
|
||||
def get_colorspace_data_subprocess(config_path):
|
||||
"""Get colorspace data via subprocess
|
||||
def get_data_subprocess(config_path, data_type):
|
||||
"""Get data via subprocess
|
||||
|
||||
Wrapper for Python 2 hosts.
|
||||
|
||||
Args:
|
||||
config_path (str): path leading to config.ocio file
|
||||
|
||||
Returns:
|
||||
dict: colorspace and family in couple
|
||||
"""
|
||||
with _make_temp_json_file() as tmp_json_path:
|
||||
# Prepare subprocess arguments
|
||||
args = [
|
||||
"run", get_ocio_config_script_path(),
|
||||
"config", "get_colorspace",
|
||||
"config", data_type,
|
||||
"--in_path", config_path,
|
||||
"--out_path", tmp_json_path
|
||||
|
||||
|
|
@ -251,6 +229,47 @@ def get_colorspace_data_subprocess(config_path):
|
|||
return json.loads(return_json_data)
|
||||
|
||||
|
||||
def compatible_python():
|
||||
"""Only 3.9 or higher can directly use PyOpenColorIO in ocio_wrapper"""
|
||||
compatible = False
|
||||
if sys.version[0] == 3 and sys.version[1] >= 9:
|
||||
compatible = True
|
||||
return compatible
|
||||
|
||||
|
||||
def get_ocio_config_colorspaces(config_path):
|
||||
"""Get all colorspace data
|
||||
|
||||
Wrapper function for aggregating all names and its families.
|
||||
Families can be used for building menu and submenus in gui.
|
||||
|
||||
Args:
|
||||
config_path (str): path leading to config.ocio file
|
||||
|
||||
Returns:
|
||||
dict: colorspace and family in couple
|
||||
"""
|
||||
if compatible_python():
|
||||
from ..scripts.ocio_wrapper import _get_colorspace_data
|
||||
return _get_colorspace_data(config_path)
|
||||
else:
|
||||
return get_colorspace_data_subprocess(config_path)
|
||||
|
||||
|
||||
def get_colorspace_data_subprocess(config_path):
|
||||
"""Get colorspace data via subprocess
|
||||
|
||||
Wrapper for Python 2 hosts.
|
||||
|
||||
Args:
|
||||
config_path (str): path leading to config.ocio file
|
||||
|
||||
Returns:
|
||||
dict: colorspace and family in couple
|
||||
"""
|
||||
return get_data_subprocess(config_path, "get_colorspace")
|
||||
|
||||
|
||||
def get_ocio_config_views(config_path):
|
||||
"""Get all viewer data
|
||||
|
||||
|
|
@ -263,12 +282,12 @@ def get_ocio_config_views(config_path):
|
|||
Returns:
|
||||
dict: `display/viewer` and viewer data
|
||||
"""
|
||||
if sys.version_info[0] == 2:
|
||||
if compatible_python():
|
||||
from ..scripts.ocio_wrapper import _get_views_data
|
||||
return _get_views_data(config_path)
|
||||
else:
|
||||
return get_views_data_subprocess(config_path)
|
||||
|
||||
from ..scripts.ocio_wrapper import _get_views_data
|
||||
return _get_views_data(config_path)
|
||||
|
||||
|
||||
def get_views_data_subprocess(config_path):
|
||||
"""Get viewers data via subprocess
|
||||
|
|
@ -281,27 +300,7 @@ def get_views_data_subprocess(config_path):
|
|||
Returns:
|
||||
dict: `display/viewer` and viewer data
|
||||
"""
|
||||
with _make_temp_json_file() as tmp_json_path:
|
||||
# Prepare subprocess arguments
|
||||
args = [
|
||||
"run", get_ocio_config_script_path(),
|
||||
"config", "get_views",
|
||||
"--in_path", config_path,
|
||||
"--out_path", tmp_json_path
|
||||
|
||||
]
|
||||
log.info("Executing: {}".format(" ".join(args)))
|
||||
|
||||
process_kwargs = {
|
||||
"logger": log,
|
||||
"env": {}
|
||||
}
|
||||
|
||||
run_openpype_process(*args, **process_kwargs)
|
||||
|
||||
# return all colorspaces
|
||||
return_json_data = open(tmp_json_path).read()
|
||||
return json.loads(return_json_data)
|
||||
return get_data_subprocess(config_path, "get_views")
|
||||
|
||||
|
||||
def get_imageio_config(
|
||||
|
|
|
|||
365
openpype/plugins/publish/extract_color_transcode.py
Normal file
365
openpype/plugins/publish/extract_color_transcode.py
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
import os
|
||||
import copy
|
||||
import clique
|
||||
import pyblish.api
|
||||
|
||||
from openpype.pipeline import publish
|
||||
from openpype.lib import (
|
||||
|
||||
is_oiio_supported,
|
||||
)
|
||||
|
||||
from openpype.lib.transcoding import (
|
||||
convert_colorspace,
|
||||
get_transcode_temp_directory,
|
||||
)
|
||||
|
||||
from openpype.lib.profiles_filtering import filter_profiles
|
||||
|
||||
|
||||
class ExtractOIIOTranscode(publish.Extractor):
|
||||
"""
|
||||
Extractor to convert colors from one colorspace to different.
|
||||
|
||||
Expects "colorspaceData" on representation. This dictionary is collected
|
||||
previously and denotes that representation files should be converted.
|
||||
This dict contains source colorspace information, collected by hosts.
|
||||
|
||||
Target colorspace is selected by profiles in the Settings, based on:
|
||||
- families
|
||||
- host
|
||||
- task types
|
||||
- task names
|
||||
- subset names
|
||||
|
||||
Can produce one or more representations (with different extensions) based
|
||||
on output definition in format:
|
||||
"output_name: {
|
||||
"extension": "png",
|
||||
"colorspace": "ACES - ACEScg",
|
||||
"display": "",
|
||||
"view": "",
|
||||
"tags": [],
|
||||
"custom_tags": []
|
||||
}
|
||||
|
||||
If 'extension' is empty original representation extension is used.
|
||||
'output_name' will be used as name of new representation. In case of value
|
||||
'passthrough' name of original representation will be used.
|
||||
|
||||
'colorspace' denotes target colorspace to be transcoded into. Could be
|
||||
empty if transcoding should be only into display and viewer colorspace.
|
||||
(In that case both 'display' and 'view' must be filled.)
|
||||
"""
|
||||
|
||||
label = "Transcode color spaces"
|
||||
order = pyblish.api.ExtractorOrder + 0.019
|
||||
|
||||
optional = True
|
||||
|
||||
# Supported extensions
|
||||
supported_exts = ["exr", "jpg", "jpeg", "png", "dpx"]
|
||||
|
||||
# Configurable by Settings
|
||||
profiles = None
|
||||
options = None
|
||||
|
||||
def process(self, instance):
|
||||
if not self.profiles:
|
||||
self.log.debug("No profiles present for color transcode")
|
||||
return
|
||||
|
||||
if "representations" not in instance.data:
|
||||
self.log.debug("No representations, skipping.")
|
||||
return
|
||||
|
||||
if not is_oiio_supported():
|
||||
self.log.warning("OIIO not supported, no transcoding possible.")
|
||||
return
|
||||
|
||||
profile = self._get_profile(instance)
|
||||
if not profile:
|
||||
return
|
||||
|
||||
new_representations = []
|
||||
repres = instance.data["representations"]
|
||||
for idx, repre in enumerate(list(repres)):
|
||||
self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"]))
|
||||
if not self._repre_is_valid(repre):
|
||||
continue
|
||||
|
||||
added_representations = False
|
||||
added_review = False
|
||||
|
||||
colorspace_data = repre["colorspaceData"]
|
||||
source_colorspace = colorspace_data["colorspace"]
|
||||
config_path = colorspace_data.get("config", {}).get("path")
|
||||
if not config_path or not os.path.exists(config_path):
|
||||
self.log.warning("Config file doesn't exist, skipping")
|
||||
continue
|
||||
|
||||
for output_name, output_def in profile.get("outputs", {}).items():
|
||||
new_repre = copy.deepcopy(repre)
|
||||
|
||||
original_staging_dir = new_repre["stagingDir"]
|
||||
new_staging_dir = get_transcode_temp_directory()
|
||||
new_repre["stagingDir"] = new_staging_dir
|
||||
|
||||
if isinstance(new_repre["files"], list):
|
||||
files_to_convert = copy.deepcopy(new_repre["files"])
|
||||
else:
|
||||
files_to_convert = [new_repre["files"]]
|
||||
|
||||
output_extension = output_def["extension"]
|
||||
output_extension = output_extension.replace('.', '')
|
||||
self._rename_in_representation(new_repre,
|
||||
files_to_convert,
|
||||
output_name,
|
||||
output_extension)
|
||||
|
||||
transcoding_type = output_def["transcoding_type"]
|
||||
|
||||
target_colorspace = view = display = None
|
||||
if transcoding_type == "colorspace":
|
||||
target_colorspace = (output_def["colorspace"] or
|
||||
colorspace_data.get("colorspace"))
|
||||
else:
|
||||
view = output_def["view"] or colorspace_data.get("view")
|
||||
display = (output_def["display"] or
|
||||
colorspace_data.get("display"))
|
||||
|
||||
# both could be already collected by DCC,
|
||||
# but could be overwritten
|
||||
if view:
|
||||
new_repre["colorspaceData"]["view"] = view
|
||||
if display:
|
||||
new_repre["colorspaceData"]["display"] = display
|
||||
|
||||
additional_command_args = (output_def["oiiotool_args"]
|
||||
["additional_command_args"])
|
||||
|
||||
files_to_convert = self._translate_to_sequence(
|
||||
files_to_convert)
|
||||
for file_name in files_to_convert:
|
||||
input_path = os.path.join(original_staging_dir,
|
||||
file_name)
|
||||
output_path = self._get_output_file_path(input_path,
|
||||
new_staging_dir,
|
||||
output_extension)
|
||||
convert_colorspace(
|
||||
input_path,
|
||||
output_path,
|
||||
config_path,
|
||||
source_colorspace,
|
||||
target_colorspace,
|
||||
view,
|
||||
display,
|
||||
additional_command_args,
|
||||
self.log
|
||||
)
|
||||
|
||||
# cleanup temporary transcoded files
|
||||
for file_name in new_repre["files"]:
|
||||
transcoded_file_path = os.path.join(new_staging_dir,
|
||||
file_name)
|
||||
instance.context.data["cleanupFullPaths"].append(
|
||||
transcoded_file_path)
|
||||
|
||||
custom_tags = output_def.get("custom_tags")
|
||||
if custom_tags:
|
||||
if new_repre.get("custom_tags") is None:
|
||||
new_repre["custom_tags"] = []
|
||||
new_repre["custom_tags"].extend(custom_tags)
|
||||
|
||||
# Add additional tags from output definition to representation
|
||||
if new_repre.get("tags") is None:
|
||||
new_repre["tags"] = []
|
||||
for tag in output_def["tags"]:
|
||||
if tag not in new_repre["tags"]:
|
||||
new_repre["tags"].append(tag)
|
||||
|
||||
if tag == "review":
|
||||
added_review = True
|
||||
|
||||
new_representations.append(new_repre)
|
||||
added_representations = True
|
||||
|
||||
if added_representations:
|
||||
self._mark_original_repre_for_deletion(repre, profile,
|
||||
added_review)
|
||||
|
||||
for repre in tuple(instance.data["representations"]):
|
||||
tags = repre.get("tags") or []
|
||||
if "delete" in tags and "thumbnail" not in tags:
|
||||
instance.data["representations"].remove(repre)
|
||||
|
||||
instance.data["representations"].extend(new_representations)
|
||||
|
||||
def _rename_in_representation(self, new_repre, files_to_convert,
|
||||
output_name, output_extension):
|
||||
"""Replace old extension with new one everywhere in representation.
|
||||
|
||||
Args:
|
||||
new_repre (dict)
|
||||
files_to_convert (list): of filenames from repre["files"],
|
||||
standardized to always list
|
||||
output_name (str): key of output definition from Settings,
|
||||
if "<passthrough>" token used, keep original repre name
|
||||
output_extension (str): extension from output definition
|
||||
"""
|
||||
if output_name != "passthrough":
|
||||
new_repre["name"] = output_name
|
||||
if not output_extension:
|
||||
return
|
||||
|
||||
new_repre["ext"] = output_extension
|
||||
|
||||
renamed_files = []
|
||||
for file_name in files_to_convert:
|
||||
file_name, _ = os.path.splitext(file_name)
|
||||
file_name = '{}.{}'.format(file_name,
|
||||
output_extension)
|
||||
renamed_files.append(file_name)
|
||||
new_repre["files"] = renamed_files
|
||||
|
||||
def _rename_in_representation(self, new_repre, files_to_convert,
|
||||
output_name, output_extension):
|
||||
"""Replace old extension with new one everywhere in representation.
|
||||
|
||||
Args:
|
||||
new_repre (dict)
|
||||
files_to_convert (list): of filenames from repre["files"],
|
||||
standardized to always list
|
||||
output_name (str): key of output definition from Settings,
|
||||
if "<passthrough>" token used, keep original repre name
|
||||
output_extension (str): extension from output definition
|
||||
"""
|
||||
if output_name != "passthrough":
|
||||
new_repre["name"] = output_name
|
||||
if not output_extension:
|
||||
return
|
||||
|
||||
new_repre["ext"] = output_extension
|
||||
|
||||
renamed_files = []
|
||||
for file_name in files_to_convert:
|
||||
file_name, _ = os.path.splitext(file_name)
|
||||
file_name = '{}.{}'.format(file_name,
|
||||
output_extension)
|
||||
renamed_files.append(file_name)
|
||||
new_repre["files"] = renamed_files
|
||||
|
||||
def _translate_to_sequence(self, files_to_convert):
|
||||
"""Returns original list or list with filename formatted in single
|
||||
sequence format.
|
||||
|
||||
Uses clique to find frame sequence, in this case it merges all frames
|
||||
into sequence format (FRAMESTART-FRAMEEND#) and returns it.
|
||||
If sequence not found, it returns original list
|
||||
|
||||
Args:
|
||||
files_to_convert (list): list of file names
|
||||
Returns:
|
||||
(list) of [file.1001-1010#.exr] or [fileA.exr, fileB.exr]
|
||||
"""
|
||||
pattern = [clique.PATTERNS["frames"]]
|
||||
collections, remainder = clique.assemble(
|
||||
files_to_convert, patterns=pattern,
|
||||
assume_padded_when_ambiguous=True)
|
||||
|
||||
if collections:
|
||||
if len(collections) > 1:
|
||||
raise ValueError(
|
||||
"Too many collections {}".format(collections))
|
||||
|
||||
collection = collections[0]
|
||||
frames = list(collection.indexes)
|
||||
frame_str = "{}-{}#".format(frames[0], frames[-1])
|
||||
file_name = "{}{}{}".format(collection.head, frame_str,
|
||||
collection.tail)
|
||||
|
||||
files_to_convert = [file_name]
|
||||
|
||||
return files_to_convert
|
||||
|
||||
def _get_output_file_path(self, input_path, output_dir,
|
||||
output_extension):
|
||||
"""Create output file name path."""
|
||||
file_name = os.path.basename(input_path)
|
||||
file_name, input_extension = os.path.splitext(file_name)
|
||||
if not output_extension:
|
||||
output_extension = input_extension.replace(".", "")
|
||||
new_file_name = '{}.{}'.format(file_name,
|
||||
output_extension)
|
||||
return os.path.join(output_dir, new_file_name)
|
||||
|
||||
def _get_profile(self, instance):
|
||||
"""Returns profile if and how repre should be color transcoded."""
|
||||
host_name = instance.context.data["hostName"]
|
||||
family = instance.data["family"]
|
||||
task_data = instance.data["anatomyData"].get("task", {})
|
||||
task_name = task_data.get("name")
|
||||
task_type = task_data.get("type")
|
||||
subset = instance.data["subset"]
|
||||
filtering_criteria = {
|
||||
"hosts": host_name,
|
||||
"families": family,
|
||||
"task_names": task_name,
|
||||
"task_types": task_type,
|
||||
"subsets": subset
|
||||
}
|
||||
profile = filter_profiles(self.profiles, filtering_criteria,
|
||||
logger=self.log)
|
||||
|
||||
if not profile:
|
||||
self.log.info((
|
||||
"Skipped instance. None of profiles in presets are for"
|
||||
" Host: \"{}\" | Families: \"{}\" | Task \"{}\""
|
||||
" | Task type \"{}\" | Subset \"{}\" "
|
||||
).format(host_name, family, task_name, task_type, subset))
|
||||
|
||||
self.log.debug("profile: {}".format(profile))
|
||||
return profile
|
||||
|
||||
def _repre_is_valid(self, repre):
|
||||
"""Validation if representation should be processed.
|
||||
|
||||
Args:
|
||||
repre (dict): Representation which should be checked.
|
||||
|
||||
Returns:
|
||||
bool: False if can't be processed else True.
|
||||
"""
|
||||
|
||||
if repre.get("ext") not in self.supported_exts:
|
||||
self.log.debug((
|
||||
"Representation '{}' of unsupported extension. Skipped."
|
||||
).format(repre["name"]))
|
||||
return False
|
||||
|
||||
if not repre.get("files"):
|
||||
self.log.debug((
|
||||
"Representation '{}' have empty files. Skipped."
|
||||
).format(repre["name"]))
|
||||
return False
|
||||
|
||||
if not repre.get("colorspaceData"):
|
||||
self.log.debug("Representation '{}' has no colorspace data. "
|
||||
"Skipped.")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _mark_original_repre_for_deletion(self, repre, profile, added_review):
|
||||
"""If new transcoded representation created, delete old."""
|
||||
if not repre.get("tags"):
|
||||
repre["tags"] = []
|
||||
|
||||
delete_original = profile["delete_original"]
|
||||
|
||||
if delete_original:
|
||||
if "delete" not in repre["tags"]:
|
||||
repre["tags"].append("delete")
|
||||
|
||||
if added_review and "review" in repre["tags"]:
|
||||
repre["tags"].remove("review")
|
||||
|
|
@ -169,7 +169,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
"Skipped representation. All output definitions from"
|
||||
" selected profile does not match to representation's"
|
||||
" custom tags. \"{}\""
|
||||
).format(str(tags)))
|
||||
).format(str(custom_tags)))
|
||||
continue
|
||||
|
||||
outputs_per_representations.append((repre, outputs))
|
||||
|
|
|
|||
|
|
@ -157,11 +157,21 @@ def _get_views_data(config_path):
|
|||
|
||||
config = ocio.Config().CreateFromFile(str(config_path))
|
||||
|
||||
return {
|
||||
f"{d}/{v}": {"display": d, "view": v}
|
||||
for d in config.getDisplays()
|
||||
for v in config.getViews(d)
|
||||
}
|
||||
data = {}
|
||||
for display in config.getDisplays():
|
||||
for view in config.getViews(display):
|
||||
colorspace = config.getDisplayViewColorSpaceName(display, view)
|
||||
# Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa
|
||||
if colorspace == "<USE_DISPLAY_NAME>":
|
||||
colorspace = display
|
||||
|
||||
data[f"{display}/{view}"] = {
|
||||
"display": display,
|
||||
"view": view,
|
||||
"colorspace": colorspace
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@
|
|||
"output": []
|
||||
}
|
||||
},
|
||||
"ExtractOIIOTranscode": {
|
||||
"enabled": true,
|
||||
"profiles": []
|
||||
},
|
||||
"ExtractReview": {
|
||||
"enabled": true,
|
||||
"profiles": [
|
||||
|
|
|
|||
|
|
@ -412,6 +412,16 @@
|
|||
"optional": false,
|
||||
"active": true
|
||||
},
|
||||
"ValidateGLSLMaterial": {
|
||||
"enabled": true,
|
||||
"optional": false,
|
||||
"active": true
|
||||
},
|
||||
"ValidateGLSLPlugin": {
|
||||
"enabled": true,
|
||||
"optional": false,
|
||||
"active": true
|
||||
},
|
||||
"ValidateRenderImageRule": {
|
||||
"enabled": true,
|
||||
"optional": false,
|
||||
|
|
@ -903,6 +913,11 @@
|
|||
"optional": true,
|
||||
"active": true,
|
||||
"bake_attributes": []
|
||||
},
|
||||
"ExtractGLB": {
|
||||
"enabled": true,
|
||||
"active": true,
|
||||
"ogsfx_path": "/maya2glTF/PBR/shaders/glTF_PBR.ogsfx"
|
||||
}
|
||||
},
|
||||
"load": {
|
||||
|
|
|
|||
|
|
@ -31,13 +31,13 @@
|
|||
"default_variants": []
|
||||
},
|
||||
"create_render_layer": {
|
||||
"mark_for_review": true,
|
||||
"mark_for_review": false,
|
||||
"default_pass_name": "beauty",
|
||||
"default_variant": "Main",
|
||||
"default_variants": []
|
||||
},
|
||||
"create_render_pass": {
|
||||
"mark_for_review": true,
|
||||
"mark_for_review": false,
|
||||
"default_variant": "Main",
|
||||
"default_variants": []
|
||||
},
|
||||
|
|
|
|||
|
|
@ -197,6 +197,136 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "ExtractOIIOTranscode",
|
||||
"label": "Extract OIIO Transcode",
|
||||
"checkbox_key": "enabled",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Configure Output Definition(s) for new representation(s). \nEmpty 'Extension' denotes keeping source extension. \nName(key) of output definition will be used as new representation name \nunless 'passthrough' value is used to keep existing name. \nFill either 'Colorspace' (for target colorspace) or \nboth 'Display' and 'View' (for display and viewer colorspaces)."
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "profiles",
|
||||
"label": "Profiles",
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "families",
|
||||
"label": "Families",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"key": "hosts",
|
||||
"label": "Host names",
|
||||
"type": "hosts-enum",
|
||||
"multiselection": true
|
||||
},
|
||||
{
|
||||
"key": "task_types",
|
||||
"label": "Task types",
|
||||
"type": "task-types-enum"
|
||||
},
|
||||
{
|
||||
"key": "task_names",
|
||||
"label": "Task names",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"key": "subsets",
|
||||
"label": "Subset names",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "delete_original",
|
||||
"label": "Delete Original Representation"
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
},
|
||||
{
|
||||
"key": "outputs",
|
||||
"label": "Output Definitions",
|
||||
"type": "dict-modifiable",
|
||||
"highlight_content": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "extension",
|
||||
"label": "Extension",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"type": "enum",
|
||||
"key": "transcoding_type",
|
||||
"label": "Transcoding type",
|
||||
"enum_items": [
|
||||
{ "colorspace": "Use Colorspace" },
|
||||
{ "display": "Use Display&View" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "colorspace",
|
||||
"label": "Colorspace",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "display",
|
||||
"label": "Display",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "view",
|
||||
"label": "View",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "oiiotool_args",
|
||||
"label": "OIIOtool arguments",
|
||||
"type": "dict",
|
||||
"highlight_content": true,
|
||||
"children": [
|
||||
{
|
||||
"key": "additional_command_args",
|
||||
"label": "Arguments",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_representation_tags"
|
||||
},
|
||||
{
|
||||
"key": "custom_tags",
|
||||
"label": "Custom Tags",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
|
|
|
|||
|
|
@ -412,6 +412,14 @@
|
|||
"key": "ValidateCurrentRenderLayerIsRenderable",
|
||||
"label": "Validate Current Render Layer Has Renderable Camera"
|
||||
},
|
||||
{
|
||||
"key": "ValidateGLSLMaterial",
|
||||
"label": "Validate GLSL Material"
|
||||
},
|
||||
{
|
||||
"key": "ValidateGLSLPlugin",
|
||||
"label": "Validate GLSL Plugin"
|
||||
},
|
||||
{
|
||||
"key": "ValidateRenderImageRule",
|
||||
"label": "Validate Images File Rule (Workspace)"
|
||||
|
|
@ -960,6 +968,30 @@
|
|||
"is_list": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "ExtractGLB",
|
||||
"label": "Extract GLB",
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "active",
|
||||
"label": "Active"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "ogsfx_path",
|
||||
"label": "GLSL Shader Directory"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
BIN
website/docs/project_settings/assets/global_oiio_transcode.png
Normal file
BIN
website/docs/project_settings/assets/global_oiio_transcode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
website/docs/project_settings/assets/global_oiio_transcode2.png
Normal file
BIN
website/docs/project_settings/assets/global_oiio_transcode2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
|
|
@ -45,6 +45,25 @@ The _input pattern_ matching uses REGEX expression syntax (try [regexr.com](http
|
|||
The **colorspace name** value is a raw string input and no validation is run after saving project settings. We recommend to open the specified `config.ocio` file and copy pasting the exact colorspace names.
|
||||
:::
|
||||
|
||||
### Extract OIIO Transcode
|
||||
OIIOTools transcoder plugin with configurable output presets. Any incoming representation with `colorspaceData` is convertable to single or multiple representations with different target colorspaces or display and viewer names found in linked **config.ocio** file.
|
||||
|
||||
`oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation.
|
||||
|
||||
Notable parameters:
|
||||
- **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation loses its 'review' tag if present.
|
||||
- **`Extension`** - target extension. If left empty, original extension is used.
|
||||
- **`Transcoding type`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both at the same time.
|
||||
- **`Colorspace`** - target colorspace, which must be available in used color config. (If `Transcoding type` is `Use Colorspace` value in configuration is used OR if empty value collected on instance from DCC).
|
||||
- **`Display & View`** - display and viewer colorspace. (If `Transcoding type` is `Use Display&View` values in configuration is used OR if empty values collected on instance from DCC).
|
||||
- **`Arguments`** - special additional command line arguments for `oiiotool`.
|
||||
|
||||
|
||||
Example here describes use case for creation of new color coded review of png image sequence. Original representation's files are kept intact, review is created from transcoded files, but these files are removed in cleanup process.
|
||||

|
||||
|
||||
Another use case is to transcode in Maya only `beauty` render layers and use collected `Display` and `View` colorspaces from DCC.
|
||||
n
|
||||
|
||||
## Profile filters
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue