mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Photoshop: fixed blank Flatten image (#5600)
* OP-6763 - refresh all visible for Flatten image
Previously newly added layers were missing.
* OP-6763 - added explicit image collector
Creator was adding 'layer' metadata from workfile only during collect_instances, it was missing for newly added layers. This should be cleaner approach
* OP-6763 - removed unnecessary method overwrite
Creator is not adding layer to instance, separate collector created.
* OP-6763 - cleanup of names
Was failing when template for subset name for image family contained {layer}
* OP-6763 - cleanup, removed adding layer metadata
Separate collector created, cleaner.
Fixed propagation of mark_for_review
* OP-6763 - using members instead of layer data
Members should be more reliable.
* OP-6763 - updated image from Settings
Explicit subset name template was removed some time ago as confusing.
* OP-6763 - added explicit local plugin
Automated plugin has different logic, local would need to handle if auto_image is disabled by artist
* OP-6763 - Hound
* OP-6345 - fix - review for image family
Image family instance contained flattened content. Now it reuses previously extracted file without need to re-extract.
This commit is contained in:
parent
ae02fe220a
commit
1fdbe05905
8 changed files with 144 additions and 31 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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