Merge branch 'develop' into enhancement/OP-7075_Validate-Camera-Attributes

This commit is contained in:
Kayla Man 2024-01-24 16:51:10 +08:00
commit feb5acde70
28 changed files with 463 additions and 193 deletions

View file

@ -35,6 +35,10 @@ body:
label: Version
description: What version are you running? Look to OpenPype Tray
options:
- 3.18.5-nightly.3
- 3.18.5-nightly.2
- 3.18.5-nightly.1
- 3.18.4
- 3.18.4-nightly.1
- 3.18.3
- 3.18.3-nightly.2
@ -131,10 +135,6 @@ body:
- 3.15.8-nightly.2
- 3.15.8-nightly.1
- 3.15.7
- 3.15.7-nightly.3
- 3.15.7-nightly.2
- 3.15.7-nightly.1
- 3.15.6
validations:
required: true
- type: dropdown

View file

@ -1,6 +1,151 @@
# Changelog
## [3.18.4](https://github.com/ynput/OpenPype/tree/3.18.4)
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.3...3.18.4)
### **🚀 Enhancements**
<details>
<summary>multiple render camera supports for 3dsmax <a href="https://github.com/ynput/OpenPype/pull/5124">#5124</a></summary>
Supports for rendering with multiple cameras in 3dsmax
- [x] Add Batch Render Layers functions
- [x] Rewrite lib.rendersetting and lib.renderproduct
- [x] Add multi-camera options in creator.
- [x] Collector with batch render-layer when multi-camera enabled.
- [x] Add instance plugin for saving scene files with different cameras respectively by using subprocess
- [x] Refactor submit_max_deadline
- [x] Check with metadata.json in submit publish job
___
</details>
<details>
<summary>Fusion: new creator for image product type <a href="https://github.com/ynput/OpenPype/pull/6057">#6057</a></summary>
In many DCC `render` product type is expected to be sequence of files. This PR adds new explicit creator for `image` product type which is focused on single frame image. Workflows for both product types might be a bit different, this gives artists more granularity to choose better workflow.
___
</details>
### **🐛 Bug fixes**
<details>
<summary>Maya: Account and ignore free image planes. <a href="https://github.com/ynput/OpenPype/pull/5993">#5993</a></summary>
Free image planes do not have the `->` path separator, so we need to account for that.
___
</details>
<details>
<summary>Blender: Fix long names for instances <a href="https://github.com/ynput/OpenPype/pull/6070">#6070</a></summary>
Changed naming for instances to use only final part of the `folderPath`.
___
</details>
<details>
<summary>Traypublisher & Chore: Instance version on follow workfile version <a href="https://github.com/ynput/OpenPype/pull/6117">#6117</a></summary>
If `follow_workfile_version` is enabled but context does not have filled workfile version, a version on instance is used instead.
___
</details>
<details>
<summary>Substance Painter: Thumbnail errors with PBR Texture Set <a href="https://github.com/ynput/OpenPype/pull/6127">#6127</a></summary>
When publishing with PBR Metallic Roughness as Output Template, Emissive Map errors out because of the missing channel in the material and the map can't be generated in Substance Painter. This PR is to make sure `imagestance.data["publish"] = False` so that the related "empty" texture instance would be skipped to generate the output.
___
</details>
<details>
<summary>Transcoding: Fix reading image sequences through oiiotool <a href="https://github.com/ynput/OpenPype/pull/6129">#6129</a></summary>
When transcoding image sequences, the second image onwards includes the invalid xml line of `Reading path/to/file.exr` of the oiiotool output.This is most likely not the best solution, but it fixes the issue and illustrates the problem.Error:
```
ERROR:pyblish.plugin:Traceback (most recent call last):
File "C:\Users\tokejepsen\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process
runner(*args)
File "C:\Users\tokejepsen\OpenPype\openpype\plugins\publish\extract_color_transcode.py", line 152, in process
File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 1136, in convert_colorspace
input_info = get_oiio_info_for_input(input_path, logger=logger)
File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 124, in get_oiio_info_for_input
output.append(parse_oiio_xml_output(xml_text, logger=logger))
File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 276, in parse_oiio_xml_output
tree = xml.etree.ElementTree.fromstring(xml_string)
File "xml\etree\ElementTree.py", line 1347, in XML
xml.etree.ElementTree.ParseError: syntax error: line 1, column 0
Traceback (most recent call last):
File "C:\Users\tokejepsen\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process
runner(*args)
File "<string>", line 152, in process
File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 1136, in convert_colorspace
input_info = get_oiio_info_for_input(input_path, logger=logger)
File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 124, in get_oiio_info_for_input
output.append(parse_oiio_xml_output(xml_text, logger=logger))
File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 276, in parse_oiio_xml_output
tree = xml.etree.ElementTree.fromstring(xml_string)
File "xml\etree\ElementTree.py", line 1347, in XML
xml.etree.ElementTree.ParseError: syntax error: line 1, column 0
```
___
</details>
<details>
<summary>AYON: Remove 'IntegrateHeroVersion' conversion <a href="https://github.com/ynput/OpenPype/pull/6130">#6130</a></summary>
Remove settings conversion for `IntegrateHeroVersion`.
___
</details>
<details>
<summary>Chore tools: Make sure style object is not garbage collected <a href="https://github.com/ynput/OpenPype/pull/6136">#6136</a></summary>
Minor fix in tool utils to make sure style C++ object is not garbage collected when not stored into variable.
___
</details>
## [3.18.3](https://github.com/ynput/OpenPype/tree/3.18.3)

View file

@ -127,8 +127,9 @@ def isolate_objects(window, objects):
context = create_blender_context(selected=objects, window=window)
bpy.ops.view3d.view_axis(context, type="FRONT")
bpy.ops.view3d.localview(context)
with bpy.context.temp_override(**context):
bpy.ops.view3d.view_axis(type="FRONT")
bpy.ops.view3d.localview()
deselect_all()
@ -270,10 +271,12 @@ def _independent_window():
"""Create capture-window context."""
context = create_blender_context()
current_windows = set(bpy.context.window_manager.windows)
bpy.ops.wm.window_new(context)
window = list(set(bpy.context.window_manager.windows) - current_windows)[0]
context["window"] = window
try:
yield window
finally:
bpy.ops.wm.window_close(context)
with bpy.context.temp_override(**context):
bpy.ops.wm.window_new()
window = list(
set(bpy.context.window_manager.windows) - current_windows)[0]
context["window"] = window
try:
yield window
finally:
bpy.ops.wm.window_close()

View file

@ -67,7 +67,8 @@ class AudioLoader(plugin.AssetLoader):
oc = bpy.context.copy()
oc["area"] = window_manager.windows[-1].screen.areas[0]
bpy.ops.sequencer.sound_strip_add(oc, filepath=libpath, frame_start=1)
with bpy.context.temp_override(**oc):
bpy.ops.sequencer.sound_strip_add(filepath=libpath, frame_start=1)
window_manager.windows[-1].screen.areas[0].type = old_type
@ -156,17 +157,18 @@ class AudioLoader(plugin.AssetLoader):
oc = bpy.context.copy()
oc["area"] = window_manager.windows[-1].screen.areas[0]
# We deselect all sequencer strips, and then select the one we
# need to remove.
bpy.ops.sequencer.select_all(oc, action='DESELECT')
scene = bpy.context.scene
scene.sequence_editor.sequences_all[old_audio].select = True
with bpy.context.temp_override(**oc):
# We deselect all sequencer strips, and then select the one we
# need to remove.
bpy.ops.sequencer.select_all(action='DESELECT')
scene = bpy.context.scene
scene.sequence_editor.sequences_all[old_audio].select = True
bpy.ops.sequencer.delete(oc)
bpy.data.sounds.remove(bpy.data.sounds[old_audio])
bpy.ops.sequencer.delete()
bpy.data.sounds.remove(bpy.data.sounds[old_audio])
bpy.ops.sequencer.sound_strip_add(
oc, filepath=str(libpath), frame_start=1)
bpy.ops.sequencer.sound_strip_add(
filepath=str(libpath), frame_start=1)
window_manager.windows[-1].screen.areas[0].type = old_type
@ -205,12 +207,13 @@ class AudioLoader(plugin.AssetLoader):
oc = bpy.context.copy()
oc["area"] = window_manager.windows[-1].screen.areas[0]
# We deselect all sequencer strips, and then select the one we
# need to remove.
bpy.ops.sequencer.select_all(oc, action='DESELECT')
bpy.context.scene.sequence_editor.sequences_all[audio].select = True
bpy.ops.sequencer.delete(oc)
with bpy.context.temp_override(**oc):
# We deselect all sequencer strips, and then select the one we
# need to remove.
bpy.ops.sequencer.select_all(action='DESELECT')
scene = bpy.context.scene
scene.sequence_editor.sequences_all[audio].select = True
bpy.ops.sequencer.delete()
window_manager.windows[-1].screen.areas[0].type = old_type

View file

@ -102,7 +102,6 @@ class BlendLoader(plugin.AssetLoader):
# Link all the container children to the collection
for obj in container.children_recursive:
print(obj)
bpy.context.scene.collection.objects.link(obj)
# Remove the library from the blend file
@ -194,8 +193,20 @@ class BlendLoader(plugin.AssetLoader):
transform = asset_group.matrix_basis.copy()
old_data = dict(asset_group.get(AVALON_PROPERTY))
old_members = old_data.get("members", [])
parent = asset_group.parent
actions = {}
objects_with_anim = [
obj for obj in asset_group.children_recursive
if obj.animation_data]
for obj in objects_with_anim:
# Check if the object has an action and, if so, add it to a dict
# so we can restore it later. Save and restore the action only
# if it wasn't originally loaded from the current asset.
if obj.animation_data.action not in old_members:
actions[obj.name] = obj.animation_data.action
self.exec_remove(container)
asset_group, members = self._process_data(libpath, group_name)
@ -206,6 +217,11 @@ class BlendLoader(plugin.AssetLoader):
asset_group.matrix_basis = transform
asset_group.parent = parent
# Restore the actions
for obj in asset_group.children_recursive:
if obj.name in actions:
obj.animation_data.action = actions[obj.name]
# Restore the old data, but reset memebers, as they don't exist anymore
# This avoids a crash, because the memory addresses of those members
# are not valid anymore

View file

@ -55,13 +55,13 @@ class ExtractAnimationABC(
context = plugin.create_blender_context(
active=asset_group, selected=selected)
# We export the abc
bpy.ops.wm.alembic_export(
context,
filepath=filepath,
selected=True,
flatten=False
)
with bpy.context.temp_override(**context):
# We export the abc
bpy.ops.wm.alembic_export(
filepath=filepath,
selected=True,
flatten=False
)
plugin.deselect_all()

View file

@ -50,19 +50,19 @@ class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin):
scale_length = bpy.context.scene.unit_settings.scale_length
bpy.context.scene.unit_settings.scale_length = 0.01
# We export the fbx
bpy.ops.export_scene.fbx(
context,
filepath=filepath,
use_active_collection=False,
use_selection=True,
bake_anim_use_nla_strips=False,
bake_anim_use_all_actions=False,
add_leaf_bones=False,
armature_nodetype='ROOT',
object_types={'CAMERA'},
bake_anim_simplify_factor=0.0
)
with bpy.context.temp_override(**context):
# We export the fbx
bpy.ops.export_scene.fbx(
filepath=filepath,
use_active_collection=False,
use_selection=True,
bake_anim_use_nla_strips=False,
bake_anim_use_all_actions=False,
add_leaf_bones=False,
armature_nodetype='ROOT',
object_types={'CAMERA'},
bake_anim_simplify_factor=0.0
)
bpy.context.scene.unit_settings.scale_length = scale_length

