mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into enhancement/OP-6191_3dsmax-switching-versions-of-maxScene-maintain-parentagelinks
This commit is contained in:
commit
199d099a59
28 changed files with 560 additions and 180 deletions
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -35,6 +35,8 @@ body:
|
|||
label: Version
|
||||
description: What version are you running? Look to OpenPype Tray
|
||||
options:
|
||||
- 3.16.7-nightly.1
|
||||
- 3.16.6
|
||||
- 3.16.6-nightly.1
|
||||
- 3.16.5
|
||||
- 3.16.5-nightly.5
|
||||
|
|
@ -133,8 +135,6 @@ body:
|
|||
- 3.14.9
|
||||
- 3.14.9-nightly.5
|
||||
- 3.14.9-nightly.4
|
||||
- 3.14.9-nightly.3
|
||||
- 3.14.9-nightly.2
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
|||
237
CHANGELOG.md
237
CHANGELOG.md
|
|
@ -1,6 +1,243 @@
|
|||
# Changelog
|
||||
|
||||
|
||||
## [3.16.6](https://github.com/ynput/OpenPype/tree/3.16.6)
|
||||
|
||||
|
||||
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.16.5...3.16.6)
|
||||
|
||||
### **🆕 New features**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Workfiles tool: Refactor workfiles tool (for AYON) <a href="https://github.com/ynput/OpenPype/pull/5550">#5550</a></summary>
|
||||
|
||||
Refactored workfiles tool to new tool. Separated backend and frontend logic. Refactored logic is AYON-centric and is used only in AYON mode, so it does not affect OpenPype.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AfterEffects: added validator for missing files in FootageItems <a href="https://github.com/ynput/OpenPype/pull/5590">#5590</a></summary>
|
||||
|
||||
Published composition in AE could contain multiple FootageItems as a layers. If FootageItem contains imported file and it doesn't exist, render triggered by Publish process will silently fail and no output is generated. This could cause failure later in the process with unclear reason. (In `ExtractReview`).This PR adds validation to protect from this.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **🚀 Enhancements**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Yeti Cache Include viewport preview settings from source <a href="https://github.com/ynput/OpenPype/pull/5561">#5561</a></summary>
|
||||
|
||||
When publishing and loading yeti caches persist the display output and preview colors + settings to ensure consistency in the view
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Houdini: validate colorspace in review rop <a href="https://github.com/ynput/OpenPype/pull/5322">#5322</a></summary>
|
||||
|
||||
Adding a validator that checks if 'OCIO Colorspace' parameter on review rop was set to a valid value.It is a step towards managing colorspace in review ropvalid values are the ones in the dropdown menuthis validator also provides some helper actions This PR is related to #4836 and #4833
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Colorspace: adding abstraction of publishing related functions <a href="https://github.com/ynput/OpenPype/pull/5497">#5497</a></summary>
|
||||
|
||||
The functionality of Colorspace has been abstracted for greater usability.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke: removing redundant workfile colorspace attributes <a href="https://github.com/ynput/OpenPype/pull/5580">#5580</a></summary>
|
||||
|
||||
Nuke root workfile colorspace data type knobs are long time configured automatically via config roles or the default values are also working well. Therefore there is no need for pipeline managed knobs.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Ftrack: Less verbose logs for Ftrack integration in artist facing logs <a href="https://github.com/ynput/OpenPype/pull/5596">#5596</a></summary>
|
||||
|
||||
- Reduce artist-facing logs for component integration for Ftrack
|
||||
- Avoid "Comment is not set" log in artist facing report for Kitsu and Ftrack
|
||||
- Remove info log about `ffprobe` inspecting a file (changed to debug log)
|
||||
- interesting to see however that it ffprobes the same jpeg twice - but maybe once for thumbnail?
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **🐛 Bug fixes**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Fix rig validators for new out_SET and controls_SET names <a href="https://github.com/ynput/OpenPype/pull/5595">#5595</a></summary>
|
||||
|
||||
Fix usage of `out_SET` and `controls_SET` since #5310 because they can now be prefixed by the subset name.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>TrayPublisher: set default frame values to sequential data <a href="https://github.com/ynput/OpenPype/pull/5530">#5530</a></summary>
|
||||
|
||||
We are inheriting default frame handles and fps data either from project or setting them to 0. This is just for case a production will decide not to injest the sequential representations with asset based metadata.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Publisher: Screenshot opacity value fix <a href="https://github.com/ynput/OpenPype/pull/5576">#5576</a></summary>
|
||||
|
||||
Fix opacity value.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AfterEffects: fix imports of image sequences <a href="https://github.com/ynput/OpenPype/pull/5581">#5581</a></summary>
|
||||
|
||||
#4602 broke imports of image sequences.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Fix representation context conversion <a href="https://github.com/ynput/OpenPype/pull/5591">#5591</a></summary>
|
||||
|
||||
Do not fix `"folder"` key in representation context until it is needed.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>ayon-nuke: default factory to lists <a href="https://github.com/ynput/OpenPype/pull/5594">#5594</a></summary>
|
||||
|
||||
Default factory were missing in settings schemas for complicated objects like lists and it was causing settings to be failing saving.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Fix look assigner showing no asset if 'not found' representations are present <a href="https://github.com/ynput/OpenPype/pull/5597">#5597</a></summary>
|
||||
|
||||
Fix Maya Look assigner failing to show any content if it finds an invalid container for which it can't find the asset in the current project. (This can happen when e.g. loading something from a library project).There was logic already to avoid this but there was a bug where it used variable `_id` which did not exist and likely had to be `asset_id`.I've fixed that and improved the logged message a bit, e.g.:
|
||||
```
|
||||
// Warning: openpype.hosts.maya.tools.mayalookassigner.commands : Id found on 22 nodes for which no asset is found database, skipping '641d78ec85c3c5b102e836b0'
|
||||
```
|
||||
Example not found representation in Loader:The issue isn't necessarily related to NOT FOUND representations but in essence boils down to finding nodes with asset ids that do not exist in the current project which could very well just be local meshes in your scene.**Note:**I've excluded logging the nodes themselves because that tends to be a very long list of nodes. Only downside to removing that is that it's unclear which nodes are related to that `id`. If there are any ideas on how to still provide a concise informational message about that that'd be great so I could add it. Things I had considered:
|
||||
- Report the containers, issue here is that it's about asset ids on nodes which don't HAVE to be in containers - it could be local geometry
|
||||
- Report the namespaces, issue here is that it could be nodes without namespaces (plus potentially not about ALL nodes in a namespace)
|
||||
- Report the short names of the nodes; it's shorter and readable but still likely a lot of nodes.@tokejepsen @LiborBatek any other ideas?
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Photoshop: fixed blank Flatten image <a href="https://github.com/ynput/OpenPype/pull/5600">#5600</a></summary>
|
||||
|
||||
Flatten image is simplified publishing approach where all visible layers are "flatten" and published together. This image could be used as a reference etc.This is implemented by auto creator which wasn't updated after first publish. This would result in missing newly created layers after `auto_image` instance was created.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Blender: Remove Hardcoded Subset Name for Reviews <a href="https://github.com/ynput/OpenPype/pull/5603">#5603</a></summary>
|
||||
|
||||
Fixes hardcoded subset name for Reviews in Blender.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>TVPaint: Fix tool callbacks <a href="https://github.com/ynput/OpenPype/pull/5608">#5608</a></summary>
|
||||
|
||||
Do not wait for callback to finish.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **🔀 Refactored code**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Chore: Remove unused variables and cleanup <a href="https://github.com/ynput/OpenPype/pull/5588">#5588</a></summary>
|
||||
|
||||
Removing some unused variables. In some cases the unused variables _seemed like they should've been used - maybe?_ so please **double check the code whether it doesn't hint to an already existing bug**.Also tweaked some other small bugs in code + tweaked logging levels.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **Merged pull requests**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Chore: Loader log deprecation warning for 'fname' attribute <a href="https://github.com/ynput/OpenPype/pull/5587">#5587</a></summary>
|
||||
|
||||
Since https://github.com/ynput/OpenPype/pull/4602 the `fname` attribute on the `LoaderPlugin` should've been deprecated and set for removal over time. However, no deprecation warning was logged whatsoever and thus one usage appears to have sneaked in (fixed with this PR) and a new one tried to sneak in with a recent PR
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.16.5](https://github.com/ynput/OpenPype/tree/3.16.5)
|
||||
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ExtensionManifest Version="8.0" ExtensionBundleId="com.openpype.AE.panel" ExtensionBundleVersion="1.0.26"
|
||||
<ExtensionManifest Version="8.0" ExtensionBundleId="com.openpype.AE.panel" ExtensionBundleVersion="1.0.27"
|
||||
ExtensionBundleName="com.openpype.AE.panel" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ExtensionList>
|
||||
<Extension Id="com.openpype.AE.panel" Version="1.0" />
|
||||
|
|
@ -10,22 +10,22 @@
|
|||
<!-- Photoshop -->
|
||||
<!--<Host Name="PHXS" Version="[14.0,19.0]" /> -->
|
||||
<!-- <Host Name="PHSP" Version="[14.0,19.0]" /> -->
|
||||
|
||||
|
||||
<!-- Illustrator -->
|
||||
<!-- <Host Name="ILST" Version="[18.0,22.0]" /> -->
|
||||
|
||||
|
||||
<!-- InDesign -->
|
||||
<!-- <Host Name="IDSN" Version="[10.0,13.0]" /> -->
|
||||
|
||||
<!-- <Host Name="IDSN" Version="[10.0,13.0]" /> -->
|
||||
|
||||
<!-- Premiere -->
|
||||
<!-- <Host Name="PPRO" Version="[8.0,12.0]" /> -->
|
||||
|
||||
|
||||
<!-- AfterEffects -->
|
||||
<Host Name="AEFT" Version="[13.0,99.0]" />
|
||||
|
||||
<!-- PRELUDE -->
|
||||
|
||||
<!-- PRELUDE -->
|
||||
<!-- <Host Name="PRLD" Version="[3.0,7.0]" /> -->
|
||||
|
||||
|
||||
<!-- FLASH Pro -->
|
||||
<!-- <Host Name="FLPR" Version="[14.0,18.0]" /> -->
|
||||
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
<Height>550</Height>
|
||||
<Width>400</Width>
|
||||
</MaxSize>-->
|
||||
|
||||
|
||||
</Geometry>
|
||||
<Icons>
|
||||
<Icon Type="Normal">./icons/iconNormal.png</Icon>
|
||||
|
|
@ -71,9 +71,9 @@
|
|||
<Icon Type="Disabled">./icons/iconDisabled.png</Icon>
|
||||
<Icon Type="DarkNormal">./icons/iconDarkNormal.png</Icon>
|
||||
<Icon Type="DarkRollOver">./icons/iconDarkRollover.png</Icon>
|
||||
</Icons>
|
||||
</Icons>
|
||||
</UI>
|
||||
</DispatchInfo>
|
||||
</Extension>
|
||||
</DispatchInfoList>
|
||||
</ExtensionManifest>
|
||||
</ExtensionManifest>
|
||||
|
|
|
|||
|
|
@ -215,6 +215,8 @@ function _getItem(item, comps, folders, footages){
|
|||
* Refactor
|
||||
*/
|
||||
var item_type = '';
|
||||
var path = '';
|
||||
var containing_comps = [];
|
||||
if (item instanceof FolderItem){
|
||||
item_type = 'folder';
|
||||
if (!folders){
|
||||
|
|
@ -222,10 +224,18 @@ function _getItem(item, comps, folders, footages){
|
|||
}
|
||||
}
|
||||
if (item instanceof FootageItem){
|
||||
item_type = 'footage';
|
||||
if (!footages){
|
||||
return "{}";
|
||||
}
|
||||
item_type = 'footage';
|
||||
if (item.file){
|
||||
path = item.file.fsName;
|
||||
}
|
||||
if (item.usedIn){
|
||||
for (j = 0; j < item.usedIn.length; ++j){
|
||||
containing_comps.push(item.usedIn[j].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item instanceof CompItem){
|
||||
item_type = 'comp';
|
||||
|
|
@ -236,7 +246,9 @@ function _getItem(item, comps, folders, footages){
|
|||
|
||||
var item = {"name": item.name,
|
||||
"id": item.id,
|
||||
"type": item_type};
|
||||
"type": item_type,
|
||||
"path": path,
|
||||
"containing_comps": containing_comps};
|
||||
return JSON.stringify(item);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ class AEItem(object):
|
|||
height = attr.ib(default=None)
|
||||
is_placeholder = attr.ib(default=False)
|
||||
uuid = attr.ib(default=False)
|
||||
path = attr.ib(default=False) # path to FootageItem to validate
|
||||
# list of composition Footage is in
|
||||
containing_comps = attr.ib(factory=list)
|
||||
|
||||
|
||||
class AfterEffectsServerStub():
|
||||
|
|
@ -704,7 +707,10 @@ class AfterEffectsServerStub():
|
|||
d.get("instance_id"),
|
||||
d.get("width"),
|
||||
d.get("height"),
|
||||
d.get("is_placeholder"))
|
||||
d.get("is_placeholder"),
|
||||
d.get("uuid"),
|
||||
d.get("path"),
|
||||
d.get("containing_comps"),)
|
||||
|
||||
ret.append(item)
|
||||
return ret
|
||||
|
|
|
|||
|
|
@ -31,13 +31,8 @@ class FileLoader(api.AfterEffectsLoader):
|
|||
|
||||
path = self.filepath_from_context(context)
|
||||
|
||||
repr_cont = context["representation"]["context"]
|
||||
if "#" not in path:
|
||||
frame = repr_cont.get("frame")
|
||||
if frame:
|
||||
padding = len(frame)
|
||||
path = path.replace(frame, "#" * padding)
|
||||
import_options['sequence'] = True
|
||||
if len(context["representation"]["files"]) > 1:
|
||||
import_options['sequence'] = True
|
||||
|
||||
if not path:
|
||||
repr_id = context["representation"]["_id"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<error id="main">
|
||||
<title>Footage item missing</title>
|
||||
<description>
|
||||
## Footage item missing
|
||||
|
||||
FootageItem `{name}` contains missing `{path}`. Render will not produce any frames and AE will stop react to any integration
|
||||
### How to repair?
|
||||
|
||||
Remove `{name}` or provide missing file.
|
||||
</description>
|
||||
</error>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Validate presence of footage items in composition
|
||||
Requires:
|
||||
"""
|
||||
import os
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from openpype.pipeline import (
|
||||
PublishXmlValidationError
|
||||
)
|
||||
from openpype.hosts.aftereffects.api import get_stub
|
||||
|
||||
|
||||
class ValidateFootageItems(pyblish.api.InstancePlugin):
|
||||
"""
|
||||
Validates if FootageItems contained in composition exist.
|
||||
|
||||
AE fails silently and doesn't render anything if footage item file is
|
||||
missing. This will result in nonresponsiveness of AE UI as it expects
|
||||
reaction from user, but it will not provide dialog.
|
||||
This validator tries to check existence of the files.
|
||||
It will not protect from missing frame in multiframes though
|
||||
(as AE api doesn't provide this information and it cannot be told how many
|
||||
frames should be there easily). Missing frame is replaced by placeholder.
|
||||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Validate Footage Items"
|
||||
families = ["render.farm", "render.local", "render"]
|
||||
hosts = ["aftereffects"]
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
"""Plugin entry point."""
|
||||
|
||||
comp_id = instance.data["comp_id"]
|
||||
for footage_item in get_stub().get_items(comps=False, folders=False,
|
||||
footages=True):
|
||||
self.log.info(footage_item)
|
||||
if comp_id not in footage_item.containing_comps:
|
||||
continue
|
||||
|
||||
path = footage_item.path
|
||||
if path and not os.path.exists(path):
|
||||
msg = f"File {path} not found."
|
||||
formatting = {"name": footage_item.name, "path": path}
|
||||
raise PublishXmlValidationError(self, msg,
|
||||
formatting_data=formatting)
|
||||
|
|
@ -39,15 +39,11 @@ class CollectReview(pyblish.api.InstancePlugin):
|
|||
]
|
||||
|
||||
if not instance.data.get("remove"):
|
||||
|
||||
task = instance.context.data["task"]
|
||||
|
||||
# Store focal length in `burninDataMembers`
|
||||
burninData = instance.data.setdefault("burninDataMembers", {})
|
||||
burninData["focalLength"] = focal_length
|
||||
|
||||
instance.data.update({
|
||||
"subset": f"{task}Review",
|
||||
"review_camera": camera,
|
||||
"frameStart": instance.context.data["frameStart"],
|
||||
"frameEnd": instance.context.data["frameEnd"],
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from openpype.lib import BoolDef
|
|||
import openpype.hosts.photoshop.api as api
|
||||
from openpype.hosts.photoshop.lib import PSAutoCreator
|
||||
from openpype.pipeline.create import get_subset_name
|
||||
from openpype.lib import prepare_template_data
|
||||
from openpype.client import get_asset_by_name
|
||||
|
||||
|
||||
|
|
@ -37,19 +38,14 @@ class AutoImageCreator(PSAutoCreator):
|
|||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
|
||||
if existing_instance is None:
|
||||
subset_name = get_subset_name(
|
||||
self.family, self.default_variant, task_name, asset_doc,
|
||||
subset_name = self.get_subset_name(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
)
|
||||
|
||||
publishable_ids = [layer.id for layer in api.stub().get_layers()
|
||||
if layer.visible]
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
# ids are "virtual" layers, won't get grouped as 'members' do
|
||||
# same difference in color coded layers in WP
|
||||
"ids": publishable_ids
|
||||
}
|
||||
|
||||
if not self.active_on_create:
|
||||
|
|
@ -69,8 +65,8 @@ class AutoImageCreator(PSAutoCreator):
|
|||
existing_instance["asset"] != asset_name
|
||||
or existing_instance["task"] != task_name
|
||||
):
|
||||
subset_name = get_subset_name(
|
||||
self.family, self.default_variant, task_name, asset_doc,
|
||||
subset_name = self.get_subset_name(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
)
|
||||
|
||||
|
|
@ -118,3 +114,29 @@ class AutoImageCreator(PSAutoCreator):
|
|||
Artist might disable this instance from publishing or from creating
|
||||
review for it though.
|
||||
"""
|
||||
|
||||
def get_subset_name(
|
||||
self,
|
||||
variant,
|
||||
task_name,
|
||||
asset_doc,
|
||||
project_name,
|
||||
host_name=None,
|
||||
instance=None
|
||||
):
|
||||
dynamic_data = prepare_template_data({"layer": "{layer}"})
|
||||
subset_name = get_subset_name(
|
||||
self.family, variant, task_name, asset_doc,
|
||||
project_name, host_name, dynamic_data=dynamic_data
|
||||
)
|
||||
return self._clean_subset_name(subset_name)
|
||||
|
||||
def _clean_subset_name(self, subset_name):
|
||||
"""Clean all variants leftover {layer} from subset name."""
|
||||
dynamic_data = prepare_template_data({"layer": "{layer}"})
|
||||
for value in dynamic_data.values():
|
||||
if value in subset_name:
|
||||
return (subset_name.replace(value, "")
|
||||
.replace("__", "_")
|
||||
.replace("..", "."))
|
||||
return subset_name
|
||||
|
|
|
|||
|
|
@ -94,12 +94,17 @@ class ImageCreator(Creator):
|
|||
name = self._clean_highlights(stub, directory)
|
||||
layer_names_in_hierarchy.append(name)
|
||||
|
||||
data.update({"subset": subset_name})
|
||||
data.update({"members": [str(group.id)]})
|
||||
data.update({"layer_name": layer_name})
|
||||
data.update({"long_name": "_".join(layer_names_in_hierarchy)})
|
||||
data_update = {
|
||||
"subset": subset_name,
|
||||
"members": [str(group.id)],
|
||||
"layer_name": layer_name,
|
||||
"long_name": "_".join(layer_names_in_hierarchy)
|
||||
}
|
||||
data.update(data_update)
|
||||
|
||||
creator_attributes = {"mark_for_review": self.mark_for_review}
|
||||
mark_for_review = (pre_create_data.get("mark_for_review") or
|
||||
self.mark_for_review)
|
||||
creator_attributes = {"mark_for_review": mark_for_review}
|
||||
data.update({"creator_attributes": creator_attributes})
|
||||
|
||||
if not self.active_on_create:
|
||||
|
|
@ -124,8 +129,6 @@ class ImageCreator(Creator):
|
|||
|
||||
if creator_id == self.identifier:
|
||||
instance_data = self._handle_legacy(instance_data)
|
||||
layer = api.stub().get_layer(instance_data["members"][0])
|
||||
instance_data["layer"] = layer
|
||||
instance = CreatedInstance.from_existing(
|
||||
instance_data, self
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import pyblish.api
|
||||
|
||||
from openpype.hosts.photoshop import api as photoshop
|
||||
|
||||
|
||||
class CollectAutoImageRefresh(pyblish.api.ContextPlugin):
|
||||
"""Refreshes auto_image instance with currently visible layers..
|
||||
"""
|
||||
|
||||
label = "Collect Auto Image Refresh"
|
||||
order = pyblish.api.CollectorOrder
|
||||
hosts = ["photoshop"]
|
||||
order = pyblish.api.CollectorOrder + 0.2
|
||||
|
||||
def process(self, context):
|
||||
for instance in context:
|
||||
creator_identifier = instance.data.get("creator_identifier")
|
||||
if creator_identifier and creator_identifier == "auto_image":
|
||||
self.log.debug("Auto image instance found, won't create new")
|
||||
# refresh existing auto image instance with current visible
|
||||
publishable_ids = [layer.id for layer in photoshop.stub().get_layers() # noqa
|
||||
if layer.visible]
|
||||
instance.data["ids"] = publishable_ids
|
||||
return
|
||||
20
openpype/hosts/photoshop/plugins/publish/collect_image.py
Normal file
20
openpype/hosts/photoshop/plugins/publish/collect_image.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import pyblish.api
|
||||
|
||||
from openpype.hosts.photoshop import api
|
||||
|
||||
|
||||
class CollectImage(pyblish.api.InstancePlugin):
|
||||
"""Collect layer metadata into a instance.
|
||||
|
||||
Used later in validation
|
||||
"""
|
||||
order = pyblish.api.CollectorOrder + 0.200
|
||||
label = 'Collect Image'
|
||||
|
||||
hosts = ["photoshop"]
|
||||
families = ["image"]
|
||||
|
||||
def process(self, instance):
|
||||
if instance.data.get("members"):
|
||||
layer = api.stub().get_layer(instance.data["members"][0])
|
||||
instance.data["layer"] = layer
|
||||
|
|
@ -45,9 +45,11 @@ class ExtractImage(pyblish.api.ContextPlugin):
|
|||
# Perform extraction
|
||||
files = {}
|
||||
ids = set()
|
||||
layer = instance.data.get("layer")
|
||||
if layer:
|
||||
ids.add(layer.id)
|
||||
# real layers and groups
|
||||
members = instance.data("members")
|
||||
if members:
|
||||
ids.update(set([int(member) for member in members]))
|
||||
# virtual groups collected by color coding or auto_image
|
||||
add_ids = instance.data.pop("ids", None)
|
||||
if add_ids:
|
||||
ids.update(set(add_ids))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import shutil
|
||||
from PIL import Image
|
||||
|
||||
from openpype.lib import (
|
||||
|
|
@ -55,6 +56,7 @@ class ExtractReview(publish.Extractor):
|
|||
}
|
||||
|
||||
if instance.data["family"] != "review":
|
||||
self.log.debug("Existing extracted file from image family used.")
|
||||
# enable creation of review, without this jpg review would clash
|
||||
# with jpg of the image family
|
||||
output_name = repre_name
|
||||
|
|
@ -62,8 +64,15 @@ class ExtractReview(publish.Extractor):
|
|||
repre_skeleton.update({"name": repre_name,
|
||||
"outputName": output_name})
|
||||
|
||||
if self.make_image_sequence and len(layers) > 1:
|
||||
self.log.info("Extract layers to image sequence.")
|
||||
img_file = self.output_seq_filename % 0
|
||||
self._prepare_file_for_image_family(img_file, instance,
|
||||
staging_dir)
|
||||
repre_skeleton.update({
|
||||
"files": img_file,
|
||||
})
|
||||
processed_img_names = [img_file]
|
||||
elif self.make_image_sequence and len(layers) > 1:
|
||||
self.log.debug("Extract layers to image sequence.")
|
||||
img_list = self._save_sequence_images(staging_dir, layers)
|
||||
|
||||
repre_skeleton.update({
|
||||
|
|
@ -72,17 +81,17 @@ class ExtractReview(publish.Extractor):
|
|||
"fps": fps,
|
||||
"files": img_list,
|
||||
})
|
||||
instance.data["representations"].append(repre_skeleton)
|
||||
processed_img_names = img_list
|
||||
else:
|
||||
self.log.info("Extract layers to flatten image.")
|
||||
img_list = self._save_flatten_image(staging_dir, layers)
|
||||
self.log.debug("Extract layers to flatten image.")
|
||||
img_file = self._save_flatten_image(staging_dir, layers)
|
||||
|
||||
repre_skeleton.update({
|
||||
"files": img_list,
|
||||
"files": img_file,
|
||||
})
|
||||
instance.data["representations"].append(repre_skeleton)
|
||||
processed_img_names = [img_list]
|
||||
processed_img_names = [img_file]
|
||||
|
||||
instance.data["representations"].append(repre_skeleton)
|
||||
|
||||
ffmpeg_args = get_ffmpeg_tool_args("ffmpeg")
|
||||
|
||||
|
|
@ -111,6 +120,35 @@ class ExtractReview(publish.Extractor):
|
|||
|
||||
self.log.info(f"Extracted {instance} to {staging_dir}")
|
||||
|
||||
def _prepare_file_for_image_family(self, img_file, instance, staging_dir):
|
||||
"""Converts existing file for image family to .jpg
|
||||
|
||||
Image instance could have its own separate review (instance per layer
|
||||
for example). This uses extracted file instead of extracting again.
|
||||
Args:
|
||||
img_file (str): name of output file (with 0000 value for ffmpeg
|
||||
later)
|
||||
instance:
|
||||
staging_dir (str): temporary folder where extracted file is located
|
||||
"""
|
||||
repre_file = instance.data["representations"][0]
|
||||
source_file_path = os.path.join(repre_file["stagingDir"],
|
||||
repre_file["files"])
|
||||
if not os.path.exists(source_file_path):
|
||||
raise RuntimeError(f"{source_file_path} doesn't exist for "
|
||||
"review to create from")
|
||||
_, ext = os.path.splitext(repre_file["files"])
|
||||
if ext != ".jpg":
|
||||
im = Image.open(source_file_path)
|
||||
# without this it produces messy low quality jpg
|
||||
rgb_im = Image.new("RGBA", (im.width, im.height), "#ffffff")
|
||||
rgb_im.alpha_composite(im)
|
||||
rgb_im.convert("RGB").save(os.path.join(staging_dir, img_file))
|
||||
else:
|
||||
# handles already .jpg
|
||||
shutil.copy(source_file_path,
|
||||
os.path.join(staging_dir, img_file))
|
||||
|
||||
def _generate_mov(self, ffmpeg_path, instance, fps, no_of_frames,
|
||||
source_files_pattern, staging_dir):
|
||||
"""Generates .mov to upload to Ftrack.
|
||||
|
|
@ -218,6 +256,11 @@ class ExtractReview(publish.Extractor):
|
|||
(list) of PSItem
|
||||
"""
|
||||
layers = []
|
||||
# creating review for existing 'image' instance
|
||||
if instance.data["family"] == "image" and instance.data.get("layer"):
|
||||
layers.append(instance.data["layer"])
|
||||
return layers
|
||||
|
||||
for image_instance in instance.context:
|
||||
if image_instance.data["family"] != "image":
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import filecmp
|
|||
import tempfile
|
||||
import threading
|
||||
import shutil
|
||||
from queue import Queue
|
||||
|
||||
from contextlib import closing
|
||||
|
||||
from aiohttp import web
|
||||
|
|
@ -319,19 +319,19 @@ class QtTVPaintRpc(BaseTVPaintRpc):
|
|||
async def workfiles_tool(self):
|
||||
log.info("Triggering Workfile tool")
|
||||
item = MainThreadItem(self.tools_helper.show_workfiles)
|
||||
self._execute_in_main_thread(item)
|
||||
self._execute_in_main_thread(item, wait=False)
|
||||
return
|
||||
|
||||
async def loader_tool(self):
|
||||
log.info("Triggering Loader tool")
|
||||
item = MainThreadItem(self.tools_helper.show_loader)
|
||||
self._execute_in_main_thread(item)
|
||||
self._execute_in_main_thread(item, wait=False)
|
||||
return
|
||||
|
||||
async def publish_tool(self):
|
||||
log.info("Triggering Publish tool")
|
||||
item = MainThreadItem(self.tools_helper.show_publisher_tool)
|
||||
self._execute_in_main_thread(item)
|
||||
self._execute_in_main_thread(item, wait=False)
|
||||
return
|
||||
|
||||
async def scene_inventory_tool(self):
|
||||
|
|
@ -350,13 +350,13 @@ class QtTVPaintRpc(BaseTVPaintRpc):
|
|||
async def library_loader_tool(self):
|
||||
log.info("Triggering Library loader tool")
|
||||
item = MainThreadItem(self.tools_helper.show_library_loader)
|
||||
self._execute_in_main_thread(item)
|
||||
self._execute_in_main_thread(item, wait=False)
|
||||
return
|
||||
|
||||
async def experimental_tools(self):
|
||||
log.info("Triggering Library loader tool")
|
||||
item = MainThreadItem(self.tools_helper.show_experimental_tools_dialog)
|
||||
self._execute_in_main_thread(item)
|
||||
self._execute_in_main_thread(item, wait=False)
|
||||
return
|
||||
|
||||
async def _async_execute_in_main_thread(self, item, **kwargs):
|
||||
|
|
@ -867,7 +867,7 @@ class QtCommunicator(BaseCommunicator):
|
|||
|
||||
def __init__(self, qt_app):
|
||||
super().__init__()
|
||||
self.callback_queue = Queue()
|
||||
self.callback_queue = collections.deque()
|
||||
self.qt_app = qt_app
|
||||
|
||||
def _create_routes(self):
|
||||
|
|
@ -880,14 +880,14 @@ class QtCommunicator(BaseCommunicator):
|
|||
|
||||
def execute_in_main_thread(self, main_thread_item, wait=True):
|
||||
"""Add `MainThreadItem` to callback queue and wait for result."""
|
||||
self.callback_queue.put(main_thread_item)
|
||||
self.callback_queue.append(main_thread_item)
|
||||
if wait:
|
||||
return main_thread_item.wait()
|
||||
return
|
||||
|
||||
async def async_execute_in_main_thread(self, main_thread_item, wait=True):
|
||||
"""Add `MainThreadItem` to callback queue and wait for result."""
|
||||
self.callback_queue.put(main_thread_item)
|
||||
self.callback_queue.append(main_thread_item)
|
||||
if wait:
|
||||
return await main_thread_item.async_wait()
|
||||
|
||||
|
|
@ -904,9 +904,9 @@ class QtCommunicator(BaseCommunicator):
|
|||
self._exit()
|
||||
return None
|
||||
|
||||
if self.callback_queue.empty():
|
||||
return None
|
||||
return self.callback_queue.get()
|
||||
if self.callback_queue:
|
||||
return self.callback_queue.popleft()
|
||||
return None
|
||||
|
||||
def _on_client_connect(self):
|
||||
super()._on_client_connect()
|
||||
|
|
|
|||
|
|
@ -573,56 +573,6 @@ void FAR PASCAL PI_Close( PIFilter* iFilter )
|
|||
}
|
||||
|
||||
|
||||
/**************************************************************************************/
|
||||
// we have something to do !
|
||||
|
||||
int FAR PASCAL PI_Parameters( PIFilter* iFilter, char* iArg )
|
||||
{
|
||||
if( !iArg )
|
||||
{
|
||||
|
||||
// If the requester is not open, we open it.
|
||||
if( Data.mReq == 0)
|
||||
{
|
||||
// Create empty requester because menu items are defined with
|
||||
// `define_menu` callback
|
||||
DWORD req = TVOpenFilterReqEx(
|
||||
iFilter,
|
||||
185,
|
||||
20,
|
||||
NULL,
|
||||
NULL,
|
||||
PIRF_STANDARD_REQ | PIRF_COLLAPSABLE_REQ,
|
||||
FILTERREQ_NO_TBAR
|
||||
);
|
||||
if( req == 0 )
|
||||
{
|
||||
TVWarning( iFilter, TXT_REQUESTER_ERROR );
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Data.mReq = req;
|
||||
// This is a very simple requester, so we create it's content right here instead
|
||||
// of waiting for the PICBREQ_OPEN message...
|
||||
// Not recommended for more complex requesters. (see the other examples)
|
||||
|
||||
// Sets the title of the requester.
|
||||
TVSetReqTitle( iFilter, Data.mReq, TXT_REQUESTER );
|
||||
// Request to listen to ticks
|
||||
TVGrabTicks(iFilter, req, PITICKS_FLAG_ON);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If it is already open, we just put it on front of all other requesters.
|
||||
TVReqToFront( iFilter, Data.mReq );
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int newMenuItemsProcess(PIFilter* iFilter) {
|
||||
// Menu items defined with `define_menu` should be propagated.
|
||||
|
||||
|
|
@ -702,6 +652,62 @@ int newMenuItemsProcess(PIFilter* iFilter) {
|
|||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**************************************************************************************/
|
||||
// we have something to do !
|
||||
|
||||
int FAR PASCAL PI_Parameters( PIFilter* iFilter, char* iArg )
|
||||
{
|
||||
if( !iArg )
|
||||
{
|
||||
|
||||
// If the requester is not open, we open it.
|
||||
if( Data.mReq == 0)
|
||||
{
|
||||
// Create empty requester because menu items are defined with
|
||||
// `define_menu` callback
|
||||
DWORD req = TVOpenFilterReqEx(
|
||||
iFilter,
|
||||
185,
|
||||
20,
|
||||
NULL,
|
||||
NULL,
|
||||
PIRF_STANDARD_REQ | PIRF_COLLAPSABLE_REQ,
|
||||
FILTERREQ_NO_TBAR
|
||||
);
|
||||
if( req == 0 )
|
||||
{
|
||||
TVWarning( iFilter, TXT_REQUESTER_ERROR );
|
||||
return 0;
|
||||
}
|
||||
|
||||
Data.mReq = req;
|
||||
|
||||
// This is a very simple requester, so we create it's content right here instead
|
||||
// of waiting for the PICBREQ_OPEN message...
|
||||
// Not recommended for more complex requesters. (see the other examples)
|
||||
|
||||
// Sets the title of the requester.
|
||||
TVSetReqTitle( iFilter, Data.mReq, TXT_REQUESTER );
|
||||
// Request to listen to ticks
|
||||
TVGrabTicks(iFilter, req, PITICKS_FLAG_ON);
|
||||
|
||||
if ( Data.firstParams == true ) {
|
||||
Data.firstParams = false;
|
||||
} else {
|
||||
newMenuItemsProcess(iFilter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If it is already open, we just put it on front of all other requesters.
|
||||
TVReqToFront( iFilter, Data.mReq );
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**************************************************************************************/
|
||||
// something happened that needs our attention.
|
||||
// Global variable where current button up data are stored
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -28,11 +28,7 @@
|
|||
"colorManagement": "Nuke",
|
||||
"OCIO_config": "nuke-default",
|
||||
"workingSpaceLUT": "linear",
|
||||
"monitorLut": "sRGB",
|
||||
"int8Lut": "sRGB",
|
||||
"int16Lut": "sRGB",
|
||||
"logLut": "Cineon",
|
||||
"floatLut": "linear"
|
||||
"monitorLut": "sRGB"
|
||||
},
|
||||
"nodes": {
|
||||
"requiredNodes": [
|
||||
|
|
|
|||
|
|
@ -106,26 +106,6 @@
|
|||
"type": "text",
|
||||
"key": "monitorLut",
|
||||
"label": "monitor"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "int8Lut",
|
||||
"label": "8-bit files"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "int16Lut",
|
||||
"label": "16-bit files"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "logLut",
|
||||
"label": "log files"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "floatLut",
|
||||
"label": "float files"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.16.6-nightly.1"
|
||||
__version__ = "3.16.7-nightly.1"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.16.5" # OpenPype
|
||||
version = "3.16.6" # OpenPype
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
|
|
@ -61,22 +61,6 @@ def ocio_configs_switcher_enum():
|
|||
|
||||
class WorkfileColorspaceSettings(BaseSettingsModel):
|
||||
"""Nuke workfile colorspace preset. """
|
||||
"""# TODO: enhance settings with host api:
|
||||
we need to add mapping to resolve properly keys.
|
||||
Nuke is excpecting camel case key names,
|
||||
but for better code consistency we need to
|
||||
be using snake_case:
|
||||
|
||||
color_management = colorManagement
|
||||
ocio_config = OCIO_config
|
||||
working_space_name = workingSpaceLUT
|
||||
monitor_name = monitorLut
|
||||
monitor_out_name = monitorOutLut
|
||||
int_8_name = int8Lut
|
||||
int_16_name = int16Lut
|
||||
log_name = logLut
|
||||
float_name = floatLut
|
||||
"""
|
||||
|
||||
colorManagement: Literal["Nuke", "OCIO"] = Field(
|
||||
title="Color Management"
|
||||
|
|
@ -95,18 +79,6 @@ class WorkfileColorspaceSettings(BaseSettingsModel):
|
|||
monitorLut: str = Field(
|
||||
title="Monitor"
|
||||
)
|
||||
int8Lut: str = Field(
|
||||
title="8-bit files"
|
||||
)
|
||||
int16Lut: str = Field(
|
||||
title="16-bit files"
|
||||
)
|
||||
logLut: str = Field(
|
||||
title="Log files"
|
||||
)
|
||||
floatLut: str = Field(
|
||||
title="Float files"
|
||||
)
|
||||
|
||||
|
||||
class ReadColorspaceRulesItems(BaseSettingsModel):
|
||||
|
|
@ -233,10 +205,6 @@ DEFAULT_IMAGEIO_SETTINGS = {
|
|||
"OCIO_config": "nuke-default",
|
||||
"workingSpaceLUT": "linear",
|
||||
"monitorLut": "sRGB",
|
||||
"int8Lut": "sRGB",
|
||||
"int16Lut": "sRGB",
|
||||
"logLut": "Cineon",
|
||||
"floatLut": "linear"
|
||||
},
|
||||
"nodes": {
|
||||
"requiredNodes": [
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ Location: Settings > Project > AfterEffects
|
|||
|
||||
## Publish plugins
|
||||
|
||||
### Collect Review
|
||||
|
||||
Enable/disable creation of auto instance of review.
|
||||
|
||||
### Validate Scene Settings
|
||||
|
||||
#### Skip Resolution Check for Tasks
|
||||
|
|
@ -28,6 +32,10 @@ Set regex pattern(s) to look for in a Task name to skip resolution check against
|
|||
|
||||
Set regex pattern(s) to look for in a Task name to skip `frameStart`, `frameEnd` check against values from DB.
|
||||
|
||||
### ValidateContainers
|
||||
|
||||
By default this validator will look loaded items with lower version than latest. This validator is context wide so it must be disabled in Context button.
|
||||
|
||||
### AfterEffects Submit to Deadline
|
||||
|
||||
* `Use Published scene` - Set to True (green) when Deadline should take published scene as a source instead of uploaded local one.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ Provides list of [variants](artist_concepts.md#variant) that will be shown to an
|
|||
Provides simplified publishing process. It will create single `image` instance for artist automatically. This instance will
|
||||
produce flatten image from all visible layers in a workfile.
|
||||
|
||||
- Subset template for flatten image - provide template for subset name for this instance (example `imageBeauty`)
|
||||
- Review - should be separate review created for this instance
|
||||
|
||||
### Create Review
|
||||
|
|
@ -111,11 +110,11 @@ Set Byte limit for review file. Applicable if gigantic `image` instances are pro
|
|||
|
||||
#### Extract jpg Options
|
||||
|
||||
Handles tags for produced `.jpg` representation. `Create review` and `Add review to Ftrack` are defaults.
|
||||
Handles tags for produced `.jpg` representation. `Create review` and `Add review to Ftrack` are defaults.
|
||||
|
||||
#### Extract mov Options
|
||||
|
||||
Handles tags for produced `.mov` representation. `Create review` and `Add review to Ftrack` are defaults.
|
||||
Handles tags for produced `.mov` representation. `Create review` and `Add review to Ftrack` are defaults.
|
||||
|
||||
|
||||
### Workfile Builder
|
||||
|
|
@ -124,4 +123,4 @@ Allows to open prepared workfile for an artist when no workfile exists. Useful t
|
|||
|
||||
Could be configured per `Task type`, eg. `composition` task type could use different `.psd` template file than `art` task.
|
||||
Workfile template must be accessible for all artists.
|
||||
(Currently not handled by [SiteSync](module_site_sync.md))
|
||||
(Currently not handled by [SiteSync](module_site_sync.md))
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 16 KiB |
Loading…
Add table
Add a link
Reference in a new issue