From bd69ae2b546d7468bb13f361a307d9a5fc8f0099 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 10 Jun 2020 11:09:04 +0100 Subject: [PATCH 1/4] Initial Photoshop integration. --- pype/hosts/photoshop/__init__.py | 24 +++++++ pype/plugins/global/publish/integrate_new.py | 2 +- pype/plugins/photoshop/create/create_image.py | 12 ++++ pype/plugins/photoshop/load/load_image.py | 43 ++++++++++++ .../photoshop/publish/collect_current_file.py | 17 +++++ .../photoshop/publish/collect_instances.py | 55 ++++++++++++++++ .../photoshop/publish/extract_image.py | 62 ++++++++++++++++++ res/app_icons/photoshop.png | Bin 0 -> 10669 bytes 8 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 pype/hosts/photoshop/__init__.py create mode 100644 pype/plugins/photoshop/create/create_image.py create mode 100644 pype/plugins/photoshop/load/load_image.py create mode 100644 pype/plugins/photoshop/publish/collect_current_file.py create mode 100644 pype/plugins/photoshop/publish/collect_instances.py create mode 100644 pype/plugins/photoshop/publish/extract_image.py create mode 100644 res/app_icons/photoshop.png diff --git a/pype/hosts/photoshop/__init__.py b/pype/hosts/photoshop/__init__.py new file mode 100644 index 00000000000..709fb24bf5a --- /dev/null +++ b/pype/hosts/photoshop/__init__.py @@ -0,0 +1,24 @@ +import os + +from avalon import api +import pyblish.api + + +def install(): + print("Installing Pype config...") + + plugins_directory = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))), + "plugins", + "photoshop" + ) + + pyblish.api.register_plugin_path( + os.path.join(plugins_directory, "publish") + ) + api.register_plugin_path( + api.Loader, os.path.join(plugins_directory, "load") + ) + api.register_plugin_path( + api.Creator, os.path.join(plugins_directory, "create") + ) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index adff8aa3fa4..f8429e8b589 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -77,7 +77,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "gizmo", "source", "matchmove", - "image" + "image", "source", "assembly", "fbx", diff --git a/pype/plugins/photoshop/create/create_image.py b/pype/plugins/photoshop/create/create_image.py new file mode 100644 index 00000000000..a840dd13a79 --- /dev/null +++ b/pype/plugins/photoshop/create/create_image.py @@ -0,0 +1,12 @@ +from avalon import photoshop + + +class CreateImage(photoshop.Creator): + """Image folder for publish.""" + + name = "imageDefault" + label = "Image" + family = "image" + + def __init__(self, *args, **kwargs): + super(CreateImage, self).__init__(*args, **kwargs) diff --git a/pype/plugins/photoshop/load/load_image.py b/pype/plugins/photoshop/load/load_image.py new file mode 100644 index 00000000000..18efe750d59 --- /dev/null +++ b/pype/plugins/photoshop/load/load_image.py @@ -0,0 +1,43 @@ +from avalon import api, photoshop + + +class ImageLoader(api.Loader): + """Load images + + Stores the imported asset in a container named after the asset. + """ + + families = ["image"] + representations = ["*"] + + def load(self, context, name=None, namespace=None, data=None): + with photoshop.maintained_selection(): + layer = photoshop.import_smart_object(self.fname) + + self[:] = [layer] + + return photoshop.containerise( + name, + namespace, + layer, + context, + self.__class__.__name__ + ) + + def update(self, container, representation): + layer = container.pop("layer") + + with photoshop.maintained_selection(): + photoshop.replace_smart_object( + layer, api.get_representation_path(representation) + ) + + photoshop.imprint( + layer, {"representation": str(representation["_id"])} + ) + + def remove(self, container): + container["layer"].Delete() + + def switch(self, container, representation): + self.update(container, representation) diff --git a/pype/plugins/photoshop/publish/collect_current_file.py b/pype/plugins/photoshop/publish/collect_current_file.py new file mode 100644 index 00000000000..4308588559d --- /dev/null +++ b/pype/plugins/photoshop/publish/collect_current_file.py @@ -0,0 +1,17 @@ +import os + +import pyblish.api +from avalon import photoshop + + +class CollectCurrentFile(pyblish.api.ContextPlugin): + """Inject the current working file into context""" + + order = pyblish.api.CollectorOrder - 0.5 + label = "Current File" + hosts = ["photoshop"] + + def process(self, context): + context.data["currentFile"] = os.path.normpath( + photoshop.app().ActiveDocument.FullName + ).replace("\\", "/") diff --git a/pype/plugins/photoshop/publish/collect_instances.py b/pype/plugins/photoshop/publish/collect_instances.py new file mode 100644 index 00000000000..b2533654659 --- /dev/null +++ b/pype/plugins/photoshop/publish/collect_instances.py @@ -0,0 +1,55 @@ +import pythoncom + +from avalon import photoshop + +import pyblish.api + + +class CollectInstances(pyblish.api.ContextPlugin): + """Gather instances by LayerSet and file metadata + + This collector takes into account assets that are associated with + an LayerSet and marked with a unique identifier; + + Identifier: + id (str): "pyblish.avalon.instance" + """ + + label = "Instances" + order = pyblish.api.CollectorOrder + hosts = ["photoshop"] + families_mapping = { + "image": [] + } + + def process(self, context): + # Necessary call when running in a different thread which pyblish-qml + # can be. + pythoncom.CoInitialize() + + for layer in photoshop.get_layers_in_document(): + layer_data = photoshop.read(layer) + + # Skip layers without metadata. + if layer_data is None: + continue + + # Skip containers. + if "container" in layer_data["id"]: + continue + + child_layers = [*layer.Layers] + if not child_layers: + self.log.info("%s skipped, it was empty." % layer.Name) + continue + + instance = context.create_instance(layer.Name) + instance.append(layer) + instance.data.update(layer_data) + instance.data["families"] = self.families_mapping[ + layer_data["family"] + ] + + # Produce diagnostic message for any graphical + # user interface interested in visualising it. + self.log.info("Found: \"%s\" " % instance.data["name"]) diff --git a/pype/plugins/photoshop/publish/extract_image.py b/pype/plugins/photoshop/publish/extract_image.py new file mode 100644 index 00000000000..da3197c7daf --- /dev/null +++ b/pype/plugins/photoshop/publish/extract_image.py @@ -0,0 +1,62 @@ +import os + +import pype.api +from avalon import photoshop + + +class ExtractImage(pype.api.Extractor): + """Produce a flattened image file from instance + + This plug-in takes into account only the layers in the group. + """ + + label = "Extract Image" + hosts = ["photoshop"] + families = ["image"] + + def process(self, instance): + + staging_dir = self.staging_dir(instance) + self.log.info("Outputting image to {}".format(staging_dir)) + + # Perform extraction + files = {} + with photoshop.maintained_selection(): + self.log.info("Extracting %s" % str(list(instance))) + with photoshop.maintained_visibility(): + # Hide all other layers. + extract_ids = [ + x.id for x in photoshop.get_layers_in_layers([instance[0]]) + ] + for layer in photoshop.get_layers_in_document(): + if layer.id not in extract_ids: + layer.Visible = False + + save_options = { + "png": photoshop.com_objects.PNGSaveOptions(), + "jpg": photoshop.com_objects.JPEGSaveOptions() + } + + for extension, save_option in save_options.items(): + photoshop.app().ActiveDocument.SaveAs( + staging_dir, save_option, True + ) + files[extension] = "{} copy.{}".format( + os.path.splitext( + photoshop.app().ActiveDocument.Name + )[0], + extension + ) + + representations = [] + for extension, filename in files.items(): + representations.append({ + "name": extension, + "ext": extension, + "files": filename, + "stagingDir": staging_dir + }) + instance.data["representations"] = representations + instance.data["stagingDir"] = staging_dir + + self.log.info(f"Extracted {instance} to {staging_dir}") diff --git a/res/app_icons/photoshop.png b/res/app_icons/photoshop.png new file mode 100644 index 0000000000000000000000000000000000000000..c7e9d1471124e9a6a3cef8144f7ca169e6ce9758 GIT binary patch literal 10669 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⪙hP4UmFgy9gqGkbvh~TBOUnv0vycJ|mAL?2tuWT~Ob$ma#zRRABSJPChLmyg2 za=~GVaXLqoFhq(+0>G~xOceM_=X{H`_MFqA?X|ThfWu^kYiGZgkP){niRnubx)A~; zs~S{LuY0*T&uui&YA+=A<2QY9N%YjvRCJeQV{GhN$matcMtm)e>@7=QshjKd-l+Vp zFHbS19v(s_-`Av#urG}saAs}dTPao6d~2p+O>&^JtYNZ*mEcohEytQ@cwNo{I5BRP zB+tq{f8t;+5`)*AM~W=Ll)cLOMBkWT9rg3Cuj(=|d~HoB_$BFSU5gFvJ_;aCTlYWu zVrAuH@i-V}rv*VAzmR+C+;7j^i0L~WWY5c&nb%>BWBu#NcI`nhx1OLb-y!!c&@H3L zR!%WWarXCRtWEaKI=vIUK;i3kt@5c0pu7<%=)OEZv%0ecxW1uP3RUI4RE6s6`yAl^ z&@8f5HkMuAbfC(vA(y!R@Z}v9()AhQ)R&Gr#{}<7IM2gAy&V6xVUSybU^_!+@j~CS z<~cOSZ|O?-JwksHDViLi;2!-9Eq1AI!8-{XJuVslPL3tX38nKRFBo*qI+46L{ZheNlf^uQ@qP*jOCHvOAOchoLj8 z7q(@;YJsZjJ`Y%;-SWc_{!964SW|8H&%3%ivC$iS62)6YwNvhP)a~&$8z%|w%Cf)6AH1`1`R)5qZ4FQUjf;lX8$7@J7AGcbYhTwoO>b#Kr|vTC z)V`x@)U6hf7`Zt`A3FNWrQEcJZ2sF_pe|O;{kWlsGJh2lc=uLmnOc@~a+ug`bXhUz zX3|8IwddsZR9hWHwT~9@eTCg>bjEK70}lll-z2C*t*lSi$FYS3#zftH2-L1V{MOj` zvW1Cx3eCJ1gr|s?7 z$A{!V;mWzhVKO^*EHi%^C?+n1>NGZmSzAC$RD)qG-KMN;I?iHTcW~8A!r}o_|rpmf%~cfmFZ&=K>c_}JL0%NOir17bQSZfY`pX;zD9`JV)*m&qO&{J5)o#~SY*dtv@r{g%CO8UeNBPP|s0Gw=+FJB&JuP`~^) z5hERxvD<*V1bFh14WB0~ua7huSC@ME1v`t*rsL5@ XSIt2=`ivCU4^*lv>nK$yScm^F^d!Rc literal 0 HcmV?d00001 From ee247ea58b8a641327f6911def5590bd13f47ae7 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 10 Jun 2020 15:13:21 +0100 Subject: [PATCH 2/4] Toggle instances with visibility. --- pype/hosts/photoshop/__init__.py | 9 +++++++++ pype/plugins/photoshop/publish/collect_instances.py | 1 + 2 files changed, 10 insertions(+) diff --git a/pype/hosts/photoshop/__init__.py b/pype/hosts/photoshop/__init__.py index 709fb24bf5a..01ed757a8df 100644 --- a/pype/hosts/photoshop/__init__.py +++ b/pype/hosts/photoshop/__init__.py @@ -22,3 +22,12 @@ def install(): api.register_plugin_path( api.Creator, os.path.join(plugins_directory, "create") ) + + pyblish.api.register_callback( + "instanceToggled", on_pyblish_instance_toggled + ) + + +def on_pyblish_instance_toggled(instance, old_value, new_value): + """Toggle layer visibility on instance toggles.""" + instance[0].Visible = new_value diff --git a/pype/plugins/photoshop/publish/collect_instances.py b/pype/plugins/photoshop/publish/collect_instances.py index b2533654659..4937f2a1e42 100644 --- a/pype/plugins/photoshop/publish/collect_instances.py +++ b/pype/plugins/photoshop/publish/collect_instances.py @@ -49,6 +49,7 @@ def process(self, context): instance.data["families"] = self.families_mapping[ layer_data["family"] ] + instance.data["publish"] = layer.Visible # Produce diagnostic message for any graphical # user interface interested in visualising it. From 0a3977d4bf27e07cbf65910e89ccbf33d1e67600 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 10 Jun 2020 15:13:36 +0100 Subject: [PATCH 3/4] Publish workfile. --- .../photoshop/publish/collect_workfile.py | 39 +++++++++++++++++++ .../photoshop/publish/extract_save_scene.py | 14 +++++++ 2 files changed, 53 insertions(+) create mode 100644 pype/plugins/photoshop/publish/collect_workfile.py create mode 100644 pype/plugins/photoshop/publish/extract_save_scene.py diff --git a/pype/plugins/photoshop/publish/collect_workfile.py b/pype/plugins/photoshop/publish/collect_workfile.py new file mode 100644 index 00000000000..766be02354a --- /dev/null +++ b/pype/plugins/photoshop/publish/collect_workfile.py @@ -0,0 +1,39 @@ +import pyblish.api +import os + + +class CollectWorkfile(pyblish.api.ContextPlugin): + """Collect current script for publish.""" + + order = pyblish.api.CollectorOrder + 0.1 + label = "Collect Workfile" + hosts = ["photoshop"] + + def process(self, context): + family = "workfile" + task = os.getenv("AVALON_TASK", None) + subset = family + task.capitalize() + + file_path = context.data["currentFile"] + staging_dir = os.path.dirname(file_path) + base_name = os.path.basename(file_path) + + # Create instance + instance = context.create_instance(subset) + instance.data.update({ + "subset": subset, + "label": base_name, + "name": base_name, + "family": family, + "families": [], + "representations": [], + "asset": os.environ["AVALON_ASSET"] + }) + + # creating representation + instance.data["representations"].append({ + "name": "psd", + "ext": "psd", + "files": base_name, + "stagingDir": staging_dir, + }) diff --git a/pype/plugins/photoshop/publish/extract_save_scene.py b/pype/plugins/photoshop/publish/extract_save_scene.py new file mode 100644 index 00000000000..b3d4f0e447b --- /dev/null +++ b/pype/plugins/photoshop/publish/extract_save_scene.py @@ -0,0 +1,14 @@ +import pype.api +from avalon import photoshop + + +class ExtractSaveScene(pype.api.Extractor): + """Save scene before extraction.""" + + order = pype.api.Extractor.order - 0.49 + label = "Extract Save Scene" + hosts = ["photoshop"] + families = ["workfile"] + + def process(self, instance): + photoshop.app().ActiveDocument.Save() From 0f5dd4681b0fb6e0d738e0624e38fbb6c3aaf1d0 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 10 Jun 2020 15:13:48 +0100 Subject: [PATCH 4/4] Validate instance asset. --- .../publish/validate_instance_asset.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 pype/plugins/photoshop/publish/validate_instance_asset.py diff --git a/pype/plugins/photoshop/publish/validate_instance_asset.py b/pype/plugins/photoshop/publish/validate_instance_asset.py new file mode 100644 index 00000000000..ab1d02269f8 --- /dev/null +++ b/pype/plugins/photoshop/publish/validate_instance_asset.py @@ -0,0 +1,48 @@ +import os + +import pyblish.api +import pype.api +from avalon import photoshop + + +class ValidateInstanceAssetRepair(pyblish.api.Action): + """Repair the instance asset.""" + + label = "Repair" + icon = "wrench" + on = "failed" + + def process(self, context, plugin): + + # Get the errored instances + failed = [] + for result in context.data["results"]: + if (result["error"] is not None and result["instance"] is not None + and result["instance"] not in failed): + failed.append(result["instance"]) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(failed, plugin) + + for instance in instances: + data = photoshop.read(instance[0]) + data["asset"] = os.environ["AVALON_ASSET"] + photoshop.imprint(instance[0], data) + + +class ValidateInstanceAsset(pyblish.api.InstancePlugin): + """Validate the instance asset is the current asset.""" + + label = "Validate Instance Asset" + hosts = ["photoshop"] + actions = [ValidateInstanceAssetRepair] + order = pype.api.ValidateContentsOrder + + def process(self, instance): + instance_asset = instance.data["asset"] + current_asset = os.environ["AVALON_ASSET"] + msg = ( + "Instance asset is not the same as current asset:" + f"\nInstance: {instance_asset}\nCurrent: {current_asset}" + ) + assert instance_asset == current_asset, msg