View file

@ -57,15 +57,15 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin):
scale_length = bpy.context.scene.unit_settings.scale_length
bpy.context.scene.unit_settings.scale_length = 0.01
# We export the fbx
bpy.ops.export_scene.fbx(
context,
filepath=filepath,
use_active_collection=False,
use_selection=True,
mesh_smooth_type='FACE',
add_leaf_bones=False
)
with bpy.context.temp_override(**context):
# We export the fbx
bpy.ops.export_scene.fbx(
filepath=filepath,
use_active_collection=False,
use_selection=True,
mesh_smooth_type='FACE',
add_leaf_bones=False
)
bpy.context.scene.unit_settings.scale_length = scale_length

View file

@ -153,17 +153,20 @@ class ExtractAnimationFBX(
override = plugin.create_blender_context(
active=root, selected=[root, armature])
bpy.ops.export_scene.fbx(
override,
filepath=filepath,
use_active_collection=False,
use_selection=True,
bake_anim_use_nla_strips=False,
bake_anim_use_all_actions=False,
add_leaf_bones=False,
armature_nodetype='ROOT',
object_types={'EMPTY', 'ARMATURE'}
)
with bpy.context.temp_override(**override):
# We export the fbx
bpy.ops.export_scene.fbx(
filepath=filepath,
use_active_collection=False,
use_selection=True,
bake_anim_use_nla_strips=False,
bake_anim_use_all_actions=False,
add_leaf_bones=False,
armature_nodetype='ROOT',
object_types={'EMPTY', 'ARMATURE'}
)
armature.name = armature_name
asset_group.name = asset_group_name
root.select_set(True)

View file

@ -80,17 +80,18 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
override = plugin.create_blender_context(
active=asset, selected=[asset, obj])
bpy.ops.export_scene.fbx(
override,
filepath=filepath,
use_active_collection=False,
use_selection=True,
bake_anim_use_nla_strips=False,
bake_anim_use_all_actions=False,
add_leaf_bones=False,
armature_nodetype='ROOT',
object_types={'EMPTY', 'ARMATURE'}
)
with bpy.context.temp_override(**override):
# We export the fbx
bpy.ops.export_scene.fbx(
filepath=filepath,
use_active_collection=False,
use_selection=True,
bake_anim_use_nla_strips=False,
bake_anim_use_all_actions=False,
add_leaf_bones=False,
armature_nodetype='ROOT',
object_types={'EMPTY', 'ARMATURE'}
)
obj.name = armature_name
asset.name = asset_group_name
asset.select_set(False)

View file

@ -9,6 +9,8 @@ class CollectClipEffects(pyblish.api.InstancePlugin):
label = "Collect Clip Effects Instances"
families = ["clip"]
effect_categories = []
def process(self, instance):
family = "effect"
effects = {}
@ -70,29 +72,62 @@ class CollectClipEffects(pyblish.api.InstancePlugin):
subset_split.insert(0, "effect")
name = "".join(subset_split)
effect_categories = {
x["name"]: x["effect_classes"] for x in self.effect_categories
}
# create new instance and inherit data
data = {}
for key, value in instance.data.items():
if "clipEffectItems" in key:
category_by_effect = {"": ""}
for key, values in effect_categories.items():
for cls in values:
category_by_effect[cls] = key
effects_categorized = {k: {} for k in effect_categories.keys()}
effects_categorized[""] = {}
for key, value in effects.items():
if key == "assignTo":
continue
data[key] = value
# change names
data["subset"] = name
data["family"] = family
data["families"] = [family]
data["name"] = data["subset"] + "_" + data["asset"]
data["label"] = "{} - {}".format(
data['asset'], data["subset"]
)
data["effects"] = effects
# Some classes can have a number in them. Like Text2.
found_cls = ""
for cls in category_by_effect.keys():
if cls in value["class"]:
found_cls = cls
# create new instance
_instance = instance.context.create_instance(**data)
self.log.info("Created instance `{}`".format(_instance))
self.log.debug("instance.data `{}`".format(_instance.data))
effects_categorized[category_by_effect[found_cls]][key] = value
categories = list(effects_categorized.keys())
for category in categories:
if not effects_categorized[category]:
effects_categorized.pop(category)
continue
effects_categorized[category]["assignTo"] = effects["assignTo"]
for category, effects in effects_categorized.items():
name = "".join(subset_split)
name += category.capitalize()
# create new instance and inherit data
data = {}
for key, value in instance.data.items():
if "clipEffectItems" in key:
continue
data[key] = value
# change names
data["subset"] = name
data["family"] = family
data["families"] = [family]
data["name"] = data["subset"] + "_" + data["asset"]
data["label"] = "{} - {}".format(
data['asset'], data["subset"]
)
data["effects"] = effects
# create new instance
_instance = instance.context.create_instance(**data)
self.log.info("Created instance `{}`".format(_instance))
self.log.debug("instance.data `{}`".format(_instance.data))
def test_overlap(self, effect_t_in, effect_t_out):
covering_exp = bool(

View file

@ -16,8 +16,9 @@ class FbxLoader(load.LoaderPlugin):
order = -10
families = ["staticMesh", "fbx"]
representations = ["fbx"]
families = ["*"]
representations = ["*"]
extensions = {"fbx"}
def load(self, context, name=None, namespace=None, data=None):

View file

@ -2778,9 +2778,37 @@ def bake_to_world_space(nodes,
list: The newly created and baked node names.
"""
@contextlib.contextmanager
def _unlock_attr(attr):
"""Unlock attribute during context if it is locked"""
if not cmds.getAttr(attr, lock=True):
# If not locked, do nothing
yield
return
try:
cmds.setAttr(attr, lock=False)
yield
finally:
cmds.setAttr(attr, lock=True)
def _get_attrs(node):
"""Workaround for buggy shape attribute listing with listAttr"""
"""Workaround for buggy shape attribute listing with listAttr
This will only return keyable settable attributes that have an
incoming connections (those that have a reason to be baked).
Technically this *may* fail to return attributes driven by complex
expressions for which maya makes no connections, e.g. doing actual
`setAttr` calls in expressions.
Arguments:
node (str): The node to list attributes for.
Returns:
list: Keyable attributes with incoming connections.
The attribute may be locked.
"""
attrs = cmds.listAttr(node,
write=True,
scalar=True,
@ -2805,14 +2833,14 @@ def bake_to_world_space(nodes,
return valid_attrs
transform_attrs = set(["t", "r", "s",
"tx", "ty", "tz",
"rx", "ry", "rz",
"sx", "sy", "sz"])
transform_attrs = {"t", "r", "s",
"tx", "ty", "tz",
"rx", "ry", "rz",
"sx", "sy", "sz"}
world_space_nodes = []
with delete_after() as delete_bin:
with ExitStack() as stack:
delete_bin = stack.enter_context(delete_after())
# Create the duplicate nodes that are in world-space connected to
# the originals
for node in nodes:
@ -2824,23 +2852,26 @@ def bake_to_world_space(nodes,
name=new_name,
renameChildren=True)[0] # noqa
# Connect all attributes on the node except for transform
# attributes
attrs = _get_attrs(node)
attrs = set(attrs) - transform_attrs if attrs else []
# Parent new node to world
if cmds.listRelatives(new_node, parent=True):
new_node = cmds.parent(new_node, world=True)[0]
# Temporarily unlock and passthrough connect all attributes
# so we can bake them over time
# Skip transform attributes because we will constrain them later
attrs = set(_get_attrs(node)) - transform_attrs
for attr in attrs:
orig_node_attr = '{0}.{1}'.format(node, attr)
new_node_attr = '{0}.{1}'.format(new_node, attr)
# unlock to avoid connection errors
cmds.setAttr(new_node_attr, lock=False)
orig_node_attr = "{}.{}".format(node, attr)
new_node_attr = "{}.{}".format(new_node, attr)
# unlock during context to avoid connection errors
stack.enter_context(_unlock_attr(new_node_attr))
cmds.connectAttr(orig_node_attr,
new_node_attr,
force=True)
# If shapes are also baked then connect those keyable attributes
# If shapes are also baked then also temporarily unlock and
# passthrough connect all shape attributes for baking
if shape:
children_shapes = cmds.listRelatives(new_node,
children=True,
@ -2855,25 +2886,19 @@ def bake_to_world_space(nodes,
children_shapes):
attrs = _get_attrs(orig_shape)
for attr in attrs:
orig_node_attr = '{0}.{1}'.format(orig_shape, attr)
new_node_attr = '{0}.{1}'.format(new_shape, attr)
# unlock to avoid connection errors
cmds.setAttr(new_node_attr, lock=False)
orig_node_attr = "{}.{}".format(orig_shape, attr)
new_node_attr = "{}.{}".format(new_shape, attr)
# unlock during context to avoid connection errors
stack.enter_context(_unlock_attr(new_node_attr))
cmds.connectAttr(orig_node_attr,
new_node_attr,
force=True)
# Parent to world
if cmds.listRelatives(new_node, parent=True):
new_node = cmds.parent(new_node, world=True)[0]
# Unlock transform attributes so constraint can be created
# Constraint transforms
for attr in transform_attrs:
cmds.setAttr('{0}.{1}'.format(new_node, attr), lock=False)
# Constraints
transform_attr = "{}.{}".format(new_node, attr)
stack.enter_context(_unlock_attr(transform_attr))
delete_bin.extend(cmds.parentConstraint(node, new_node, mo=False))
delete_bin.extend(cmds.scaleConstraint(node, new_node, mo=False))

View file

@ -265,13 +265,16 @@ def transfer_image_planes(source_cameras, target_cameras,
try:
for source_camera, target_camera in zip(source_cameras,
target_cameras):
image_planes = cmds.listConnections(source_camera,
image_plane_plug = "{}.imagePlane".format(source_camera)
image_planes = cmds.listConnections(image_plane_plug,
source=True,
destination=False,
type="imagePlane") or []
# Split of the parent path they are attached - we want
# the image plane node name.
# the image plane node name if attached to a camera.
# TODO: Does this still mean the image plane name is unique?
image_planes = [x.split("->", 1)[1] for x in image_planes]
image_planes = [x.split("->", 1)[-1] for x in image_planes]
if not image_planes:
continue
@ -282,7 +285,7 @@ def transfer_image_planes(source_cameras, target_cameras,
if source_camera == target_camera:
continue
_attach_image_plane(target_camera, image_plane)
else: # explicitly dettaching image planes
else: # explicitly detach image planes
cmds.imagePlane(image_plane, edit=True, detach=True)
originals[source_camera].append(image_plane)
yield

View file

@ -44,17 +44,17 @@ XML_CHAR_REF_REGEX_HEX = re.compile(r"&#x?[0-9a-fA-F]+;")
ARRAY_TYPE_REGEX = re.compile(r"^(int|float|string)\[\d+\]$")
IMAGE_EXTENSIONS = {
".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", ".cal",
".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", ".fits",
".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", ".icer",
".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2",
".jng", ".jpeg", ".jpeg-ls", ".jpeg", ".2000", ".jpg", ".xr",
".jpeg", ".xt", ".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd",
".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf",
".pictor", ".png", ".psd", ".psb", ".psp", ".qtvr", ".ras",
".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep",
".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf",
".xpm", ".xwd"
".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave",
".cal", ".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr",
".fits", ".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc",
".icer", ".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2",
".jng", ".jpeg", ".jpeg-ls", ".jpeg-hdr", ".2000", ".jpg",
".kra", ".logluv", ".mng", ".miff", ".nrrd", ".ora",
".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf",
".pictor", ".png", ".psd", ".psb", ".psp", ".qtvr",
".ras", ".rgbe", ".sgi", ".tga",
".tif", ".tiff", ".tiff/ep", ".tiff/it", ".ufo", ".ufp",
".wbmp", ".webp", ".xr", ".xt", ".xbm", ".xcf", ".xpm", ".xwd"
}
VIDEO_EXTENSIONS = {
@ -110,8 +110,9 @@ def get_oiio_info_for_input(filepath, logger=None, subimages=False):
if line == "</ImageSpec>":
subimages_lines.append(lines)
lines = []
xml_started = False
if not xml_started:
if not subimages_lines:
raise ValueError(
"Failed to read input file \"{}\".\nOutput:\n{}".format(
filepath, output

View file

@ -542,7 +542,8 @@ def _load_modules():
module_dirs.insert(0, current_dir)
addons_dir = os.path.join(os.path.dirname(current_dir), "addons")
module_dirs.append(addons_dir)
if os.path.exists(addons_dir):
module_dirs.append(addons_dir)
ignored_host_names = set(IGNORED_HOSTS_IN_AYON)
ignored_current_dir_filenames = set(IGNORED_DEFAULT_FILENAMES)

View file

@ -200,9 +200,15 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
self._fill_task_data(instance, project_task_types, anatomy_data)
# Define version
version_number = None
if self.follow_workfile_version:
version_number = context.data("version")
else:
# Even if 'follow_workfile_version' is enabled, it may not be set
# because workfile version was not collected to 'context.data'
# - that can happen e.g. in 'traypublisher' or other hosts without
# a workfile
if version_number is None:
version_number = instance.data.get("version")
# use latest version (+1) if already any exist
@ -404,9 +410,9 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
"""
hierarchy_queue = collections.deque()
hierarchy_queue.append(hierarchy_context)
hierarchy_queue.append(copy.deepcopy(hierarchy_context))
while hierarchy_queue:
item = hierarchy_context.popleft()
item = hierarchy_queue.popleft()
if asset_name in item:
return item[asset_name].get("tasks") or {}

View file

@ -30,8 +30,7 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin):
if not AYON_SERVER_ENABLED:
return
hierarchy_context = context.data.get("hierarchyContext")
if not hierarchy_context:
if not context.data.get("hierarchyContext"):
self.log.debug("Skipping ExtractHierarchyToAYON")
return

View file

@ -1291,12 +1291,6 @@ def _convert_global_project_settings(ayon_settings, output, default_settings):
for extract_burnin_def in extract_burnin_defs
}
ayon_integrate_hero = ayon_publish["IntegrateHeroVersion"]
for profile in ayon_integrate_hero["template_name_profiles"]:
if "product_types" not in profile:
break
profile["families"] = profile.pop("product_types")
if "IntegrateProductGroup" in ayon_publish:
subset_group = ayon_publish.pop("IntegrateProductGroup")
subset_group_profiles = subset_group.pop("product_grouping_profiles")

View file

@ -91,7 +91,8 @@ def set_style_property(widget, property_name, property_value):
if cur_value == property_value:
return
widget.setProperty(property_name, property_value)
widget.style().polish(widget)
style = widget.style()
style.polish(widget)
def paint_image_with_color(image, color):

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
__version__ = "3.18.4-nightly.1"
__version__ = "3.18.5-nightly.3"

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
version = "3.18.3" # OpenPype
version = "3.18.4" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team <info@openpype.io>"]
license = "MIT License"

View file

@ -307,9 +307,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--nukeassist"],
"darwin": [],
"linux": []
"linux": ["--nukeassist"]
},
"environment": "{}",
"use_python_2": false
@ -329,9 +329,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--nukeassist"],
"darwin": [],
"linux": []
"linux": ["--nukeassist"]
},
"environment": "{}",
"use_python_2": false
@ -351,9 +351,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--nukeassist"],
"darwin": [],
"linux": []
"linux": ["--nukeassist"]
},
"environment": "{}",
"use_python_2": false
@ -382,9 +382,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--nukex"],
"darwin": [],
"linux": []
"linux": ["--nukex"]
},
"environment": "{}",
"use_python_2": false
@ -404,9 +404,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--nukex"],
"darwin": [],
"linux": []
"linux": ["--nukex"]
},
"environment": "{}",
"use_python_2": false
@ -426,9 +426,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--nukex"],
"darwin": [],
"linux": []
"linux": ["--nukex"]
},
"environment": "{}",
"use_python_2": false
@ -457,9 +457,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--studio"],
"darwin": [],
"linux": []
"linux": ["--studio"]
},
"environment": "{}",
"use_python_2": false
@ -479,9 +479,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--studio"],
"darwin": [],
"linux": []
"linux": ["--studio"]
},
"environment": "{}",
"use_python_2": false
@ -501,9 +501,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--studio"],
"darwin": [],
"linux": []
"linux": ["--studio"]
},
"environment": "{}",
"use_python_2": false
@ -532,9 +532,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--hiero"],
"darwin": [],
"linux": []
"linux": ["--hiero"]
},
"environment": "{}",
"use_python_2": false
@ -554,9 +554,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--hiero"],
"darwin": [],
"linux": []
"linux": ["--hiero"]
},
"environment": "{}",
"use_python_2": false
@ -576,9 +576,9 @@
]
},
"arguments": {
"windows": [],
"windows": ["--hiero"],
"darwin": [],
"linux": []
"linux": ["--hiero"]
},
"environment": "{}",
"use_python_2": false

