mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge branch 'develop' into enhancement/OP-5808_Enhancement-3dsmax-rendering-time-data-from-instance
This commit is contained in:
commit
f90dea5e26
32 changed files with 264 additions and 162 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -35,6 +35,7 @@ body:
|
||||||
label: Version
|
label: Version
|
||||||
description: What version are you running? Look to OpenPype Tray
|
description: What version are you running? Look to OpenPype Tray
|
||||||
options:
|
options:
|
||||||
|
- 3.15.7-nightly.3
|
||||||
- 3.15.7-nightly.2
|
- 3.15.7-nightly.2
|
||||||
- 3.15.7-nightly.1
|
- 3.15.7-nightly.1
|
||||||
- 3.15.6
|
- 3.15.6
|
||||||
|
|
@ -134,7 +135,6 @@ body:
|
||||||
- 3.14.1-nightly.4
|
- 3.14.1-nightly.4
|
||||||
- 3.14.1-nightly.3
|
- 3.14.1-nightly.3
|
||||||
- 3.14.1-nightly.2
|
- 3.14.1-nightly.2
|
||||||
- 3.14.1-nightly.1
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -112,3 +112,9 @@ tools/run_eventserver.*
|
||||||
tools/dev_*
|
tools/dev_*
|
||||||
|
|
||||||
.github_changelog_generator
|
.github_changelog_generator
|
||||||
|
|
||||||
|
|
||||||
|
# Addons
|
||||||
|
########
|
||||||
|
/openpype/addons/*
|
||||||
|
!/openpype/addons/README.md
|
||||||
|
|
|
||||||
3
openpype/addons/README.md
Normal file
3
openpype/addons/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
This directory is for storing external addons that needs to be included in the pipeline when distributed.
|
||||||
|
|
||||||
|
The directory is ignored by Git, but included in the zip and installation files.
|
||||||
|
|
@ -1,29 +1,39 @@
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
|
|
||||||
|
from openpype.pipeline import OptionalPyblishPluginMixin
|
||||||
|
from openpype.pipeline import KnownPublishError
|
||||||
|
|
||||||
class FusionIncrementCurrentFile(pyblish.api.ContextPlugin):
|
|
||||||
|
class FusionIncrementCurrentFile(
|
||||||
|
pyblish.api.ContextPlugin, OptionalPyblishPluginMixin
|
||||||
|
):
|
||||||
"""Increment the current file.
|
"""Increment the current file.
|
||||||
|
|
||||||
Saves the current file with an increased version number.
|
Saves the current file with an increased version number.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
label = "Increment current file"
|
label = "Increment workfile version"
|
||||||
order = pyblish.api.IntegratorOrder + 9.0
|
order = pyblish.api.IntegratorOrder + 9.0
|
||||||
hosts = ["fusion"]
|
hosts = ["fusion"]
|
||||||
families = ["workfile"]
|
|
||||||
optional = True
|
optional = True
|
||||||
|
|
||||||
def process(self, context):
|
def process(self, context):
|
||||||
|
if not self.is_active(context.data):
|
||||||
|
return
|
||||||
|
|
||||||
from openpype.lib import version_up
|
from openpype.lib import version_up
|
||||||
from openpype.pipeline.publish import get_errored_plugins_from_context
|
from openpype.pipeline.publish import get_errored_plugins_from_context
|
||||||
|
|
||||||
errored_plugins = get_errored_plugins_from_context(context)
|
errored_plugins = get_errored_plugins_from_context(context)
|
||||||
if any(plugin.__name__ == "FusionSubmitDeadline"
|
if any(
|
||||||
for plugin in errored_plugins):
|
plugin.__name__ == "FusionSubmitDeadline"
|
||||||
raise RuntimeError("Skipping incrementing current file because "
|
for plugin in errored_plugins
|
||||||
"submission to render farm failed.")
|
):
|
||||||
|
raise KnownPublishError(
|
||||||
|
"Skipping incrementing current file because "
|
||||||
|
"submission to render farm failed."
|
||||||
|
)
|
||||||
|
|
||||||
comp = context.data.get("currentComp")
|
comp = context.data.get("currentComp")
|
||||||
assert comp, "Must have comp"
|
assert comp, "Must have comp"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
|
|
||||||
from openpype.pipeline.publish import RepairAction
|
from openpype.pipeline import (
|
||||||
from openpype.pipeline import PublishValidationError
|
publish,
|
||||||
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
from openpype.hosts.fusion.api.action import SelectInvalidAction
|
from openpype.hosts.fusion.api.action import SelectInvalidAction
|
||||||
|
|
||||||
|
|
||||||
class ValidateBackgroundDepth(pyblish.api.InstancePlugin):
|
class ValidateBackgroundDepth(
|
||||||
|
pyblish.api.InstancePlugin, OptionalPyblishPluginMixin
|
||||||
|
):
|
||||||
"""Validate if all Background tool are set to float32 bit"""
|
"""Validate if all Background tool are set to float32 bit"""
|
||||||
|
|
||||||
order = pyblish.api.ValidatorOrder
|
order = pyblish.api.ValidatorOrder
|
||||||
|
|
@ -15,11 +20,10 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin):
|
||||||
families = ["render"]
|
families = ["render"]
|
||||||
optional = True
|
optional = True
|
||||||
|
|
||||||
actions = [SelectInvalidAction, RepairAction]
|
actions = [SelectInvalidAction, publish.RepairAction]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_invalid(cls, instance):
|
def get_invalid(cls, instance):
|
||||||
|
|
||||||
context = instance.context
|
context = instance.context
|
||||||
comp = context.data.get("currentComp")
|
comp = context.data.get("currentComp")
|
||||||
assert comp, "Must have Comp object"
|
assert comp, "Must have Comp object"
|
||||||
|
|
@ -31,12 +35,16 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin):
|
||||||
return [i for i in backgrounds if i.GetInput("Depth") != 4.0]
|
return [i for i in backgrounds if i.GetInput("Depth") != 4.0]
|
||||||
|
|
||||||
def process(self, instance):
|
def process(self, instance):
|
||||||
|
if not self.is_active(instance.data):
|
||||||
|
return
|
||||||
|
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
if invalid:
|
if invalid:
|
||||||
raise PublishValidationError(
|
raise PublishValidationError(
|
||||||
"Found {} Backgrounds tools which"
|
"Found {} Backgrounds tools which"
|
||||||
" are not set to float32".format(len(invalid)),
|
" are not set to float32".format(len(invalid)),
|
||||||
title=self.label)
|
title=self.label,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def repair(cls, instance):
|
def repair(cls, instance):
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,13 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
||||||
# TODO: make sure this doesn't trigger when
|
# TODO: make sure this doesn't trigger when
|
||||||
# opening with last workfile.
|
# opening with last workfile.
|
||||||
_set_context_settings()
|
_set_context_settings()
|
||||||
shelves.generate_shelves()
|
|
||||||
|
if not IS_HEADLESS:
|
||||||
|
import hdefereval # noqa, hdefereval is only available in ui mode
|
||||||
|
# Defer generation of shelves due to issue on Windows where shelf
|
||||||
|
# initialization during start up delays Houdini UI by minutes
|
||||||
|
# making it extremely slow to launch.
|
||||||
|
hdefereval.executeDeferred(shelves.generate_shelves)
|
||||||
|
|
||||||
if not IS_HEADLESS:
|
if not IS_HEADLESS:
|
||||||
import hdefereval # noqa, hdefereval is only available in ui mode
|
import hdefereval # noqa, hdefereval is only available in ui mode
|
||||||
|
|
|
||||||
|
|
@ -173,10 +173,16 @@ def set_scene_resolution(width: int, height: int):
|
||||||
None
|
None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# make sure the render dialog is closed
|
||||||
|
# for the update of resolution
|
||||||
|
# Changing the Render Setup dialog settingsshould be done
|
||||||
|
# with the actual Render Setup dialog in a closed state.
|
||||||
|
if rt.renderSceneDialog.isOpen():
|
||||||
|
rt.renderSceneDialog.close()
|
||||||
|
|
||||||
rt.renderWidth = width
|
rt.renderWidth = width
|
||||||
rt.renderHeight = height
|
rt.renderHeight = height
|
||||||
|
|
||||||
|
|
||||||
def reset_scene_resolution():
|
def reset_scene_resolution():
|
||||||
"""Apply the scene resolution from the project definition
|
"""Apply the scene resolution from the project definition
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,9 @@ class RenderSettings(object):
|
||||||
|
|
||||||
rt.rendSaveFile = True
|
rt.rendSaveFile = True
|
||||||
|
|
||||||
|
if rt.renderSceneDialog.isOpen():
|
||||||
|
rt.renderSceneDialog.close()
|
||||||
|
|
||||||
def arnold_setup(self):
|
def arnold_setup(self):
|
||||||
# get Arnold RenderView run in the background
|
# get Arnold RenderView run in the background
|
||||||
# for setting up renderable camera
|
# for setting up renderable camera
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher):
|
||||||
|
|
||||||
def context_setting():
|
def context_setting():
|
||||||
return lib.set_context_setting()
|
return lib.set_context_setting()
|
||||||
|
|
||||||
rt.callbacks.addScript(rt.Name('systemPostNew'),
|
rt.callbacks.addScript(rt.Name('systemPostNew'),
|
||||||
context_setting)
|
context_setting)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,11 @@ class CreateRender(plugin.MaxCreator):
|
||||||
# for additional work on the node:
|
# for additional work on the node:
|
||||||
# instance_node = rt.getNodeByName(instance.get("instance_node"))
|
# instance_node = rt.getNodeByName(instance.get("instance_node"))
|
||||||
|
|
||||||
|
# make sure the render dialog is closed
|
||||||
|
# for the update of resolution
|
||||||
|
# Changing the Render Setup dialog settings should be done
|
||||||
|
# with the actual Render Setup dialog in a closed state.
|
||||||
|
|
||||||
# set viewport camera for rendering(mandatory for deadline)
|
# set viewport camera for rendering(mandatory for deadline)
|
||||||
RenderSettings().set_render_camera(sel_obj)
|
RenderSettings().set_render_camera(sel_obj)
|
||||||
# set output paths for rendering(mandatory for deadline)
|
# set output paths for rendering(mandatory for deadline)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
import pyblish.api
|
||||||
|
from openpype.pipeline import (
|
||||||
|
PublishValidationError,
|
||||||
|
OptionalPyblishPluginMixin
|
||||||
|
)
|
||||||
|
from pymxs import runtime as rt
|
||||||
|
from openpype.hosts.max.api.lib import reset_scene_resolution
|
||||||
|
|
||||||
|
from openpype.pipeline.context_tools import (
|
||||||
|
get_current_project_asset,
|
||||||
|
get_current_project
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidateResolutionSetting(pyblish.api.InstancePlugin,
|
||||||
|
OptionalPyblishPluginMixin):
|
||||||
|
"""Validate the resolution setting aligned with DB"""
|
||||||
|
|
||||||
|
order = pyblish.api.ValidatorOrder - 0.01
|
||||||
|
families = ["maxrender"]
|
||||||
|
hosts = ["max"]
|
||||||
|
label = "Validate Resolution Setting"
|
||||||
|
optional = True
|
||||||
|
|
||||||
|
def process(self, instance):
|
||||||
|
if not self.is_active(instance.data):
|
||||||
|
return
|
||||||
|
width, height = self.get_db_resolution(instance)
|
||||||
|
current_width = rt.renderwidth
|
||||||
|
current_height = rt.renderHeight
|
||||||
|
if current_width != width and current_height != height:
|
||||||
|
raise PublishValidationError("Resolution Setting "
|
||||||
|
"not matching resolution "
|
||||||
|
"set on asset or shot.")
|
||||||
|
if current_width != width:
|
||||||
|
raise PublishValidationError("Width in Resolution Setting "
|
||||||
|
"not matching resolution set "
|
||||||
|
"on asset or shot.")
|
||||||
|
|
||||||
|
if current_height != height:
|
||||||
|
raise PublishValidationError("Height in Resolution Setting "
|
||||||
|
"not matching resolution set "
|
||||||
|
"on asset or shot.")
|
||||||
|
|
||||||
|
def get_db_resolution(self, instance):
|
||||||
|
data = ["data.resolutionWidth", "data.resolutionHeight"]
|
||||||
|
project_resolution = get_current_project(fields=data)
|
||||||
|
project_resolution_data = project_resolution["data"]
|
||||||
|
asset_resolution = get_current_project_asset(fields=data)
|
||||||
|
asset_resolution_data = asset_resolution["data"]
|
||||||
|
# Set project resolution
|
||||||
|
project_width = int(
|
||||||
|
project_resolution_data.get("resolutionWidth", 1920))
|
||||||
|
project_height = int(
|
||||||
|
project_resolution_data.get("resolutionHeight", 1080))
|
||||||
|
width = int(
|
||||||
|
asset_resolution_data.get("resolutionWidth", project_width))
|
||||||
|
height = int(
|
||||||
|
asset_resolution_data.get("resolutionHeight", project_height))
|
||||||
|
|
||||||
|
return width, height
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def repair(cls, instance):
|
||||||
|
reset_scene_resolution()
|
||||||
|
|
@ -50,7 +50,8 @@ class ValidateShaderName(pyblish.api.InstancePlugin):
|
||||||
asset_name = instance.data.get("asset", None)
|
asset_name = instance.data.get("asset", None)
|
||||||
|
|
||||||
# Check the number of connected shadingEngines per shape
|
# Check the number of connected shadingEngines per shape
|
||||||
r = re.compile(cls.regex)
|
regex_compile = re.compile(cls.regex)
|
||||||
|
error_message = "object {0} has invalid shader name {1}"
|
||||||
for shape in shapes:
|
for shape in shapes:
|
||||||
shading_engines = cmds.listConnections(shape,
|
shading_engines = cmds.listConnections(shape,
|
||||||
destination=True,
|
destination=True,
|
||||||
|
|
@ -60,19 +61,18 @@ class ValidateShaderName(pyblish.api.InstancePlugin):
|
||||||
)
|
)
|
||||||
|
|
||||||
for shader in shaders:
|
for shader in shaders:
|
||||||
m = r.match(cls.regex, shader)
|
m = regex_compile.match(shader)
|
||||||
if m is None:
|
if m is None:
|
||||||
invalid.append(shape)
|
invalid.append(shape)
|
||||||
cls.log.error(
|
cls.log.error(error_message.format(shape, shader))
|
||||||
"object {0} has invalid shader name {1}".format(shape,
|
|
||||||
shader)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
if 'asset' in r.groupindex:
|
if 'asset' in regex_compile.groupindex:
|
||||||
if m.group('asset') != asset_name:
|
if m.group('asset') != asset_name:
|
||||||
invalid.append(shape)
|
invalid.append(shape)
|
||||||
cls.log.error(("object {0} has invalid "
|
message = error_message
|
||||||
"shader name {1}").format(shape,
|
message += " with missing asset name \"{2}\""
|
||||||
shader))
|
cls.log.error(
|
||||||
|
message.format(shape, shader, asset_name)
|
||||||
|
)
|
||||||
|
|
||||||
return invalid
|
return invalid
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,10 @@ class UnrealAddon(OpenPypeModule, IHostAddon):
|
||||||
UNREAL_ROOT_DIR, "integration", f"UE_{ue_version}", "Ayon"
|
UNREAL_ROOT_DIR, "integration", f"UE_{ue_version}", "Ayon"
|
||||||
)
|
)
|
||||||
if not Path(unreal_plugin_path).exists():
|
if not Path(unreal_plugin_path).exists():
|
||||||
if compatible_versions := get_compatible_integration(
|
compatible_versions = get_compatible_integration(
|
||||||
ue_version, Path(UNREAL_ROOT_DIR) / "integration"
|
ue_version, Path(UNREAL_ROOT_DIR) / "integration"
|
||||||
):
|
)
|
||||||
|
if compatible_versions:
|
||||||
unreal_plugin_path = compatible_versions[-1] / "Ayon"
|
unreal_plugin_path = compatible_versions[-1] / "Ayon"
|
||||||
unreal_plugin_path = unreal_plugin_path.as_posix()
|
unreal_plugin_path = unreal_plugin_path.as_posix()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ class StaticMeshAlembicLoader(plugin.Loader):
|
||||||
source_path = get_representation_path(representation)
|
source_path = get_representation_path(representation)
|
||||||
destination_path = container["namespace"]
|
destination_path = container["namespace"]
|
||||||
|
|
||||||
task = self.get_task(source_path, destination_path, name, True)
|
task = self.get_task(source_path, destination_path, name, True, False)
|
||||||
|
|
||||||
# do import fbx and replace existing data
|
# do import fbx and replace existing data
|
||||||
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,8 @@ class CollectRenderInstances(pyblish.api.InstancePlugin):
|
||||||
new_data["level"] = data.get("level")
|
new_data["level"] = data.get("level")
|
||||||
new_data["output"] = s.get('output')
|
new_data["output"] = s.get('output')
|
||||||
new_data["fps"] = seq.get_display_rate().numerator
|
new_data["fps"] = seq.get_display_rate().numerator
|
||||||
new_data["frameStart"] = s.get('frame_range')[0]
|
new_data["frameStart"] = int(s.get('frame_range')[0])
|
||||||
new_data["frameEnd"] = s.get('frame_range')[1]
|
new_data["frameEnd"] = int(s.get('frame_range')[1])
|
||||||
new_data["sequence"] = seq.get_path_name()
|
new_data["sequence"] = seq.get_path_name()
|
||||||
new_data["master_sequence"] = data["master_sequence"]
|
new_data["master_sequence"] = data["master_sequence"]
|
||||||
new_data["master_level"] = data["master_level"]
|
new_data["master_level"] = data["master_level"]
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import unreal
|
|
||||||
|
|
||||||
from openpype.pipeline import publish
|
|
||||||
|
|
||||||
|
|
||||||
class ExtractRender(publish.Extractor):
|
|
||||||
"""Extract render."""
|
|
||||||
|
|
||||||
label = "Extract Render"
|
|
||||||
hosts = ["unreal"]
|
|
||||||
families = ["render"]
|
|
||||||
optional = True
|
|
||||||
|
|
||||||
def process(self, instance):
|
|
||||||
# Define extract output file path
|
|
||||||
stagingdir = self.staging_dir(instance)
|
|
||||||
|
|
||||||
# Perform extraction
|
|
||||||
self.log.info("Performing extraction..")
|
|
||||||
|
|
||||||
# Get the render output directory
|
|
||||||
project_dir = unreal.Paths.project_dir()
|
|
||||||
render_dir = (f"{project_dir}/Saved/MovieRenders/"
|
|
||||||
f"{instance.data['subset']}")
|
|
||||||
|
|
||||||
assert unreal.Paths.directory_exists(render_dir), \
|
|
||||||
"Render directory does not exist"
|
|
||||||
|
|
||||||
render_path = Path(render_dir)
|
|
||||||
|
|
||||||
frames = []
|
|
||||||
|
|
||||||
for x in render_path.iterdir():
|
|
||||||
if x.is_file() and x.suffix == '.png':
|
|
||||||
frames.append(str(x))
|
|
||||||
|
|
||||||
if "representations" not in instance.data:
|
|
||||||
instance.data["representations"] = []
|
|
||||||
|
|
||||||
render_representation = {
|
|
||||||
'name': 'png',
|
|
||||||
'ext': 'png',
|
|
||||||
'files': frames,
|
|
||||||
"stagingDir": stagingdir,
|
|
||||||
}
|
|
||||||
instance.data["representations"].append(render_representation)
|
|
||||||
|
|
@ -311,6 +311,7 @@ def _load_modules():
|
||||||
# Look for OpenPype modules in paths defined with `get_module_dirs`
|
# Look for OpenPype modules in paths defined with `get_module_dirs`
|
||||||
# - dynamically imported OpenPype modules and addons
|
# - dynamically imported OpenPype modules and addons
|
||||||
module_dirs = get_module_dirs()
|
module_dirs = get_module_dirs()
|
||||||
|
|
||||||
# Add current directory at first place
|
# Add current directory at first place
|
||||||
# - has small differences in import logic
|
# - has small differences in import logic
|
||||||
current_dir = os.path.abspath(os.path.dirname(__file__))
|
current_dir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
@ -318,8 +319,11 @@ def _load_modules():
|
||||||
module_dirs.insert(0, hosts_dir)
|
module_dirs.insert(0, hosts_dir)
|
||||||
module_dirs.insert(0, current_dir)
|
module_dirs.insert(0, current_dir)
|
||||||
|
|
||||||
|
addons_dir = os.path.join(os.path.dirname(current_dir), "addons")
|
||||||
|
module_dirs.append(addons_dir)
|
||||||
|
|
||||||
processed_paths = set()
|
processed_paths = set()
|
||||||
for dirpath in module_dirs:
|
for dirpath in frozenset(module_dirs):
|
||||||
# Skip already processed paths
|
# Skip already processed paths
|
||||||
if dirpath in processed_paths:
|
if dirpath in processed_paths:
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -378,7 +378,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin):
|
||||||
existing_tasks.append(task_name_low)
|
existing_tasks.append(task_name_low)
|
||||||
|
|
||||||
for instance in instances_by_task_name[task_name_low]:
|
for instance in instances_by_task_name[task_name_low]:
|
||||||
instance["ftrackTask"] = child
|
instance.data["ftrackTask"] = child
|
||||||
|
|
||||||
for task_name in tasks:
|
for task_name in tasks:
|
||||||
task_type = tasks[task_name]["type"]
|
task_type = tasks[task_name]["type"]
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin):
|
||||||
|
|
||||||
order = pyblish.api.IntegratorOrder
|
order = pyblish.api.IntegratorOrder
|
||||||
label = "Kitsu Note and Status"
|
label = "Kitsu Note and Status"
|
||||||
families = ["render", "kitsu"]
|
families = ["render", "image", "online", "plate", "kitsu"]
|
||||||
|
|
||||||
# status settings
|
# status settings
|
||||||
set_status_note = False
|
set_status_note = False
|
||||||
|
|
@ -52,8 +52,9 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin):
|
||||||
for instance in context:
|
for instance in context:
|
||||||
# Check if instance is a review by checking its family
|
# Check if instance is a review by checking its family
|
||||||
# Allow a match to primary family or any of families
|
# Allow a match to primary family or any of families
|
||||||
families = set([instance.data["family"]] +
|
families = set(
|
||||||
instance.data.get("families", []))
|
[instance.data["family"]] + instance.data.get("families", [])
|
||||||
|
)
|
||||||
if "review" not in families:
|
if "review" not in families:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,10 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin):
|
||||||
|
|
||||||
order = pyblish.api.IntegratorOrder + 0.01
|
order = pyblish.api.IntegratorOrder + 0.01
|
||||||
label = "Kitsu Review"
|
label = "Kitsu Review"
|
||||||
families = ["render", "kitsu"]
|
families = ["render", "image", "online", "plate", "kitsu"]
|
||||||
optional = True
|
optional = True
|
||||||
|
|
||||||
def process(self, instance):
|
def process(self, instance):
|
||||||
|
|
||||||
# Check comment has been created
|
# Check comment has been created
|
||||||
comment_id = instance.data.get("kitsu_comment", {}).get("id")
|
comment_id = instance.data.get("kitsu_comment", {}).get("id")
|
||||||
if not comment_id:
|
if not comment_id:
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ from . import (
|
||||||
register_inventory_action_path,
|
register_inventory_action_path,
|
||||||
register_creator_plugin_path,
|
register_creator_plugin_path,
|
||||||
deregister_loader_plugin_path,
|
deregister_loader_plugin_path,
|
||||||
|
deregister_inventory_action_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -54,6 +55,7 @@ PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins")
|
||||||
# Global plugin paths
|
# Global plugin paths
|
||||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
||||||
|
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
|
||||||
|
|
||||||
|
|
||||||
def _get_modules_manager():
|
def _get_modules_manager():
|
||||||
|
|
@ -158,6 +160,7 @@ def install_openpype_plugins(project_name=None, host_name=None):
|
||||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||||
pyblish.api.register_discovery_filter(filter_pyblish_plugins)
|
pyblish.api.register_discovery_filter(filter_pyblish_plugins)
|
||||||
register_loader_plugin_path(LOAD_PATH)
|
register_loader_plugin_path(LOAD_PATH)
|
||||||
|
register_inventory_action_path(INVENTORY_PATH)
|
||||||
|
|
||||||
if host_name is None:
|
if host_name is None:
|
||||||
host_name = os.environ.get("AVALON_APP")
|
host_name = os.environ.get("AVALON_APP")
|
||||||
|
|
@ -223,6 +226,7 @@ def uninstall_host():
|
||||||
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
||||||
pyblish.api.deregister_discovery_filter(filter_pyblish_plugins)
|
pyblish.api.deregister_discovery_filter(filter_pyblish_plugins)
|
||||||
deregister_loader_plugin_path(LOAD_PATH)
|
deregister_loader_plugin_path(LOAD_PATH)
|
||||||
|
deregister_inventory_action_path(INVENTORY_PATH)
|
||||||
log.info("Global plug-ins unregistred")
|
log.info("Global plug-ins unregistred")
|
||||||
|
|
||||||
deregister_host()
|
deregister_host()
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ from openpype.lib.attribute_definitions import (
|
||||||
get_default_values,
|
get_default_values,
|
||||||
)
|
)
|
||||||
from openpype.host import IPublishHost, IWorkfileHost
|
from openpype.host import IPublishHost, IWorkfileHost
|
||||||
from openpype.pipeline import legacy_io
|
from openpype.pipeline import legacy_io, Anatomy
|
||||||
from openpype.pipeline.plugin_discover import DiscoverResult
|
from openpype.pipeline.plugin_discover import DiscoverResult
|
||||||
|
|
||||||
from .creator_plugins import (
|
from .creator_plugins import (
|
||||||
|
|
@ -1383,6 +1383,8 @@ class CreateContext:
|
||||||
self._current_task_name = None
|
self._current_task_name = None
|
||||||
self._current_workfile_path = None
|
self._current_workfile_path = None
|
||||||
|
|
||||||
|
self._current_project_anatomy = None
|
||||||
|
|
||||||
self._host_is_valid = host_is_valid
|
self._host_is_valid = host_is_valid
|
||||||
# Currently unused variable
|
# Currently unused variable
|
||||||
self.headless = headless
|
self.headless = headless
|
||||||
|
|
@ -1546,6 +1548,18 @@ class CreateContext:
|
||||||
|
|
||||||
return self._current_workfile_path
|
return self._current_workfile_path
|
||||||
|
|
||||||
|
def get_current_project_anatomy(self):
|
||||||
|
"""Project anatomy for current project.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Anatomy: Anatomy object ready to be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._current_project_anatomy is None:
|
||||||
|
self._current_project_anatomy = Anatomy(
|
||||||
|
self._current_project_name)
|
||||||
|
return self._current_project_anatomy
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def context_has_changed(self):
|
def context_has_changed(self):
|
||||||
"""Host context has changed.
|
"""Host context has changed.
|
||||||
|
|
@ -1568,6 +1582,7 @@ class CreateContext:
|
||||||
)
|
)
|
||||||
|
|
||||||
project_name = property(get_current_project_name)
|
project_name = property(get_current_project_name)
|
||||||
|
project_anatomy = property(get_current_project_anatomy)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def log(self):
|
def log(self):
|
||||||
|
|
@ -1680,6 +1695,8 @@ class CreateContext:
|
||||||
self._current_task_name = task_name
|
self._current_task_name = task_name
|
||||||
self._current_workfile_path = workfile_path
|
self._current_workfile_path = workfile_path
|
||||||
|
|
||||||
|
self._current_project_anatomy = None
|
||||||
|
|
||||||
def reset_plugins(self, discover_publish_plugins=True):
|
def reset_plugins(self, discover_publish_plugins=True):
|
||||||
"""Reload plugins.
|
"""Reload plugins.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -231,10 +231,24 @@ class BaseCreator:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def project_name(self):
|
def project_name(self):
|
||||||
"""Family that plugin represents."""
|
"""Current project name.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Name of a project.
|
||||||
|
"""
|
||||||
|
|
||||||
return self.create_context.project_name
|
return self.create_context.project_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def project_anatomy(self):
|
||||||
|
"""Current project anatomy.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Anatomy: Project anatomy object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.create_context.project_anatomy
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host(self):
|
def host(self):
|
||||||
return self.create_context.host
|
return self.create_context.host
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ class BuildWorkfile:
|
||||||
|
|
||||||
if link_context_profiles:
|
if link_context_profiles:
|
||||||
# Find and append linked assets if preset has set linked mapping
|
# Find and append linked assets if preset has set linked mapping
|
||||||
link_assets = get_linked_assets(current_asset_entity)
|
link_assets = get_linked_assets(project_name, current_asset_entity)
|
||||||
if link_assets:
|
if link_assets:
|
||||||
assets.extend(link_assets)
|
assets.extend(link_assets)
|
||||||
|
|
||||||
|
|
|
||||||
51
openpype/plugins/inventory/remove_and_load.py
Normal file
51
openpype/plugins/inventory/remove_and_load.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
from openpype.pipeline import InventoryAction
|
||||||
|
from openpype.pipeline import get_current_project_name
|
||||||
|
from openpype.pipeline.load.plugins import discover_loader_plugins
|
||||||
|
from openpype.pipeline.load.utils import (
|
||||||
|
get_loader_identifier,
|
||||||
|
remove_container,
|
||||||
|
load_container,
|
||||||
|
)
|
||||||
|
from openpype.client import get_representation_by_id
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveAndLoad(InventoryAction):
|
||||||
|
"""Delete inventory item and reload it."""
|
||||||
|
|
||||||
|
label = "Remove and load"
|
||||||
|
icon = "refresh"
|
||||||
|
|
||||||
|
def process(self, containers):
|
||||||
|
project_name = get_current_project_name()
|
||||||
|
loaders_by_name = {
|
||||||
|
get_loader_identifier(plugin): plugin
|
||||||
|
for plugin in discover_loader_plugins(project_name=project_name)
|
||||||
|
}
|
||||||
|
for container in containers:
|
||||||
|
# Get loader
|
||||||
|
loader_name = container["loader"]
|
||||||
|
loader = loaders_by_name.get(loader_name, None)
|
||||||
|
if not loader:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Failed to get loader '{}', can't remove "
|
||||||
|
"and load container".format(loader_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get representation
|
||||||
|
representation = get_representation_by_id(
|
||||||
|
project_name, container["representation"]
|
||||||
|
)
|
||||||
|
if not representation:
|
||||||
|
self.log.warning(
|
||||||
|
"Skipping remove and load because representation id is not"
|
||||||
|
" found in database: '{}'".format(
|
||||||
|
container["representation"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Remove container
|
||||||
|
remove_container(container)
|
||||||
|
|
||||||
|
# Load container
|
||||||
|
load_container(loader, representation)
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
import clique
|
|
||||||
import pyblish.api
|
|
||||||
|
|
||||||
|
|
||||||
class ValidateSequenceFrames(pyblish.api.InstancePlugin):
|
|
||||||
"""Ensure the sequence of frames is complete
|
|
||||||
|
|
||||||
The files found in the folder are checked against the startFrame and
|
|
||||||
endFrame of the instance. If the first or last file is not
|
|
||||||
corresponding with the first or last frame it is flagged as invalid.
|
|
||||||
|
|
||||||
Used regular expression pattern handles numbers in the file names
|
|
||||||
(eg "Main_beauty.v001.1001.exr", "Main_beauty_v001.1001.exr",
|
|
||||||
"Main_beauty.1001.1001.exr") but not numbers behind frames (eg.
|
|
||||||
"Main_beauty.1001.v001.exr")
|
|
||||||
"""
|
|
||||||
|
|
||||||
order = pyblish.api.ValidatorOrder
|
|
||||||
label = "Validate Sequence Frames"
|
|
||||||
families = ["imagesequence", "render"]
|
|
||||||
hosts = ["shell", "unreal"]
|
|
||||||
|
|
||||||
def process(self, instance):
|
|
||||||
representations = instance.data.get("representations")
|
|
||||||
if not representations:
|
|
||||||
return
|
|
||||||
for repr in representations:
|
|
||||||
repr_files = repr["files"]
|
|
||||||
if isinstance(repr_files, str):
|
|
||||||
continue
|
|
||||||
|
|
||||||
ext = repr.get("ext")
|
|
||||||
if not ext:
|
|
||||||
_, ext = os.path.splitext(repr_files[0])
|
|
||||||
elif not ext.startswith("."):
|
|
||||||
ext = ".{}".format(ext)
|
|
||||||
pattern = r"\D?(?P<index>(?P<padding>0*)\d+){}$".format(
|
|
||||||
re.escape(ext))
|
|
||||||
patterns = [pattern]
|
|
||||||
|
|
||||||
collections, remainder = clique.assemble(
|
|
||||||
repr_files, minimum_items=1, patterns=patterns)
|
|
||||||
|
|
||||||
assert not remainder, "Must not have remainder"
|
|
||||||
assert len(collections) == 1, "Must detect single collection"
|
|
||||||
collection = collections[0]
|
|
||||||
frames = list(collection.indexes)
|
|
||||||
|
|
||||||
if instance.data.get("slate"):
|
|
||||||
# Slate is not part of the frame range
|
|
||||||
frames = frames[1:]
|
|
||||||
|
|
||||||
current_range = (frames[0], frames[-1])
|
|
||||||
|
|
||||||
required_range = (instance.data["frameStart"],
|
|
||||||
instance.data["frameEnd"])
|
|
||||||
|
|
||||||
if current_range != required_range:
|
|
||||||
raise ValueError(f"Invalid frame range: {current_range} - "
|
|
||||||
f"expected: {required_range}")
|
|
||||||
|
|
||||||
missing = collection.holes().indexes
|
|
||||||
assert not missing, "Missing frames: %s" % (missing,)
|
|
||||||
|
|
@ -82,7 +82,8 @@
|
||||||
"png": {
|
"png": {
|
||||||
"ext": "png",
|
"ext": "png",
|
||||||
"tags": [
|
"tags": [
|
||||||
"ftrackreview"
|
"ftrackreview",
|
||||||
|
"kitsureview"
|
||||||
],
|
],
|
||||||
"burnins": [],
|
"burnins": [],
|
||||||
"ffmpeg_args": {
|
"ffmpeg_args": {
|
||||||
|
|
|
||||||
|
|
@ -734,6 +734,7 @@
|
||||||
"ValidateShaderName": {
|
"ValidateShaderName": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"active": true,
|
||||||
"regex": "(?P<asset>.*)_(.*)_SHD"
|
"regex": "(?P<asset>.*)_(.*)_SHD"
|
||||||
},
|
},
|
||||||
"ValidateShadingEngine": {
|
"ValidateShadingEngine": {
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,11 @@
|
||||||
"key": "optional",
|
"key": "optional",
|
||||||
"label": "Optional"
|
"label": "Optional"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "active",
|
||||||
|
"label": "Active"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
"label": "Shader name regex can use named capture group <b>asset</b> to validate against current asset name.<p><b>Example:</b><br/><code>^.*(?P=<asset>.+)_SHD</code></p>"
|
"label": "Shader name regex can use named capture group <b>asset</b> to validate against current asset name.<p><b>Example:</b><br/><code>^.*(?P=<asset>.+)_SHD</code></p>"
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ class ScriptsMenu(QtWidgets.QMenu):
|
||||||
return menu
|
return menu
|
||||||
|
|
||||||
def add_script(self, parent, title, command, sourcetype, icon=None,
|
def add_script(self, parent, title, command, sourcetype, icon=None,
|
||||||
tags=None, label=None, tooltip=None):
|
tags=None, label=None, tooltip=None, shortcut=None):
|
||||||
"""Create an action item which runs a script when clicked
|
"""Create an action item which runs a script when clicked
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -134,6 +134,8 @@ class ScriptsMenu(QtWidgets.QMenu):
|
||||||
|
|
||||||
tooltip (str): A tip for the user about the usage fo the tool
|
tooltip (str): A tip for the user about the usage fo the tool
|
||||||
|
|
||||||
|
shortcut (str): A shortcut to run the command
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
QtWidget.QAction instance
|
QtWidget.QAction instance
|
||||||
|
|
||||||
|
|
@ -166,6 +168,9 @@ class ScriptsMenu(QtWidgets.QMenu):
|
||||||
raise RuntimeError("Script action can't be "
|
raise RuntimeError("Script action can't be "
|
||||||
"processed: {}".format(e))
|
"processed: {}".format(e))
|
||||||
|
|
||||||
|
if shortcut:
|
||||||
|
script_action.setShortcut(shortcut)
|
||||||
|
|
||||||
if icon:
|
if icon:
|
||||||
iconfile = os.path.expandvars(icon)
|
iconfile = os.path.expandvars(icon)
|
||||||
script_action.iconfile = iconfile
|
script_action.iconfile = iconfile
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Package declaring Pype version."""
|
"""Package declaring Pype version."""
|
||||||
__version__ = "3.15.7-nightly.2"
|
__version__ = "3.15.7-nightly.3"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue