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:
Petr Kalis 2023-09-11 17:21:38 +02:00 committed by GitHub
parent ae02fe220a
commit 1fdbe05905
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 144 additions and 31 deletions

View file

@ -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

View file

@ -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
)

View file

@ -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

View 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

View file

@ -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))

View file

@ -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

View file

@ -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

Before After
Before After