View file

@ -1 +1 @@
__version__ = "0.1.3"
__version__ = "0.1.4"

View file

@ -87,7 +87,7 @@ class MayaSubmitDeadlineModel(BaseSettingsModel):
title="Disable Strict Error Check profiles"
)
@validator("limit", "scene_patches")
@validator("scene_patches")
def validate_unique_names(cls, value):
ensure_unique_names(value)
return value

View file

@ -1 +1 @@
__version__ = "0.1.6"
__version__ = "0.1.7"

View file

@ -1,5 +1,7 @@
from pydantic import Field
from ayon_server.settings import BaseSettingsModel
from pydantic import Field, validator
from ayon_server.settings import (
BaseSettingsModel, ensure_unique_names, normalize_name
)
class CollectInstanceVersionModel(BaseSettingsModel):
@ -9,6 +11,30 @@ class CollectInstanceVersionModel(BaseSettingsModel):
)
class CollectClipEffectsDefModel(BaseSettingsModel):
_layout = "expanded"
name: str = Field("", title="Name")
effect_classes: list[str] = Field(
default_factory=list, title="Effect Classes"
)
@validator("name")
def validate_name(cls, value):
"""Ensure name does not contain weird characters"""
return normalize_name(value)
class CollectClipEffectsModel(BaseSettingsModel):
effect_categories: list[CollectClipEffectsDefModel] = Field(
default_factory=list, title="Effect Categories"
)
@validator("effect_categories")
def validate_unique_outputs(cls, value):
ensure_unique_names(value)
return value
class ExtractReviewCutUpVideoModel(BaseSettingsModel):
enabled: bool = Field(
True,
@ -25,6 +51,10 @@ class PublishPuginsModel(BaseSettingsModel):
default_factory=CollectInstanceVersionModel,
title="Collect Instance Version"
)
CollectClipEffects: CollectClipEffectsModel = Field(
default_factory=CollectClipEffectsModel,
title="Collect Clip Effects"
)
"""# TODO: enhance settings with host api:
Rename class name and plugin name
to match title (it makes more sense)
@ -44,5 +74,8 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = {
"tags_addition": [
"review"
]
},
"CollectClipEffectsModel": {
"effect_categories": []
}
}

View file

@ -1 +1 @@
__version__ = "0.1.1"
__version__ = "0.1.2"