Merge branch 'develop' into feature/add_pyblish_debug_stepper_to_exprimental_tools

This commit is contained in:
Mustafa Jafar 2024-10-02 10:49:25 +03:00 committed by GitHub
commit 4d6f7d4331
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 238 additions and 99 deletions

12
.github/workflows/release_trigger.yml vendored Normal file
View file

@ -0,0 +1,12 @@
name: 🚀 Release Trigger
on:
workflow_dispatch:
jobs:
call-release-trigger:
uses: ynput/ops-repo-automation/.github/workflows/release_trigger.yml@main
secrets:
token: ${{ secrets.YNPUT_BOT_TOKEN }}
email: ${{ secrets.CI_EMAIL }}
user: ${{ secrets.CI_USER }}

View file

@ -19,7 +19,8 @@ class OCIOEnvHook(PreLaunchHook):
"nuke",
"hiero",
"resolve",
"openrv"
"openrv",
"cinema4d"
}
launch_types = set()

View file

@ -81,7 +81,10 @@ def collect_frames(files):
dict: {'/folder/product_v001.0001.png': '0001', ....}
"""
patterns = [clique.PATTERNS["frames"]]
# clique.PATTERNS["frames"] supports only `.1001.exr` not `_1001.exr` so
# we use a customized pattern.
pattern = "[_.](?P<index>(?P<padding>0*)\\d+)\\.\\D+\\d?$"
patterns = [pattern]
collections, remainder = clique.assemble(
files, minimum_items=1, patterns=patterns)

View file

@ -3,7 +3,6 @@ from .constants import (
AVALON_INSTANCE_ID,
AYON_CONTAINER_ID,
AYON_INSTANCE_ID,
HOST_WORKFILE_EXTENSIONS,
)
from .anatomy import Anatomy
@ -114,7 +113,6 @@ __all__ = (
"AVALON_INSTANCE_ID",
"AYON_CONTAINER_ID",
"AYON_INSTANCE_ID",
"HOST_WORKFILE_EXTENSIONS",
# --- Anatomy ---
"Anatomy",

View file

@ -4,20 +4,3 @@ AYON_INSTANCE_ID = "ayon.create.instance"
# Backwards compatibility
AVALON_CONTAINER_ID = "pyblish.avalon.container"
AVALON_INSTANCE_ID = "pyblish.avalon.instance"
# TODO get extensions from host implementations
HOST_WORKFILE_EXTENSIONS = {
"blender": [".blend"],
"celaction": [".scn"],
"tvpaint": [".tvpp"],
"fusion": [".comp"],
"harmony": [".zip"],
"houdini": [".hip", ".hiplc", ".hipnc"],
"maya": [".ma", ".mb"],
"nuke": [".nk"],
"hiero": [".hrox"],
"photoshop": [".psd", ".psb"],
"premiere": [".prproj"],
"resolve": [".drp"],
"aftereffects": [".aep"]
}

View file

@ -788,6 +788,11 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data,
colorspace = product.colorspace
break
if isinstance(files, (list, tuple)):
files = [os.path.basename(f) for f in files]
else:
files = os.path.basename(files)
rep = {
"name": ext,
"ext": ext,

View file

@ -17,8 +17,7 @@ from ayon_core.pipeline.load import get_representation_path_with_anatomy
from ayon_core.pipeline.delivery import (
get_format_dict,
check_destination_path,
deliver_single_file,
deliver_sequence,
deliver_single_file
)
@ -231,51 +230,55 @@ class DeliveryOptionsDialog(QtWidgets.QDialog):
self.log
]
if repre.get("files"):
src_paths = []
for repre_file in repre["files"]:
src_path = self.anatomy.fill_root(repre_file["path"])
src_paths.append(src_path)
sources_and_frames = collect_frames(src_paths)
# TODO: This will currently incorrectly detect 'resources'
# that are published along with the publish, because those should
# not adhere to the template directly but are ingested in a
# customized way. For example, maya look textures or any publish
# that directly adds files into `instance.data["transfers"]`
src_paths = []
for repre_file in repre["files"]:
src_path = self.anatomy.fill_root(repre_file["path"])
src_paths.append(src_path)
sources_and_frames = collect_frames(src_paths)
frames = set(sources_and_frames.values())
frames.discard(None)
first_frame = None
if frames:
first_frame = min(frames)
frames = set(sources_and_frames.values())
frames.discard(None)
first_frame = None
if frames:
first_frame = min(frames)
for src_path, frame in sources_and_frames.items():
args[0] = src_path
# Renumber frames
if renumber_frame and frame is not None:
# Calculate offset between
# first frame and current frame
# - '0' for first frame
offset = frame_offset - int(first_frame)
# Add offset to new frame start
dst_frame = int(frame) + offset
if dst_frame < 0:
msg = "Renumber frame has a smaller number than original frame" # noqa
report_items[msg].append(src_path)
self.log.warning("{} <{}>".format(
msg, dst_frame))
continue
frame = dst_frame
for src_path, frame in sources_and_frames.items():
args[0] = src_path
# Renumber frames
if renumber_frame and frame is not None:
# Calculate offset between
# first frame and current frame
# - '0' for first frame
offset = frame_offset - int(first_frame)
# Add offset to new frame start
dst_frame = int(frame) + offset
if dst_frame < 0:
msg = "Renumber frame has a smaller number than original frame" # noqa
report_items[msg].append(src_path)
self.log.warning("{} <{}>".format(
msg, dst_frame))
continue
frame = dst_frame
if frame is not None:
if frame is not None:
if repre["context"].get("frame"):
anatomy_data["frame"] = frame
new_report_items, uploaded = deliver_single_file(*args)
report_items.update(new_report_items)
self._update_progress(uploaded)
else: # fallback for Pype2 and representations without files
frame = repre["context"].get("frame")
if frame:
repre["context"]["frame"] = len(str(frame)) * "#"
if not frame:
new_report_items, uploaded = deliver_single_file(*args)
else:
new_report_items, uploaded = deliver_sequence(*args)
elif repre["context"].get("udim"):
anatomy_data["udim"] = frame
else:
# Fallback
self.log.warning(
"Representation context has no frame or udim"
" data. Supplying sequence frame to '{frame}'"
" formatting data."
)
anatomy_data["frame"] = frame
new_report_items, uploaded = deliver_single_file(*args)
report_items.update(new_report_items)
self._update_progress(uploaded)

View file

@ -122,13 +122,22 @@ class ExtractOIIOTranscode(publish.Extractor):
transcoding_type = output_def["transcoding_type"]
target_colorspace = view = display = None
# NOTE: we use colorspace_data as the fallback values for
# the target colorspace.
if transcoding_type == "colorspace":
# TODO: Should we fallback to the colorspace
# (which used as source above) ?
# or should we compute the target colorspace from
# current view and display ?
target_colorspace = (output_def["colorspace"] or
colorspace_data.get("colorspace"))
else:
view = output_def["view"] or colorspace_data.get("view")
display = (output_def["display"] or
colorspace_data.get("display"))
elif transcoding_type == "display_view":
display_view = output_def["display_view"]
view = display_view["view"] or colorspace_data.get("view")
display = (
display_view["display"]
or colorspace_data.get("display")
)
# both could be already collected by DCC,
# but could be overwritten when transcoding
@ -192,7 +201,7 @@ class ExtractOIIOTranscode(publish.Extractor):
new_repre["files"] = new_repre["files"][0]
# If the source representation has "review" tag, but its not
# part of the output defintion tags, then both the
# part of the output definition tags, then both the
# representations will be transcoded in ExtractReview and
# their outputs will clash in integration.
if "review" in repre.get("tags", []):

View file

@ -509,8 +509,11 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
if not is_sequence_representation:
files = [files]
if any(os.path.isabs(fname) for fname in files):
raise KnownPublishError("Given file names contain full paths")
for fname in files:
if os.path.isabs(fname):
raise KnownPublishError(
f"Representation file names contains full paths: {fname}"
)
if not is_sequence_representation:
return

View file

@ -9,7 +9,14 @@ from ayon_api import (
class IntegrateInputLinksAYON(pyblish.api.ContextPlugin):
"""Connecting version level dependency links"""
"""Connecting version level dependency links
Handles links:
- generative - what gets produced from workfile
- reference - what was loaded into workfile
It expects workfile instance is being published.
"""
order = pyblish.api.IntegratorOrder + 0.2
label = "Connect Dependency InputLinks AYON"
@ -47,6 +54,11 @@ class IntegrateInputLinksAYON(pyblish.api.ContextPlugin):
self.create_links_on_server(context, new_links_by_type)
def split_instances(self, context):
"""Separates published instances into workfile and other
Returns:
(tuple(pyblish.plugin.Instance), list(pyblish.plugin.Instance))
"""
workfile_instance = None
other_instances = []
@ -83,6 +95,15 @@ class IntegrateInputLinksAYON(pyblish.api.ContextPlugin):
def create_workfile_links(
self, workfile_instance, other_instances, new_links_by_type
):
"""Adds links (generative and reference) for workfile.
Args:
workfile_instance (pyblish.plugin.Instance): published workfile
other_instances (list[pyblish.plugin.Instance]): other published
instances
new_links_by_type (dict[str, list[str]]): dictionary collecting new
created links by its type
"""
if workfile_instance is None:
self.log.warn("No workfile in this publish session.")
return
@ -97,7 +118,7 @@ class IntegrateInputLinksAYON(pyblish.api.ContextPlugin):
instance.data["versionEntity"]["id"],
)
loaded_versions = workfile_instance.context.get("loadedVersions")
loaded_versions = workfile_instance.context.data.get("loadedVersions")
if not loaded_versions:
return

View file

@ -36,7 +36,8 @@ class ValidateCurrentSaveFile(pyblish.api.ContextPlugin):
label = "Validate File Saved"
order = pyblish.api.ValidatorOrder - 0.1
hosts = ["fusion", "houdini", "max", "maya", "nuke", "substancepainter"]
hosts = ["fusion", "houdini", "max", "maya", "nuke", "substancepainter",
"cinema4d"]
actions = [SaveByVersionUpAction, ShowWorkfilesAction]
def process(self, context):

View file

@ -1,6 +1,9 @@
import collections
from ayon_api import get_representations, get_versions_links
from ayon_api import (
get_representations,
get_versions_links,
)
from ayon_core.lib import Logger, NestedCacheItem
from ayon_core.addon import AddonsManager
@ -509,18 +512,19 @@ class SiteSyncModel:
"reference"
)
for link_repre_id in links:
try:
if not self._sitesync_addon.is_representation_on_site(
project_name,
link_repre_id,
site_name
):
print("Adding {} to linked representation: {}".format(
site_name, link_repre_id))
self._sitesync_addon.add_site(
project_name,
link_repre_id,
site_name,
force=False
force=True
)
except Exception:
# do not add/reset working site for references
log.debug("Site present", exc_info=True)
def _get_linked_representation_id(
self,
@ -575,7 +579,7 @@ class SiteSyncModel:
project_name,
versions_to_check,
link_types=link_types,
link_direction="out")
link_direction="in") # looking for 'in'puts for version
versions_to_check = set()
for links in versions_links.values():
@ -584,9 +588,6 @@ class SiteSyncModel:
if link["entityType"] != "version":
continue
entity_id = link["entityId"]
# Skip already found linked version ids
if entity_id in linked_version_ids:
continue
linked_version_ids.add(entity_id)
versions_to_check.add(entity_id)

View file

@ -439,10 +439,13 @@ class PublisherWindow(QtWidgets.QDialog):
def make_sure_is_visible(self):
if self._window_is_visible:
self.setWindowState(QtCore.Qt.WindowActive)
else:
self.show()
self.raise_()
self.activateWindow()
self.showNormal()
def showEvent(self, event):
self._window_is_visible = True
super().showEvent(event)

View file

@ -71,10 +71,42 @@ def _convert_validate_version_0_3_3(publish_overrides):
validate_version["plugin_state_profiles"] = [profile]
def _convert_oiio_transcode_0_4_5(publish_overrides):
"""ExtractOIIOTranscode plugin changed in 0.4.5."""
if "ExtractOIIOTranscode" not in publish_overrides:
return
transcode_profiles = publish_overrides["ExtractOIIOTranscode"].get("profiles")
if not transcode_profiles:
return
for profile in transcode_profiles:
outputs = profile.get("outputs")
if outputs is None:
return
for output in outputs :
# Already new settings
if "display_view" in output:
break
# Fix 'display' -> 'display_view' in 'transcoding_type'
transcode_type = output.get("transcoding_type")
if transcode_type == "display":
output["transcoding_type"] = "display_view"
# Convert 'display' and 'view' to new values
output["display_view"] = {
"display": output.pop("display", ""),
"view": output.pop("view", ""),
}
def _conver_publish_plugins(overrides):
if "publish" not in overrides:
return
_convert_validate_version_0_3_3(overrides["publish"])
_convert_oiio_transcode_0_4_5(overrides["publish"])
def convert_settings_overrides(

View file

@ -268,13 +268,36 @@ class ExtractThumbnailModel(BaseSettingsModel):
def _extract_oiio_transcoding_type():
return [
{"value": "colorspace", "label": "Use Colorspace"},
{"value": "display", "label": "Use Display&View"}
{"value": "display_view", "label": "Use Display&View"}
]
class OIIOToolArgumentsModel(BaseSettingsModel):
additional_command_args: list[str] = SettingsField(
default_factory=list, title="Arguments")
default_factory=list,
title="Arguments",
description="Additional command line arguments for *oiiotool*."
)
class UseDisplayViewModel(BaseSettingsModel):
_layout = "expanded"
display: str = SettingsField(
"",
title="Target Display",
description=(
"Display of the target transform. If left empty, the"
" source Display value will be used."
)
)
view: str = SettingsField(
"",
title="Target View",
description=(
"View of the target transform. If left empty, the"
" source View value will be used."
)
)
class ExtractOIIOTranscodeOutputModel(BaseSettingsModel):
@ -285,22 +308,57 @@ class ExtractOIIOTranscodeOutputModel(BaseSettingsModel):
description="Output name (no space)",
regex=r"[a-zA-Z0-9_]([a-zA-Z0-9_\.\-]*[a-zA-Z0-9_])?$",
)
extension: str = SettingsField("", title="Extension")
extension: str = SettingsField(
"",
title="Extension",
description=(
"Target extension. If left empty, original"
" extension is used."
),
)
transcoding_type: str = SettingsField(
"colorspace",
title="Transcoding type",
enum_resolver=_extract_oiio_transcoding_type
enum_resolver=_extract_oiio_transcoding_type,
conditionalEnum=True,
description=(
"Select the transcoding type for your output, choosing either "
"*Colorspace* or *Display&View* transform."
" Only one option can be applied per output definition."
),
)
colorspace: str = SettingsField("", title="Colorspace")
display: str = SettingsField("", title="Display")
view: str = SettingsField("", title="View")
colorspace: str = SettingsField(
"",
title="Target Colorspace",
description=(
"Choose the desired target colorspace, confirming its availability"
" in the active OCIO config. If left empty, the"
" source colorspace value will be used, resulting in no"
" colorspace conversion."
)
)
display_view: UseDisplayViewModel = SettingsField(
title="Use Display&View",
default_factory=UseDisplayViewModel
)
oiiotool_args: OIIOToolArgumentsModel = SettingsField(
default_factory=OIIOToolArgumentsModel,
title="OIIOtool arguments")
tags: list[str] = SettingsField(default_factory=list, title="Tags")
tags: list[str] = SettingsField(
default_factory=list,
title="Tags",
description=(
"Additional tags that will be added to the created representation."
"\nAdd *review* tag to create review from the transcoded"
" representation instead of the original."
)
)
custom_tags: list[str] = SettingsField(
default_factory=list, title="Custom Tags"
default_factory=list,
title="Custom Tags",
description="Additional custom tags that will be added to the created representation."
)
@ -328,7 +386,13 @@ class ExtractOIIOTranscodeProfileModel(BaseSettingsModel):
)
delete_original: bool = SettingsField(
True,
title="Delete Original Representation"
title="Delete Original Representation",
description=(
"Choose to preserve or remove the original representation.\n"
"Keep in mind that if the transcoded representation includes"
" a `review` tag, it will take precedence over"
" the original for creating reviews."
),
)
outputs: list[ExtractOIIOTranscodeOutputModel] = SettingsField(
default_factory=list,
@ -371,7 +435,7 @@ class ExtractReviewFFmpegModel(BaseSettingsModel):
def extract_review_filter_enum():
return [
{
"value": "everytime",
"value": "everytime", # codespell:ignore everytime
"label": "Always"
},
{
@ -393,7 +457,7 @@ class ExtractReviewFilterModel(BaseSettingsModel):
default_factory=list, title="Custom Tags"
)
single_frame_filter: str = SettingsField(
"everytime",
"everytime", # codespell:ignore everytime
description=(
"Use output <b>always</b> / only if input <b>is 1 frame</b>"
" image / only if has <b>2+ frames</b> or <b>is video</b>"
@ -791,7 +855,7 @@ class IntegrateHeroVersionModel(BaseSettingsModel):
class CleanUpModel(BaseSettingsModel):
_isGroup = True
paterns: list[str] = SettingsField(
paterns: list[str] = SettingsField( # codespell:ignore paterns
default_factory=list,
title="Patterns (regex)"
)
@ -1225,7 +1289,7 @@ DEFAULT_PUBLISH_VALUES = {
"use_hardlinks": False
},
"CleanUp": {
"paterns": [],
"paterns": [], # codespell:ignore paterns
"remove_temp_renders": False
},
"CleanUpFarm": {