mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +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
|
||||
description: What version are you running? Look to OpenPype Tray
|
||||
options:
|
||||
- 3.15.7-nightly.3
|
||||
- 3.15.7-nightly.2
|
||||
- 3.15.7-nightly.1
|
||||
- 3.15.6
|
||||
|
|
@ -134,7 +135,6 @@ body:
|
|||
- 3.14.1-nightly.4
|
||||
- 3.14.1-nightly.3
|
||||
- 3.14.1-nightly.2
|
||||
- 3.14.1-nightly.1
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
|||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -112,3 +112,9 @@ tools/run_eventserver.*
|
|||
tools/dev_*
|
||||
|
||||
.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
|
||||
|
||||
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.
|
||||
|
||||
Saves the current file with an increased version number.
|
||||
|
||||
"""
|
||||
|
||||
label = "Increment current file"
|
||||
label = "Increment workfile version"
|
||||
order = pyblish.api.IntegratorOrder + 9.0
|
||||
hosts = ["fusion"]
|
||||
families = ["workfile"]
|
||||
optional = True
|
||||
|
||||
def process(self, context):
|
||||
if not self.is_active(context.data):
|
||||
return
|
||||
|
||||
from openpype.lib import version_up
|
||||
from openpype.pipeline.publish import get_errored_plugins_from_context
|
||||
|
||||
errored_plugins = get_errored_plugins_from_context(context)
|
||||
if any(plugin.__name__ == "FusionSubmitDeadline"
|
||||
for plugin in errored_plugins):
|
||||
raise RuntimeError("Skipping incrementing current file because "
|
||||
"submission to render farm failed.")
|
||||
if any(
|
||||
plugin.__name__ == "FusionSubmitDeadline"
|
||||
for plugin in errored_plugins
|
||||
):
|
||||
raise KnownPublishError(
|
||||
"Skipping incrementing current file because "
|
||||
"submission to render farm failed."
|
||||
)
|
||||
|
||||
comp = context.data.get("currentComp")
|
||||
assert comp, "Must have comp"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
import pyblish.api
|
||||
|
||||
from openpype.pipeline.publish import RepairAction
|
||||
from openpype.pipeline import PublishValidationError
|
||||
from openpype.pipeline import (
|
||||
publish,
|
||||
OptionalPyblishPluginMixin,
|
||||
PublishValidationError,
|
||||
)
|
||||
|
||||
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"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
|
|
@ -15,11 +20,10 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin):
|
|||
families = ["render"]
|
||||
optional = True
|
||||
|
||||
actions = [SelectInvalidAction, RepairAction]
|
||||
actions = [SelectInvalidAction, publish.RepairAction]
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
||||
context = instance.context
|
||||
comp = context.data.get("currentComp")
|
||||
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]
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
"Found {} Backgrounds tools which"
|
||||
" are not set to float32".format(len(invalid)),
|
||||
title=self.label)
|
||||
title=self.label,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
|
|
|
|||
|
|
@ -81,7 +81,13 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
# TODO: make sure this doesn't trigger when
|
||||
# opening with last workfile.
|
||||
_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:
|
||||
import hdefereval # noqa, hdefereval is only available in ui mode
|
||||
|
|
|
|||
|
|
@ -173,10 +173,16 @@ def set_scene_resolution(width: int, height: int):
|
|||
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.renderHeight = height
|
||||
|
||||
|
||||
def reset_scene_resolution():
|
||||
"""Apply the scene resolution from the project definition
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,9 @@ class RenderSettings(object):
|
|||
|
||||
rt.rendSaveFile = True
|
||||
|
||||
if rt.renderSceneDialog.isOpen():
|
||||
rt.renderSceneDialog.close()
|
||||
|
||||
def arnold_setup(self):
|
||||
# get Arnold RenderView run in the background
|
||||
# for setting up renderable camera
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher):
|
|||
|
||||
def context_setting():
|
||||
return lib.set_context_setting()
|
||||
|
||||
rt.callbacks.addScript(rt.Name('systemPostNew'),
|
||||
context_setting)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ class CreateRender(plugin.MaxCreator):
|
|||
# for additional work on the 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)
|
||||
RenderSettings().set_render_camera(sel_obj)
|
||||
# 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)
|
||||
|
||||
# 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:
|
||||
shading_engines = cmds.listConnections(shape,
|
||||
destination=True,
|
||||
|
|
@ -60,19 +61,18 @@ class ValidateShaderName(pyblish.api.InstancePlugin):
|
|||
)
|
||||
|
||||
for shader in shaders:
|
||||
m = r.match(cls.regex, shader)
|
||||
m = regex_compile.match(shader)
|
||||
if m is None:
|
||||
invalid.append(shape)
|
||||
cls.log.error(
|
||||
"object {0} has invalid shader name {1}".format(shape,
|
||||
shader)
|
||||
)
|
||||
cls.log.error(error_message.format(shape, shader))
|
||||
else:
|
||||
if 'asset' in r.groupindex:
|
||||
if 'asset' in regex_compile.groupindex:
|
||||
if m.group('asset') != asset_name:
|
||||
invalid.append(shape)
|
||||
cls.log.error(("object {0} has invalid "
|
||||
"shader name {1}").format(shape,
|
||||
shader))
|
||||
message = error_message
|
||||
message += " with missing asset name \"{2}\""
|
||||
cls.log.error(
|
||||
message.format(shape, shader, asset_name)
|
||||
)
|
||||
|
||||
return invalid
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@ class UnrealAddon(OpenPypeModule, IHostAddon):
|
|||
UNREAL_ROOT_DIR, "integration", f"UE_{ue_version}", "Ayon"
|
||||
)
|
||||
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"
|
||||
):
|
||||
)
|
||||
if compatible_versions:
|
||||
unreal_plugin_path = compatible_versions[-1] / "Ayon"
|
||||
unreal_plugin_path = unreal_plugin_path.as_posix()
|
||||
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ class StaticMeshAlembicLoader(plugin.Loader):
|
|||
source_path = get_representation_path(representation)
|
||||
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
|
||||
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["output"] = s.get('output')
|
||||
new_data["fps"] = seq.get_display_rate().numerator
|
||||
new_data["frameStart"] = s.get('frame_range')[0]
|
||||
new_data["frameEnd"] = s.get('frame_range')[1]
|
||||
new_data["frameStart"] = int(s.get('frame_range')[0])
|
||||
new_data["frameEnd"] = int(s.get('frame_range')[1])
|
||||
new_data["sequence"] = seq.get_path_name()
|
||||
new_data["master_sequence"] = data["master_sequence"]
|
||||
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`
|
||||
# - dynamically imported OpenPype modules and addons
|
||||
module_dirs = get_module_dirs()
|
||||
|
||||
# Add current directory at first place
|
||||
# - has small differences in import logic
|
||||
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, current_dir)
|
||||
|
||||
addons_dir = os.path.join(os.path.dirname(current_dir), "addons")
|
||||
module_dirs.append(addons_dir)
|
||||
|
||||
processed_paths = set()
|
||||
for dirpath in module_dirs:
|
||||
for dirpath in frozenset(module_dirs):
|
||||
# Skip already processed paths
|
||||
if dirpath in processed_paths:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin):
|
|||
existing_tasks.append(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:
|
||||
task_type = tasks[task_name]["type"]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin):
|
|||
|
||||
order = pyblish.api.IntegratorOrder
|
||||
label = "Kitsu Note and Status"
|
||||
families = ["render", "kitsu"]
|
||||
families = ["render", "image", "online", "plate", "kitsu"]
|
||||
|
||||
# status settings
|
||||
set_status_note = False
|
||||
|
|
@ -52,8 +52,9 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin):
|
|||
for instance in context:
|
||||
# Check if instance is a review by checking its family
|
||||
# Allow a match to primary family or any of families
|
||||
families = set([instance.data["family"]] +
|
||||
instance.data.get("families", []))
|
||||
families = set(
|
||||
[instance.data["family"]] + instance.data.get("families", [])
|
||||
)
|
||||
if "review" not in families:
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,10 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin):
|
|||
|
||||
order = pyblish.api.IntegratorOrder + 0.01
|
||||
label = "Kitsu Review"
|
||||
families = ["render", "kitsu"]
|
||||
families = ["render", "image", "online", "plate", "kitsu"]
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
# Check comment has been created
|
||||
comment_id = instance.data.get("kitsu_comment", {}).get("id")
|
||||
if not comment_id:
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ from . import (
|
|||
register_inventory_action_path,
|
||||
register_creator_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
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
||||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
|
||||
|
||||
|
||||
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_discovery_filter(filter_pyblish_plugins)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
register_inventory_action_path(INVENTORY_PATH)
|
||||
|
||||
if host_name is None:
|
||||
host_name = os.environ.get("AVALON_APP")
|
||||
|
|
@ -223,6 +226,7 @@ def uninstall_host():
|
|||
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
||||
pyblish.api.deregister_discovery_filter(filter_pyblish_plugins)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
deregister_inventory_action_path(INVENTORY_PATH)
|
||||
log.info("Global plug-ins unregistred")
|
||||
|
||||
deregister_host()
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ from openpype.lib.attribute_definitions import (
|
|||
get_default_values,
|
||||
)
|
||||
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 .creator_plugins import (
|
||||
|
|
@ -1383,6 +1383,8 @@ class CreateContext:
|
|||
self._current_task_name = None
|
||||
self._current_workfile_path = None
|
||||
|
||||
self._current_project_anatomy = None
|
||||
|
||||
self._host_is_valid = host_is_valid
|
||||
# Currently unused variable
|
||||
self.headless = headless
|
||||
|
|
@ -1546,6 +1548,18 @@ class CreateContext:
|
|||
|
||||
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
|
||||
def context_has_changed(self):
|
||||
"""Host context has changed.
|
||||
|
|
@ -1568,6 +1582,7 @@ class CreateContext:
|
|||
)
|
||||
|
||||
project_name = property(get_current_project_name)
|
||||
project_anatomy = property(get_current_project_anatomy)
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
|
|
@ -1680,6 +1695,8 @@ class CreateContext:
|
|||
self._current_task_name = task_name
|
||||
self._current_workfile_path = workfile_path
|
||||
|
||||
self._current_project_anatomy = None
|
||||
|
||||
def reset_plugins(self, discover_publish_plugins=True):
|
||||
"""Reload plugins.
|
||||
|
||||
|
|
|
|||
|
|
@ -231,10 +231,24 @@ class BaseCreator:
|
|||
|
||||
@property
|
||||
def project_name(self):
|
||||
"""Family that plugin represents."""
|
||||
"""Current project name.
|
||||
|
||||
Returns:
|
||||
str: Name of a project.
|
||||
"""
|
||||
|
||||
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
|
||||
def host(self):
|
||||
return self.create_context.host
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ class BuildWorkfile:
|
|||
|
||||
if link_context_profiles:
|
||||
# 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:
|
||||
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": {
|
||||
"ext": "png",
|
||||
"tags": [
|
||||
"ftrackreview"
|
||||
"ftrackreview",
|
||||
"kitsureview"
|
||||
],
|
||||
"burnins": [],
|
||||
"ffmpeg_args": {
|
||||
|
|
|
|||
|
|
@ -734,6 +734,7 @@
|
|||
"ValidateShaderName": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
"active": true,
|
||||
"regex": "(?P<asset>.*)_(.*)_SHD"
|
||||
},
|
||||
"ValidateShadingEngine": {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,11 @@
|
|||
"key": "optional",
|
||||
"label": "Optional"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "active",
|
||||
"label": "Active"
|
||||
},
|
||||
{
|
||||
"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>"
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ class ScriptsMenu(QtWidgets.QMenu):
|
|||
|
||||
Args:
|
||||
title (str): the name of the root menu which will be created
|
||||
|
||||
|
||||
parent (QtWidgets.QObject) : the QObject to parent the menu to
|
||||
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ class ScriptsMenu(QtWidgets.QMenu):
|
|||
parent(QtWidgets.QWidget): the object to parent the menu to
|
||||
|
||||
title(str): the title of the menu
|
||||
|
||||
|
||||
Returns:
|
||||
QtWidget.QMenu instance
|
||||
"""
|
||||
|
|
@ -111,7 +111,7 @@ class ScriptsMenu(QtWidgets.QMenu):
|
|||
return menu
|
||||
|
||||
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
|
||||
|
||||
Args:
|
||||
|
|
@ -134,6 +134,8 @@ class ScriptsMenu(QtWidgets.QMenu):
|
|||
|
||||
tooltip (str): A tip for the user about the usage fo the tool
|
||||
|
||||
shortcut (str): A shortcut to run the command
|
||||
|
||||
Returns:
|
||||
QtWidget.QAction instance
|
||||
|
||||
|
|
@ -166,6 +168,9 @@ class ScriptsMenu(QtWidgets.QMenu):
|
|||
raise RuntimeError("Script action can't be "
|
||||
"processed: {}".format(e))
|
||||
|
||||
if shortcut:
|
||||
script_action.setShortcut(shortcut)
|
||||
|
||||
if icon:
|
||||
iconfile = os.path.expandvars(icon)
|
||||
script_action.iconfile = iconfile
|
||||
|
|
@ -253,7 +258,7 @@ class ScriptsMenu(QtWidgets.QMenu):
|
|||
|
||||
def _update_search(self, search):
|
||||
"""Hide all the samples which do not match the user's import
|
||||
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""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