From fe3cfc24422580c99f837c6bf02de029e80d54a9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 19 Aug 2020 20:50:14 +0200 Subject: [PATCH] Added group_selected_layers, get_selected_layers, import_smart_object, replace_smart_object Fixed imprint for performance --- .../clients/photoshop_client.py | 157 ++++++++++++++---- .../websocket_server/websocket_server.py | 6 +- pype/plugins/photoshop/load/load_image.py | 9 +- .../photoshop/publish/collect_instances.py | 6 +- 4 files changed, 138 insertions(+), 40 deletions(-) diff --git a/pype/modules/websocket_server/clients/photoshop_client.py b/pype/modules/websocket_server/clients/photoshop_client.py index 6d3615e1a4..eea297954f 100644 --- a/pype/modules/websocket_server/clients/photoshop_client.py +++ b/pype/modules/websocket_server/clients/photoshop_client.py @@ -6,18 +6,38 @@ from pype.modules.websocket_server import WebSocketServer import json from collections import namedtuple + class PhotoshopClientStub(): + """ + Stub for calling function on client (Photoshop js) side. + Expects that client is already connected (started when avalon menu + is opened). + """ def __init__(self): self.websocketserver = WebSocketServer.get_instance() self.client = self.websocketserver.get_client() def read(self, layer): + """ + Parses layer metadata from Headline field of active document + :param layer: + :return: + """ layers_data = self._get_layers_metadata() return layers_data.get(str(layer.id)) - def imprint(self, layer, data): + def imprint(self, layer, data, all_layers=None): + """ + Save layer metadata to Headline field of active document + :param layer: Layer("id": XXX, "name":'YYY') + :param data: json representation for single layer + :param all_layers: - for performance, could be + injected for usage in loop, if not, single call will be + triggered + :return: None + """ layers_data = self._get_layers_metadata() # json.dumps writes integer values in a dictionary to string, so # anticipating it here. @@ -27,7 +47,9 @@ class PhotoshopClientStub(): layers_data[str(layer.id)] = data # Ensure only valid ids are stored. - layer_ids = [layer.id for layer in self.get_layers()] + if not all_layers: + all_layers = self.get_layers() + layer_ids = [layer.id for layer in all_layers] cleaned_data = {} for id in layers_data: @@ -36,10 +58,9 @@ class PhotoshopClientStub(): payload = json.dumps(cleaned_data, indent=4) - res = self.websocketserver.call(self.client.call - ('Photoshop.imprint', - payload=payload) - ) + self.websocketserver.call(self.client.call + ('Photoshop.imprint', payload=payload) + ) def get_layers(self): """ @@ -51,20 +72,10 @@ class PhotoshopClientStub(): 'type': 'GUIDE'|'FG'|'BG'|'OBJ' 'visible': 'true'|'false' """ - layers = {} res = self.websocketserver.call(self.client.call ('Photoshop.get_layers')) - print("get_layers:: {}".format(res)) - try: - layers_data = json.loads(res) - except json.decoder.JSONDecodeError: - raise ValueError("Received broken JSON {}".format(res)) - ret = [] - # convert to namedtuple to use dot donation - for d in layers_data: - ret.append(namedtuple('Layer', d.keys())(*d.values())) - return ret + return self._to_records(res) def get_layers_in_layers(self, layers): """ @@ -87,14 +98,35 @@ class PhotoshopClientStub(): return ret + def group_selected_layers(self): + """ + Group selected layers into new layer + :return: + """ + self.websocketserver.call(self.client.call + ('Photoshop.group_selected_layers')) + + def get_selected_layers(self): + """ + Get a list of actually selected layers + :return: + """ + res = self.websocketserver.call(self.client.call + ('Photoshop.get_selected_layers')) + return self._to_records(res) def select_layers(self, layers): + """ + Selecte specified layers in Photoshop + :param layers: + :return: None + """ layer_ids = [layer.id for layer in layers] - res = self.websocketserver.call(self.client.call - ('Photoshop.get_layers', - layers=layer_ids) - ) + self.websocketserver.call(self.client.call + ('Photoshop.get_layers', + layers=layer_ids) + ) def get_active_document_full_name(self): """ @@ -121,25 +153,41 @@ class PhotoshopClientStub(): Saves active document :return: None """ - res = self.websocketserver.call(self.client.call - ('Photoshop.save')) - + self.websocketserver.call(self.client.call + ('Photoshop.save')) def saveAs(self, image_path, ext, as_copy): - res = self.websocketserver.call(self.client.call - ('Photoshop.saveAs', - image_path=image_path, - ext=ext, - as_copy=as_copy)) + """ + Saves active document to psd (copy) or png or jpg + :param image_path: full local path + :param ext: + :param as_copy: + :return: None + """ + self.websocketserver.call(self.client.call + ('Photoshop.saveAs', + image_path=image_path, + ext=ext, + as_copy=as_copy)) def set_visible(self, layer_id, visibility): - print("set_visible {}, {}".format(layer_id, visibility)) - res = self.websocketserver.call(self.client.call - ('Photoshop.set_visible', - layer_id=layer_id, - visibility=visibility)) + """ + Set layer with 'layer_id' to 'visibility' + :param layer_id: + :param visibility: + :return: None + """ + self.websocketserver.call(self.client.call + ('Photoshop.set_visible', + layer_id=layer_id, + visibility=visibility)) def _get_layers_metadata(self): + """ + Reads layers metadata from Headline from active document in PS. + (Headline accessible by File > File Info) + :return: - json documents + """ layers_data = {} res = self.websocketserver.call(self.client.call('Photoshop.read')) try: @@ -148,6 +196,47 @@ class PhotoshopClientStub(): pass return layers_data + def import_smart_object(self, path): + """ + Import the file at `path` as a smart object to active document. + + Args: + path (str): File path to import. + """ + + def replace_smart_object(self, layer, path): + """ + Replace the smart object `layer` with file at `path` + + Args: + layer (namedTuple): Layer("id":XX, "name":"YY"..). + path (str): File to import. + """ + self.websocketserver.call(self.client.call + ('Photoshop.replace_smart_object', + layer=layer, + path=path)) + def close(self): self.client.close() + def _to_records(self, res): + """ + Converts string json representation into list of named tuples for + dot notation access to work. + :return: + :param res: - json representation + """ + try: + layers_data = json.loads(res) + except json.decoder.JSONDecodeError: + raise ValueError("Received broken JSON {}".format(res)) + ret = [] + # convert to namedtuple to use dot donation + for d in layers_data: + ret.append(namedtuple('Layer', d.keys())(*d.values())) + return ret + + + + diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index f9be7c88a9..02fde4d56a 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -96,8 +96,10 @@ class WebSocketServer(): :return: client """ clients = WebSocketAsync.get_clients() - key = list(clients.keys())[0] - client = clients.get(key) + client = None + if len(clients) > 0: + key = list(clients.keys())[0] + client = clients.get(key) return client diff --git a/pype/plugins/photoshop/load/load_image.py b/pype/plugins/photoshop/load/load_image.py index 18efe750d5..0e437b15ba 100644 --- a/pype/plugins/photoshop/load/load_image.py +++ b/pype/plugins/photoshop/load/load_image.py @@ -1,5 +1,10 @@ from avalon import api, photoshop +from pype.modules.websocket_server.clients.photoshop_client \ + import PhotoshopClientStub + +photoshopClient = PhotoshopClientStub() + class ImageLoader(api.Loader): """Load images @@ -28,11 +33,11 @@ class ImageLoader(api.Loader): layer = container.pop("layer") with photoshop.maintained_selection(): - photoshop.replace_smart_object( + photoshopClient.replace_smart_object( layer, api.get_representation_path(representation) ) - photoshop.imprint( + photoshopClient.imprint( layer, {"representation": str(representation["_id"])} ) diff --git a/pype/plugins/photoshop/publish/collect_instances.py b/pype/plugins/photoshop/publish/collect_instances.py index d94adde00b..f2d1c141fd 100644 --- a/pype/plugins/photoshop/publish/collect_instances.py +++ b/pype/plugins/photoshop/publish/collect_instances.py @@ -4,7 +4,9 @@ from avalon import photoshop import pyblish.api -from pype.modules.websocket_server.clients.photoshop_client import PhotoshopClientStub +from pype.modules.websocket_server.clients.photoshop_client \ + import PhotoshopClientStub + class CollectInstances(pyblish.api.ContextPlugin): """Gather instances by LayerSet and file metadata @@ -38,7 +40,7 @@ class CollectInstances(pyblish.api.ContextPlugin): layer_data = photoshop_client.read(layer) self.log.info("layer_data {}".format(layer_data)) - photoshop_client.imprint(layer, layer_data) + photoshop_client.imprint(layer, layer_data, layers) new_layer_data = photoshop_client.read(layer) assert layer_data == new_layer_data