mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into enhancement/AY-1675_Nuke---baking-inherits-channels-configuration
This commit is contained in:
commit
de59582b98
63 changed files with 2487 additions and 1145 deletions
|
|
@ -1,7 +1,7 @@
|
|||
from ayon_applications import PreLaunchHook
|
||||
|
||||
from ayon_core.pipeline.colorspace import get_imageio_config
|
||||
from ayon_core.pipeline.template_data import get_template_data_with_names
|
||||
from ayon_core.pipeline.colorspace import get_imageio_config_preset
|
||||
from ayon_core.pipeline.template_data import get_template_data
|
||||
|
||||
|
||||
class OCIOEnvHook(PreLaunchHook):
|
||||
|
|
@ -26,32 +26,38 @@ class OCIOEnvHook(PreLaunchHook):
|
|||
def execute(self):
|
||||
"""Hook entry method."""
|
||||
|
||||
template_data = get_template_data_with_names(
|
||||
project_name=self.data["project_name"],
|
||||
folder_path=self.data["folder_path"],
|
||||
task_name=self.data["task_name"],
|
||||
folder_entity = self.data["folder_entity"]
|
||||
|
||||
template_data = get_template_data(
|
||||
self.data["project_entity"],
|
||||
folder_entity=folder_entity,
|
||||
task_entity=self.data["task_entity"],
|
||||
host_name=self.host_name,
|
||||
settings=self.data["project_settings"]
|
||||
settings=self.data["project_settings"],
|
||||
)
|
||||
|
||||
config_data = get_imageio_config(
|
||||
project_name=self.data["project_name"],
|
||||
host_name=self.host_name,
|
||||
project_settings=self.data["project_settings"],
|
||||
anatomy_data=template_data,
|
||||
config_data = get_imageio_config_preset(
|
||||
self.data["project_name"],
|
||||
self.data["folder_path"],
|
||||
self.data["task_name"],
|
||||
self.host_name,
|
||||
anatomy=self.data["anatomy"],
|
||||
project_settings=self.data["project_settings"],
|
||||
template_data=template_data,
|
||||
env=self.launch_context.env,
|
||||
folder_id=folder_entity["id"],
|
||||
)
|
||||
|
||||
if config_data:
|
||||
ocio_path = config_data["path"]
|
||||
|
||||
if self.host_name in ["nuke", "hiero"]:
|
||||
ocio_path = ocio_path.replace("\\", "/")
|
||||
|
||||
self.log.info(
|
||||
f"Setting OCIO environment to config path: {ocio_path}")
|
||||
|
||||
self.launch_context.env["OCIO"] = ocio_path
|
||||
else:
|
||||
if not config_data:
|
||||
self.log.debug("OCIO not set or enabled")
|
||||
return
|
||||
|
||||
ocio_path = config_data["path"]
|
||||
|
||||
if self.host_name in ["nuke", "hiero"]:
|
||||
ocio_path = ocio_path.replace("\\", "/")
|
||||
|
||||
self.log.info(
|
||||
f"Setting OCIO environment to config path: {ocio_path}")
|
||||
|
||||
self.launch_context.env["OCIO"] = ocio_path
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ def main(*subprocess_args):
|
|||
)
|
||||
)
|
||||
|
||||
elif os.environ.get("AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH", True):
|
||||
elif os.environ.get("AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH", True):
|
||||
save = False
|
||||
if os.getenv("WORKFILES_SAVE_AS"):
|
||||
save = True
|
||||
|
|
|
|||
|
|
@ -43,7 +43,10 @@ class AbcCameraLoader(plugin.AssetLoader):
|
|||
def _process(self, libpath, asset_group, group_name):
|
||||
plugin.deselect_all()
|
||||
|
||||
bpy.ops.wm.alembic_import(filepath=libpath)
|
||||
# Force the creation of the transform cache even if the camera
|
||||
# doesn't have an animation. We use the cache to update the camera.
|
||||
bpy.ops.wm.alembic_import(
|
||||
filepath=libpath, always_add_cache_reader=True)
|
||||
|
||||
objects = lib.get_selection()
|
||||
|
||||
|
|
@ -178,12 +181,33 @@ class AbcCameraLoader(plugin.AssetLoader):
|
|||
self.log.info("Library already loaded, not updating...")
|
||||
return
|
||||
|
||||
mat = asset_group.matrix_basis.copy()
|
||||
for obj in asset_group.children:
|
||||
found = False
|
||||
for constraint in obj.constraints:
|
||||
if constraint.type == "TRANSFORM_CACHE":
|
||||
constraint.cache_file.filepath = libpath.as_posix()
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
# This is to keep compatibility with cameras loaded with
|
||||
# the old loader
|
||||
# Create a new constraint for the cache file
|
||||
constraint = obj.constraints.new("TRANSFORM_CACHE")
|
||||
bpy.ops.cachefile.open(filepath=libpath.as_posix())
|
||||
constraint.cache_file = bpy.data.cache_files[-1]
|
||||
constraint.cache_file.scale = 1.0
|
||||
|
||||
self._remove(asset_group)
|
||||
self._process(str(libpath), asset_group, object_name)
|
||||
# This is a workaround to set the object path. Blender doesn't
|
||||
# load the list of object paths until the object is evaluated.
|
||||
# This is a hack to force the object to be evaluated.
|
||||
# The modifier doesn't need to be removed because camera
|
||||
# objects don't have modifiers.
|
||||
obj.modifiers.new(
|
||||
name='MeshSequenceCache', type='MESH_SEQUENCE_CACHE')
|
||||
bpy.context.evaluated_depsgraph_get()
|
||||
|
||||
asset_group.matrix_basis = mat
|
||||
constraint.object_path = (
|
||||
constraint.cache_file.object_paths[0].path)
|
||||
|
||||
metadata["libpath"] = str(libpath)
|
||||
metadata["representation"] = repre_entity["id"]
|
||||
|
|
|
|||
|
|
@ -1110,10 +1110,7 @@ def apply_colorspace_project():
|
|||
'''
|
||||
# backward compatibility layer
|
||||
# TODO: remove this after some time
|
||||
config_data = get_imageio_config(
|
||||
project_name=get_current_project_name(),
|
||||
host_name="hiero"
|
||||
)
|
||||
config_data = get_current_context_imageio_config_preset()
|
||||
|
||||
if config_data:
|
||||
presets.update({
|
||||
|
|
|
|||
|
|
@ -13,11 +13,17 @@ class CreateArnoldRop(plugin.HoudiniCreator):
|
|||
# Default extension
|
||||
ext = "exr"
|
||||
|
||||
# Default to split export and render jobs
|
||||
export_job = True
|
||||
# Default render target
|
||||
render_target = "farm_split"
|
||||
|
||||
def create(self, product_name, instance_data, pre_create_data):
|
||||
import hou
|
||||
# Transfer settings from pre create to instance
|
||||
creator_attributes = instance_data.setdefault(
|
||||
"creator_attributes", dict())
|
||||
for key in ["render_target", "review"]:
|
||||
if key in pre_create_data:
|
||||
creator_attributes[key] = pre_create_data[key]
|
||||
|
||||
# Remove the active, we are checking the bypass flag of the nodes
|
||||
instance_data.pop("active", None)
|
||||
|
|
@ -25,8 +31,6 @@ class CreateArnoldRop(plugin.HoudiniCreator):
|
|||
|
||||
# Add chunk size attribute
|
||||
instance_data["chunkSize"] = 1
|
||||
# Submit for job publishing
|
||||
instance_data["farm"] = pre_create_data.get("farm")
|
||||
|
||||
instance = super(CreateArnoldRop, self).create(
|
||||
product_name,
|
||||
|
|
@ -51,7 +55,7 @@ class CreateArnoldRop(plugin.HoudiniCreator):
|
|||
"ar_exr_half_precision": 1 # half precision
|
||||
}
|
||||
|
||||
if pre_create_data.get("export_job"):
|
||||
if pre_create_data.get("render_target") == "farm_split":
|
||||
ass_filepath = \
|
||||
"{export_dir}{product_name}/{product_name}.$F4.ass".format(
|
||||
export_dir=hou.text.expandString("$HIP/pyblish/ass/"),
|
||||
|
|
@ -66,23 +70,41 @@ class CreateArnoldRop(plugin.HoudiniCreator):
|
|||
to_lock = ["productType", "id"]
|
||||
self.lock_parameters(instance_node, to_lock)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
attrs = super(CreateArnoldRop, self).get_pre_create_attr_defs()
|
||||
def get_instance_attr_defs(self):
|
||||
"""get instance attribute definitions.
|
||||
|
||||
Attributes defined in this method are exposed in
|
||||
publish tab in the publisher UI.
|
||||
"""
|
||||
|
||||
render_target_items = {
|
||||
"local": "Local machine rendering",
|
||||
"local_no_render": "Use existing frames (local)",
|
||||
"farm": "Farm Rendering",
|
||||
"farm_split": "Farm Rendering - Split export & render jobs",
|
||||
}
|
||||
|
||||
return [
|
||||
BoolDef("review",
|
||||
label="Review",
|
||||
tooltip="Mark as reviewable",
|
||||
default=True),
|
||||
EnumDef("render_target",
|
||||
items=render_target_items,
|
||||
label="Render target",
|
||||
default=self.render_target),
|
||||
]
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
image_format_enum = [
|
||||
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
|
||||
"rad", "rat", "rta", "sgi", "tga", "tif",
|
||||
]
|
||||
|
||||
return attrs + [
|
||||
BoolDef("farm",
|
||||
label="Submitting to Farm",
|
||||
default=True),
|
||||
BoolDef("export_job",
|
||||
label="Split export and render jobs",
|
||||
default=self.export_job),
|
||||
attrs = [
|
||||
EnumDef("image_format",
|
||||
image_format_enum,
|
||||
default=self.ext,
|
||||
label="Image Format Options")
|
||||
label="Image Format Options"),
|
||||
]
|
||||
return attrs + self.get_instance_attr_defs()
|
||||
|
|
|
|||
|
|
@ -11,15 +11,23 @@ class CreateKarmaROP(plugin.HoudiniCreator):
|
|||
product_type = "karma_rop"
|
||||
icon = "magic"
|
||||
|
||||
# Default render target
|
||||
render_target = "farm"
|
||||
|
||||
def create(self, product_name, instance_data, pre_create_data):
|
||||
import hou # noqa
|
||||
# Transfer settings from pre create to instance
|
||||
creator_attributes = instance_data.setdefault(
|
||||
"creator_attributes", dict())
|
||||
|
||||
for key in ["render_target", "review"]:
|
||||
if key in pre_create_data:
|
||||
creator_attributes[key] = pre_create_data[key]
|
||||
|
||||
instance_data.pop("active", None)
|
||||
instance_data.update({"node_type": "karma"})
|
||||
# Add chunk size attribute
|
||||
instance_data["chunkSize"] = 10
|
||||
# Submit for job publishing
|
||||
instance_data["farm"] = pre_create_data.get("farm")
|
||||
|
||||
instance = super(CreateKarmaROP, self).create(
|
||||
product_name,
|
||||
|
|
@ -86,18 +94,40 @@ class CreateKarmaROP(plugin.HoudiniCreator):
|
|||
to_lock = ["productType", "id"]
|
||||
self.lock_parameters(instance_node, to_lock)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
attrs = super(CreateKarmaROP, self).get_pre_create_attr_defs()
|
||||
def get_instance_attr_defs(self):
|
||||
"""get instance attribute definitions.
|
||||
|
||||
Attributes defined in this method are exposed in
|
||||
publish tab in the publisher UI.
|
||||
"""
|
||||
|
||||
render_target_items = {
|
||||
"local": "Local machine rendering",
|
||||
"local_no_render": "Use existing frames (local)",
|
||||
"farm": "Farm Rendering",
|
||||
}
|
||||
|
||||
return [
|
||||
BoolDef("review",
|
||||
label="Review",
|
||||
tooltip="Mark as reviewable",
|
||||
default=True),
|
||||
EnumDef("render_target",
|
||||
items=render_target_items,
|
||||
label="Render target",
|
||||
default=self.render_target)
|
||||
]
|
||||
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
image_format_enum = [
|
||||
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
|
||||
"rad", "rat", "rta", "sgi", "tga", "tif",
|
||||
]
|
||||
|
||||
return attrs + [
|
||||
BoolDef("farm",
|
||||
label="Submitting to Farm",
|
||||
default=True),
|
||||
attrs = super(CreateKarmaROP, self).get_pre_create_attr_defs()
|
||||
|
||||
attrs += [
|
||||
EnumDef("image_format",
|
||||
image_format_enum,
|
||||
default="exr",
|
||||
|
|
@ -112,5 +142,6 @@ class CreateKarmaROP(plugin.HoudiniCreator):
|
|||
decimals=0),
|
||||
BoolDef("cam_res",
|
||||
label="Camera Resolution",
|
||||
default=False)
|
||||
default=False),
|
||||
]
|
||||
return attrs + self.get_instance_attr_defs()
|
||||
|
|
|
|||
|
|
@ -11,18 +11,22 @@ class CreateMantraROP(plugin.HoudiniCreator):
|
|||
product_type = "mantra_rop"
|
||||
icon = "magic"
|
||||
|
||||
# Default to split export and render jobs
|
||||
export_job = True
|
||||
# Default render target
|
||||
render_target = "farm_split"
|
||||
|
||||
def create(self, product_name, instance_data, pre_create_data):
|
||||
import hou # noqa
|
||||
# Transfer settings from pre create to instance
|
||||
creator_attributes = instance_data.setdefault(
|
||||
"creator_attributes", dict())
|
||||
for key in ["render_target", "review"]:
|
||||
if key in pre_create_data:
|
||||
creator_attributes[key] = pre_create_data[key]
|
||||
|
||||
instance_data.pop("active", None)
|
||||
instance_data.update({"node_type": "ifd"})
|
||||
# Add chunk size attribute
|
||||
instance_data["chunkSize"] = 10
|
||||
# Submit for job publishing
|
||||
instance_data["farm"] = pre_create_data.get("farm")
|
||||
|
||||
instance = super(CreateMantraROP, self).create(
|
||||
product_name,
|
||||
|
|
@ -46,7 +50,7 @@ class CreateMantraROP(plugin.HoudiniCreator):
|
|||
"vm_picture": filepath,
|
||||
}
|
||||
|
||||
if pre_create_data.get("export_job"):
|
||||
if pre_create_data.get("render_target") == "farm_split":
|
||||
ifd_filepath = \
|
||||
"{export_dir}{product_name}/{product_name}.$F4.ifd".format(
|
||||
export_dir=hou.text.expandString("$HIP/pyblish/ifd/"),
|
||||
|
|
@ -77,21 +81,40 @@ class CreateMantraROP(plugin.HoudiniCreator):
|
|||
to_lock = ["productType", "id"]
|
||||
self.lock_parameters(instance_node, to_lock)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
attrs = super(CreateMantraROP, self).get_pre_create_attr_defs()
|
||||
def get_instance_attr_defs(self):
|
||||
"""get instance attribute definitions.
|
||||
|
||||
Attributes defined in this method are exposed in
|
||||
publish tab in the publisher UI.
|
||||
"""
|
||||
|
||||
render_target_items = {
|
||||
"local": "Local machine rendering",
|
||||
"local_no_render": "Use existing frames (local)",
|
||||
"farm": "Farm Rendering",
|
||||
"farm_split": "Farm Rendering - Split export & render jobs",
|
||||
}
|
||||
|
||||
return [
|
||||
BoolDef("review",
|
||||
label="Review",
|
||||
tooltip="Mark as reviewable",
|
||||
default=True),
|
||||
EnumDef("render_target",
|
||||
items=render_target_items,
|
||||
label="Render target",
|
||||
default=self.render_target)
|
||||
]
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
image_format_enum = [
|
||||
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
|
||||
"rad", "rat", "rta", "sgi", "tga", "tif",
|
||||
]
|
||||
|
||||
return attrs + [
|
||||
BoolDef("farm",
|
||||
label="Submitting to Farm",
|
||||
default=True),
|
||||
BoolDef("export_job",
|
||||
label="Split export and render jobs",
|
||||
default=self.export_job),
|
||||
attrs = super(CreateMantraROP, self).get_pre_create_attr_defs()
|
||||
|
||||
attrs += [
|
||||
EnumDef("image_format",
|
||||
image_format_enum,
|
||||
default="exr",
|
||||
|
|
@ -100,5 +123,6 @@ class CreateMantraROP(plugin.HoudiniCreator):
|
|||
label="Override Camera Resolution",
|
||||
tooltip="Override the current camera "
|
||||
"resolution, recommended for IPR.",
|
||||
default=False)
|
||||
default=False),
|
||||
]
|
||||
return attrs + self.get_instance_attr_defs()
|
||||
|
|
|
|||
|
|
@ -17,17 +17,21 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
|||
ext = "exr"
|
||||
multi_layered_mode = "No Multi-Layered EXR File"
|
||||
|
||||
# Default to split export and render jobs
|
||||
split_render = True
|
||||
# Default render target
|
||||
render_target = "farm_split"
|
||||
|
||||
def create(self, product_name, instance_data, pre_create_data):
|
||||
# Transfer settings from pre create to instance
|
||||
creator_attributes = instance_data.setdefault(
|
||||
"creator_attributes", dict())
|
||||
for key in ["render_target", "review"]:
|
||||
if key in pre_create_data:
|
||||
creator_attributes[key] = pre_create_data[key]
|
||||
|
||||
instance_data.pop("active", None)
|
||||
instance_data.update({"node_type": "Redshift_ROP"})
|
||||
# Add chunk size attribute
|
||||
instance_data["chunkSize"] = 10
|
||||
# Submit for job publishing
|
||||
instance_data["farm"] = pre_create_data.get("farm")
|
||||
|
||||
instance = super(CreateRedshiftROP, self).create(
|
||||
product_name,
|
||||
|
|
@ -99,7 +103,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
|||
rs_filepath = f"{export_dir}{product_name}/{product_name}.$F4.rs"
|
||||
parms["RS_archive_file"] = rs_filepath
|
||||
|
||||
if pre_create_data.get("split_render", self.split_render):
|
||||
if pre_create_data.get("render_target") == "farm_split":
|
||||
parms["RS_archive_enable"] = 1
|
||||
|
||||
instance_node.setParms(parms)
|
||||
|
|
@ -118,24 +122,44 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
|||
|
||||
return super(CreateRedshiftROP, self).remove_instances(instances)
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
"""get instance attribute definitions.
|
||||
|
||||
Attributes defined in this method are exposed in
|
||||
publish tab in the publisher UI.
|
||||
"""
|
||||
|
||||
render_target_items = {
|
||||
"local": "Local machine rendering",
|
||||
"local_no_render": "Use existing frames (local)",
|
||||
"farm": "Farm Rendering",
|
||||
"farm_split": "Farm Rendering - Split export & render jobs",
|
||||
}
|
||||
|
||||
return [
|
||||
BoolDef("review",
|
||||
label="Review",
|
||||
tooltip="Mark as reviewable",
|
||||
default=True),
|
||||
EnumDef("render_target",
|
||||
items=render_target_items,
|
||||
label="Render target",
|
||||
default=self.render_target)
|
||||
]
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
attrs = super(CreateRedshiftROP, self).get_pre_create_attr_defs()
|
||||
|
||||
image_format_enum = [
|
||||
"exr", "tif", "jpg", "png",
|
||||
]
|
||||
|
||||
multi_layered_mode = [
|
||||
"No Multi-Layered EXR File",
|
||||
"Full Multi-Layered EXR File"
|
||||
]
|
||||
|
||||
|
||||
return attrs + [
|
||||
BoolDef("farm",
|
||||
label="Submitting to Farm",
|
||||
default=True),
|
||||
BoolDef("split_render",
|
||||
label="Split export and render jobs",
|
||||
default=self.split_render),
|
||||
attrs = super(CreateRedshiftROP, self).get_pre_create_attr_defs()
|
||||
attrs += [
|
||||
EnumDef("image_format",
|
||||
image_format_enum,
|
||||
default=self.ext,
|
||||
|
|
@ -143,5 +167,6 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
|||
EnumDef("multi_layered_mode",
|
||||
multi_layered_mode,
|
||||
default=self.multi_layered_mode,
|
||||
label="Multi-Layered EXR")
|
||||
label="Multi-Layered EXR"),
|
||||
]
|
||||
return attrs + self.get_instance_attr_defs()
|
||||
|
|
|
|||
|
|
@ -16,17 +16,21 @@ class CreateVrayROP(plugin.HoudiniCreator):
|
|||
icon = "magic"
|
||||
ext = "exr"
|
||||
|
||||
# Default to split export and render jobs
|
||||
export_job = True
|
||||
# Default render target
|
||||
render_target = "farm_split"
|
||||
|
||||
def create(self, product_name, instance_data, pre_create_data):
|
||||
# Transfer settings from pre create to instance
|
||||
creator_attributes = instance_data.setdefault(
|
||||
"creator_attributes", dict())
|
||||
for key in ["render_target", "review"]:
|
||||
if key in pre_create_data:
|
||||
creator_attributes[key] = pre_create_data[key]
|
||||
|
||||
instance_data.pop("active", None)
|
||||
instance_data.update({"node_type": "vray_renderer"})
|
||||
# Add chunk size attribute
|
||||
instance_data["chunkSize"] = 10
|
||||
# Submit for job publishing
|
||||
instance_data["farm"] = pre_create_data.get("farm")
|
||||
|
||||
instance = super(CreateVrayROP, self).create(
|
||||
product_name,
|
||||
|
|
@ -55,7 +59,7 @@ class CreateVrayROP(plugin.HoudiniCreator):
|
|||
"SettingsEXR_bits_per_channel": "16" # half precision
|
||||
}
|
||||
|
||||
if pre_create_data.get("export_job"):
|
||||
if pre_create_data.get("render_target") == "farm_split":
|
||||
scene_filepath = \
|
||||
"{export_dir}{product_name}/{product_name}.$F4.vrscene".format(
|
||||
export_dir=hou.text.expandString("$HIP/pyblish/vrscene/"),
|
||||
|
|
@ -143,20 +147,41 @@ class CreateVrayROP(plugin.HoudiniCreator):
|
|||
|
||||
return super(CreateVrayROP, self).remove_instances(instances)
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
"""get instance attribute definitions.
|
||||
|
||||
Attributes defined in this method are exposed in
|
||||
publish tab in the publisher UI.
|
||||
"""
|
||||
|
||||
|
||||
render_target_items = {
|
||||
"local": "Local machine rendering",
|
||||
"local_no_render": "Use existing frames (local)",
|
||||
"farm": "Farm Rendering",
|
||||
"farm_split": "Farm Rendering - Split export & render jobs",
|
||||
}
|
||||
|
||||
return [
|
||||
BoolDef("review",
|
||||
label="Review",
|
||||
tooltip="Mark as reviewable",
|
||||
default=True),
|
||||
EnumDef("render_target",
|
||||
items=render_target_items,
|
||||
label="Render target",
|
||||
default=self.render_target)
|
||||
]
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
attrs = super(CreateVrayROP, self).get_pre_create_attr_defs()
|
||||
image_format_enum = [
|
||||
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
|
||||
"rad", "rat", "rta", "sgi", "tga", "tif",
|
||||
]
|
||||
|
||||
return attrs + [
|
||||
BoolDef("farm",
|
||||
label="Submitting to Farm",
|
||||
default=True),
|
||||
BoolDef("export_job",
|
||||
label="Split export and render jobs",
|
||||
default=self.export_job),
|
||||
attrs = super(CreateVrayROP, self).get_pre_create_attr_defs()
|
||||
|
||||
attrs += [
|
||||
EnumDef("image_format",
|
||||
image_format_enum,
|
||||
default=self.ext,
|
||||
|
|
@ -172,3 +197,4 @@ class CreateVrayROP(plugin.HoudiniCreator):
|
|||
"if enabled",
|
||||
default=False)
|
||||
]
|
||||
return attrs + self.get_instance_attr_defs()
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ class CreateWorkfile(plugin.HoudiniCreatorBase, AutoCreator):
|
|||
# write workfile information to context container.
|
||||
op_ctx = hou.node(CONTEXT_CONTAINER)
|
||||
if not op_ctx:
|
||||
op_ctx = self.create_context_node()
|
||||
op_ctx = self.host.create_context_node()
|
||||
|
||||
workfile_data = {"workfile": current_instance.data_to_store()}
|
||||
imprint(op_ctx, workfile_data)
|
||||
|
|
|
|||
|
|
@ -40,12 +40,9 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
default_prefix = evalParmNoFrame(rop, "ar_picture")
|
||||
render_products = []
|
||||
|
||||
# Store whether we are splitting the render job (export + render)
|
||||
split_render = bool(rop.parm("ar_ass_export_enable").eval())
|
||||
instance.data["splitRender"] = split_render
|
||||
export_prefix = None
|
||||
export_products = []
|
||||
if split_render:
|
||||
if instance.data["splitRender"]:
|
||||
export_prefix = evalParmNoFrame(
|
||||
rop, "ar_ass_file", pad_character="0"
|
||||
)
|
||||
|
|
@ -68,7 +65,12 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
"": self.generate_expected_files(instance, beauty_product)
|
||||
}
|
||||
|
||||
# Assume it's a multipartExr Render.
|
||||
multipartExr = True
|
||||
|
||||
num_aovs = rop.evalParm("ar_aovs")
|
||||
# TODO: Check the following logic.
|
||||
# as it always assumes that all AOV are not merged.
|
||||
for index in range(1, num_aovs + 1):
|
||||
# Skip disabled AOVs
|
||||
if not rop.evalParm("ar_enable_aov{}".format(index)):
|
||||
|
|
@ -85,6 +87,14 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
files_by_aov[label] = self.generate_expected_files(instance,
|
||||
aov_product)
|
||||
|
||||
# Set to False as soon as we have a separated aov.
|
||||
multipartExr = False
|
||||
|
||||
# Review Logic expects this key to exist and be True
|
||||
# if render is a multipart Exr.
|
||||
# As long as we have one AOV then multipartExr should be True.
|
||||
instance.data["multipartExr"] = multipartExr
|
||||
|
||||
for product in render_products:
|
||||
self.log.debug("Found render product: {}".format(product))
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectFarmInstances(pyblish.api.InstancePlugin):
|
||||
"""Collect instances for farm render."""
|
||||
|
||||
order = pyblish.api.CollectorOrder
|
||||
families = ["mantra_rop",
|
||||
"karma_rop",
|
||||
"redshift_rop",
|
||||
"arnold_rop",
|
||||
"vray_rop"]
|
||||
|
||||
hosts = ["houdini"]
|
||||
targets = ["local", "remote"]
|
||||
label = "Collect farm instances"
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
creator_attribute = instance.data["creator_attributes"]
|
||||
|
||||
# Collect Render Target
|
||||
if creator_attribute.get("render_target") not in {
|
||||
"farm_split", "farm"
|
||||
}:
|
||||
instance.data["farm"] = False
|
||||
instance.data["splitRender"] = False
|
||||
self.log.debug("Render on farm is disabled. "
|
||||
"Skipping farm collecting.")
|
||||
return
|
||||
|
||||
instance.data["farm"] = True
|
||||
instance.data["splitRender"] = (
|
||||
creator_attribute.get("render_target") == "farm_split"
|
||||
)
|
||||
|
|
@ -55,6 +55,12 @@ class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
beauty_product)
|
||||
}
|
||||
|
||||
# Review Logic expects this key to exist and be True
|
||||
# if render is a multipart Exr.
|
||||
# As long as we have one AOV then multipartExr should be True.
|
||||
# By default karma render is a multipart Exr.
|
||||
instance.data["multipartExr"] = True
|
||||
|
||||
filenames = list(render_products)
|
||||
instance.data["files"] = filenames
|
||||
instance.data["renderProducts"] = colorspace.ARenderProduct()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
from ayon_core.pipeline.create import get_product_name
|
||||
from ayon_core.pipeline.farm.patterning import match_aov_pattern
|
||||
from ayon_core.pipeline.publish import (
|
||||
get_plugin_settings,
|
||||
apply_plugin_settings_automatically
|
||||
)
|
||||
|
||||
|
||||
class CollectLocalRenderInstances(pyblish.api.InstancePlugin):
|
||||
"""Collect instances for local render.
|
||||
|
||||
Agnostic Local Render Collector.
|
||||
"""
|
||||
|
||||
# this plugin runs after Collect Render Products
|
||||
order = pyblish.api.CollectorOrder + 0.12
|
||||
families = ["mantra_rop",
|
||||
"karma_rop",
|
||||
"redshift_rop",
|
||||
"arnold_rop",
|
||||
"vray_rop"]
|
||||
|
||||
hosts = ["houdini"]
|
||||
label = "Collect local render instances"
|
||||
|
||||
use_deadline_aov_filter = False
|
||||
aov_filter = {"host_name": "houdini",
|
||||
"value": [".*([Bb]eauty).*"]}
|
||||
|
||||
@classmethod
|
||||
def apply_settings(cls, project_settings):
|
||||
# Preserve automatic settings applying logic
|
||||
settings = get_plugin_settings(plugin=cls,
|
||||
project_settings=project_settings,
|
||||
log=cls.log,
|
||||
category="houdini")
|
||||
apply_plugin_settings_automatically(cls, settings, logger=cls.log)
|
||||
|
||||
if not cls.use_deadline_aov_filter:
|
||||
# get aov_filter from collector settings
|
||||
# and restructure it as match_aov_pattern requires.
|
||||
cls.aov_filter = {
|
||||
cls.aov_filter["host_name"]: cls.aov_filter["value"]
|
||||
}
|
||||
else:
|
||||
# get aov_filter from deadline settings
|
||||
cls.aov_filter = project_settings["deadline"]["publish"]["ProcessSubmittedJobOnFarm"]["aov_filter"]
|
||||
cls.aov_filter = {
|
||||
item["name"]: item["value"]
|
||||
for item in cls.aov_filter
|
||||
}
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
if instance.data["farm"]:
|
||||
self.log.debug("Render on farm is enabled. "
|
||||
"Skipping local render collecting.")
|
||||
return
|
||||
|
||||
# Create Instance for each AOV.
|
||||
context = instance.context
|
||||
expectedFiles = next(iter(instance.data["expectedFiles"]), {})
|
||||
|
||||
product_type = "render" # is always render
|
||||
product_group = get_product_name(
|
||||
context.data["projectName"],
|
||||
context.data["taskEntity"]["name"],
|
||||
context.data["taskEntity"]["taskType"],
|
||||
context.data["hostName"],
|
||||
product_type,
|
||||
instance.data["productName"]
|
||||
)
|
||||
|
||||
for aov_name, aov_filepaths in expectedFiles.items():
|
||||
product_name = product_group
|
||||
|
||||
if aov_name:
|
||||
product_name = "{}_{}".format(product_name, aov_name)
|
||||
|
||||
# Create instance for each AOV
|
||||
aov_instance = context.create_instance(product_name)
|
||||
|
||||
# Prepare Representation for each AOV
|
||||
aov_filenames = [os.path.basename(path) for path in aov_filepaths]
|
||||
staging_dir = os.path.dirname(aov_filepaths[0])
|
||||
ext = aov_filepaths[0].split(".")[-1]
|
||||
|
||||
# Decide if instance is reviewable
|
||||
preview = False
|
||||
if instance.data.get("multipartExr", False):
|
||||
# Add preview tag because its multipartExr.
|
||||
preview = True
|
||||
else:
|
||||
# Add Preview tag if the AOV matches the filter.
|
||||
preview = match_aov_pattern(
|
||||
"houdini", self.aov_filter, aov_filenames[0]
|
||||
)
|
||||
|
||||
preview = preview and instance.data.get("review", False)
|
||||
|
||||
# Support Single frame.
|
||||
# The integrator wants single files to be a single
|
||||
# filename instead of a list.
|
||||
# More info: https://github.com/ynput/ayon-core/issues/238
|
||||
if len(aov_filenames) == 1:
|
||||
aov_filenames = aov_filenames[0]
|
||||
|
||||
aov_instance.data.update({
|
||||
# 'label': label,
|
||||
"task": instance.data["task"],
|
||||
"folderPath": instance.data["folderPath"],
|
||||
"frameStart": instance.data["frameStartHandle"],
|
||||
"frameEnd": instance.data["frameEndHandle"],
|
||||
"productType": product_type,
|
||||
"family": product_type,
|
||||
"productName": product_name,
|
||||
"productGroup": product_group,
|
||||
"families": ["render.local.hou", "review"],
|
||||
"instance_node": instance.data["instance_node"],
|
||||
"representations": [
|
||||
{
|
||||
"stagingDir": staging_dir,
|
||||
"ext": ext,
|
||||
"name": ext,
|
||||
"tags": ["review"] if preview else [],
|
||||
"files": aov_filenames,
|
||||
"frameStart": instance.data["frameStartHandle"],
|
||||
"frameEnd": instance.data["frameEndHandle"]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
# Remove original render instance
|
||||
# I can't remove it here as I still need it to trigger the render.
|
||||
# context.remove(instance)
|
||||
|
|
@ -44,12 +44,9 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
default_prefix = evalParmNoFrame(rop, "vm_picture")
|
||||
render_products = []
|
||||
|
||||
# Store whether we are splitting the render job (export + render)
|
||||
split_render = bool(rop.parm("soho_outputmode").eval())
|
||||
instance.data["splitRender"] = split_render
|
||||
export_prefix = None
|
||||
export_products = []
|
||||
if split_render:
|
||||
if instance.data["splitRender"]:
|
||||
export_prefix = evalParmNoFrame(
|
||||
rop, "soho_diskfile", pad_character="0"
|
||||
)
|
||||
|
|
@ -74,6 +71,11 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
beauty_product)
|
||||
}
|
||||
|
||||
# Assume it's a multipartExr Render.
|
||||
multipartExr = True
|
||||
|
||||
# TODO: This logic doesn't take into considerations
|
||||
# cryptomatte defined in 'Images > Cryptomatte'
|
||||
aov_numbers = rop.evalParm("vm_numaux")
|
||||
if aov_numbers > 0:
|
||||
# get the filenames of the AOVs
|
||||
|
|
@ -93,6 +95,14 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
|
||||
files_by_aov[var] = self.generate_expected_files(instance, aov_product) # noqa
|
||||
|
||||
# Set to False as soon as we have a separated aov.
|
||||
multipartExr = False
|
||||
|
||||
# Review Logic expects this key to exist and be True
|
||||
# if render is a multipart Exr.
|
||||
# As long as we have one AOV then multipartExr should be True.
|
||||
instance.data["multipartExr"] = multipartExr
|
||||
|
||||
for product in render_products:
|
||||
self.log.debug("Found render product: %s" % product)
|
||||
|
||||
|
|
|
|||
|
|
@ -42,11 +42,9 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
|
||||
default_prefix = evalParmNoFrame(rop, "RS_outputFileNamePrefix")
|
||||
beauty_suffix = rop.evalParm("RS_outputBeautyAOVSuffix")
|
||||
# Store whether we are splitting the render job (export + render)
|
||||
split_render = bool(rop.parm("RS_archive_enable").eval())
|
||||
instance.data["splitRender"] = split_render
|
||||
|
||||
export_products = []
|
||||
if split_render:
|
||||
if instance.data["splitRender"]:
|
||||
export_prefix = evalParmNoFrame(
|
||||
rop, "RS_archive_file", pad_character="0"
|
||||
)
|
||||
|
|
@ -63,9 +61,12 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
full_exr_mode = (rop.evalParm("RS_outputMultilayerMode") == "2")
|
||||
if full_exr_mode:
|
||||
# Ignore beauty suffix if full mode is enabled
|
||||
# As this is what the rop does.
|
||||
# As this is what the rop does.
|
||||
beauty_suffix = ""
|
||||
|
||||
# Assume it's a multipartExr Render.
|
||||
multipartExr = True
|
||||
|
||||
# Default beauty/main layer AOV
|
||||
beauty_product = self.get_render_product_name(
|
||||
prefix=default_prefix, suffix=beauty_suffix
|
||||
|
|
@ -75,7 +76,7 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
beauty_suffix: self.generate_expected_files(instance,
|
||||
beauty_product)
|
||||
}
|
||||
|
||||
|
||||
aovs_rop = rop.parm("RS_aovGetFromNode").evalAsNode()
|
||||
if aovs_rop:
|
||||
rop = aovs_rop
|
||||
|
|
@ -98,13 +99,21 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
|
||||
if rop.parm(f"RS_aovID_{i}").evalAsString() == "CRYPTOMATTE" or \
|
||||
not full_exr_mode:
|
||||
|
||||
|
||||
aov_product = self.get_render_product_name(aov_prefix, aov_suffix)
|
||||
render_products.append(aov_product)
|
||||
|
||||
files_by_aov[aov_suffix] = self.generate_expected_files(instance,
|
||||
aov_product) # noqa
|
||||
|
||||
# Set to False as soon as we have a separated aov.
|
||||
multipartExr = False
|
||||
|
||||
# Review Logic expects this key to exist and be True
|
||||
# if render is a multipart Exr.
|
||||
# As long as we have one AOV then multipartExr should be True.
|
||||
instance.data["multipartExr"] = multipartExr
|
||||
|
||||
for product in render_products:
|
||||
self.log.debug("Found render product: %s" % product)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin):
|
|||
label = "Collect Review Data"
|
||||
# This specific order value is used so that
|
||||
# this plugin runs after CollectRopFrameRange
|
||||
order = pyblish.api.CollectorOrder + 0.1
|
||||
# Also after CollectLocalRenderInstances
|
||||
order = pyblish.api.CollectorOrder + 0.13
|
||||
hosts = ["houdini"]
|
||||
families = ["review"]
|
||||
|
||||
|
|
@ -28,7 +29,8 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin):
|
|||
ropnode_path = instance.data["instance_node"]
|
||||
ropnode = hou.node(ropnode_path)
|
||||
|
||||
camera_path = ropnode.parm("camera").eval()
|
||||
# Get camera based on the instance_node type.
|
||||
camera_path = self._get_camera_path(ropnode)
|
||||
camera_node = hou.node(camera_path)
|
||||
if not camera_node:
|
||||
self.log.warning("No valid camera node found on review node: "
|
||||
|
|
@ -55,3 +57,29 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin):
|
|||
# Store focal length in `burninDataMembers`
|
||||
burnin_members = instance.data.setdefault("burninDataMembers", {})
|
||||
burnin_members["focalLength"] = focal_length
|
||||
|
||||
def _get_camera_path(self, ropnode):
|
||||
"""Get the camera path associated with the given rop node.
|
||||
|
||||
This function evaluates the camera parameter according to the
|
||||
type of the given rop node.
|
||||
|
||||
Returns:
|
||||
Union[str, None]: Camera path or None.
|
||||
|
||||
This function can return empty string if the camera
|
||||
path is empty i.e. no camera path.
|
||||
"""
|
||||
|
||||
if ropnode.type().name() in {
|
||||
"opengl", "karma", "ifd", "arnold"
|
||||
}:
|
||||
return ropnode.parm("camera").eval()
|
||||
|
||||
elif ropnode.type().name() == "Redshift_ROP":
|
||||
return ropnode.parm("RS_renderCamera").eval()
|
||||
|
||||
elif ropnode.type().name() == "vray_renderer":
|
||||
return ropnode.parm("render_camera").eval()
|
||||
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectReviewableInstances(pyblish.api.InstancePlugin):
|
||||
"""Collect Reviewable Instances.
|
||||
|
||||
Basically, all instances of the specified families
|
||||
with creator_attribure["review"]
|
||||
"""
|
||||
|
||||
order = pyblish.api.CollectorOrder
|
||||
label = "Collect Reviewable Instances"
|
||||
families = ["mantra_rop",
|
||||
"karma_rop",
|
||||
"redshift_rop",
|
||||
"arnold_rop",
|
||||
"vray_rop"]
|
||||
|
||||
def process(self, instance):
|
||||
creator_attribute = instance.data["creator_attributes"]
|
||||
|
||||
instance.data["review"] = creator_attribute.get("review", False)
|
||||
|
|
@ -45,12 +45,9 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
render_products = []
|
||||
# TODO: add render elements if render element
|
||||
|
||||
# Store whether we are splitting the render job in an export + render
|
||||
split_render = rop.parm("render_export_mode").eval() == "2"
|
||||
instance.data["splitRender"] = split_render
|
||||
export_prefix = None
|
||||
export_products = []
|
||||
if split_render:
|
||||
if instance.data["splitRender"]:
|
||||
export_prefix = evalParmNoFrame(
|
||||
rop, "render_export_filepath", pad_character="0"
|
||||
)
|
||||
|
|
@ -70,6 +67,9 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
"": self.generate_expected_files(instance,
|
||||
beauty_product)}
|
||||
|
||||
# Assume it's a multipartExr Render.
|
||||
multipartExr = True
|
||||
|
||||
if instance.data.get("RenderElement", True):
|
||||
render_element = self.get_render_element_name(rop, default_prefix)
|
||||
if render_element:
|
||||
|
|
@ -77,7 +77,13 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin):
|
|||
render_products.append(renderpass)
|
||||
files_by_aov[aov] = self.generate_expected_files(
|
||||
instance, renderpass)
|
||||
# Set to False as soon as we have a separated aov.
|
||||
multipartExr = False
|
||||
|
||||
# Review Logic expects this key to exist and be True
|
||||
# if render is a multipart Exr.
|
||||
# As long as we have one AOV then multipartExr should be True.
|
||||
instance.data["multipartExr"] = multipartExr
|
||||
|
||||
for product in render_products:
|
||||
self.log.debug("Found render product: %s" % product)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,16 @@ class ExtractOpenGL(publish.Extractor,
|
|||
def process(self, instance):
|
||||
ropnode = hou.node(instance.data.get("instance_node"))
|
||||
|
||||
# This plugin is triggered when marking render as reviewable.
|
||||
# Therefore, this plugin will run on over wrong instances.
|
||||
# TODO: Don't run this plugin on wrong instances.
|
||||
# This plugin should run only on review product type
|
||||
# with instance node of opengl type.
|
||||
if ropnode.type().name() != "opengl":
|
||||
self.log.debug("Skipping OpenGl extraction. Rop node {} "
|
||||
"is not an OpenGl node.".format(ropnode.path()))
|
||||
return
|
||||
|
||||
output = ropnode.evalParm("picture")
|
||||
staging_dir = os.path.normpath(os.path.dirname(output))
|
||||
instance.data["stagingDir"] = staging_dir
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
import pyblish.api
|
||||
|
||||
from ayon_core.pipeline import publish
|
||||
from ayon_core.hosts.houdini.api.lib import render_rop
|
||||
import hou
|
||||
import os
|
||||
|
||||
|
||||
class ExtractRender(publish.Extractor):
|
||||
|
||||
order = pyblish.api.ExtractorOrder
|
||||
label = "Extract Render"
|
||||
hosts = ["houdini"]
|
||||
families = ["mantra_rop",
|
||||
"karma_rop",
|
||||
"redshift_rop",
|
||||
"arnold_rop",
|
||||
"vray_rop"]
|
||||
|
||||
def process(self, instance):
|
||||
creator_attribute = instance.data["creator_attributes"]
|
||||
product_type = instance.data["productType"]
|
||||
rop_node = hou.node(instance.data.get("instance_node"))
|
||||
|
||||
# Align split parameter value on rop node to the render target.
|
||||
if instance.data["splitRender"]:
|
||||
if product_type == "arnold_rop":
|
||||
rop_node.setParms({"ar_ass_export_enable": 1})
|
||||
elif product_type == "mantra_rop":
|
||||
rop_node.setParms({"soho_outputmode": 1})
|
||||
elif product_type == "redshift_rop":
|
||||
rop_node.setParms({"RS_archive_enable": 1})
|
||||
elif product_type == "vray_rop":
|
||||
rop_node.setParms({"render_export_mode": "2"})
|
||||
else:
|
||||
if product_type == "arnold_rop":
|
||||
rop_node.setParms({"ar_ass_export_enable": 0})
|
||||
elif product_type == "mantra_rop":
|
||||
rop_node.setParms({"soho_outputmode": 0})
|
||||
elif product_type == "redshift_rop":
|
||||
rop_node.setParms({"RS_archive_enable": 0})
|
||||
elif product_type == "vray_rop":
|
||||
rop_node.setParms({"render_export_mode": "1"})
|
||||
|
||||
if instance.data.get("farm"):
|
||||
self.log.debug("Render should be processed on farm, skipping local render.")
|
||||
return
|
||||
|
||||
if creator_attribute.get("render_target") == "local":
|
||||
ropnode = hou.node(instance.data.get("instance_node"))
|
||||
render_rop(ropnode)
|
||||
|
||||
# `ExpectedFiles` is a list that includes one dict.
|
||||
expected_files = instance.data["expectedFiles"][0]
|
||||
# Each key in that dict is a list of files.
|
||||
# Combine lists of files into one big list.
|
||||
all_frames = []
|
||||
for value in expected_files.values():
|
||||
if isinstance(value, str):
|
||||
all_frames.append(value)
|
||||
elif isinstance(value, list):
|
||||
all_frames.extend(value)
|
||||
# Check missing frames.
|
||||
# Frames won't exist if user cancels the render.
|
||||
missing_frames = [
|
||||
frame
|
||||
for frame in all_frames
|
||||
if not os.path.exists(frame)
|
||||
]
|
||||
if missing_frames:
|
||||
# TODO: Use user friendly error reporting.
|
||||
raise RuntimeError("Failed to complete render extraction. "
|
||||
"Missing output files: {}".format(
|
||||
missing_frames))
|
||||
|
|
@ -17,11 +17,13 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin):
|
|||
order = pyblish.api.IntegratorOrder + 9.0
|
||||
hosts = ["houdini"]
|
||||
families = ["workfile",
|
||||
"redshift_rop",
|
||||
"arnold_rop",
|
||||
"usdrender",
|
||||
"mantra_rop",
|
||||
"karma_rop",
|
||||
"usdrender",
|
||||
"redshift_rop",
|
||||
"arnold_rop",
|
||||
"vray_rop",
|
||||
"render.local.hou",
|
||||
"publish.hou"]
|
||||
optional = True
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,18 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin,
|
|||
|
||||
def process(self, instance):
|
||||
|
||||
rop_node = hou.node(instance.data["instance_node"])
|
||||
|
||||
# This plugin is triggered when marking render as reviewable.
|
||||
# Therefore, this plugin will run on over wrong instances.
|
||||
# TODO: Don't run this plugin on wrong instances.
|
||||
# This plugin should run only on review product type
|
||||
# with instance node of opengl type.
|
||||
if rop_node.type().name() != "opengl":
|
||||
self.log.debug("Skipping Validation. Rop node {} "
|
||||
"is not an OpenGl node.".format(rop_node.path()))
|
||||
return
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
|
|
@ -66,7 +78,6 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin,
|
|||
)
|
||||
return
|
||||
|
||||
rop_node = hou.node(instance.data["instance_node"])
|
||||
if rop_node.evalParm("colorcorrect") != 2:
|
||||
# any colorspace settings other than default requires
|
||||
# 'Color Correct' parm to be set to 'OpenColorIO'
|
||||
|
|
|
|||
|
|
@ -20,6 +20,16 @@ class ValidateSceneReview(pyblish.api.InstancePlugin):
|
|||
report = []
|
||||
instance_node = hou.node(instance.data.get("instance_node"))
|
||||
|
||||
# This plugin is triggered when marking render as reviewable.
|
||||
# Therefore, this plugin will run on over wrong instances.
|
||||
# TODO: Don't run this plugin on wrong instances.
|
||||
# This plugin should run only on review product type
|
||||
# with instance node of opengl type.
|
||||
if instance_node.type().name() != "opengl":
|
||||
self.log.debug("Skipping Validation. Rop node {} "
|
||||
"is not an OpenGl node.".format(instance_node.path()))
|
||||
return
|
||||
|
||||
invalid = self.get_invalid_scene_path(instance_node)
|
||||
if invalid:
|
||||
report.append(invalid)
|
||||
|
|
|
|||
29
client/ayon_core/hosts/houdini/startup/OPmenu.xml
Normal file
29
client/ayon_core/hosts/houdini/startup/OPmenu.xml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- OPMenu Stencil.
|
||||
It's used to extend the OPMenu.
|
||||
-->
|
||||
|
||||
<menuDocument>
|
||||
<menu>
|
||||
<!-- Operator type and asset options. -->
|
||||
<subMenu id="opmenu.vhda_options_create">
|
||||
<insertBefore>opmenu.unsynchronize</insertBefore>
|
||||
<scriptItem id="opmenu.vhda_create_ayon">
|
||||
<insertAfter>opmenu.vhda_create</insertAfter>
|
||||
<label>Create New (AYON)...</label>
|
||||
<context>
|
||||
</context>
|
||||
<scriptCode>
|
||||
<![CDATA[
|
||||
from ayon_core.hosts.houdini.api.creator_node_shelves import create_interactive
|
||||
|
||||
node = kwargs["node"]
|
||||
if node not in hou.selectedNodes():
|
||||
node.setSelected(True)
|
||||
create_interactive("io.openpype.creators.houdini.hda", **kwargs)
|
||||
]]>
|
||||
</scriptCode>
|
||||
</scriptItem>
|
||||
</subMenu>
|
||||
</menu>
|
||||
</menuDocument>
|
||||
|
|
@ -369,12 +369,8 @@ def reset_colorspace():
|
|||
"""
|
||||
if int(get_max_version()) < 2024:
|
||||
return
|
||||
project_name = get_current_project_name()
|
||||
colorspace_mgr = rt.ColorPipelineMgr
|
||||
project_settings = get_project_settings(project_name)
|
||||
|
||||
max_config_data = colorspace.get_imageio_config(
|
||||
project_name, "max", project_settings)
|
||||
max_config_data = colorspace.get_current_context_imageio_config_preset()
|
||||
if max_config_data:
|
||||
ocio_config_path = max_config_data["path"]
|
||||
colorspace_mgr = rt.ColorPipelineMgr
|
||||
|
|
@ -389,10 +385,7 @@ def check_colorspace():
|
|||
"because Max main window can't be found.")
|
||||
if int(get_max_version()) >= 2024:
|
||||
color_mgr = rt.ColorPipelineMgr
|
||||
project_name = get_current_project_name()
|
||||
project_settings = get_project_settings(project_name)
|
||||
max_config_data = colorspace.get_imageio_config(
|
||||
project_name, "max", project_settings)
|
||||
max_config_data = colorspace.get_current_context_imageio_config_preset()
|
||||
if max_config_data and color_mgr.Mode != rt.Name("OCIO_Custom"):
|
||||
if not is_headless():
|
||||
from ayon_core.tools.utils import SimplePopup
|
||||
|
|
|
|||
|
|
@ -52,11 +52,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
|
||||
self._has_been_setup = True
|
||||
|
||||
def context_setting():
|
||||
return lib.set_context_setting()
|
||||
|
||||
rt.callbacks.addScript(rt.Name('systemPostNew'),
|
||||
context_setting)
|
||||
rt.callbacks.addScript(rt.Name('systemPostNew'), on_new)
|
||||
|
||||
rt.callbacks.addScript(rt.Name('filePostOpen'),
|
||||
lib.check_colorspace)
|
||||
|
|
@ -163,6 +159,14 @@ def ls() -> list:
|
|||
yield lib.read(container)
|
||||
|
||||
|
||||
def on_new():
|
||||
lib.set_context_setting()
|
||||
if rt.checkForSave():
|
||||
rt.resetMaxFile(rt.Name("noPrompt"))
|
||||
rt.clearUndoBuffer()
|
||||
rt.redrawViews()
|
||||
|
||||
|
||||
def containerise(name: str, nodes: list, context,
|
||||
namespace=None, loader=None, suffix="_CON"):
|
||||
data = {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class FBXExtractor:
|
|||
"smoothMesh": bool,
|
||||
"instances": bool,
|
||||
# "referencedContainersContent": bool, # deprecated in Maya 2016+
|
||||
"bakeComplexAnimation": int,
|
||||
"bakeComplexAnimation": bool,
|
||||
"bakeComplexStart": int,
|
||||
"bakeComplexEnd": int,
|
||||
"bakeComplexStep": int,
|
||||
|
|
@ -59,6 +59,7 @@ class FBXExtractor:
|
|||
"constraints": bool,
|
||||
"lights": bool,
|
||||
"embeddedTextures": bool,
|
||||
"includeChildren": bool,
|
||||
"inputConnections": bool,
|
||||
"upAxis": str, # x, y or z,
|
||||
"triangulate": bool,
|
||||
|
|
@ -102,6 +103,7 @@ class FBXExtractor:
|
|||
"constraints": False,
|
||||
"lights": True,
|
||||
"embeddedTextures": False,
|
||||
"includeChildren": True,
|
||||
"inputConnections": True,
|
||||
"upAxis": "y",
|
||||
"triangulate": False,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from ayon_core.pipeline import (
|
|||
from ayon_core.pipeline.load.utils import get_representation_path_from_context
|
||||
from ayon_core.pipeline.colorspace import (
|
||||
get_imageio_file_rules_colorspace_from_filepath,
|
||||
get_imageio_config,
|
||||
get_current_context_imageio_config_preset,
|
||||
get_imageio_file_rules
|
||||
)
|
||||
from ayon_core.settings import get_project_settings
|
||||
|
|
@ -270,8 +270,7 @@ class FileNodeLoader(load.LoaderPlugin):
|
|||
host_name = get_current_host_name()
|
||||
project_settings = get_project_settings(project_name)
|
||||
|
||||
config_data = get_imageio_config(
|
||||
project_name, host_name,
|
||||
config_data = get_current_context_imageio_config_preset(
|
||||
project_settings=project_settings
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ class ExtractFBXAnimation(publish.Extractor):
|
|||
fbx_exporter = fbx.FBXExtractor(log=self.log)
|
||||
out_members = instance.data.get("animated_skeleton", [])
|
||||
# Export
|
||||
instance.data["constraints"] = True
|
||||
# TODO: need to set up the options for users to set up
|
||||
# the flags they intended to export
|
||||
instance.data["skeletonDefinitions"] = True
|
||||
instance.data["referencedAssetsContent"] = True
|
||||
fbx_exporter.set_options_from_instance(instance)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ from ayon_core.pipeline import (
|
|||
from ayon_core.pipeline.context_tools import (
|
||||
get_current_context_custom_workfile_template
|
||||
)
|
||||
from ayon_core.pipeline.colorspace import get_imageio_config
|
||||
from ayon_core.pipeline.colorspace import (
|
||||
get_current_context_imageio_config_preset
|
||||
)
|
||||
from ayon_core.pipeline.workfile import BuildWorkfile
|
||||
from . import gizmo_menu
|
||||
from .constants import ASSIST
|
||||
|
|
@ -1552,10 +1554,7 @@ class WorkfileSettings(object):
|
|||
imageio_host (dict): host colorspace configurations
|
||||
|
||||
'''
|
||||
config_data = get_imageio_config(
|
||||
project_name=get_current_project_name(),
|
||||
host_name="nuke"
|
||||
)
|
||||
config_data = get_current_context_imageio_config_preset()
|
||||
|
||||
workfile_settings = imageio_host["workfile"]
|
||||
color_management = workfile_settings["color_management"]
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class LoadBackdropNodes(load.LoaderPlugin):
|
|||
}
|
||||
|
||||
# add attributes from the version to imprint to metadata knob
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# getting file path
|
||||
|
|
@ -206,7 +206,7 @@ class LoadBackdropNodes(load.LoaderPlugin):
|
|||
"colorspaceInput": colorspace,
|
||||
}
|
||||
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# adding nodes to node graph
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class AlembicCameraLoader(load.LoaderPlugin):
|
|||
"frameEnd": last,
|
||||
"version": version_entity["version"],
|
||||
}
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# getting file path
|
||||
|
|
@ -123,7 +123,7 @@ class AlembicCameraLoader(load.LoaderPlugin):
|
|||
}
|
||||
|
||||
# add attributes from the version to imprint to metadata knob
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# getting file path
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ from ayon_core.pipeline import (
|
|||
get_representation_path,
|
||||
)
|
||||
from ayon_core.pipeline.colorspace import (
|
||||
get_imageio_file_rules_colorspace_from_filepath
|
||||
get_imageio_file_rules_colorspace_from_filepath,
|
||||
get_current_context_imageio_config_preset,
|
||||
)
|
||||
from ayon_core.hosts.nuke.api.lib import (
|
||||
get_imageio_input_colorspace,
|
||||
|
|
@ -197,7 +198,6 @@ class LoadClip(plugin.NukeLoader):
|
|||
"frameStart",
|
||||
"frameEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps",
|
||||
"handleStart",
|
||||
"handleEnd",
|
||||
|
|
@ -347,8 +347,7 @@ class LoadClip(plugin.NukeLoader):
|
|||
"source": version_attributes.get("source"),
|
||||
"handleStart": str(self.handle_start),
|
||||
"handleEnd": str(self.handle_end),
|
||||
"fps": str(version_attributes.get("fps")),
|
||||
"author": version_attributes.get("author")
|
||||
"fps": str(version_attributes.get("fps"))
|
||||
}
|
||||
|
||||
last_version_entity = ayon_api.get_last_version_by_product_id(
|
||||
|
|
@ -547,9 +546,10 @@ class LoadClip(plugin.NukeLoader):
|
|||
f"Colorspace from representation colorspaceData: {colorspace}"
|
||||
)
|
||||
|
||||
config_data = get_current_context_imageio_config_preset()
|
||||
# check if any filerules are not applicable
|
||||
new_parsed_colorspace = get_imageio_file_rules_colorspace_from_filepath( # noqa
|
||||
filepath, "nuke", project_name
|
||||
filepath, "nuke", project_name, config_data=config_data
|
||||
)
|
||||
self.log.debug(f"Colorspace new filerules: {new_parsed_colorspace}")
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ class LoadEffects(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
@ -189,7 +188,6 @@ class LoadEffects(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps",
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
@ -192,7 +191,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ class LoadGizmo(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
@ -139,7 +138,6 @@ class LoadGizmo(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
@ -145,7 +144,6 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class LoadImage(load.LoaderPlugin):
|
|||
"version": version_entity["version"],
|
||||
"colorspace": colorspace,
|
||||
}
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes.get(k, str(None))
|
||||
|
||||
r["tile_color"].setValue(int("0x4ecd25ff", 16))
|
||||
|
|
@ -207,7 +207,6 @@ class LoadImage(load.LoaderPlugin):
|
|||
"colorspace": version_attributes.get("colorSpace"),
|
||||
"source": version_attributes.get("source"),
|
||||
"fps": str(version_attributes.get("fps")),
|
||||
"author": version_attributes.get("author")
|
||||
}
|
||||
|
||||
# change color of node
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class AlembicModelLoader(load.LoaderPlugin):
|
|||
"version": version_entity["version"]
|
||||
}
|
||||
# add attributes from the version to imprint to metadata knob
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# getting file path
|
||||
|
|
@ -130,7 +130,7 @@ class AlembicModelLoader(load.LoaderPlugin):
|
|||
}
|
||||
|
||||
# add additional metadata from the version to imprint to Avalon knob
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# getting file path
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ class LinkAsGroup(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
@ -131,7 +130,6 @@ class LinkAsGroup(load.LoaderPlugin):
|
|||
"colorspace": version_attributes.get("colorSpace"),
|
||||
"source": version_attributes.get("source"),
|
||||
"fps": version_attributes.get("fps"),
|
||||
"author": version_attributes.get("author")
|
||||
}
|
||||
|
||||
# Update the imprinted representation
|
||||
|
|
|
|||
|
|
@ -156,14 +156,9 @@ This creator publishes color space look file (LUT).
|
|||
]
|
||||
|
||||
def apply_settings(self, project_settings):
|
||||
host = self.create_context.host
|
||||
host_name = host.name
|
||||
project_name = host.get_current_project_name()
|
||||
config_data = colorspace.get_imageio_config(
|
||||
project_name, host_name,
|
||||
config_data = colorspace.get_current_context_imageio_config_preset(
|
||||
project_settings=project_settings
|
||||
)
|
||||
|
||||
if not config_data:
|
||||
self.enabled = False
|
||||
return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
from pathlib import Path
|
||||
|
||||
from ayon_core.pipeline import (
|
||||
CreatedInstance,
|
||||
)
|
||||
|
||||
from ayon_core.lib.attribute_definitions import (
|
||||
FileDef,
|
||||
BoolDef,
|
||||
TextDef,
|
||||
)
|
||||
from ayon_core.hosts.traypublisher.api.plugin import TrayPublishCreator
|
||||
|
||||
|
||||
class EditorialPackageCreator(TrayPublishCreator):
|
||||
"""Creates instance for OTIO file from published folder.
|
||||
|
||||
Folder contains OTIO file and exported .mov files. Process should publish
|
||||
whole folder as single `editorial_pckg` product type and (possibly) convert
|
||||
.mov files into different format and copy them into `publish` `resources`
|
||||
subfolder.
|
||||
"""
|
||||
identifier = "editorial_pckg"
|
||||
label = "Editorial package"
|
||||
product_type = "editorial_pckg"
|
||||
description = "Publish folder with OTIO file and resources"
|
||||
|
||||
# Position batch creator after simple creators
|
||||
order = 120
|
||||
|
||||
conversion_enabled = False
|
||||
|
||||
def apply_settings(self, project_settings):
|
||||
self.conversion_enabled = (
|
||||
project_settings["traypublisher"]
|
||||
["publish"]
|
||||
["ExtractEditorialPckgConversion"]
|
||||
["conversion_enabled"]
|
||||
)
|
||||
|
||||
def get_icon(self):
|
||||
return "fa.folder"
|
||||
|
||||
def create(self, product_name, instance_data, pre_create_data):
|
||||
folder_path = pre_create_data.get("folder_path")
|
||||
if not folder_path:
|
||||
return
|
||||
|
||||
instance_data["creator_attributes"] = {
|
||||
"folder_path": (Path(folder_path["directory"]) /
|
||||
Path(folder_path["filenames"][0])).as_posix(),
|
||||
"conversion_enabled": pre_create_data["conversion_enabled"]
|
||||
}
|
||||
|
||||
# Create new instance
|
||||
new_instance = CreatedInstance(self.product_type, product_name,
|
||||
instance_data, self)
|
||||
self._store_new_instance(new_instance)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
# Use same attributes as for instance attributes
|
||||
return [
|
||||
FileDef(
|
||||
"folder_path",
|
||||
folders=True,
|
||||
single_item=True,
|
||||
extensions=[],
|
||||
allow_sequences=False,
|
||||
label="Folder path"
|
||||
),
|
||||
BoolDef("conversion_enabled",
|
||||
tooltip="Convert to output defined in Settings.",
|
||||
default=self.conversion_enabled,
|
||||
label="Convert resources"),
|
||||
]
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
return [
|
||||
TextDef(
|
||||
"folder_path",
|
||||
label="Folder path",
|
||||
disabled=True
|
||||
),
|
||||
BoolDef("conversion_enabled",
|
||||
tooltip="Convert to output defined in Settings.",
|
||||
label="Convert resources"),
|
||||
]
|
||||
|
||||
def get_detail_description(self):
|
||||
return """# Publish folder with OTIO file and video clips
|
||||
|
||||
Folder contains OTIO file and exported .mov files. Process should
|
||||
publish whole folder as single `editorial_pckg` product type and
|
||||
(possibly) convert .mov files into different format and copy them into
|
||||
`publish` `resources` subfolder.
|
||||
"""
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
"""Produces instance.data["editorial_pckg"] data used during integration.
|
||||
|
||||
Requires:
|
||||
instance.data["creator_attributes"]["path"] - from creator
|
||||
|
||||
Provides:
|
||||
instance -> editorial_pckg (dict):
|
||||
folder_path (str)
|
||||
otio_path (str) - from dragged folder
|
||||
resource_paths (list)
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from ayon_core.lib.transcoding import VIDEO_EXTENSIONS
|
||||
|
||||
|
||||
class CollectEditorialPackage(pyblish.api.InstancePlugin):
|
||||
"""Collects path to OTIO file and resources"""
|
||||
|
||||
label = "Collect Editorial Package"
|
||||
order = pyblish.api.CollectorOrder - 0.1
|
||||
|
||||
hosts = ["traypublisher"]
|
||||
families = ["editorial_pckg"]
|
||||
|
||||
def process(self, instance):
|
||||
folder_path = instance.data["creator_attributes"]["folder_path"]
|
||||
if not folder_path or not os.path.exists(folder_path):
|
||||
self.log.info((
|
||||
"Instance doesn't contain collected existing folder path."
|
||||
))
|
||||
return
|
||||
|
||||
instance.data["editorial_pckg"] = {}
|
||||
instance.data["editorial_pckg"]["folder_path"] = folder_path
|
||||
|
||||
otio_path, resource_paths = (
|
||||
self._get_otio_and_resource_paths(folder_path))
|
||||
|
||||
instance.data["editorial_pckg"]["otio_path"] = otio_path
|
||||
instance.data["editorial_pckg"]["resource_paths"] = resource_paths
|
||||
|
||||
def _get_otio_and_resource_paths(self, folder_path):
|
||||
otio_path = None
|
||||
resource_paths = []
|
||||
|
||||
file_names = os.listdir(folder_path)
|
||||
for filename in file_names:
|
||||
_, ext = os.path.splitext(filename)
|
||||
file_path = os.path.join(folder_path, filename)
|
||||
if ext == ".otio":
|
||||
otio_path = file_path
|
||||
elif ext in VIDEO_EXTENSIONS:
|
||||
resource_paths.append(file_path)
|
||||
return otio_path, resource_paths
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
import pyblish.api
|
||||
from ayon_core.pipeline import (
|
||||
publish,
|
||||
registered_host
|
||||
)
|
||||
from ayon_core.lib import EnumDef
|
||||
from ayon_core.pipeline import colorspace
|
||||
from ayon_core.pipeline import publish
|
||||
from ayon_core.pipeline.publish import KnownPublishError
|
||||
|
||||
|
||||
|
|
@ -19,9 +16,10 @@ class CollectColorspace(pyblish.api.InstancePlugin,
|
|||
families = ["render", "plate", "reference", "image", "online"]
|
||||
enabled = False
|
||||
|
||||
colorspace_items = [
|
||||
default_colorspace_items = [
|
||||
(None, "Don't override")
|
||||
]
|
||||
colorspace_items = list(default_colorspace_items)
|
||||
colorspace_attr_show = False
|
||||
config_items = None
|
||||
|
||||
|
|
@ -69,14 +67,13 @@ class CollectColorspace(pyblish.api.InstancePlugin,
|
|||
|
||||
@classmethod
|
||||
def apply_settings(cls, project_settings):
|
||||
host = registered_host()
|
||||
host_name = host.name
|
||||
project_name = host.get_current_project_name()
|
||||
config_data = colorspace.get_imageio_config(
|
||||
project_name, host_name,
|
||||
config_data = colorspace.get_current_context_imageio_config_preset(
|
||||
project_settings=project_settings
|
||||
)
|
||||
|
||||
enabled = False
|
||||
colorspace_items = list(cls.default_colorspace_items)
|
||||
config_items = None
|
||||
if config_data:
|
||||
filepath = config_data["path"]
|
||||
config_items = colorspace.get_ocio_config_colorspaces(filepath)
|
||||
|
|
@ -85,9 +82,11 @@ class CollectColorspace(pyblish.api.InstancePlugin,
|
|||
include_aliases=True,
|
||||
include_roles=True
|
||||
)
|
||||
cls.config_items = config_items
|
||||
cls.colorspace_items.extend(labeled_colorspaces)
|
||||
cls.enabled = True
|
||||
colorspace_items.extend(labeled_colorspaces)
|
||||
|
||||
cls.config_items = config_items
|
||||
cls.colorspace_items = colorspace_items
|
||||
cls.enabled = enabled
|
||||
|
||||
@classmethod
|
||||
def get_attribute_defs(cls):
|
||||
|
|
|
|||
|
|
@ -10,9 +10,13 @@ class CollectFrameDataFromAssetEntity(pyblish.api.InstancePlugin):
|
|||
|
||||
order = pyblish.api.CollectorOrder + 0.491
|
||||
label = "Collect Missing Frame Data From Folder"
|
||||
families = ["plate", "pointcache",
|
||||
"vdbcache", "online",
|
||||
"render"]
|
||||
families = [
|
||||
"plate",
|
||||
"pointcache",
|
||||
"vdbcache",
|
||||
"online",
|
||||
"render",
|
||||
]
|
||||
hosts = ["traypublisher"]
|
||||
|
||||
def process(self, instance):
|
||||
|
|
@ -22,16 +26,26 @@ class CollectFrameDataFromAssetEntity(pyblish.api.InstancePlugin):
|
|||
"frameStart",
|
||||
"frameEnd",
|
||||
"handleStart",
|
||||
"handleEnd"
|
||||
"handleEnd",
|
||||
):
|
||||
if key not in instance.data:
|
||||
missing_keys.append(key)
|
||||
|
||||
# Skip the logic if all keys are already collected.
|
||||
# NOTE: In editorial is not 'folderEntity' filled, so it would crash
|
||||
# even if we don't need it.
|
||||
if not missing_keys:
|
||||
return
|
||||
|
||||
keys_set = []
|
||||
folder_attributes = instance.data["folderEntity"]["attrib"]
|
||||
for key in missing_keys:
|
||||
if key in folder_attributes:
|
||||
instance.data[key] = folder_attributes[key]
|
||||
keys_set.append(key)
|
||||
|
||||
if keys_set:
|
||||
self.log.debug(f"Frame range data {keys_set} "
|
||||
"has been collected from folder entity.")
|
||||
self.log.debug(
|
||||
f"Frame range data {keys_set} "
|
||||
"has been collected from folder entity."
|
||||
)
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
import copy
|
||||
import os.path
|
||||
import subprocess
|
||||
|
||||
import opentimelineio
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from ayon_core.lib import get_ffmpeg_tool_args, run_subprocess
|
||||
from ayon_core.pipeline import publish
|
||||
|
||||
|
||||
class ExtractEditorialPckgConversion(publish.Extractor):
|
||||
"""Replaces movie paths in otio file with publish rootless
|
||||
|
||||
Prepares movie resources for integration (adds them to `transfers`).
|
||||
Converts .mov files according to output definition.
|
||||
"""
|
||||
|
||||
label = "Extract Editorial Package"
|
||||
order = pyblish.api.ExtractorOrder - 0.45
|
||||
hosts = ["traypublisher"]
|
||||
families = ["editorial_pckg"]
|
||||
|
||||
def process(self, instance):
|
||||
editorial_pckg_data = instance.data.get("editorial_pckg")
|
||||
|
||||
otio_path = editorial_pckg_data["otio_path"]
|
||||
otio_basename = os.path.basename(otio_path)
|
||||
staging_dir = self.staging_dir(instance)
|
||||
|
||||
editorial_pckg_repre = {
|
||||
'name': "editorial_pckg",
|
||||
'ext': "otio",
|
||||
'files': otio_basename,
|
||||
"stagingDir": staging_dir,
|
||||
}
|
||||
otio_staging_path = os.path.join(staging_dir, otio_basename)
|
||||
|
||||
instance.data["representations"].append(editorial_pckg_repre)
|
||||
|
||||
publish_resource_folder = self._get_publish_resource_folder(instance)
|
||||
resource_paths = editorial_pckg_data["resource_paths"]
|
||||
transfers = self._get_transfers(resource_paths,
|
||||
publish_resource_folder)
|
||||
|
||||
project_settings = instance.context.data["project_settings"]
|
||||
output_def = (project_settings["traypublisher"]
|
||||
["publish"]
|
||||
["ExtractEditorialPckgConversion"]
|
||||
["output"])
|
||||
|
||||
conversion_enabled = (instance.data["creator_attributes"]
|
||||
["conversion_enabled"])
|
||||
|
||||
if conversion_enabled and output_def["ext"]:
|
||||
transfers = self._convert_resources(output_def, transfers)
|
||||
|
||||
instance.data["transfers"] = transfers
|
||||
|
||||
source_to_rootless = self._get_resource_path_mapping(instance,
|
||||
transfers)
|
||||
|
||||
otio_data = editorial_pckg_data["otio_data"]
|
||||
otio_data = self._replace_target_urls(otio_data, source_to_rootless)
|
||||
|
||||
opentimelineio.adapters.write_to_file(otio_data, otio_staging_path)
|
||||
|
||||
self.log.info("Added Editorial Package representation: {}".format(
|
||||
editorial_pckg_repre))
|
||||
|
||||
def _get_publish_resource_folder(self, instance):
|
||||
"""Calculates publish folder and create it."""
|
||||
publish_path = self._get_published_path(instance)
|
||||
publish_folder = os.path.dirname(publish_path)
|
||||
publish_resource_folder = os.path.join(publish_folder, "resources")
|
||||
|
||||
if not os.path.exists(publish_resource_folder):
|
||||
os.makedirs(publish_resource_folder, exist_ok=True)
|
||||
return publish_resource_folder
|
||||
|
||||
def _get_resource_path_mapping(self, instance, transfers):
|
||||
"""Returns dict of {source_mov_path: rootless_published_path}."""
|
||||
replace_paths = {}
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
for source, destination in transfers:
|
||||
rootless_path = self._get_rootless(anatomy, destination)
|
||||
source_file_name = os.path.basename(source)
|
||||
replace_paths[source_file_name] = rootless_path
|
||||
return replace_paths
|
||||
|
||||
def _get_transfers(self, resource_paths, publish_resource_folder):
|
||||
"""Returns list of tuples (source, destination) with movie paths."""
|
||||
transfers = []
|
||||
for res_path in resource_paths:
|
||||
res_basename = os.path.basename(res_path)
|
||||
pub_res_path = os.path.join(publish_resource_folder, res_basename)
|
||||
transfers.append((res_path, pub_res_path))
|
||||
return transfers
|
||||
|
||||
def _replace_target_urls(self, otio_data, replace_paths):
|
||||
"""Replace original movie paths with published rootless ones."""
|
||||
for track in otio_data.tracks:
|
||||
for clip in track:
|
||||
# Check if the clip has a media reference
|
||||
if clip.media_reference is not None:
|
||||
# Access the target_url from the media reference
|
||||
target_url = clip.media_reference.target_url
|
||||
if not target_url:
|
||||
continue
|
||||
file_name = os.path.basename(target_url)
|
||||
replace_path = replace_paths.get(file_name)
|
||||
if replace_path:
|
||||
clip.media_reference.target_url = replace_path
|
||||
if clip.name == file_name:
|
||||
clip.name = os.path.basename(replace_path)
|
||||
|
||||
return otio_data
|
||||
|
||||
def _get_rootless(self, anatomy, path):
|
||||
"""Try to find rootless {root[work]} path from `path`"""
|
||||
success, rootless_path = anatomy.find_root_template_from_path(
|
||||
path)
|
||||
if not success:
|
||||
# `rootless_path` is not set to `output_dir` if none of roots match
|
||||
self.log.warning(
|
||||
f"Could not find root path for remapping '{path}'."
|
||||
)
|
||||
rootless_path = path
|
||||
|
||||
return rootless_path
|
||||
|
||||
def _get_published_path(self, instance):
|
||||
"""Calculates expected `publish` folder"""
|
||||
# determine published path from Anatomy.
|
||||
template_data = instance.data.get("anatomyData")
|
||||
rep = instance.data["representations"][0]
|
||||
template_data["representation"] = rep.get("name")
|
||||
template_data["ext"] = rep.get("ext")
|
||||
template_data["comment"] = None
|
||||
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
template_data["root"] = anatomy.roots
|
||||
template = anatomy.get_template_item("publish", "default", "path")
|
||||
template_filled = template.format_strict(template_data)
|
||||
return os.path.normpath(template_filled)
|
||||
|
||||
def _convert_resources(self, output_def, transfers):
|
||||
"""Converts all resource files to configured format."""
|
||||
out_extension = output_def["ext"]
|
||||
if not out_extension:
|
||||
self.log.warning("No output extension configured in "
|
||||
"ayon+settings://traypublisher/publish/ExtractEditorialPckgConversion") # noqa
|
||||
return transfers
|
||||
|
||||
final_transfers = []
|
||||
out_def_ffmpeg_args = output_def["ffmpeg_args"]
|
||||
ffmpeg_input_args = [
|
||||
value.strip()
|
||||
for value in out_def_ffmpeg_args["input"]
|
||||
if value.strip()
|
||||
]
|
||||
ffmpeg_video_filters = [
|
||||
value.strip()
|
||||
for value in out_def_ffmpeg_args["video_filters"]
|
||||
if value.strip()
|
||||
]
|
||||
ffmpeg_audio_filters = [
|
||||
value.strip()
|
||||
for value in out_def_ffmpeg_args["audio_filters"]
|
||||
if value.strip()
|
||||
]
|
||||
ffmpeg_output_args = [
|
||||
value.strip()
|
||||
for value in out_def_ffmpeg_args["output"]
|
||||
if value.strip()
|
||||
]
|
||||
ffmpeg_input_args = self._split_ffmpeg_args(ffmpeg_input_args)
|
||||
|
||||
generic_args = [
|
||||
subprocess.list2cmdline(get_ffmpeg_tool_args("ffmpeg"))
|
||||
]
|
||||
generic_args.extend(ffmpeg_input_args)
|
||||
if ffmpeg_video_filters:
|
||||
generic_args.append("-filter:v")
|
||||
generic_args.append(
|
||||
"\"{}\"".format(",".join(ffmpeg_video_filters)))
|
||||
|
||||
if ffmpeg_audio_filters:
|
||||
generic_args.append("-filter:a")
|
||||
generic_args.append(
|
||||
"\"{}\"".format(",".join(ffmpeg_audio_filters)))
|
||||
|
||||
for source, destination in transfers:
|
||||
base_name = os.path.basename(destination)
|
||||
file_name, ext = os.path.splitext(base_name)
|
||||
dest_path = os.path.join(os.path.dirname(destination),
|
||||
f"{file_name}.{out_extension}")
|
||||
final_transfers.append((source, dest_path))
|
||||
|
||||
all_args = copy.deepcopy(generic_args)
|
||||
all_args.append(f"-i \"{source}\"")
|
||||
all_args.extend(ffmpeg_output_args) # order matters
|
||||
all_args.append(f"\"{dest_path}\"")
|
||||
subprcs_cmd = " ".join(all_args)
|
||||
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(subprcs_cmd))
|
||||
run_subprocess(subprcs_cmd, shell=True, logger=self.log)
|
||||
return final_transfers
|
||||
|
||||
def _split_ffmpeg_args(self, 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.
|
||||
"""
|
||||
splitted_args = []
|
||||
for arg in in_args:
|
||||
sub_args = arg.split(" -")
|
||||
if len(sub_args) == 1:
|
||||
if arg and arg not in splitted_args:
|
||||
splitted_args.append(arg)
|
||||
continue
|
||||
|
||||
for idx, arg in enumerate(sub_args):
|
||||
if idx != 0:
|
||||
arg = "-" + arg
|
||||
|
||||
if arg and arg not in splitted_args:
|
||||
splitted_args.append(arg)
|
||||
return splitted_args
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import os
|
||||
import opentimelineio
|
||||
|
||||
import pyblish.api
|
||||
from ayon_core.pipeline import PublishValidationError
|
||||
|
||||
|
||||
class ValidateEditorialPackage(pyblish.api.InstancePlugin):
|
||||
"""Checks that published folder contains all resources from otio
|
||||
|
||||
Currently checks only by file names and expects flat structure.
|
||||
It ignores path to resources in otio file as folder might be dragged in and
|
||||
published from different location than it was created.
|
||||
"""
|
||||
|
||||
label = "Validate Editorial Package"
|
||||
order = pyblish.api.ValidatorOrder - 0.49
|
||||
|
||||
hosts = ["traypublisher"]
|
||||
families = ["editorial_pckg"]
|
||||
|
||||
def process(self, instance):
|
||||
editorial_pckg_data = instance.data.get("editorial_pckg")
|
||||
if not editorial_pckg_data:
|
||||
raise PublishValidationError("Editorial package not collected")
|
||||
|
||||
folder_path = editorial_pckg_data["folder_path"]
|
||||
|
||||
otio_path = editorial_pckg_data["otio_path"]
|
||||
if not otio_path:
|
||||
raise PublishValidationError(
|
||||
f"Folder {folder_path} missing otio file")
|
||||
|
||||
resource_paths = editorial_pckg_data["resource_paths"]
|
||||
|
||||
resource_file_names = {os.path.basename(path)
|
||||
for path in resource_paths}
|
||||
|
||||
otio_data = opentimelineio.adapters.read_from_file(otio_path)
|
||||
|
||||
target_urls = self._get_all_target_urls(otio_data)
|
||||
missing_files = set()
|
||||
for target_url in target_urls:
|
||||
target_basename = os.path.basename(target_url)
|
||||
if target_basename not in resource_file_names:
|
||||
missing_files.add(target_basename)
|
||||
|
||||
if missing_files:
|
||||
raise PublishValidationError(
|
||||
f"Otio file contains missing files `{missing_files}`.\n\n"
|
||||
f"Please add them to `{folder_path}` and republish.")
|
||||
|
||||
instance.data["editorial_pckg"]["otio_data"] = otio_data
|
||||
|
||||
def _get_all_target_urls(self, otio_data):
|
||||
target_urls = []
|
||||
|
||||
# Iterate through tracks, clips, or other elements
|
||||
for track in otio_data.tracks:
|
||||
for clip in track:
|
||||
# Check if the clip has a media reference
|
||||
if clip.media_reference is not None:
|
||||
# Access the target_url from the media reference
|
||||
target_url = clip.media_reference.target_url
|
||||
if target_url:
|
||||
target_urls.append(target_url)
|
||||
|
||||
return target_urls
|
||||
|
|
@ -85,7 +85,7 @@ class HoudiniSubmitDeadline(
|
|||
priority = 50
|
||||
chunk_size = 1
|
||||
group = ""
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_attribute_defs(cls):
|
||||
return [
|
||||
|
|
@ -188,7 +188,7 @@ class HoudiniSubmitDeadline(
|
|||
|
||||
job_info.Pool = instance.data.get("primaryPool")
|
||||
job_info.SecondaryPool = instance.data.get("secondaryPool")
|
||||
|
||||
|
||||
if split_render_job and is_export_job:
|
||||
job_info.Priority = attribute_values.get(
|
||||
"export_priority", self.export_priority
|
||||
|
|
@ -309,6 +309,11 @@ class HoudiniSubmitDeadline(
|
|||
return attr.asdict(plugin_info)
|
||||
|
||||
def process(self, instance):
|
||||
if not instance.data["farm"]:
|
||||
self.log.debug("Render on farm is disabled. "
|
||||
"Skipping deadline submission.")
|
||||
return
|
||||
|
||||
super(HoudiniSubmitDeadline, self).process(instance)
|
||||
|
||||
# TODO: Avoid the need for this logic here, needed for submit publish
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from ayon_core.lib import Logger, run_subprocess, AYONSettingsRegistry
|
|||
from ayon_core.lib.vendor_bin_utils import find_tool_in_custom_paths
|
||||
|
||||
from .rr_job import SubmitFile
|
||||
from .rr_job import RRjob, SubmitterParameter # noqa F401
|
||||
from .rr_job import RRJob, SubmitterParameter # noqa F401
|
||||
|
||||
|
||||
class Api:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -459,36 +459,6 @@ def is_representation_from_latest(representation):
|
|||
)
|
||||
|
||||
|
||||
def get_template_data_from_session(session=None, settings=None):
|
||||
"""Template data for template fill from session keys.
|
||||
|
||||
Args:
|
||||
session (Union[Dict[str, str], None]): The Session to use. If not
|
||||
provided use the currently active global Session.
|
||||
settings (Optional[Dict[str, Any]]): Prepared studio or project
|
||||
settings.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: All available data from session.
|
||||
"""
|
||||
|
||||
if session is not None:
|
||||
project_name = session["AYON_PROJECT_NAME"]
|
||||
folder_path = session["AYON_FOLDER_PATH"]
|
||||
task_name = session["AYON_TASK_NAME"]
|
||||
host_name = session["AYON_HOST_NAME"]
|
||||
else:
|
||||
context = get_current_context()
|
||||
project_name = context["project_name"]
|
||||
folder_path = context["folder_path"]
|
||||
task_name = context["task_name"]
|
||||
host_name = get_current_host_name()
|
||||
|
||||
return get_template_data_with_names(
|
||||
project_name, folder_path, task_name, host_name, settings
|
||||
)
|
||||
|
||||
|
||||
def get_current_context_template_data(settings=None):
|
||||
"""Prepare template data for current context.
|
||||
|
||||
|
|
|
|||
|
|
@ -108,69 +108,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
|||
|
||||
label = "Integrate Asset"
|
||||
order = pyblish.api.IntegratorOrder
|
||||
families = ["workfile",
|
||||
"pointcache",
|
||||
"pointcloud",
|
||||
"proxyAbc",
|
||||
"camera",
|
||||
"animation",
|
||||
"model",
|
||||
"maxScene",
|
||||
"mayaAscii",
|
||||
"mayaScene",
|
||||
"setdress",
|
||||
"layout",
|
||||
"ass",
|
||||
"assProxy",
|
||||
"vdbcache",
|
||||
"scene",
|
||||
"vrayproxy",
|
||||
"vrayscene_layer",
|
||||
"render",
|
||||
"prerender",
|
||||
"imagesequence",
|
||||
"review",
|
||||
"rendersetup",
|
||||
"rig",
|
||||
"plate",
|
||||
"look",
|
||||
"ociolook",
|
||||
"audio",
|
||||
"yetiRig",
|
||||
"yeticache",
|
||||
"nukenodes",
|
||||
"gizmo",
|
||||
"source",
|
||||
"matchmove",
|
||||
"image",
|
||||
"assembly",
|
||||
"fbx",
|
||||
"gltf",
|
||||
"textures",
|
||||
"action",
|
||||
"harmony.template",
|
||||
"harmony.palette",
|
||||
"editorial",
|
||||
"background",
|
||||
"camerarig",
|
||||
"redshiftproxy",
|
||||
"effect",
|
||||
"xgen",
|
||||
"hda",
|
||||
"usd",
|
||||
"staticMesh",
|
||||
"skeletalMesh",
|
||||
"mvLook",
|
||||
"mvUsd",
|
||||
"mvUsdComposition",
|
||||
"mvUsdOverride",
|
||||
"online",
|
||||
"uasset",
|
||||
"blendScene",
|
||||
"yeticacheUE",
|
||||
"tycache",
|
||||
"csv_ingest_file",
|
||||
]
|
||||
|
||||
default_template_name = "publish"
|
||||
|
||||
|
|
@ -360,7 +297,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
|||
|
||||
# Compute the resource file infos once (files belonging to the
|
||||
# version instance instead of an individual representation) so
|
||||
# we can re-use those file infos per representation
|
||||
# we can reuse those file infos per representation
|
||||
resource_file_infos = self.get_files_info(
|
||||
resource_destinations, anatomy
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,28 +1,31 @@
|
|||
"""OpenColorIO Wrapper.
|
||||
|
||||
Only to be interpreted by Python 3. It is run in subprocess in case
|
||||
Python 2 hosts needs to use it. Or it is used as module for Python 3
|
||||
processing.
|
||||
|
||||
Providing functionality:
|
||||
- get_colorspace - console command - python 2
|
||||
- returning all available color spaces
|
||||
found in input config path.
|
||||
- _get_colorspace_data - python 3 - module function
|
||||
- returning all available colorspaces
|
||||
found in input config path.
|
||||
- get_views - console command - python 2
|
||||
- returning all available viewers
|
||||
found in input config path.
|
||||
- _get_views_data - python 3 - module function
|
||||
- returning all available viewers
|
||||
found in input config path.
|
||||
Receive OpenColorIO information and store it in JSON format for processed
|
||||
that don't have access to OpenColorIO or their version of OpenColorIO is
|
||||
not compatible.
|
||||
"""
|
||||
|
||||
import click
|
||||
import json
|
||||
from pathlib import Path
|
||||
import PyOpenColorIO as ocio
|
||||
|
||||
import click
|
||||
|
||||
from ayon_core.pipeline.colorspace import (
|
||||
has_compatible_ocio_package,
|
||||
get_display_view_colorspace_name,
|
||||
get_config_file_rules_colorspace_from_filepath,
|
||||
get_config_version_data,
|
||||
get_ocio_config_views,
|
||||
get_ocio_config_colorspaces,
|
||||
)
|
||||
|
||||
|
||||
def _save_output_to_json_file(output, output_path):
|
||||
json_path = Path(output_path)
|
||||
with open(json_path, "w") as stream:
|
||||
json.dump(output, stream)
|
||||
|
||||
print(f"Data are saved to '{json_path}'")
|
||||
|
||||
|
||||
@click.group()
|
||||
|
|
@ -30,404 +33,185 @@ def main():
|
|||
pass # noqa: WPS100
|
||||
|
||||
|
||||
@main.group()
|
||||
def config():
|
||||
"""Config related commands group
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config <command> *args
|
||||
"""
|
||||
pass # noqa: WPS100
|
||||
|
||||
|
||||
@main.group()
|
||||
def colorspace():
|
||||
"""Colorspace related commands group
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config <command> *args
|
||||
"""
|
||||
pass # noqa: WPS100
|
||||
|
||||
|
||||
@config.command(
|
||||
name="get_colorspace",
|
||||
help=(
|
||||
"return all colorspaces from config file "
|
||||
"--path input arg is required"
|
||||
)
|
||||
)
|
||||
@click.option("--in_path", required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option("--out_path", required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def get_colorspace(in_path, out_path):
|
||||
@main.command(
|
||||
name="get_ocio_config_colorspaces",
|
||||
help="return all colorspaces from config file")
|
||||
@click.option(
|
||||
"--config_path",
|
||||
required=True,
|
||||
help="OCIO config path to read ocio config file.",
|
||||
type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--output_path",
|
||||
required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def _get_ocio_config_colorspaces(config_path, output_path):
|
||||
"""Aggregate all colorspace to file.
|
||||
|
||||
Python 2 wrapped console command
|
||||
|
||||
Args:
|
||||
in_path (str): config file path string
|
||||
out_path (str): temp json file path string
|
||||
config_path (str): config file path string
|
||||
output_path (str): temp json file path string
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config get_colorspace
|
||||
--in_path=<path> --out_path=<path>
|
||||
--config_path <path> --output_path <path>
|
||||
"""
|
||||
json_path = Path(out_path)
|
||||
|
||||
out_data = _get_colorspace_data(in_path)
|
||||
|
||||
with open(json_path, "w") as f_:
|
||||
json.dump(out_data, f_)
|
||||
|
||||
print(f"Colorspace data are saved to '{json_path}'")
|
||||
|
||||
|
||||
def _get_colorspace_data(config_path):
|
||||
"""Return all found colorspace data.
|
||||
|
||||
Args:
|
||||
config_path (str): path string leading to config.ocio
|
||||
|
||||
Raises:
|
||||
IOError: Input config does not exist.
|
||||
|
||||
Returns:
|
||||
dict: aggregated available colorspaces
|
||||
"""
|
||||
config_path = Path(config_path)
|
||||
|
||||
if not config_path.is_file():
|
||||
raise IOError(
|
||||
f"Input path `{config_path}` should be `config.ocio` file")
|
||||
|
||||
config = ocio.Config().CreateFromFile(str(config_path))
|
||||
|
||||
colorspace_data = {
|
||||
"roles": {},
|
||||
"colorspaces": {
|
||||
color.getName(): {
|
||||
"family": color.getFamily(),
|
||||
"categories": list(color.getCategories()),
|
||||
"aliases": list(color.getAliases()),
|
||||
"equalitygroup": color.getEqualityGroup(),
|
||||
}
|
||||
for color in config.getColorSpaces()
|
||||
},
|
||||
"displays_views": {
|
||||
f"{view} ({display})": {
|
||||
"display": display,
|
||||
"view": view
|
||||
|
||||
}
|
||||
for display in config.getDisplays()
|
||||
for view in config.getViews(display)
|
||||
},
|
||||
"looks": {}
|
||||
}
|
||||
|
||||
# add looks
|
||||
looks = config.getLooks()
|
||||
if looks:
|
||||
colorspace_data["looks"] = {
|
||||
look.getName(): {"process_space": look.getProcessSpace()}
|
||||
for look in looks
|
||||
}
|
||||
|
||||
# add roles
|
||||
roles = config.getRoles()
|
||||
if roles:
|
||||
colorspace_data["roles"] = {
|
||||
role: {"colorspace": colorspace}
|
||||
for (role, colorspace) in roles
|
||||
}
|
||||
|
||||
return colorspace_data
|
||||
|
||||
|
||||
@config.command(
|
||||
name="get_views",
|
||||
help=(
|
||||
"return all viewers from config file "
|
||||
"--path input arg is required"
|
||||
_save_output_to_json_file(
|
||||
get_ocio_config_colorspaces(config_path),
|
||||
output_path
|
||||
)
|
||||
)
|
||||
@click.option("--in_path", required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option("--out_path", required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def get_views(in_path, out_path):
|
||||
|
||||
|
||||
@main.command(
|
||||
name="get_ocio_config_views",
|
||||
help="All viewers from config file")
|
||||
@click.option(
|
||||
"--config_path",
|
||||
required=True,
|
||||
help="OCIO config path to read ocio config file.",
|
||||
type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--output_path",
|
||||
required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def _get_ocio_config_views(config_path, output_path):
|
||||
"""Aggregate all viewers to file.
|
||||
|
||||
Python 2 wrapped console command
|
||||
|
||||
Args:
|
||||
in_path (str): config file path string
|
||||
out_path (str): temp json file path string
|
||||
config_path (str): config file path string
|
||||
output_path (str): temp json file path string
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config get_views \
|
||||
--in_path=<path> --out_path=<path>
|
||||
--config_path <path> --output <path>
|
||||
"""
|
||||
json_path = Path(out_path)
|
||||
|
||||
out_data = _get_views_data(in_path)
|
||||
|
||||
with open(json_path, "w") as f_:
|
||||
json.dump(out_data, f_)
|
||||
|
||||
print(f"Viewer data are saved to '{json_path}'")
|
||||
|
||||
|
||||
def _get_views_data(config_path):
|
||||
"""Return all found viewer data.
|
||||
|
||||
Args:
|
||||
config_path (str): path string leading to config.ocio
|
||||
|
||||
Raises:
|
||||
IOError: Input config does not exist.
|
||||
|
||||
Returns:
|
||||
dict: aggregated available viewers
|
||||
"""
|
||||
config_path = Path(config_path)
|
||||
|
||||
if not config_path.is_file():
|
||||
raise IOError("Input path should be `config.ocio` file")
|
||||
|
||||
config = ocio.Config().CreateFromFile(str(config_path))
|
||||
|
||||
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_
|
||||
|
||||
|
||||
@config.command(
|
||||
name="get_version",
|
||||
help=(
|
||||
"return major and minor version from config file "
|
||||
"--config_path input arg is required"
|
||||
"--out_path input arg is required"
|
||||
_save_output_to_json_file(
|
||||
get_ocio_config_views(config_path),
|
||||
output_path
|
||||
)
|
||||
)
|
||||
@click.option("--config_path", required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option("--out_path", required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def get_version(config_path, out_path):
|
||||
"""Get version of config.
|
||||
|
||||
Python 2 wrapped console command
|
||||
|
||||
@main.command(
|
||||
name="get_config_version_data",
|
||||
help="Get major and minor version from config file")
|
||||
@click.option(
|
||||
"--config_path",
|
||||
required=True,
|
||||
help="OCIO config path to read ocio config file.",
|
||||
type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--output_path",
|
||||
required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def _get_config_version_data(config_path, output_path):
|
||||
"""Get version of config.
|
||||
|
||||
Args:
|
||||
config_path (str): ocio config file path string
|
||||
out_path (str): temp json file path string
|
||||
output_path (str): temp json file path string
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config get_version \
|
||||
--config_path=<path> --out_path=<path>
|
||||
--config_path <path> --output_path <path>
|
||||
"""
|
||||
json_path = Path(out_path)
|
||||
|
||||
out_data = _get_version_data(config_path)
|
||||
|
||||
with open(json_path, "w") as f_:
|
||||
json.dump(out_data, f_)
|
||||
|
||||
print(f"Config version data are saved to '{json_path}'")
|
||||
|
||||
|
||||
def _get_version_data(config_path):
|
||||
"""Return major and minor version info.
|
||||
|
||||
Args:
|
||||
config_path (str): path string leading to config.ocio
|
||||
|
||||
Raises:
|
||||
IOError: Input config does not exist.
|
||||
|
||||
Returns:
|
||||
dict: minor and major keys with values
|
||||
"""
|
||||
config_path = Path(config_path)
|
||||
|
||||
if not config_path.is_file():
|
||||
raise IOError("Input path should be `config.ocio` file")
|
||||
|
||||
config = ocio.Config().CreateFromFile(str(config_path))
|
||||
|
||||
return {
|
||||
"major": config.getMajorVersion(),
|
||||
"minor": config.getMinorVersion()
|
||||
}
|
||||
|
||||
|
||||
@colorspace.command(
|
||||
name="get_config_file_rules_colorspace_from_filepath",
|
||||
help=(
|
||||
"return colorspace from filepath "
|
||||
"--config_path - ocio config file path (input arg is required) "
|
||||
"--filepath - any file path (input arg is required) "
|
||||
"--out_path - temp json file path (input arg is required)"
|
||||
_save_output_to_json_file(
|
||||
get_config_version_data(config_path),
|
||||
output_path
|
||||
)
|
||||
)
|
||||
@click.option("--config_path", required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option("--filepath", required=True,
|
||||
help="path to file to get colorspace from",
|
||||
type=click.Path())
|
||||
@click.option("--out_path", required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def get_config_file_rules_colorspace_from_filepath(
|
||||
config_path, filepath, out_path
|
||||
|
||||
|
||||
@main.command(
|
||||
name="get_config_file_rules_colorspace_from_filepath",
|
||||
help="Colorspace file rules from filepath")
|
||||
@click.option(
|
||||
"--config_path",
|
||||
required=True,
|
||||
help="OCIO config path to read ocio config file.",
|
||||
type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--filepath",
|
||||
required=True,
|
||||
help="Path to file to get colorspace from.",
|
||||
type=click.Path())
|
||||
@click.option(
|
||||
"--output_path",
|
||||
required=True,
|
||||
help="Path where to write output json file.",
|
||||
type=click.Path())
|
||||
def _get_config_file_rules_colorspace_from_filepath(
|
||||
config_path, filepath, output_path
|
||||
):
|
||||
"""Get colorspace from file path wrapper.
|
||||
|
||||
Python 2 wrapped console command
|
||||
|
||||
Args:
|
||||
config_path (str): config file path string
|
||||
filepath (str): path string leading to file
|
||||
out_path (str): temp json file path string
|
||||
output_path (str): temp json file path string
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py \
|
||||
> python.exe ./ocio_wrapper.py \
|
||||
colorspace get_config_file_rules_colorspace_from_filepath \
|
||||
--config_path=<path> --filepath=<path> --out_path=<path>
|
||||
--config_path <path> --filepath <path> --output_path <path>
|
||||
"""
|
||||
json_path = Path(out_path)
|
||||
|
||||
colorspace = _get_config_file_rules_colorspace_from_filepath(
|
||||
config_path, filepath)
|
||||
|
||||
with open(json_path, "w") as f_:
|
||||
json.dump(colorspace, f_)
|
||||
|
||||
print(f"Colorspace name is saved to '{json_path}'")
|
||||
_save_output_to_json_file(
|
||||
get_config_file_rules_colorspace_from_filepath(config_path, filepath),
|
||||
output_path
|
||||
)
|
||||
|
||||
|
||||
def _get_config_file_rules_colorspace_from_filepath(config_path, filepath):
|
||||
"""Return found colorspace data found in v2 file rules.
|
||||
|
||||
Args:
|
||||
config_path (str): path string leading to config.ocio
|
||||
filepath (str): path string leading to v2 file rules
|
||||
|
||||
Raises:
|
||||
IOError: Input config does not exist.
|
||||
|
||||
Returns:
|
||||
dict: aggregated available colorspaces
|
||||
"""
|
||||
config_path = Path(config_path)
|
||||
|
||||
if not config_path.is_file():
|
||||
raise IOError(
|
||||
f"Input path `{config_path}` should be `config.ocio` file")
|
||||
|
||||
config = ocio.Config().CreateFromFile(str(config_path))
|
||||
|
||||
# TODO: use `parseColorSpaceFromString` instead if ocio v1
|
||||
colorspace = config.getColorSpaceFromFilepath(str(filepath))
|
||||
|
||||
return colorspace
|
||||
|
||||
|
||||
def _get_display_view_colorspace_name(config_path, display, view):
|
||||
"""Returns the colorspace attribute of the (display, view) pair.
|
||||
|
||||
Args:
|
||||
config_path (str): path string leading to config.ocio
|
||||
display (str): display name e.g. "ACES"
|
||||
view (str): view name e.g. "sRGB"
|
||||
|
||||
|
||||
Raises:
|
||||
IOError: Input config does not exist.
|
||||
|
||||
Returns:
|
||||
view color space name (str) e.g. "Output - sRGB"
|
||||
"""
|
||||
|
||||
config_path = Path(config_path)
|
||||
|
||||
if not config_path.is_file():
|
||||
raise IOError("Input path should be `config.ocio` file")
|
||||
|
||||
config = ocio.Config.CreateFromFile(str(config_path))
|
||||
colorspace = config.getDisplayViewColorSpaceName(display, view)
|
||||
|
||||
return colorspace
|
||||
|
||||
|
||||
@config.command(
|
||||
@main.command(
|
||||
name="get_display_view_colorspace_name",
|
||||
help=(
|
||||
"return default view colorspace name "
|
||||
"for the given display and view "
|
||||
"--path input arg is required"
|
||||
)
|
||||
)
|
||||
@click.option("--in_path", required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option("--out_path", required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
@click.option("--display", required=True,
|
||||
help="display name",
|
||||
type=click.STRING)
|
||||
@click.option("--view", required=True,
|
||||
help="view name",
|
||||
type=click.STRING)
|
||||
def get_display_view_colorspace_name(in_path, out_path,
|
||||
display, view):
|
||||
"Default view colorspace name for the given display and view"
|
||||
))
|
||||
@click.option(
|
||||
"--config_path",
|
||||
required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--display",
|
||||
required=True,
|
||||
help="Display name",
|
||||
type=click.STRING)
|
||||
@click.option(
|
||||
"--view",
|
||||
required=True,
|
||||
help="view name",
|
||||
type=click.STRING)
|
||||
@click.option(
|
||||
"--output_path",
|
||||
required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def _get_display_view_colorspace_name(
|
||||
config_path, display, view, output_path
|
||||
):
|
||||
"""Aggregate view colorspace name to file.
|
||||
|
||||
Wrapper command for processes without access to OpenColorIO
|
||||
|
||||
Args:
|
||||
in_path (str): config file path string
|
||||
out_path (str): temp json file path string
|
||||
config_path (str): config file path string
|
||||
output_path (str): temp json file path string
|
||||
display (str): display name e.g. "ACES"
|
||||
view (str): view name e.g. "sRGB"
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config \
|
||||
get_display_view_colorspace_name --in_path=<path> \
|
||||
--out_path=<path> --display=<display> --view=<view>
|
||||
get_display_view_colorspace_name --config_path <path> \
|
||||
--output_path <path> --display <display> --view <view>
|
||||
"""
|
||||
_save_output_to_json_file(
|
||||
get_display_view_colorspace_name(config_path, display, view),
|
||||
output_path
|
||||
)
|
||||
|
||||
out_data = _get_display_view_colorspace_name(in_path,
|
||||
display,
|
||||
view)
|
||||
|
||||
with open(out_path, "w") as f:
|
||||
json.dump(out_data, f)
|
||||
|
||||
print(f"Display view colorspace saved to '{out_path}'")
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
if not has_compatible_ocio_package():
|
||||
raise RuntimeError("OpenColorIO is not available.")
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring AYON core addon version."""
|
||||
__version__ = "0.3.1-dev.1"
|
||||
__version__ = "0.3.2-dev.1"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ aiohttp_json_rpc = "*" # TVPaint server
|
|||
aiohttp-middlewares = "^2.0.0"
|
||||
wsrpc_aiohttp = "^3.1.1" # websocket server
|
||||
Click = "^8"
|
||||
OpenTimelineIO = "0.14.1"
|
||||
OpenTimelineIO = "0.16.0"
|
||||
opencolorio = "2.2.1"
|
||||
Pillow = "9.5.0"
|
||||
pynput = "^1.7.2" # Timers manager - TODO remove
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
name = "core"
|
||||
title = "Core"
|
||||
version = "0.3.1-dev.1"
|
||||
version = "0.3.2-dev.1"
|
||||
|
||||
client_dir = "ayon_core"
|
||||
|
||||
plugin_for = ["ayon_server"]
|
||||
requires = [
|
||||
"~ayon_server-1.0.3+<2.0.0",
|
||||
]
|
||||
|
||||
ayon_server_version = ">=1.0.3,<2.0.0"
|
||||
ayon_launcher_version = ">=1.0.2"
|
||||
ayon_required_addons = {}
|
||||
ayon_compatible_addons = {}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Any
|
||||
|
||||
from ayon_server.addons import BaseServerAddon
|
||||
|
||||
from .settings import CoreSettings, DEFAULT_VALUES
|
||||
|
|
@ -9,3 +11,53 @@ class CoreAddon(BaseServerAddon):
|
|||
async def get_default_settings(self):
|
||||
settings_model_cls = self.get_settings_model()
|
||||
return settings_model_cls(**DEFAULT_VALUES)
|
||||
|
||||
async def convert_settings_overrides(
|
||||
self,
|
||||
source_version: str,
|
||||
overrides: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
self._convert_imagio_configs_0_3_1(overrides)
|
||||
# Use super conversion
|
||||
return await super().convert_settings_overrides(
|
||||
source_version, overrides
|
||||
)
|
||||
|
||||
def _convert_imagio_configs_0_3_1(self, overrides):
|
||||
"""Imageio config settings did change to profiles since 0.3.1. ."""
|
||||
imageio_overrides = overrides.get("imageio") or {}
|
||||
if (
|
||||
"ocio_config" not in imageio_overrides
|
||||
or "filepath" not in imageio_overrides["ocio_config"]
|
||||
):
|
||||
return
|
||||
|
||||
ocio_config = imageio_overrides.pop("ocio_config")
|
||||
|
||||
filepath = ocio_config["filepath"]
|
||||
if not filepath:
|
||||
return
|
||||
first_filepath = filepath[0]
|
||||
ocio_config_profiles = imageio_overrides.setdefault(
|
||||
"ocio_config_profiles", []
|
||||
)
|
||||
base_value = {
|
||||
"type": "builtin_path",
|
||||
"product_name": "",
|
||||
"host_names": [],
|
||||
"task_names": [],
|
||||
"task_types": [],
|
||||
"custom_path": "",
|
||||
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio"
|
||||
}
|
||||
if first_filepath in (
|
||||
"{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
||||
"{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio",
|
||||
):
|
||||
base_value["type"] = "builtin_path"
|
||||
base_value["builtin_path"] = first_filepath
|
||||
else:
|
||||
base_value["type"] = "custom_path"
|
||||
base_value["custom_path"] = first_filepath
|
||||
|
||||
ocio_config_profiles.append(base_value)
|
||||
|
|
|
|||
|
|
@ -54,9 +54,67 @@ class CoreImageIOFileRulesModel(BaseSettingsModel):
|
|||
return value
|
||||
|
||||
|
||||
class CoreImageIOConfigModel(BaseSettingsModel):
|
||||
filepath: list[str] = SettingsField(
|
||||
default_factory=list, title="Config path"
|
||||
def _ocio_config_profile_types():
|
||||
return [
|
||||
{"value": "builtin_path", "label": "AYON built-in OCIO config"},
|
||||
{"value": "custom_path", "label": "Path to OCIO config"},
|
||||
{"value": "product_name", "label": "Published product"},
|
||||
]
|
||||
|
||||
|
||||
def _ocio_built_in_paths():
|
||||
return [
|
||||
{
|
||||
"value": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
||||
"label": "ACES 1.2",
|
||||
"description": "Aces 1.2 OCIO config file."
|
||||
},
|
||||
{
|
||||
"value": "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio",
|
||||
"label": "Nuke default",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class CoreImageIOConfigProfilesModel(BaseSettingsModel):
|
||||
_layout = "expanded"
|
||||
host_names: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Host names"
|
||||
)
|
||||
task_types: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Task types",
|
||||
enum_resolver=task_types_enum
|
||||
)
|
||||
task_names: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Task names"
|
||||
)
|
||||
type: str = SettingsField(
|
||||
title="Profile type",
|
||||
enum_resolver=_ocio_config_profile_types,
|
||||
conditionalEnum=True,
|
||||
default="builtin_path",
|
||||
section="---",
|
||||
)
|
||||
builtin_path: str = SettingsField(
|
||||
"ACES 1.2",
|
||||
title="Built-in OCIO config",
|
||||
enum_resolver=_ocio_built_in_paths,
|
||||
)
|
||||
custom_path: str = SettingsField(
|
||||
"",
|
||||
title="OCIO config path",
|
||||
description="Path to OCIO config. Anatomy formatting is supported.",
|
||||
)
|
||||
product_name: str = SettingsField(
|
||||
"",
|
||||
title="Product name",
|
||||
description=(
|
||||
"Published product name to get OCIO config from. "
|
||||
"Partial match is supported."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -65,9 +123,8 @@ class CoreImageIOBaseModel(BaseSettingsModel):
|
|||
False,
|
||||
title="Enable Color Management"
|
||||
)
|
||||
ocio_config: CoreImageIOConfigModel = SettingsField(
|
||||
default_factory=CoreImageIOConfigModel,
|
||||
title="OCIO config"
|
||||
ocio_config_profiles: list[CoreImageIOConfigProfilesModel] = SettingsField(
|
||||
default_factory=list, title="OCIO config profiles"
|
||||
)
|
||||
file_rules: CoreImageIOFileRulesModel = SettingsField(
|
||||
default_factory=CoreImageIOFileRulesModel,
|
||||
|
|
@ -186,12 +243,17 @@ class CoreSettings(BaseSettingsModel):
|
|||
DEFAULT_VALUES = {
|
||||
"imageio": {
|
||||
"activate_global_color_management": False,
|
||||
"ocio_config": {
|
||||
"filepath": [
|
||||
"{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
||||
"{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio"
|
||||
]
|
||||
},
|
||||
"ocio_config_profiles": [
|
||||
{
|
||||
"host_names": [],
|
||||
"task_types": [],
|
||||
"task_names": [],
|
||||
"type": "builtin_path",
|
||||
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
||||
"custom_path": "",
|
||||
"product_name": "",
|
||||
}
|
||||
],
|
||||
"file_rules": {
|
||||
"activate_global_file_rules": False,
|
||||
"rules": [
|
||||
|
|
@ -199,42 +261,57 @@ DEFAULT_VALUES = {
|
|||
"name": "example",
|
||||
"pattern": ".*(beauty).*",
|
||||
"colorspace": "ACES - ACEScg",
|
||||
"ext": "exr"
|
||||
"ext": "exr",
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
"studio_name": "",
|
||||
"studio_code": "",
|
||||
"environments": "{\n\"STUDIO_SW\": {\n \"darwin\": \"/mnt/REPO_SW\",\n \"linux\": \"/mnt/REPO_SW\",\n \"windows\": \"P:/REPO_SW\"\n }\n}",
|
||||
"environments": json.dumps(
|
||||
{
|
||||
"STUDIO_SW": {
|
||||
"darwin": "/mnt/REPO_SW",
|
||||
"linux": "/mnt/REPO_SW",
|
||||
"windows": "P:/REPO_SW"
|
||||
}
|
||||
},
|
||||
indent=4
|
||||
),
|
||||
"tools": DEFAULT_TOOLS_VALUES,
|
||||
"version_start_category": {
|
||||
"profiles": []
|
||||
},
|
||||
"publish": DEFAULT_PUBLISH_VALUES,
|
||||
"project_folder_structure": json.dumps({
|
||||
"__project_root__": {
|
||||
"prod": {},
|
||||
"resources": {
|
||||
"footage": {
|
||||
"plates": {},
|
||||
"offline": {}
|
||||
"project_folder_structure": json.dumps(
|
||||
{
|
||||
"__project_root__": {
|
||||
"prod": {},
|
||||
"resources": {
|
||||
"footage": {
|
||||
"plates": {},
|
||||
"offline": {}
|
||||
},
|
||||
"audio": {},
|
||||
"art_dept": {}
|
||||
},
|
||||
"audio": {},
|
||||
"art_dept": {}
|
||||
},
|
||||
"editorial": {},
|
||||
"assets": {
|
||||
"characters": {},
|
||||
"locations": {}
|
||||
},
|
||||
"shots": {}
|
||||
}
|
||||
}, indent=4),
|
||||
"editorial": {},
|
||||
"assets": {
|
||||
"characters": {},
|
||||
"locations": {}
|
||||
},
|
||||
"shots": {}
|
||||
}
|
||||
},
|
||||
indent=4
|
||||
),
|
||||
"project_plugins": {
|
||||
"windows": [],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"project_environments": "{}"
|
||||
"project_environments": json.dumps(
|
||||
{},
|
||||
indent=4
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name = "houdini"
|
||||
title = "Houdini"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
from ayon_server.settings import BaseSettingsModel, SettingsField
|
||||
from ayon_server.settings import (
|
||||
BaseSettingsModel,
|
||||
SettingsField
|
||||
)
|
||||
|
||||
|
||||
# Publish Plugins
|
||||
|
|
@ -20,6 +23,27 @@ class CollectChunkSizeModel(BaseSettingsModel):
|
|||
title="Frames Per Task")
|
||||
|
||||
|
||||
class AOVFilterSubmodel(BaseSettingsModel):
|
||||
"""You should use the same host name you are using for Houdini."""
|
||||
host_name: str = SettingsField("", title="Houdini Host name")
|
||||
value: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="AOV regex"
|
||||
)
|
||||
|
||||
class CollectLocalRenderInstancesModel(BaseSettingsModel):
|
||||
|
||||
use_deadline_aov_filter: bool = SettingsField(
|
||||
False,
|
||||
title="Use Deadline AOV Filter"
|
||||
)
|
||||
|
||||
aov_filter: AOVFilterSubmodel = SettingsField(
|
||||
default_factory=AOVFilterSubmodel,
|
||||
title="Reviewable products filter"
|
||||
)
|
||||
|
||||
|
||||
class ValidateWorkfilePathsModel(BaseSettingsModel):
|
||||
enabled: bool = SettingsField(title="Enabled")
|
||||
optional: bool = SettingsField(title="Optional")
|
||||
|
|
@ -49,6 +73,10 @@ class PublishPluginsModel(BaseSettingsModel):
|
|||
default_factory=CollectChunkSizeModel,
|
||||
title="Collect Chunk Size."
|
||||
)
|
||||
CollectLocalRenderInstances: CollectLocalRenderInstancesModel = SettingsField(
|
||||
default_factory=CollectLocalRenderInstancesModel,
|
||||
title="Collect Local Render Instances."
|
||||
)
|
||||
ValidateContainers: BasicValidateModel = SettingsField(
|
||||
default_factory=BasicValidateModel,
|
||||
title="Validate Latest Containers.",
|
||||
|
|
@ -82,6 +110,15 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = {
|
|||
"optional": True,
|
||||
"chunk_size": 999999
|
||||
},
|
||||
"CollectLocalRenderInstances": {
|
||||
"use_deadline_aov_filter": False,
|
||||
"aov_filter" : {
|
||||
"host_name": "houdini",
|
||||
"value": [
|
||||
".*([Bb]eauty).*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"ValidateContainers": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name = "traypublisher"
|
||||
title = "TrayPublisher"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
from ayon_server.settings import BaseSettingsModel, SettingsField
|
||||
from ayon_server.settings import (
|
||||
BaseSettingsModel,
|
||||
SettingsField,
|
||||
)
|
||||
|
||||
|
||||
class ValidatePluginModel(BaseSettingsModel):
|
||||
|
|
@ -14,6 +17,45 @@ class ValidateFrameRangeModel(ValidatePluginModel):
|
|||
'my_asset_to_publish.mov')"""
|
||||
|
||||
|
||||
class ExtractEditorialPckgFFmpegModel(BaseSettingsModel):
|
||||
video_filters: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Video filters"
|
||||
)
|
||||
audio_filters: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Audio filters"
|
||||
)
|
||||
input: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Input arguments"
|
||||
)
|
||||
output: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Output arguments"
|
||||
)
|
||||
|
||||
|
||||
class ExtractEditorialPckgOutputDefModel(BaseSettingsModel):
|
||||
_layout = "expanded"
|
||||
ext: str = SettingsField("", title="Output extension")
|
||||
|
||||
ffmpeg_args: ExtractEditorialPckgFFmpegModel = SettingsField(
|
||||
default_factory=ExtractEditorialPckgFFmpegModel,
|
||||
title="FFmpeg arguments"
|
||||
)
|
||||
|
||||
|
||||
class ExtractEditorialPckgConversionModel(BaseSettingsModel):
|
||||
"""Set output definition if resource files should be converted."""
|
||||
conversion_enabled: bool = SettingsField(True,
|
||||
title="Conversion enabled")
|
||||
output: ExtractEditorialPckgOutputDefModel = SettingsField(
|
||||
default_factory=ExtractEditorialPckgOutputDefModel,
|
||||
title="Output Definitions",
|
||||
)
|
||||
|
||||
|
||||
class TrayPublisherPublishPlugins(BaseSettingsModel):
|
||||
CollectFrameDataFromAssetEntity: ValidatePluginModel = SettingsField(
|
||||
default_factory=ValidatePluginModel,
|
||||
|
|
@ -28,6 +70,13 @@ class TrayPublisherPublishPlugins(BaseSettingsModel):
|
|||
default_factory=ValidatePluginModel,
|
||||
)
|
||||
|
||||
ExtractEditorialPckgConversion: ExtractEditorialPckgConversionModel = (
|
||||
SettingsField(
|
||||
default_factory=ExtractEditorialPckgConversionModel,
|
||||
title="Extract Editorial Package Conversion"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
DEFAULT_PUBLISH_PLUGINS = {
|
||||
"CollectFrameDataFromAssetEntity": {
|
||||
|
|
@ -44,5 +93,24 @@ DEFAULT_PUBLISH_PLUGINS = {
|
|||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ExtractEditorialPckgConversion": {
|
||||
"optional": False,
|
||||
"conversion_enabled": True,
|
||||
"output": {
|
||||
"ext": "",
|
||||
"ffmpeg_args": {
|
||||
"video_filters": [],
|
||||
"audio_filters": [],
|
||||
"input": [
|
||||
"-apply_trc gamma22"
|
||||
],
|
||||
"output": [
|
||||
"-pix_fmt yuv420p",
|
||||
"-crf 18",
|
||||
"-intra"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue