mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
Merge branch 'develop' of github.com:pypeclub/OpenPype into feature/OP-2766_PS-to-new-publisher
This commit is contained in:
commit
0fa0d8ad92
14 changed files with 252 additions and 27 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -1,19 +1,28 @@
|
|||
# Changelog
|
||||
|
||||
## [3.9.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
## [3.9.1-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.0...HEAD)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Nuke: ExtractReviewSlate can handle more codes and profiles [\#2879](https://github.com/pypeclub/OpenPype/pull/2879)
|
||||
- Flame: sequence used for reference video [\#2869](https://github.com/pypeclub/OpenPype/pull/2869)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Pyblish Pype - ensure current state is correct when entering new group order [\#2899](https://github.com/pypeclub/OpenPype/pull/2899)
|
||||
- SceneInventory: Fix import of load function [\#2894](https://github.com/pypeclub/OpenPype/pull/2894)
|
||||
- Harmony - fixed creator issue [\#2891](https://github.com/pypeclub/OpenPype/pull/2891)
|
||||
- General: Remove forgotten use of avalon Creator [\#2885](https://github.com/pypeclub/OpenPype/pull/2885)
|
||||
- General: Avoid circular import [\#2884](https://github.com/pypeclub/OpenPype/pull/2884)
|
||||
- Fixes for attaching loaded containers \(\#2837\) [\#2874](https://github.com/pypeclub/OpenPype/pull/2874)
|
||||
- Maya: Deformer node ids validation plugin [\#2826](https://github.com/pypeclub/OpenPype/pull/2826)
|
||||
|
||||
**🔀 Refactored code**
|
||||
|
||||
- General: Reduce style usage to OpenPype repository [\#2889](https://github.com/pypeclub/OpenPype/pull/2889)
|
||||
- General: Move loader logic from avalon to openpype [\#2886](https://github.com/pypeclub/OpenPype/pull/2886)
|
||||
|
||||
## [3.9.0](https://github.com/pypeclub/OpenPype/tree/3.9.0) (2022-03-14)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import re
|
||||
import pyblish
|
||||
import openpype
|
||||
import openpype.hosts.flame.api as opfapi
|
||||
|
|
@ -6,6 +7,10 @@ from openpype.hosts.flame.otio import flame_export
|
|||
# # developer reload modules
|
||||
from pprint import pformat
|
||||
|
||||
# constatns
|
||||
NUM_PATERN = re.compile(r"([0-9\.]+)")
|
||||
TXT_PATERN = re.compile(r"([a-zA-Z]+)")
|
||||
|
||||
|
||||
class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
||||
"""Collect all Timeline segment selection."""
|
||||
|
|
@ -16,6 +21,16 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
|||
|
||||
audio_track_items = []
|
||||
|
||||
# TODO: add to settings
|
||||
# settings
|
||||
xml_preset_attrs_from_comments = {
|
||||
"width": "number",
|
||||
"height": "number",
|
||||
"pixelRatio": "float",
|
||||
"resizeType": "string",
|
||||
"resizeFilter": "string"
|
||||
}
|
||||
|
||||
def process(self, context):
|
||||
project = context.data["flameProject"]
|
||||
sequence = context.data["flameSequence"]
|
||||
|
|
@ -26,6 +41,10 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
|||
# process all sellected
|
||||
with opfapi.maintained_segment_selection(sequence) as segments:
|
||||
for segment in segments:
|
||||
comment_attributes = self._get_comment_attributes(segment)
|
||||
self.log.debug("_ comment_attributes: {}".format(
|
||||
pformat(comment_attributes)))
|
||||
|
||||
clip_data = opfapi.get_segment_attributes(segment)
|
||||
clip_name = clip_data["segment_name"]
|
||||
self.log.debug("clip_name: {}".format(clip_name))
|
||||
|
|
@ -101,6 +120,9 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
|||
# add resolution
|
||||
self._get_resolution_to_data(inst_data, context)
|
||||
|
||||
# add comment attributes if any
|
||||
inst_data.update(comment_attributes)
|
||||
|
||||
# create instance
|
||||
instance = context.create_instance(**inst_data)
|
||||
|
||||
|
|
@ -126,6 +148,94 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
|||
if marker_data.get("reviewTrack") is not None:
|
||||
instance.data["reviewAudio"] = True
|
||||
|
||||
def _get_comment_attributes(self, segment):
|
||||
comment = segment.comment.get_value()
|
||||
|
||||
# try to find attributes
|
||||
attributes = {
|
||||
"xml_overrides": {
|
||||
"pixelRatio": 1.00}
|
||||
}
|
||||
# search for `:`
|
||||
for split in self._split_comments(comment):
|
||||
# make sure we ignore if not `:` in key
|
||||
if ":" not in split:
|
||||
continue
|
||||
|
||||
self._get_xml_preset_attrs(
|
||||
attributes, split)
|
||||
|
||||
# add xml overides resolution to instance data
|
||||
xml_overrides = attributes["xml_overrides"]
|
||||
if xml_overrides.get("width"):
|
||||
attributes.update({
|
||||
"resolutionWidth": xml_overrides["width"],
|
||||
"resolutionHeight": xml_overrides["height"],
|
||||
"pixelAspect": xml_overrides["pixelRatio"]
|
||||
})
|
||||
|
||||
return attributes
|
||||
|
||||
def _get_xml_preset_attrs(self, attributes, split):
|
||||
|
||||
# split to key and value
|
||||
key, value = split.split(":")
|
||||
|
||||
for a_name, a_type in self.xml_preset_attrs_from_comments.items():
|
||||
# exclude all not related attributes
|
||||
if a_name.lower() not in key.lower():
|
||||
continue
|
||||
|
||||
# get pattern defined by type
|
||||
pattern = TXT_PATERN
|
||||
if a_type in ("number" , "float"):
|
||||
pattern = NUM_PATERN
|
||||
|
||||
res_goup = pattern.findall(value)
|
||||
|
||||
# raise if nothing is found as it is not correctly defined
|
||||
if not res_goup:
|
||||
raise ValueError((
|
||||
"Value for `{}` attribute is not "
|
||||
"set correctly: `{}`").format(a_name, split))
|
||||
|
||||
if "string" in a_type:
|
||||
_value = res_goup[0]
|
||||
if "float" in a_type:
|
||||
_value = float(res_goup[0])
|
||||
if "number" in a_type:
|
||||
_value = int(res_goup[0])
|
||||
|
||||
attributes["xml_overrides"][a_name] = _value
|
||||
|
||||
# condition for resolution in key
|
||||
if "resolution" in key.lower():
|
||||
res_goup = NUM_PATERN.findall(value)
|
||||
# check if axpect was also defined
|
||||
# 1920x1080x1.5
|
||||
aspect = res_goup[2] if len(res_goup) > 2 else 1
|
||||
|
||||
width = int(res_goup[0])
|
||||
height = int(res_goup[1])
|
||||
pixel_ratio = float(aspect)
|
||||
attributes["xml_overrides"].update({
|
||||
"width": width,
|
||||
"height": height,
|
||||
"pixelRatio": pixel_ratio
|
||||
})
|
||||
|
||||
def _split_comments(self, comment_string):
|
||||
# first split comment by comma
|
||||
split_comments = []
|
||||
if "," in comment_string:
|
||||
split_comments.extend(comment_string.split(","))
|
||||
elif ";" in comment_string:
|
||||
split_comments.extend(comment_string.split(";"))
|
||||
else:
|
||||
split_comments.append(comment_string)
|
||||
|
||||
return split_comments
|
||||
|
||||
def _get_head_tail(self, clip_data, first_frame):
|
||||
# calculate head and tail with forward compatibility
|
||||
head = clip_data.get("segment_head")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
from pprint import pformat
|
||||
from copy import deepcopy
|
||||
|
||||
import pyblish.api
|
||||
import openpype.api
|
||||
from openpype.hosts.flame import api as opfapi
|
||||
|
|
@ -23,6 +24,7 @@ class ExtractSubsetResources(openpype.api.Extractor):
|
|||
"xml_preset_file": "Jpeg (8-bit).xml",
|
||||
"xml_preset_dir": "",
|
||||
"export_type": "File Sequence",
|
||||
"ignore_comment_attrs": True,
|
||||
"colorspace_out": "Output - sRGB",
|
||||
"representation_add_range": False,
|
||||
"representation_tags": ["thumbnail"]
|
||||
|
|
@ -32,6 +34,7 @@ class ExtractSubsetResources(openpype.api.Extractor):
|
|||
"xml_preset_file": "Apple iPad (1920x1080).xml",
|
||||
"xml_preset_dir": "",
|
||||
"export_type": "Movie",
|
||||
"ignore_comment_attrs": True,
|
||||
"colorspace_out": "Output - Rec.709",
|
||||
"representation_add_range": True,
|
||||
"representation_tags": [
|
||||
|
|
@ -102,6 +105,7 @@ class ExtractSubsetResources(openpype.api.Extractor):
|
|||
preset_dir = preset_config["xml_preset_dir"]
|
||||
export_type = preset_config["export_type"]
|
||||
repre_tags = preset_config["representation_tags"]
|
||||
ignore_comment_attrs = preset_config["ignore_comment_attrs"]
|
||||
color_out = preset_config["colorspace_out"]
|
||||
|
||||
# get frame range with handles for representation range
|
||||
|
|
@ -131,6 +135,14 @@ class ExtractSubsetResources(openpype.api.Extractor):
|
|||
"startFrame": frame_start
|
||||
})
|
||||
|
||||
if not ignore_comment_attrs:
|
||||
# add any xml overrides collected form segment.comment
|
||||
modify_xml_data.update(instance.data["xml_overrides"])
|
||||
|
||||
self.log.debug("__ modify_xml_data: {}".format(pformat(
|
||||
modify_xml_data
|
||||
)))
|
||||
|
||||
# with maintained duplication loop all presets
|
||||
with opfapi.maintained_object_duplication(
|
||||
exporting_clip) as duplclip:
|
||||
|
|
|
|||
|
|
@ -1937,18 +1937,26 @@ def remove_other_uv_sets(mesh):
|
|||
cmds.removeMultiInstance(attr, b=True)
|
||||
|
||||
|
||||
def get_id_from_history(node):
|
||||
def get_id_from_sibling(node, history_only=True):
|
||||
"""Return first node id in the history chain that matches this node.
|
||||
|
||||
The nodes in history must be of the exact same node type and must be
|
||||
parented under the same parent.
|
||||
|
||||
Optionally, if no matching node is found from the history, all the
|
||||
siblings of the node that are of the same type are checked.
|
||||
Additionally to having the same parent, the sibling must be marked as
|
||||
'intermediate object'.
|
||||
|
||||
Args:
|
||||
node (str): node to retrieve the
|
||||
node (str): node to retrieve the history from
|
||||
history_only (bool): if True and if nothing found in history,
|
||||
look for an 'intermediate object' in all the node's siblings
|
||||
of same type
|
||||
|
||||
Returns:
|
||||
str or None: The id from the node in history or None when no id found
|
||||
on any valid nodes in the history.
|
||||
str or None: The id from the sibling node or None when no id found
|
||||
on any valid nodes in the history or siblings.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -1977,6 +1985,45 @@ def get_id_from_history(node):
|
|||
if _id:
|
||||
return _id
|
||||
|
||||
if not history_only:
|
||||
# Get siblings of same type
|
||||
similar_nodes = cmds.listRelatives(parent,
|
||||
type=node_type,
|
||||
fullPath=True)
|
||||
similar_nodes = cmds.ls(similar_nodes, exactType=node_type, long=True)
|
||||
|
||||
# Exclude itself
|
||||
similar_nodes = [x for x in similar_nodes if x != node]
|
||||
|
||||
# Get all unique ids from siblings in order since
|
||||
# we consistently take the first one found
|
||||
sibling_ids = OrderedDict()
|
||||
for similar_node in similar_nodes:
|
||||
# Check if "intermediate object"
|
||||
if not cmds.getAttr(similar_node + ".intermediateObject"):
|
||||
continue
|
||||
|
||||
_id = get_id(similar_node)
|
||||
if not _id:
|
||||
continue
|
||||
|
||||
if _id in sibling_ids:
|
||||
sibling_ids[_id].append(similar_node)
|
||||
else:
|
||||
sibling_ids[_id] = [similar_node]
|
||||
|
||||
if sibling_ids:
|
||||
first_id, found_nodes = next(iter(sibling_ids.items()))
|
||||
|
||||
# Log a warning if we've found multiple unique ids
|
||||
if len(sibling_ids) > 1:
|
||||
log.warning(("Found more than 1 intermediate shape with"
|
||||
" unique id for '{}'. Using id of first"
|
||||
" found: '{}'".format(node, found_nodes[0])))
|
||||
|
||||
return first_id
|
||||
|
||||
|
||||
|
||||
# Project settings
|
||||
def set_scene_fps(fps, update=True):
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin):
|
|||
# if a deformer has been created on the shape
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise RuntimeError("Nodes found with non-related "
|
||||
"asset IDs: {0}".format(invalid))
|
||||
raise RuntimeError("Nodes found with mismatching "
|
||||
"IDs: {0}".format(invalid))
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
|
@ -65,7 +65,7 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin):
|
|||
invalid.append(node)
|
||||
continue
|
||||
|
||||
history_id = lib.get_id_from_history(node)
|
||||
history_id = lib.get_id_from_sibling(node)
|
||||
if history_id is not None and node_id != history_id:
|
||||
invalid.append(node)
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin):
|
|||
|
||||
for node in cls.get_invalid(instance):
|
||||
# Get the original id from history
|
||||
history_id = lib.get_id_from_history(node)
|
||||
history_id = lib.get_id_from_sibling(node)
|
||||
if not history_id:
|
||||
cls.log.error("Could not find ID in history for '%s'", node)
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin):
|
|||
|
||||
invalid = []
|
||||
for shape in shapes:
|
||||
history_id = lib.get_id_from_history(shape)
|
||||
history_id = lib.get_id_from_sibling(shape)
|
||||
if history_id:
|
||||
current_id = lib.get_id(shape)
|
||||
if current_id != history_id:
|
||||
|
|
@ -61,7 +61,7 @@ class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin):
|
|||
|
||||
for node in cls.get_invalid(instance):
|
||||
# Get the original id from history
|
||||
history_id = lib.get_id_from_history(node)
|
||||
history_id = lib.get_id_from_sibling(node)
|
||||
if not history_id:
|
||||
cls.log.error("Could not find ID in history for '%s'", node)
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin):
|
|||
openpype.hosts.maya.api.action.SelectInvalidAction,
|
||||
openpype.api.RepairAction
|
||||
]
|
||||
allow_history_only = False
|
||||
|
||||
def process(self, instance):
|
||||
"""Process all meshes"""
|
||||
|
|
@ -32,8 +33,8 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin):
|
|||
# if a deformer has been created on the shape
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise RuntimeError("Nodes found with non-related "
|
||||
"asset IDs: {0}".format(invalid))
|
||||
raise RuntimeError("Nodes found with mismatching "
|
||||
"IDs: {0}".format(invalid))
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
|
@ -51,10 +52,13 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin):
|
|||
noIntermediate=True)
|
||||
|
||||
for shape in shapes:
|
||||
history_id = lib.get_id_from_history(shape)
|
||||
if history_id:
|
||||
sibling_id = lib.get_id_from_sibling(
|
||||
shape,
|
||||
history_only=cls.allow_history_only
|
||||
)
|
||||
if sibling_id:
|
||||
current_id = lib.get_id(shape)
|
||||
if current_id != history_id:
|
||||
if current_id != sibling_id:
|
||||
invalid.append(shape)
|
||||
|
||||
return invalid
|
||||
|
|
@ -63,10 +67,13 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin):
|
|||
def repair(cls, instance):
|
||||
|
||||
for node in cls.get_invalid(instance):
|
||||
# Get the original id from history
|
||||
history_id = lib.get_id_from_history(node)
|
||||
if not history_id:
|
||||
cls.log.error("Could not find ID in history for '%s'", node)
|
||||
# Get the original id from sibling
|
||||
sibling_id = lib.get_id_from_sibling(
|
||||
node,
|
||||
history_only=cls.allow_history_only
|
||||
)
|
||||
if not sibling_id:
|
||||
cls.log.error("Could not find ID in siblings for '%s'", node)
|
||||
continue
|
||||
|
||||
lib.set_id(node, history_id, overwrite=True)
|
||||
lib.set_id(node, sibling_id, overwrite=True)
|
||||
|
|
|
|||
|
|
@ -747,10 +747,14 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
collections = clique.assemble(files)[0]
|
||||
assert len(collections) == 1, "Multiple collections found."
|
||||
col = collections[0]
|
||||
# do nothing if sequence is complete
|
||||
if list(col.indexes)[0] == start_frame and \
|
||||
list(col.indexes)[-1] == end_frame and \
|
||||
col.is_contiguous():
|
||||
|
||||
# do nothing if no gap is found in input range
|
||||
not_gap = True
|
||||
for fr in range(start_frame, end_frame + 1):
|
||||
if fr not in col.indexes:
|
||||
not_gap = False
|
||||
|
||||
if not_gap:
|
||||
return []
|
||||
|
||||
holes = col.holes()
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
"xml_preset_file": "OpenEXR (16-bit fp DWAA).xml",
|
||||
"xml_preset_dir": "",
|
||||
"export_type": "File Sequence",
|
||||
"ignore_comment_attrs": false,
|
||||
"colorspace_out": "ACES - ACEScg",
|
||||
"representation_add_range": true,
|
||||
"representation_tags": []
|
||||
|
|
|
|||
|
|
@ -385,6 +385,10 @@
|
|||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateRigOutSetNodeIds": {
|
||||
"enabled": true,
|
||||
"allow_history_only": false
|
||||
},
|
||||
"ValidateCameraAttributes": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
|
|
|
|||
|
|
@ -189,6 +189,17 @@
|
|||
]
|
||||
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "ignore_comment_attrs",
|
||||
"label": "Ignore attributes parsed from a segment comments"
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"key": "colorspace_out",
|
||||
"label": "Output color (imageio)",
|
||||
|
|
|
|||
|
|
@ -514,6 +514,26 @@
|
|||
"label": "Validate Rig Controllers"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"key": "ValidateRigOutSetNodeIds",
|
||||
"label": "Validate Rig Out Set Node Ids",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "allow_history_only",
|
||||
"label": "Allow history only"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.9.1-nightly.1"
|
||||
__version__ = "3.9.1-nightly.2"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.9.1-nightly.1" # OpenPype
|
||||
version = "3.9.1-nightly.2" # OpenPype
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue