From 68822216ca15a263130b71dc6711d8ec1605477b Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Tue, 11 Aug 2020 10:23:54 +0200 Subject: [PATCH 01/17] Added client support --- .../websocket_server/websocket_server.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index 56e71ea895..9d0d01d156 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -22,12 +22,16 @@ class WebSocketServer(): WIP """ + _instance = None def __init__(self): self.qaction = None self.failed_icon = None self._is_running = False default_port = 8099 + WebSocketServer._instance = self + self.client = None + self.handlers = {} try: self.presets = config.get_presets()["services"]["websocket_server"] @@ -76,8 +80,26 @@ class WebSocketServer(): module = importlib.import_module(module_name) cls = getattr(module, class_name) WebSocketAsync.add_route(class_name, cls) + self.handlers[class_name] = cls() # TODO refactor sys.path.pop() + def call(self, func): + log.debug("websocket.call {}".format(func)) + return self.websocket_thread.call_async(func) + + def task_finished(self, task): + print("task finished {}".format(task.result)) + print("client socket {}".format(self.client.client.socket)) + + def get_routes(self): + WebSocketAsync.get_routes() + + @staticmethod + def get_instance(): + if WebSocketServer._instance == None: + WebSocketServer() + return WebSocketServer._instance + def tray_start(self): self.websocket_thread.start() @@ -124,6 +146,7 @@ class WebsocketServerThread(threading.Thread): self.loop = None self.runner = None self.site = None + self.tasks = [] def run(self): self.is_running = True @@ -153,6 +176,20 @@ class WebsocketServerThread(threading.Thread): self.module.thread_stopped() log.info("Websocket server stopped") + def call_async(self, func): + # log.debug("call async") + # print("call aysnc") + # log.debug("my loop {}".format(self.loop)) + # task = self.loop.create_task(func) + # print("waitning") + # log.debug("waiting for task {}".format(func)) + # self.loop.run_until_complete(task) + # log.debug("returned value {}".format(task.result)) + # return task.result + task = self.loop.create_task(func) + task.add_done_callback(self.module.task_finished) + self.tasks.append(task) + async def start_server(self): """ Starts runner and TCPsite """ self.runner = web.AppRunner(self.module.app) @@ -169,6 +206,12 @@ class WebsocketServerThread(threading.Thread): periodically. """ while self.is_running: + while self.tasks: + task = self.tasks.pop(0) + log.debug("waiting for task {}".format(task)) + await task + log.debug("returned value {}".format(task.result)) + await asyncio.sleep(0.5) log.debug("Starting shutdown") From 41c101695238e3667c6392e155a5996dd335f85f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 18 Aug 2020 18:03:35 +0200 Subject: [PATCH 02/17] Working implementation of collect_instances\n extract_review, etract_image --- .../clients/photoshop_client.py | 102 ++++++++++++++++++ .../websocket_server/hosts/photoshop.py | 34 ++++++ .../websocket_server/websocket_server.py | 35 +++--- .../photoshop/publish/collect_instances.py | 23 ++-- .../photoshop/publish/extract_image.py | 43 +++++--- .../photoshop/publish/extract_review.py | 62 +++++++---- .../photoshop/publish/extract_save_scene.py | 5 +- 7 files changed, 240 insertions(+), 64 deletions(-) create mode 100644 pype/modules/websocket_server/clients/photoshop_client.py create mode 100644 pype/modules/websocket_server/hosts/photoshop.py diff --git a/pype/modules/websocket_server/clients/photoshop_client.py b/pype/modules/websocket_server/clients/photoshop_client.py new file mode 100644 index 0000000000..0090c10db2 --- /dev/null +++ b/pype/modules/websocket_server/clients/photoshop_client.py @@ -0,0 +1,102 @@ +from pype.modules.websocket_server import WebSocketServer +""" + Stub handling connection from server to client. + Used anywhere solution is calling client methods. +""" +import json +from collections import namedtuple + +class PhotoshopClientStub(): + + def __init__(self): + self.websocketserver = WebSocketServer.get_instance() + self.client = self.websocketserver.get_client() + + def read(self, layer): + layers_data = {} + res = self.websocketserver.call(self.client.call('Photoshop.read')) + try: + layers_data = json.loads(res) + except json.decoder.JSONDecodeError: + pass + + return layers_data.get(str(layer.id)) + + def get_layers(self): + """ + Returns JSON document with all(?) layers in active document. + + :return: + Format of tuple: { 'id':'123', + 'name': 'My Layer 1', + '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 + + def get_layers_in_layers(self, layers): + """ + Return all layers that belong to layers (might be groups). + :param layers: + :return: + """ + all_layers = self.get_layers() + print("get_layers_in_layers {}".format(layers)) + print("get_layers_in_layers len {}".format(len(layers))) + print("get_layers_in_layers type {}".format(type(layers))) + ret = [] + layer_ids = [lay.id for lay in layers] + layer_group_ids = [ll.groupId for ll in layers if ll.group] + for layer in all_layers: + if layer.groupId in layer_group_ids: # all from group + ret.append(layer) + if layer.id in layer_ids: + ret.append(layer) + + return ret + + + def select_layers(self, layers): + layer_ids = [layer.id for layer in layers] + + res = self.websocketserver.call(self.client.call + ('Photoshop.get_layers', + layers=layer_ids) + ) + + def get_active_document_name(self): + res = self.websocketserver.call(self.client.call + ('Photoshop.get_active_document_name')) + + return res + + 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)) + + 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)) + + def close(self): + self.client.close() + diff --git a/pype/modules/websocket_server/hosts/photoshop.py b/pype/modules/websocket_server/hosts/photoshop.py new file mode 100644 index 0000000000..9092530e48 --- /dev/null +++ b/pype/modules/websocket_server/hosts/photoshop.py @@ -0,0 +1,34 @@ +import asyncio + +from pype.api import Logger +from wsrpc_aiohttp import WebSocketRoute + +log = Logger().get_logger("WebsocketServer") + + +class Photoshop(WebSocketRoute): + """ + One route, mimicking external application (like Harmony, etc). + All functions could be called from client. + 'do_notify' function calls function on the client - mimicking + notification after long running job on the server or similar + """ + instance = None + + def init(self, **kwargs): + # Python __init__ must be return "self". + # This method might return anything. + log.debug("someone called Photoshop route") + self.instance = self + return kwargs + + # server functions + async def ping(self): + log.debug("someone called Photoshop route ping") + + # This method calls function on the client side + # client functions + + async def read(self): + log.debug("photoshop.read client calls server server calls Photo client") + return await self.socket.call('Photoshop.read') diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index 9d0d01d156..f9be7c88a9 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -80,19 +80,26 @@ class WebSocketServer(): module = importlib.import_module(module_name) cls = getattr(module, class_name) WebSocketAsync.add_route(class_name, cls) - self.handlers[class_name] = cls() # TODO refactor sys.path.pop() def call(self, func): log.debug("websocket.call {}".format(func)) - return self.websocket_thread.call_async(func) + future = asyncio.run_coroutine_threadsafe(func, + self.websocket_thread.loop) + result = future.result() + return result - def task_finished(self, task): - print("task finished {}".format(task.result)) - print("client socket {}".format(self.client.client.socket)) + def get_client(self): + """ + Return first connected client to WebSocket + TODO implement selection by Route + :return: client + """ + clients = WebSocketAsync.get_clients() + key = list(clients.keys())[0] + client = clients.get(key) - def get_routes(self): - WebSocketAsync.get_routes() + return client @staticmethod def get_instance(): @@ -176,20 +183,6 @@ class WebsocketServerThread(threading.Thread): self.module.thread_stopped() log.info("Websocket server stopped") - def call_async(self, func): - # log.debug("call async") - # print("call aysnc") - # log.debug("my loop {}".format(self.loop)) - # task = self.loop.create_task(func) - # print("waitning") - # log.debug("waiting for task {}".format(func)) - # self.loop.run_until_complete(task) - # log.debug("returned value {}".format(task.result)) - # return task.result - task = self.loop.create_task(func) - task.add_done_callback(self.module.task_finished) - self.tasks.append(task) - async def start_server(self): """ Starts runner and TCPsite """ self.runner = web.AppRunner(self.module.app) diff --git a/pype/plugins/photoshop/publish/collect_instances.py b/pype/plugins/photoshop/publish/collect_instances.py index 4937f2a1e4..cc7341f384 100644 --- a/pype/plugins/photoshop/publish/collect_instances.py +++ b/pype/plugins/photoshop/publish/collect_instances.py @@ -4,6 +4,7 @@ from avalon import photoshop import pyblish.api +from pype.modules.websocket_server.clients.photoshop_client import PhotoshopClientStub class CollectInstances(pyblish.api.ContextPlugin): """Gather instances by LayerSet and file metadata @@ -27,8 +28,13 @@ class CollectInstances(pyblish.api.ContextPlugin): # can be. pythoncom.CoInitialize() - for layer in photoshop.get_layers_in_document(): - layer_data = photoshop.read(layer) + from datetime import datetime + start = datetime.now() + # for timing + photoshop_client = PhotoshopClientStub() + layers = photoshop_client.get_layers() + for layer in layers: + layer_data = photoshop_client.read(layer) # Skip layers without metadata. if layer_data is None: @@ -38,18 +44,19 @@ class CollectInstances(pyblish.api.ContextPlugin): 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 + # child_layers = [*layer.Layers] + # self.log.debug("child_layers {}".format(child_layers)) + # if not child_layers: + # self.log.info("%s skipped, it was empty." % layer.Name) + # continue - instance = context.create_instance(layer.Name) + instance = context.create_instance(layer.name) instance.append(layer) instance.data.update(layer_data) instance.data["families"] = self.families_mapping[ layer_data["family"] ] - instance.data["publish"] = layer.Visible + instance.data["publish"] = layer.visible # Produce diagnostic message for any graphical # user interface interested in visualising it. diff --git a/pype/plugins/photoshop/publish/extract_image.py b/pype/plugins/photoshop/publish/extract_image.py index 6dfccdc4f2..0451308ef1 100644 --- a/pype/plugins/photoshop/publish/extract_image.py +++ b/pype/plugins/photoshop/publish/extract_image.py @@ -3,6 +3,8 @@ import os import pype.api from avalon import photoshop +from pype.modules.websocket_server.clients.photoshop_client import \ + PhotoshopClientStub class ExtractImage(pype.api.Extractor): """Produce a flattened image file from instance @@ -20,36 +22,49 @@ class ExtractImage(pype.api.Extractor): staging_dir = self.staging_dir(instance) self.log.info("Outputting image to {}".format(staging_dir)) + layers = [] + for image_instance in instance.context: + if image_instance.data["family"] != "image": + continue + layers.append(image_instance[0]) + # Perform extraction + photoshop_client = PhotoshopClientStub() 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 + extract_ids = set([ll.id for ll in photoshop_client. + get_layers_in_layers(layers)]) - save_options = {} + for layer in photoshop_client.get_layers(): + # limit unnecessary calls to client + if layer.visible and layer.id not in extract_ids: + photoshop_client.set_visible(layer.id, + False) + if not layer.visible and layer.id in extract_ids: + photoshop_client.set_visible(layer.id, + True) + + save_options = [] if "png" in self.formats: - save_options["png"] = photoshop.com_objects.PNGSaveOptions() + save_options.append('png') if "jpg" in self.formats: - save_options["jpg"] = photoshop.com_objects.JPEGSaveOptions() + save_options.append('jpg') file_basename = os.path.splitext( - photoshop.app().ActiveDocument.Name + photoshop_client.get_active_document_name() )[0] - for extension, save_option in save_options.items(): + for extension in save_options: _filename = "{}.{}".format(file_basename, extension) files[extension] = _filename full_filename = os.path.join(staging_dir, _filename) - photoshop.app().ActiveDocument.SaveAs( - full_filename, save_option, True - ) + photoshop_client.saveAs(full_filename, + extension, + True) + representations = [] for extension, filename in files.items(): diff --git a/pype/plugins/photoshop/publish/extract_review.py b/pype/plugins/photoshop/publish/extract_review.py index 078ee53899..1c3aeaffb5 100644 --- a/pype/plugins/photoshop/publish/extract_review.py +++ b/pype/plugins/photoshop/publish/extract_review.py @@ -4,6 +4,10 @@ import pype.api import pype.lib from avalon import photoshop +from datetime import datetime +from pype.modules.websocket_server.clients.photoshop_client import \ + PhotoshopClientStub + class ExtractReview(pype.api.Extractor): """Produce a flattened image file from all instances.""" @@ -13,10 +17,12 @@ class ExtractReview(pype.api.Extractor): families = ["review"] def process(self, instance): - + start = datetime.now() staging_dir = self.staging_dir(instance) self.log.info("Outputting image to {}".format(staging_dir)) + photoshop_client = PhotoshopClientStub() + layers = [] for image_instance in instance.context: if image_instance.data["family"] != "image": @@ -25,26 +31,39 @@ class ExtractReview(pype.api.Extractor): # Perform extraction output_image = "{}.jpg".format( - os.path.splitext(photoshop.app().ActiveDocument.Name)[0] + os.path.splitext(photoshop_client.get_active_document_name())[0] ) output_image_path = os.path.join(staging_dir, output_image) + self.log.info( + "first part took {}".format(datetime.now() - start)) with photoshop.maintained_visibility(): # Hide all other layers. - extract_ids = [ - x.id for x in photoshop.get_layers_in_layers(layers) - ] - for layer in photoshop.get_layers_in_document(): - if layer.id in extract_ids: - layer.Visible = True - else: - layer.Visible = False + start = datetime.now() + extract_ids = set([ll.id for ll in photoshop_client. + get_layers_in_layers(layers)]) + self.log.info("extract_ids {}".format(extract_ids)) - photoshop.app().ActiveDocument.SaveAs( - output_image_path, - photoshop.com_objects.JPEGSaveOptions(), - True - ) + for layer in photoshop_client.get_layers(): + # limit unnecessary calls to client + if layer.visible and layer.id not in extract_ids: + photoshop_client.set_visible(layer.id, + False) + if not layer.visible and layer.id in extract_ids: + photoshop_client.set_visible(layer.id, + True) + self.log.info( + "get_layers_in_layers took {}".format(datetime.now() - start)) + start = datetime.now() + + self.log.info("output_image_path {}".format(output_image_path)) + photoshop_client.saveAs(output_image_path, + 'jpg', + True) + self.log.info( + "saveAs {} took {}".format('JPG', datetime.now() - start)) + + start = datetime.now() ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") instance.data["representations"].append({ @@ -65,7 +84,8 @@ class ExtractReview(pype.api.Extractor): thumbnail_path ] output = pype.lib._subprocess(args) - + self.log.info( + "thumbnail {} took {}".format('JPG', datetime.now() - start)) self.log.debug(output) instance.data["representations"].append({ @@ -75,7 +95,7 @@ class ExtractReview(pype.api.Extractor): "stagingDir": staging_dir, "tags": ["thumbnail"] }) - + start = datetime.now() # Generate mov. mov_path = os.path.join(staging_dir, "review.mov") args = [ @@ -86,9 +106,10 @@ class ExtractReview(pype.api.Extractor): mov_path ] output = pype.lib._subprocess(args) - + self.log.info( + "review {} took {}".format('JPG', datetime.now() - start)) self.log.debug(output) - + start = datetime.now() instance.data["representations"].append({ "name": "mov", "ext": "mov", @@ -105,5 +126,6 @@ class ExtractReview(pype.api.Extractor): instance.data["frameStart"] = 1 instance.data["frameEnd"] = 1 instance.data["fps"] = 25 - + self.log.info( + "end {} took {}".format('JPG', datetime.now() - start)) self.log.info(f"Extracted {instance} to {staging_dir}") diff --git a/pype/plugins/photoshop/publish/extract_save_scene.py b/pype/plugins/photoshop/publish/extract_save_scene.py index b3d4f0e447..e2068501db 100644 --- a/pype/plugins/photoshop/publish/extract_save_scene.py +++ b/pype/plugins/photoshop/publish/extract_save_scene.py @@ -1,7 +1,7 @@ import pype.api from avalon import photoshop - +from datetime import datetime class ExtractSaveScene(pype.api.Extractor): """Save scene before extraction.""" @@ -11,4 +11,7 @@ class ExtractSaveScene(pype.api.Extractor): families = ["workfile"] def process(self, instance): + start = datetime.now() photoshop.app().ActiveDocument.Save() + self.log.info( + "ExtractSaveScene took {}".format(datetime.now() - start)) From a9f146e2fca896a8558c3c8d65d6df1a0f3af3ec Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 19 Aug 2020 12:54:42 +0200 Subject: [PATCH 03/17] Fixed fullName, implemented imprint --- .../clients/photoshop_client.py | 73 ++++++++++++++++--- .../photoshop/publish/collect_current_file.py | 6 +- .../photoshop/publish/collect_instances.py | 6 ++ .../photoshop/publish/extract_save_scene.py | 6 +- .../photoshop/publish/increment_workfile.py | 5 +- .../publish/validate_instance_asset.py | 11 ++- .../photoshop/publish/validate_naming.py | 9 ++- 7 files changed, 96 insertions(+), 20 deletions(-) diff --git a/pype/modules/websocket_server/clients/photoshop_client.py b/pype/modules/websocket_server/clients/photoshop_client.py index 0090c10db2..6d3615e1a4 100644 --- a/pype/modules/websocket_server/clients/photoshop_client.py +++ b/pype/modules/websocket_server/clients/photoshop_client.py @@ -13,15 +13,34 @@ class PhotoshopClientStub(): self.client = self.websocketserver.get_client() def read(self, layer): - layers_data = {} - res = self.websocketserver.call(self.client.call('Photoshop.read')) - try: - layers_data = json.loads(res) - except json.decoder.JSONDecodeError: - pass + layers_data = self._get_layers_metadata() return layers_data.get(str(layer.id)) + def imprint(self, layer, data): + layers_data = self._get_layers_metadata() + # json.dumps writes integer values in a dictionary to string, so + # anticipating it here. + if str(layer.id) in layers_data: + layers_data[str(layer.id)].update(data) + else: + layers_data[str(layer.id)] = data + + # Ensure only valid ids are stored. + layer_ids = [layer.id for layer in self.get_layers()] + cleaned_data = {} + + for id in layers_data: + if int(id) in layer_ids: + cleaned_data[id] = layers_data[id] + + payload = json.dumps(cleaned_data, indent=4) + + res = self.websocketserver.call(self.client.call + ('Photoshop.imprint', + payload=payload) + ) + def get_layers(self): """ Returns JSON document with all(?) layers in active document. @@ -77,18 +96,34 @@ class PhotoshopClientStub(): layers=layer_ids) ) + def get_active_document_full_name(self): + """ + Returns full name with path of active document via ws call + :return: full path with name + """ + res = self.websocketserver.call( + self.client.call('Photoshop.get_active_document_full_name')) + + return res + def get_active_document_name(self): + """ + Returns just a name of active document via ws call + :return: file name + """ res = self.websocketserver.call(self.client.call ('Photoshop.get_active_document_name')) return res - def set_visible(self, layer_id, visibility): - print("set_visible {}, {}".format(layer_id, visibility)) + def save(self): + """ + Saves active document + :return: None + """ res = self.websocketserver.call(self.client.call - ('Photoshop.set_visible', - layer_id=layer_id, - visibility=visibility)) + ('Photoshop.save')) + def saveAs(self, image_path, ext, as_copy): res = self.websocketserver.call(self.client.call @@ -97,6 +132,22 @@ class PhotoshopClientStub(): 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)) + + def _get_layers_metadata(self): + layers_data = {} + res = self.websocketserver.call(self.client.call('Photoshop.read')) + try: + layers_data = json.loads(res) + except json.decoder.JSONDecodeError: + pass + return layers_data + def close(self): self.client.close() diff --git a/pype/plugins/photoshop/publish/collect_current_file.py b/pype/plugins/photoshop/publish/collect_current_file.py index 4308588559..bb81718bcc 100644 --- a/pype/plugins/photoshop/publish/collect_current_file.py +++ b/pype/plugins/photoshop/publish/collect_current_file.py @@ -3,6 +3,9 @@ import os import pyblish.api from avalon import photoshop +from pype.modules.websocket_server.clients.photoshop_client import \ + PhotoshopClientStub + class CollectCurrentFile(pyblish.api.ContextPlugin): """Inject the current working file into context""" @@ -12,6 +15,7 @@ class CollectCurrentFile(pyblish.api.ContextPlugin): hosts = ["photoshop"] def process(self, context): + photoshop_client = PhotoshopClientStub() context.data["currentFile"] = os.path.normpath( - photoshop.app().ActiveDocument.FullName + photoshop_client.get_active_document_full_name() ).replace("\\", "/") diff --git a/pype/plugins/photoshop/publish/collect_instances.py b/pype/plugins/photoshop/publish/collect_instances.py index cc7341f384..d94adde00b 100644 --- a/pype/plugins/photoshop/publish/collect_instances.py +++ b/pype/plugins/photoshop/publish/collect_instances.py @@ -33,8 +33,14 @@ class CollectInstances(pyblish.api.ContextPlugin): # for timing photoshop_client = PhotoshopClientStub() layers = photoshop_client.get_layers() + for layer in layers: layer_data = photoshop_client.read(layer) + self.log.info("layer_data {}".format(layer_data)) + + photoshop_client.imprint(layer, layer_data) + new_layer_data = photoshop_client.read(layer) + assert layer_data == new_layer_data # Skip layers without metadata. if layer_data is None: diff --git a/pype/plugins/photoshop/publish/extract_save_scene.py b/pype/plugins/photoshop/publish/extract_save_scene.py index e2068501db..ea7bdda9af 100644 --- a/pype/plugins/photoshop/publish/extract_save_scene.py +++ b/pype/plugins/photoshop/publish/extract_save_scene.py @@ -1,6 +1,9 @@ import pype.api from avalon import photoshop +from pype.modules.websocket_server.clients.photoshop_client import \ + PhotoshopClientStub + from datetime import datetime class ExtractSaveScene(pype.api.Extractor): """Save scene before extraction.""" @@ -11,7 +14,8 @@ class ExtractSaveScene(pype.api.Extractor): families = ["workfile"] def process(self, instance): + photoshop_client = PhotoshopClientStub() start = datetime.now() - photoshop.app().ActiveDocument.Save() + photoshop_client.save() self.log.info( "ExtractSaveScene took {}".format(datetime.now() - start)) diff --git a/pype/plugins/photoshop/publish/increment_workfile.py b/pype/plugins/photoshop/publish/increment_workfile.py index ba9ab8606a..0ae7e9772f 100644 --- a/pype/plugins/photoshop/publish/increment_workfile.py +++ b/pype/plugins/photoshop/publish/increment_workfile.py @@ -3,6 +3,8 @@ from pype.action import get_errored_plugins_from_data from pype.lib import version_up from avalon import photoshop +from pype.modules.websocket_server.clients.photoshop_client import \ + PhotoshopClientStub class IncrementWorkfile(pyblish.api.InstancePlugin): """Increment the current workfile. @@ -24,6 +26,7 @@ class IncrementWorkfile(pyblish.api.InstancePlugin): ) scene_path = version_up(instance.context.data["currentFile"]) - photoshop.app().ActiveDocument.SaveAs(scene_path) + photoshop_client = PhotoshopClientStub() + photoshop_client.saveAs(scene_path, 'psd', True) self.log.info("Incremented workfile to: {}".format(scene_path)) diff --git a/pype/plugins/photoshop/publish/validate_instance_asset.py b/pype/plugins/photoshop/publish/validate_instance_asset.py index ab1d02269f..6a0a408878 100644 --- a/pype/plugins/photoshop/publish/validate_instance_asset.py +++ b/pype/plugins/photoshop/publish/validate_instance_asset.py @@ -4,6 +4,8 @@ import pyblish.api import pype.api from avalon import photoshop +from pype.modules.websocket_server.clients.photoshop_client import \ + PhotoshopClientStub class ValidateInstanceAssetRepair(pyblish.api.Action): """Repair the instance asset.""" @@ -23,11 +25,14 @@ class ValidateInstanceAssetRepair(pyblish.api.Action): # Apply pyblish.logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(failed, plugin) - + photoshop_client = PhotoshopClientStub() for instance in instances: - data = photoshop.read(instance[0]) + self.log.info("validate_instance_asset instance[0] {}".format(instance[0])) + self.log.info("validate_instance_asset instance {}".format(instance)) + data = photoshop_client.read(instance[0]) + data["asset"] = os.environ["AVALON_ASSET"] - photoshop.imprint(instance[0], data) + photoshop_client.imprint(instance[0], data) class ValidateInstanceAsset(pyblish.api.InstancePlugin): diff --git a/pype/plugins/photoshop/publish/validate_naming.py b/pype/plugins/photoshop/publish/validate_naming.py index 51e00da352..7734a0e5a0 100644 --- a/pype/plugins/photoshop/publish/validate_naming.py +++ b/pype/plugins/photoshop/publish/validate_naming.py @@ -2,6 +2,8 @@ import pyblish.api import pype.api from avalon import photoshop +from pype.modules.websocket_server.clients.photoshop_client import \ + PhotoshopClientStub class ValidateNamingRepair(pyblish.api.Action): """Repair the instance asset.""" @@ -21,13 +23,14 @@ class ValidateNamingRepair(pyblish.api.Action): # Apply pyblish.logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(failed, plugin) - + photoshop_client = PhotoshopClientStub() for instance in instances: + self.log.info("validate_naming instance {}".format(instance)) name = instance.data["name"].replace(" ", "_") instance[0].Name = name - data = photoshop.read(instance[0]) + data = photoshop_client.read(instance[0]) data["subset"] = "image" + name - photoshop.imprint(instance[0], data) + photoshop_client.imprint(instance[0], data) return True From fe3cfc24422580c99f837c6bf02de029e80d54a9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 19 Aug 2020 20:50:14 +0200 Subject: [PATCH 04/17] 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 From 180035731b329fe617460389bebdc995e79260ab Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 19 Aug 2020 21:10:51 +0200 Subject: [PATCH 05/17] Hound --- .../websocket_server/hosts/photoshop.py | 5 ++-- .../websocket_server/websocket_server.py | 2 +- pype/plugins/photoshop/load/load_image.py | 2 +- .../photoshop/publish/collect_current_file.py | 3 +- .../photoshop/publish/collect_instances.py | 7 +---- .../photoshop/publish/extract_image.py | 4 +-- .../photoshop/publish/extract_review.py | 28 ++----------------- .../photoshop/publish/extract_save_scene.py | 7 ++--- .../photoshop/publish/increment_workfile.py | 4 +-- .../publish/validate_instance_asset.py | 2 -- .../photoshop/publish/validate_naming.py | 3 +- 11 files changed, 17 insertions(+), 50 deletions(-) diff --git a/pype/modules/websocket_server/hosts/photoshop.py b/pype/modules/websocket_server/hosts/photoshop.py index 9092530e48..c63f66865e 100644 --- a/pype/modules/websocket_server/hosts/photoshop.py +++ b/pype/modules/websocket_server/hosts/photoshop.py @@ -1,5 +1,3 @@ -import asyncio - from pype.api import Logger from wsrpc_aiohttp import WebSocketRoute @@ -30,5 +28,6 @@ class Photoshop(WebSocketRoute): # client functions async def read(self): - log.debug("photoshop.read client calls server server calls Photo client") + log.debug("photoshop.read client calls server server calls " + "Photo client") return await self.socket.call('Photoshop.read') diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index 02fde4d56a..6b730d4eb3 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -105,7 +105,7 @@ class WebSocketServer(): @staticmethod def get_instance(): - if WebSocketServer._instance == None: + if WebSocketServer._instance is None: WebSocketServer() return WebSocketServer._instance diff --git a/pype/plugins/photoshop/load/load_image.py b/pype/plugins/photoshop/load/load_image.py index 0e437b15ba..a24280553c 100644 --- a/pype/plugins/photoshop/load/load_image.py +++ b/pype/plugins/photoshop/load/load_image.py @@ -1,7 +1,7 @@ from avalon import api, photoshop from pype.modules.websocket_server.clients.photoshop_client \ - import PhotoshopClientStub + import PhotoshopClientStub photoshopClient = PhotoshopClientStub() diff --git a/pype/plugins/photoshop/publish/collect_current_file.py b/pype/plugins/photoshop/publish/collect_current_file.py index bb81718bcc..604ce97f89 100644 --- a/pype/plugins/photoshop/publish/collect_current_file.py +++ b/pype/plugins/photoshop/publish/collect_current_file.py @@ -1,10 +1,9 @@ import os import pyblish.api -from avalon import photoshop from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub + PhotoshopClientStub class CollectCurrentFile(pyblish.api.ContextPlugin): diff --git a/pype/plugins/photoshop/publish/collect_instances.py b/pype/plugins/photoshop/publish/collect_instances.py index f2d1c141fd..82a0c35311 100644 --- a/pype/plugins/photoshop/publish/collect_instances.py +++ b/pype/plugins/photoshop/publish/collect_instances.py @@ -1,11 +1,9 @@ import pythoncom -from avalon import photoshop - import pyblish.api from pype.modules.websocket_server.clients.photoshop_client \ - import PhotoshopClientStub + import PhotoshopClientStub class CollectInstances(pyblish.api.ContextPlugin): @@ -30,9 +28,6 @@ class CollectInstances(pyblish.api.ContextPlugin): # can be. pythoncom.CoInitialize() - from datetime import datetime - start = datetime.now() - # for timing photoshop_client = PhotoshopClientStub() layers = photoshop_client.get_layers() diff --git a/pype/plugins/photoshop/publish/extract_image.py b/pype/plugins/photoshop/publish/extract_image.py index 0451308ef1..4cbac38ce7 100644 --- a/pype/plugins/photoshop/publish/extract_image.py +++ b/pype/plugins/photoshop/publish/extract_image.py @@ -4,7 +4,8 @@ import pype.api from avalon import photoshop from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub + PhotoshopClientStub + class ExtractImage(pype.api.Extractor): """Produce a flattened image file from instance @@ -65,7 +66,6 @@ class ExtractImage(pype.api.Extractor): extension, True) - representations = [] for extension, filename in files.items(): representations.append({ diff --git a/pype/plugins/photoshop/publish/extract_review.py b/pype/plugins/photoshop/publish/extract_review.py index 1c3aeaffb5..1e8d726720 100644 --- a/pype/plugins/photoshop/publish/extract_review.py +++ b/pype/plugins/photoshop/publish/extract_review.py @@ -4,9 +4,8 @@ import pype.api import pype.lib from avalon import photoshop -from datetime import datetime from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub + PhotoshopClientStub class ExtractReview(pype.api.Extractor): @@ -17,7 +16,6 @@ class ExtractReview(pype.api.Extractor): families = ["review"] def process(self, instance): - start = datetime.now() staging_dir = self.staging_dir(instance) self.log.info("Outputting image to {}".format(staging_dir)) @@ -34,14 +32,10 @@ class ExtractReview(pype.api.Extractor): os.path.splitext(photoshop_client.get_active_document_name())[0] ) output_image_path = os.path.join(staging_dir, output_image) - self.log.info( - "first part took {}".format(datetime.now() - start)) with photoshop.maintained_visibility(): # Hide all other layers. - start = datetime.now() extract_ids = set([ll.id for ll in photoshop_client. - get_layers_in_layers(layers)]) - self.log.info("extract_ids {}".format(extract_ids)) + get_layers_in_layers(layers)]) for layer in photoshop_client.get_layers(): # limit unnecessary calls to client @@ -51,19 +45,11 @@ class ExtractReview(pype.api.Extractor): if not layer.visible and layer.id in extract_ids: photoshop_client.set_visible(layer.id, True) - self.log.info( - "get_layers_in_layers took {}".format(datetime.now() - start)) - start = datetime.now() - - self.log.info("output_image_path {}".format(output_image_path)) photoshop_client.saveAs(output_image_path, 'jpg', True) - self.log.info( - "saveAs {} took {}".format('JPG', datetime.now() - start)) - start = datetime.now() ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") instance.data["representations"].append({ @@ -84,9 +70,6 @@ class ExtractReview(pype.api.Extractor): thumbnail_path ] output = pype.lib._subprocess(args) - self.log.info( - "thumbnail {} took {}".format('JPG', datetime.now() - start)) - self.log.debug(output) instance.data["representations"].append({ "name": "thumbnail", @@ -95,7 +78,6 @@ class ExtractReview(pype.api.Extractor): "stagingDir": staging_dir, "tags": ["thumbnail"] }) - start = datetime.now() # Generate mov. mov_path = os.path.join(staging_dir, "review.mov") args = [ @@ -106,10 +88,7 @@ class ExtractReview(pype.api.Extractor): mov_path ] output = pype.lib._subprocess(args) - self.log.info( - "review {} took {}".format('JPG', datetime.now() - start)) self.log.debug(output) - start = datetime.now() instance.data["representations"].append({ "name": "mov", "ext": "mov", @@ -126,6 +105,5 @@ class ExtractReview(pype.api.Extractor): instance.data["frameStart"] = 1 instance.data["frameEnd"] = 1 instance.data["fps"] = 25 - self.log.info( - "end {} took {}".format('JPG', datetime.now() - start)) + self.log.info(f"Extracted {instance} to {staging_dir}") diff --git a/pype/plugins/photoshop/publish/extract_save_scene.py b/pype/plugins/photoshop/publish/extract_save_scene.py index ea7bdda9af..3357a05f24 100644 --- a/pype/plugins/photoshop/publish/extract_save_scene.py +++ b/pype/plugins/photoshop/publish/extract_save_scene.py @@ -2,9 +2,9 @@ import pype.api from avalon import photoshop from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub + PhotoshopClientStub + -from datetime import datetime class ExtractSaveScene(pype.api.Extractor): """Save scene before extraction.""" @@ -15,7 +15,4 @@ class ExtractSaveScene(pype.api.Extractor): def process(self, instance): photoshop_client = PhotoshopClientStub() - start = datetime.now() photoshop_client.save() - self.log.info( - "ExtractSaveScene took {}".format(datetime.now() - start)) diff --git a/pype/plugins/photoshop/publish/increment_workfile.py b/pype/plugins/photoshop/publish/increment_workfile.py index 0ae7e9772f..4298eb8e77 100644 --- a/pype/plugins/photoshop/publish/increment_workfile.py +++ b/pype/plugins/photoshop/publish/increment_workfile.py @@ -1,10 +1,10 @@ import pyblish.api from pype.action import get_errored_plugins_from_data from pype.lib import version_up -from avalon import photoshop from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub + PhotoshopClientStub + class IncrementWorkfile(pyblish.api.InstancePlugin): """Increment the current workfile. diff --git a/pype/plugins/photoshop/publish/validate_instance_asset.py b/pype/plugins/photoshop/publish/validate_instance_asset.py index 6a0a408878..4bbea69eb4 100644 --- a/pype/plugins/photoshop/publish/validate_instance_asset.py +++ b/pype/plugins/photoshop/publish/validate_instance_asset.py @@ -27,8 +27,6 @@ class ValidateInstanceAssetRepair(pyblish.api.Action): instances = pyblish.api.instances_by_plugin(failed, plugin) photoshop_client = PhotoshopClientStub() for instance in instances: - self.log.info("validate_instance_asset instance[0] {}".format(instance[0])) - self.log.info("validate_instance_asset instance {}".format(instance)) data = photoshop_client.read(instance[0]) data["asset"] = os.environ["AVALON_ASSET"] diff --git a/pype/plugins/photoshop/publish/validate_naming.py b/pype/plugins/photoshop/publish/validate_naming.py index 7734a0e5a0..ba8a3e997e 100644 --- a/pype/plugins/photoshop/publish/validate_naming.py +++ b/pype/plugins/photoshop/publish/validate_naming.py @@ -3,7 +3,8 @@ import pype.api from avalon import photoshop from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub + PhotoshopClientStub + class ValidateNamingRepair(pyblish.api.Action): """Repair the instance asset.""" From de9aca5f8f3ecdb3e635079c4cbd37c79b1c91a7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 20 Aug 2020 10:52:18 +0200 Subject: [PATCH 06/17] Speedup of collect_instances.py --- .../clients/photoshop_client.py | 25 +++++++++++-------- .../photoshop/publish/collect_instances.py | 14 ++++------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/pype/modules/websocket_server/clients/photoshop_client.py b/pype/modules/websocket_server/clients/photoshop_client.py index eea297954f..bf72c1bc5a 100644 --- a/pype/modules/websocket_server/clients/photoshop_client.py +++ b/pype/modules/websocket_server/clients/photoshop_client.py @@ -18,17 +18,19 @@ class PhotoshopClientStub(): self.websocketserver = WebSocketServer.get_instance() self.client = self.websocketserver.get_client() - def read(self, layer): + def read(self, layer, layers_meta=None): """ Parses layer metadata from Headline field of active document - :param layer: + :param layer: Layer("id": XXX, "name":'YYY') @@ -38,13 +40,14 @@ class PhotoshopClientStub(): triggered :return: None """ - layers_data = self._get_layers_metadata() + if not layers_meta: + layers_meta = self._get_layers_metadata() # json.dumps writes integer values in a dictionary to string, so # anticipating it here. - if str(layer.id) in layers_data: - layers_data[str(layer.id)].update(data) + if str(layer.id) in layers_meta and layers_meta[str(layer.id)]: + layers_meta[str(layer.id)].update(data) else: - layers_data[str(layer.id)] = data + layers_meta[str(layer.id)] = data # Ensure only valid ids are stored. if not all_layers: @@ -52,9 +55,9 @@ class PhotoshopClientStub(): layer_ids = [layer.id for layer in all_layers] cleaned_data = {} - for id in layers_data: + for id in layers_meta: if int(id) in layer_ids: - cleaned_data[id] = layers_data[id] + cleaned_data[id] = layers_meta[id] payload = json.dumps(cleaned_data, indent=4) diff --git a/pype/plugins/photoshop/publish/collect_instances.py b/pype/plugins/photoshop/publish/collect_instances.py index 82a0c35311..47584272d2 100644 --- a/pype/plugins/photoshop/publish/collect_instances.py +++ b/pype/plugins/photoshop/publish/collect_instances.py @@ -2,8 +2,9 @@ import pythoncom 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): @@ -30,14 +31,9 @@ class CollectInstances(pyblish.api.ContextPlugin): photoshop_client = PhotoshopClientStub() layers = photoshop_client.get_layers() - + layers_meta = photoshop_client._get_layers_metadata() for layer in layers: - layer_data = photoshop_client.read(layer) - self.log.info("layer_data {}".format(layer_data)) - - photoshop_client.imprint(layer, layer_data, layers) - new_layer_data = photoshop_client.read(layer) - assert layer_data == new_layer_data + layer_data = photoshop_client.read(layer, layers_meta) # Skip layers without metadata. if layer_data is None: From e551039c124cfe82252479dcf6b506aac17cd31d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 20 Aug 2020 15:42:43 +0200 Subject: [PATCH 07/17] Speedup of collect_instances.py --- .../websocket_server/clients/photoshop_client.py | 13 ++++++++----- pype/plugins/photoshop/publish/extract_review.py | 7 ++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pype/modules/websocket_server/clients/photoshop_client.py b/pype/modules/websocket_server/clients/photoshop_client.py index bf72c1bc5a..00e5355786 100644 --- a/pype/modules/websocket_server/clients/photoshop_client.py +++ b/pype/modules/websocket_server/clients/photoshop_client.py @@ -91,14 +91,17 @@ class PhotoshopClientStub(): print("get_layers_in_layers len {}".format(len(layers))) print("get_layers_in_layers type {}".format(type(layers))) ret = [] - layer_ids = [lay.id for lay in layers] - layer_group_ids = [ll.groupId for ll in layers if ll.group] + parent_ids = set([lay.id for lay in layers]) + print("parent_ids ".format(parent_ids)) for layer in all_layers: - if layer.groupId in layer_group_ids: # all from group + print("layer {}".format(layer)) + parents = set(layer.parents) + print("parents {}".format(layer)) + if len(parent_ids & parents) > 0: ret.append(layer) - if layer.id in layer_ids: + if layer.id in parent_ids: ret.append(layer) - + print("ret {}".format(ret)) return ret def group_selected_layers(self): diff --git a/pype/plugins/photoshop/publish/extract_review.py b/pype/plugins/photoshop/publish/extract_review.py index 1e8d726720..806b59341b 100644 --- a/pype/plugins/photoshop/publish/extract_review.py +++ b/pype/plugins/photoshop/publish/extract_review.py @@ -4,8 +4,9 @@ import pype.api import pype.lib from avalon import photoshop -from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub +from pype.modules.websocket_server.clients.photoshop_client import ( + PhotoshopClientStub +) class ExtractReview(pype.api.Extractor): @@ -36,7 +37,7 @@ class ExtractReview(pype.api.Extractor): # Hide all other layers. extract_ids = set([ll.id for ll in photoshop_client. get_layers_in_layers(layers)]) - + self.log.info("extract_ids {}".format(extract_ids)) for layer in photoshop_client.get_layers(): # limit unnecessary calls to client if layer.visible and layer.id not in extract_ids: From 41e2149d1295d5f89363648bd64da9cd11ec9f5c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 20 Aug 2020 17:15:36 +0200 Subject: [PATCH 08/17] Fix select correct layers for multiple images --- .../clients/photoshop_client.py | 29 ++++++++----------- .../photoshop/publish/collect_instances.py | 2 +- .../photoshop/publish/extract_image.py | 13 +++------ 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/pype/modules/websocket_server/clients/photoshop_client.py b/pype/modules/websocket_server/clients/photoshop_client.py index 00e5355786..330c2ceff0 100644 --- a/pype/modules/websocket_server/clients/photoshop_client.py +++ b/pype/modules/websocket_server/clients/photoshop_client.py @@ -26,7 +26,7 @@ class PhotoshopClientStub(): :return: """ if layers_meta is None: - layers_meta = self._get_layers_metadata() + layers_meta = self.get_layers_metadata() return layers_meta.get(str(layer.id)) @@ -38,10 +38,13 @@ class PhotoshopClientStub(): :param all_layers: - for performance, could be injected for usage in loop, if not, single call will be triggered + :param layers_meta: json representation from Headline + (for performance - provide only if imprint is in + loop - value should be same) :return: None """ if not layers_meta: - layers_meta = self._get_layers_metadata() + layers_meta = self.get_layers_metadata() # json.dumps writes integer values in a dictionary to string, so # anticipating it here. if str(layer.id) in layers_meta and layers_meta[str(layer.id)]: @@ -83,25 +86,20 @@ class PhotoshopClientStub(): def get_layers_in_layers(self, layers): """ Return all layers that belong to layers (might be groups). - :param layers: - :return: + :param layers: + :return: """ all_layers = self.get_layers() - print("get_layers_in_layers {}".format(layers)) - print("get_layers_in_layers len {}".format(len(layers))) - print("get_layers_in_layers type {}".format(type(layers))) ret = [] parent_ids = set([lay.id for lay in layers]) - print("parent_ids ".format(parent_ids)) + for layer in all_layers: - print("layer {}".format(layer)) parents = set(layer.parents) - print("parents {}".format(layer)) if len(parent_ids & parents) > 0: ret.append(layer) if layer.id in parent_ids: ret.append(layer) - print("ret {}".format(ret)) + return ret def group_selected_layers(self): @@ -140,7 +138,8 @@ class PhotoshopClientStub(): :return: full path with name """ res = self.websocketserver.call( - self.client.call('Photoshop.get_active_document_full_name')) + self.client.call + ('Photoshop.get_active_document_full_name')) return res @@ -188,7 +187,7 @@ class PhotoshopClientStub(): layer_id=layer_id, visibility=visibility)) - def _get_layers_metadata(self): + def get_layers_metadata(self): """ Reads layers metadata from Headline from active document in PS. (Headline accessible by File > File Info) @@ -242,7 +241,3 @@ class PhotoshopClientStub(): for d in layers_data: ret.append(namedtuple('Layer', d.keys())(*d.values())) return ret - - - - diff --git a/pype/plugins/photoshop/publish/collect_instances.py b/pype/plugins/photoshop/publish/collect_instances.py index 47584272d2..7e433bc92f 100644 --- a/pype/plugins/photoshop/publish/collect_instances.py +++ b/pype/plugins/photoshop/publish/collect_instances.py @@ -31,7 +31,7 @@ class CollectInstances(pyblish.api.ContextPlugin): photoshop_client = PhotoshopClientStub() layers = photoshop_client.get_layers() - layers_meta = photoshop_client._get_layers_metadata() + layers_meta = photoshop_client.get_layers_metadata() for layer in layers: layer_data = photoshop_client.read(layer, layers_meta) diff --git a/pype/plugins/photoshop/publish/extract_image.py b/pype/plugins/photoshop/publish/extract_image.py index 4cbac38ce7..e32444c641 100644 --- a/pype/plugins/photoshop/publish/extract_image.py +++ b/pype/plugins/photoshop/publish/extract_image.py @@ -3,8 +3,9 @@ import os import pype.api from avalon import photoshop -from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub +from pype.modules.websocket_server.clients.photoshop_client import ( + PhotoshopClientStub +) class ExtractImage(pype.api.Extractor): @@ -23,12 +24,6 @@ class ExtractImage(pype.api.Extractor): staging_dir = self.staging_dir(instance) self.log.info("Outputting image to {}".format(staging_dir)) - layers = [] - for image_instance in instance.context: - if image_instance.data["family"] != "image": - continue - layers.append(image_instance[0]) - # Perform extraction photoshop_client = PhotoshopClientStub() files = {} @@ -37,7 +32,7 @@ class ExtractImage(pype.api.Extractor): with photoshop.maintained_visibility(): # Hide all other layers. extract_ids = set([ll.id for ll in photoshop_client. - get_layers_in_layers(layers)]) + get_layers_in_layers([instance[0]])]) for layer in photoshop_client.get_layers(): # limit unnecessary calls to client From dce5de49380c618b89cf899424e356e7bfe82c90 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 20 Aug 2020 17:31:20 +0200 Subject: [PATCH 09/17] Finish loader --- pype/modules/websocket_server/clients/photoshop_client.py | 3 +++ pype/plugins/photoshop/load/load_image.py | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pype/modules/websocket_server/clients/photoshop_client.py b/pype/modules/websocket_server/clients/photoshop_client.py index 330c2ceff0..a81870a4ee 100644 --- a/pype/modules/websocket_server/clients/photoshop_client.py +++ b/pype/modules/websocket_server/clients/photoshop_client.py @@ -208,6 +208,9 @@ class PhotoshopClientStub(): Args: path (str): File path to import. """ + self.websocketserver.call(self.client.call + ('Photoshop.import_smart_object', + path=path)) def replace_smart_object(self, layer, path): """ diff --git a/pype/plugins/photoshop/load/load_image.py b/pype/plugins/photoshop/load/load_image.py index a24280553c..1856155b2a 100644 --- a/pype/plugins/photoshop/load/load_image.py +++ b/pype/plugins/photoshop/load/load_image.py @@ -1,7 +1,8 @@ from avalon import api, photoshop -from pype.modules.websocket_server.clients.photoshop_client \ - import PhotoshopClientStub +from pype.modules.websocket_server.clients.photoshop_client import ( + PhotoshopClientStub +) photoshopClient = PhotoshopClientStub() @@ -17,7 +18,7 @@ class ImageLoader(api.Loader): def load(self, context, name=None, namespace=None, data=None): with photoshop.maintained_selection(): - layer = photoshop.import_smart_object(self.fname) + layer = photoshopClient.import_smart_object(self.fname) self[:] = [layer] From fc2018e22b5a3e69a34577b123c07b7d522254c4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 20 Aug 2020 20:12:03 +0200 Subject: [PATCH 10/17] Finished Creator --- .../clients/photoshop_client.py | 27 +++++++++++++--- pype/plugins/photoshop/create/create_image.py | 32 ++++++++++--------- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/pype/modules/websocket_server/clients/photoshop_client.py b/pype/modules/websocket_server/clients/photoshop_client.py index a81870a4ee..4f0bca99cc 100644 --- a/pype/modules/websocket_server/clients/photoshop_client.py +++ b/pype/modules/websocket_server/clients/photoshop_client.py @@ -102,13 +102,28 @@ class PhotoshopClientStub(): return ret - def group_selected_layers(self): + def create_group(self, name): """ - Group selected layers into new layer - :return: + Create new group (eg. LayerSet) + :return: """ - self.websocketserver.call(self.client.call - ('Photoshop.group_selected_layers')) + ret = self.websocketserver.call(self.client.call + ('Photoshop.create_group', + name=name)) + # create group on PS is asynchronous, returns only id + layer = {"id": ret, "name": name, "group": True} + return namedtuple('Layer', layer.keys())(*layer.values()) + + def group_selected_layers(self, name): + """ + Group selected layers into new LayerSet (eg. group) + :return: + """ + res = self.websocketserver.call(self.client.call + ('Photoshop.group_selected_layers', + name=name) + ) + return self._to_records(res) def get_selected_layers(self): """ @@ -241,6 +256,8 @@ class PhotoshopClientStub(): raise ValueError("Received broken JSON {}".format(res)) ret = [] # convert to namedtuple to use dot donation + if isinstance(layers_data, dict): # TODO refactore + layers_data = [layers_data] for d in layers_data: ret.append(namedtuple('Layer', d.keys())(*d.values())) return ret diff --git a/pype/plugins/photoshop/create/create_image.py b/pype/plugins/photoshop/create/create_image.py index 5b2f9f7981..6b54b2a036 100644 --- a/pype/plugins/photoshop/create/create_image.py +++ b/pype/plugins/photoshop/create/create_image.py @@ -1,5 +1,8 @@ -from avalon import api, photoshop +from avalon import api from avalon.vendor import Qt +from pype.modules.websocket_server.clients.photoshop_client import ( + PhotoshopClientStub +) class CreateImage(api.Creator): @@ -13,11 +16,12 @@ class CreateImage(api.Creator): groups = [] layers = [] create_group = False - group_constant = photoshop.get_com_objects().constants().psLayerSet + + photoshopClient = PhotoshopClientStub() if (self.options or {}).get("useSelection"): multiple_instances = False - selection = photoshop.get_selected_layers() - + selection = photoshopClient.get_selected_layers() + self.log.info("selection {}".format(selection)) if len(selection) > 1: # Ask user whether to create one image or image per selected # item. @@ -40,19 +44,19 @@ class CreateImage(api.Creator): if multiple_instances: for item in selection: - if item.LayerType == group_constant: + if item.group: groups.append(item) else: layers.append(item) else: - group = photoshop.group_selected_layers() - group.Name = self.name + group = photoshopClient.group_selected_layers() + group.name = self.name groups.append(group) elif len(selection) == 1: # One selected item. Use group if its a LayerSet (group), else # create a new group. - if selection[0].LayerType == group_constant: + if selection[0].group: groups.append(selection[0]) else: layers.append(selection[0]) @@ -63,16 +67,14 @@ class CreateImage(api.Creator): create_group = True if create_group: - group = photoshop.app().ActiveDocument.LayerSets.Add() - group.Name = self.name + group = photoshopClient.create_group(self.name) groups.append(group) for layer in layers: - photoshop.select_layers([layer]) - group = photoshop.group_selected_layers() - group.Name = layer.Name + photoshopClient.select_layers([layer]) + group = photoshopClient.group_selected_layers(layer.name) groups.append(group) for group in groups: - self.data.update({"subset": "image" + group.Name}) - photoshop.imprint(group, self.data) + self.data.update({"subset": "image" + group.name}) + photoshopClient.imprint(group, self.data) From 4fb557c8376ed44280e1a73f50ba10a5c2e0d1e1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 20 Aug 2020 20:15:09 +0200 Subject: [PATCH 11/17] Hound --- pype/plugins/photoshop/publish/collect_current_file.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/plugins/photoshop/publish/collect_current_file.py b/pype/plugins/photoshop/publish/collect_current_file.py index 604ce97f89..7877caa137 100644 --- a/pype/plugins/photoshop/publish/collect_current_file.py +++ b/pype/plugins/photoshop/publish/collect_current_file.py @@ -2,8 +2,9 @@ import os import pyblish.api -from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub +from pype.modules.websocket_server.clients.photoshop_client import ( + PhotoshopClientStub +) class CollectCurrentFile(pyblish.api.ContextPlugin): From 7f5fc953ce98167b5f70127d6eb0506ec2d05cd3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 20 Aug 2020 20:15:09 +0200 Subject: [PATCH 12/17] Hound --- pype/plugins/photoshop/publish/collect_current_file.py | 5 +++-- pype/plugins/photoshop/publish/extract_save_scene.py | 5 +++-- pype/plugins/photoshop/publish/increment_workfile.py | 5 +++-- pype/plugins/photoshop/publish/validate_instance_asset.py | 6 ++++-- pype/plugins/photoshop/publish/validate_naming.py | 5 +++-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/pype/plugins/photoshop/publish/collect_current_file.py b/pype/plugins/photoshop/publish/collect_current_file.py index 604ce97f89..7877caa137 100644 --- a/pype/plugins/photoshop/publish/collect_current_file.py +++ b/pype/plugins/photoshop/publish/collect_current_file.py @@ -2,8 +2,9 @@ import os import pyblish.api -from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub +from pype.modules.websocket_server.clients.photoshop_client import ( + PhotoshopClientStub +) class CollectCurrentFile(pyblish.api.ContextPlugin): diff --git a/pype/plugins/photoshop/publish/extract_save_scene.py b/pype/plugins/photoshop/publish/extract_save_scene.py index 3357a05f24..c56e5418c9 100644 --- a/pype/plugins/photoshop/publish/extract_save_scene.py +++ b/pype/plugins/photoshop/publish/extract_save_scene.py @@ -1,8 +1,9 @@ import pype.api from avalon import photoshop -from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub +from pype.modules.websocket_server.clients.photoshop_client import ( + PhotoshopClientStub +) class ExtractSaveScene(pype.api.Extractor): diff --git a/pype/plugins/photoshop/publish/increment_workfile.py b/pype/plugins/photoshop/publish/increment_workfile.py index 4298eb8e77..af8ba0b6ae 100644 --- a/pype/plugins/photoshop/publish/increment_workfile.py +++ b/pype/plugins/photoshop/publish/increment_workfile.py @@ -2,8 +2,9 @@ import pyblish.api from pype.action import get_errored_plugins_from_data from pype.lib import version_up -from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub +from pype.modules.websocket_server.clients.photoshop_client import ( + PhotoshopClientStub +) class IncrementWorkfile(pyblish.api.InstancePlugin): diff --git a/pype/plugins/photoshop/publish/validate_instance_asset.py b/pype/plugins/photoshop/publish/validate_instance_asset.py index 4bbea69eb4..aa8d2661ff 100644 --- a/pype/plugins/photoshop/publish/validate_instance_asset.py +++ b/pype/plugins/photoshop/publish/validate_instance_asset.py @@ -4,8 +4,10 @@ import pyblish.api import pype.api from avalon import photoshop -from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub +from pype.modules.websocket_server.clients.photoshop_client import ( + PhotoshopClientStub +) + class ValidateInstanceAssetRepair(pyblish.api.Action): """Repair the instance asset.""" diff --git a/pype/plugins/photoshop/publish/validate_naming.py b/pype/plugins/photoshop/publish/validate_naming.py index ba8a3e997e..c612270802 100644 --- a/pype/plugins/photoshop/publish/validate_naming.py +++ b/pype/plugins/photoshop/publish/validate_naming.py @@ -2,8 +2,9 @@ import pyblish.api import pype.api from avalon import photoshop -from pype.modules.websocket_server.clients.photoshop_client import \ - PhotoshopClientStub +from pype.modules.websocket_server.clients.photoshop_client import ( + PhotoshopClientStub +) class ValidateNamingRepair(pyblish.api.Action): From 690bb9f8b16b85c36616ae0031a39874322827c5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 21 Aug 2020 10:54:38 +0200 Subject: [PATCH 13/17] Fix missed providing name to group_selected_layers --- pype/plugins/photoshop/create/create_image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/plugins/photoshop/create/create_image.py b/pype/plugins/photoshop/create/create_image.py index 6b54b2a036..0a019fe2f8 100644 --- a/pype/plugins/photoshop/create/create_image.py +++ b/pype/plugins/photoshop/create/create_image.py @@ -49,8 +49,7 @@ class CreateImage(api.Creator): else: layers.append(item) else: - group = photoshopClient.group_selected_layers() - group.name = self.name + group = photoshopClient.group_selected_layers(self.name) groups.append(group) elif len(selection) == 1: From f8743e3b80dedfb37da392fd4c7108e5680ae2f6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 21 Aug 2020 15:23:35 +0200 Subject: [PATCH 14/17] Removed usage of http_server --- .../websocket_server/hosts/photoshop.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pype/modules/websocket_server/hosts/photoshop.py b/pype/modules/websocket_server/hosts/photoshop.py index c63f66865e..ae72963b1b 100644 --- a/pype/modules/websocket_server/hosts/photoshop.py +++ b/pype/modules/websocket_server/hosts/photoshop.py @@ -1,5 +1,8 @@ from pype.api import Logger from wsrpc_aiohttp import WebSocketRoute +import functools + +import avalon.photoshop as photoshop log = Logger().get_logger("WebsocketServer") @@ -31,3 +34,31 @@ class Photoshop(WebSocketRoute): log.debug("photoshop.read client calls server server calls " "Photo client") return await self.socket.call('Photoshop.read') + + # panel routes for tools + async def creator_route(self): + self._tool_route("creator") + + async def workfiles_route(self): + self._tool_route("workfiles") + + async def loader_route(self): + self._tool_route("loader") + + async def publish_route(self): + self._tool_route("publish") + + async def sceneinventory_route(self): + self._tool_route("sceneinventory") + + async def projectmanager_route(self): + self._tool_route("projectmanager") + + def _tool_route(self, tool_name): + """The address accessed when clicking on the buttons.""" + partial_method = functools.partial(photoshop.show, tool_name) + + photoshop.execute_in_main_thread(partial_method) + + # Required return statement. + return "nothing" \ No newline at end of file From f31fb3e03426830a1e1b3d10502230e453fa9e6f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 24 Aug 2020 17:44:16 +0200 Subject: [PATCH 15/17] Removed com32 objects Refactore - changed names to highlight 'stub' approach Small fixes --- .../photoshop_server_stub.py} | 35 +++++++++++++++---- pype/plugins/photoshop/create/create_image.py | 18 +++++----- pype/plugins/photoshop/load/load_image.py | 12 +++---- .../photoshop/publish/collect_current_file.py | 7 ++-- .../photoshop/publish/collect_instances.py | 12 +++---- .../photoshop/publish/extract_image.py | 22 ++++-------- .../photoshop/publish/extract_review.py | 22 ++++-------- .../photoshop/publish/extract_save_scene.py | 7 +--- .../photoshop/publish/increment_workfile.py | 7 ++-- .../publish/validate_instance_asset.py | 10 ++---- .../photoshop/publish/validate_naming.py | 10 ++---- 11 files changed, 70 insertions(+), 92 deletions(-) rename pype/modules/websocket_server/{clients/photoshop_client.py => stubs/photoshop_server_stub.py} (89%) diff --git a/pype/modules/websocket_server/clients/photoshop_client.py b/pype/modules/websocket_server/stubs/photoshop_server_stub.py similarity index 89% rename from pype/modules/websocket_server/clients/photoshop_client.py rename to pype/modules/websocket_server/stubs/photoshop_server_stub.py index 4f0bca99cc..f798a09b92 100644 --- a/pype/modules/websocket_server/clients/photoshop_client.py +++ b/pype/modules/websocket_server/stubs/photoshop_server_stub.py @@ -7,17 +7,28 @@ import json from collections import namedtuple -class PhotoshopClientStub(): +class PhotoshopServerStub(): """ Stub for calling function on client (Photoshop js) side. Expects that client is already connected (started when avalon menu is opened). + 'self.websocketserver.call' is used as async wrapper """ def __init__(self): self.websocketserver = WebSocketServer.get_instance() self.client = self.websocketserver.get_client() + def open(self, path): + """ + Open file located at 'path' (local). + :param path: file path locally + :return: None + """ + self.websocketserver.call(self.client.call + ('Photoshop.open', path=path) + ) + def read(self, layer, layers_meta=None): """ Parses layer metadata from Headline field of active document @@ -107,9 +118,9 @@ class PhotoshopClientStub(): Create new group (eg. LayerSet) :return: """ - ret = self.websocketserver.call(self.client.call - ('Photoshop.create_group', - name=name)) + ret = self.websocketserver.call(self.client.call + ('Photoshop.create_group', + name=name)) # create group on PS is asynchronous, returns only id layer = {"id": ret, "name": name, "group": True} return namedtuple('Layer', layer.keys())(*layer.values()) @@ -168,6 +179,14 @@ class PhotoshopClientStub(): return res + def is_saved(self): + """ + Returns true if no changes in active document + :return: + """ + return self.websocketserver.call(self.client.call + ('Photoshop.is_saved')) + def save(self): """ Saves active document @@ -223,9 +242,11 @@ class PhotoshopClientStub(): Args: path (str): File path to import. """ - self.websocketserver.call(self.client.call - ('Photoshop.import_smart_object', - path=path)) + res = self.websocketserver.call(self.client.call + ('Photoshop.import_smart_object', + path=path)) + + return self._to_records(res).pop() def replace_smart_object(self, layer, path): """ diff --git a/pype/plugins/photoshop/create/create_image.py b/pype/plugins/photoshop/create/create_image.py index 0a019fe2f8..c1a7d92a2c 100644 --- a/pype/plugins/photoshop/create/create_image.py +++ b/pype/plugins/photoshop/create/create_image.py @@ -1,8 +1,6 @@ from avalon import api from avalon.vendor import Qt -from pype.modules.websocket_server.clients.photoshop_client import ( - PhotoshopClientStub -) +from avalon import photoshop class CreateImage(api.Creator): @@ -17,10 +15,10 @@ class CreateImage(api.Creator): layers = [] create_group = False - photoshopClient = PhotoshopClientStub() + stub = photoshop.stub() if (self.options or {}).get("useSelection"): multiple_instances = False - selection = photoshopClient.get_selected_layers() + selection = stub.get_selected_layers() self.log.info("selection {}".format(selection)) if len(selection) > 1: # Ask user whether to create one image or image per selected @@ -49,7 +47,7 @@ class CreateImage(api.Creator): else: layers.append(item) else: - group = photoshopClient.group_selected_layers(self.name) + group = stub.group_selected_layers(self.name) groups.append(group) elif len(selection) == 1: @@ -66,14 +64,14 @@ class CreateImage(api.Creator): create_group = True if create_group: - group = photoshopClient.create_group(self.name) + group = stub.create_group(self.name) groups.append(group) for layer in layers: - photoshopClient.select_layers([layer]) - group = photoshopClient.group_selected_layers(layer.name) + stub.select_layers([layer]) + group = stub.group_selected_layers(layer.name) groups.append(group) for group in groups: self.data.update({"subset": "image" + group.name}) - photoshopClient.imprint(group, self.data) + stub.imprint(group, self.data) diff --git a/pype/plugins/photoshop/load/load_image.py b/pype/plugins/photoshop/load/load_image.py index 1856155b2a..75c02bb327 100644 --- a/pype/plugins/photoshop/load/load_image.py +++ b/pype/plugins/photoshop/load/load_image.py @@ -1,10 +1,6 @@ from avalon import api, photoshop -from pype.modules.websocket_server.clients.photoshop_client import ( - PhotoshopClientStub -) - -photoshopClient = PhotoshopClientStub() +stub = photoshop.stub() class ImageLoader(api.Loader): @@ -18,7 +14,7 @@ class ImageLoader(api.Loader): def load(self, context, name=None, namespace=None, data=None): with photoshop.maintained_selection(): - layer = photoshopClient.import_smart_object(self.fname) + layer = stub.import_smart_object(self.fname) self[:] = [layer] @@ -34,11 +30,11 @@ class ImageLoader(api.Loader): layer = container.pop("layer") with photoshop.maintained_selection(): - photoshopClient.replace_smart_object( + stub.replace_smart_object( layer, api.get_representation_path(representation) ) - photoshopClient.imprint( + stub.imprint( layer, {"representation": str(representation["_id"])} ) diff --git a/pype/plugins/photoshop/publish/collect_current_file.py b/pype/plugins/photoshop/publish/collect_current_file.py index 7877caa137..3cc3e3f636 100644 --- a/pype/plugins/photoshop/publish/collect_current_file.py +++ b/pype/plugins/photoshop/publish/collect_current_file.py @@ -2,9 +2,7 @@ import os import pyblish.api -from pype.modules.websocket_server.clients.photoshop_client import ( - PhotoshopClientStub -) +from avalon import photoshop class CollectCurrentFile(pyblish.api.ContextPlugin): @@ -15,7 +13,6 @@ class CollectCurrentFile(pyblish.api.ContextPlugin): hosts = ["photoshop"] def process(self, context): - photoshop_client = PhotoshopClientStub() context.data["currentFile"] = os.path.normpath( - photoshop_client.get_active_document_full_name() + photoshop.stub().get_active_document_full_name() ).replace("\\", "/") diff --git a/pype/plugins/photoshop/publish/collect_instances.py b/pype/plugins/photoshop/publish/collect_instances.py index 7e433bc92f..81d1c80bf6 100644 --- a/pype/plugins/photoshop/publish/collect_instances.py +++ b/pype/plugins/photoshop/publish/collect_instances.py @@ -2,9 +2,7 @@ import pythoncom import pyblish.api -from pype.modules.websocket_server.clients.photoshop_client import ( - PhotoshopClientStub -) +from avalon import photoshop class CollectInstances(pyblish.api.ContextPlugin): @@ -29,11 +27,11 @@ class CollectInstances(pyblish.api.ContextPlugin): # can be. pythoncom.CoInitialize() - photoshop_client = PhotoshopClientStub() - layers = photoshop_client.get_layers() - layers_meta = photoshop_client.get_layers_metadata() + stub = photoshop.stub() + layers = stub.get_layers() + layers_meta = stub.get_layers_metadata() for layer in layers: - layer_data = photoshop_client.read(layer, layers_meta) + layer_data = stub.read(layer, layers_meta) # Skip layers without metadata. if layer_data is None: diff --git a/pype/plugins/photoshop/publish/extract_image.py b/pype/plugins/photoshop/publish/extract_image.py index e32444c641..38920b5557 100644 --- a/pype/plugins/photoshop/publish/extract_image.py +++ b/pype/plugins/photoshop/publish/extract_image.py @@ -3,10 +3,6 @@ import os import pype.api from avalon import photoshop -from pype.modules.websocket_server.clients.photoshop_client import ( - PhotoshopClientStub -) - class ExtractImage(pype.api.Extractor): """Produce a flattened image file from instance @@ -25,23 +21,21 @@ class ExtractImage(pype.api.Extractor): self.log.info("Outputting image to {}".format(staging_dir)) # Perform extraction - photoshop_client = PhotoshopClientStub() + stub = photoshop.stub() files = {} with photoshop.maintained_selection(): self.log.info("Extracting %s" % str(list(instance))) with photoshop.maintained_visibility(): # Hide all other layers. - extract_ids = set([ll.id for ll in photoshop_client. + extract_ids = set([ll.id for ll in stub. get_layers_in_layers([instance[0]])]) - for layer in photoshop_client.get_layers(): + for layer in stub.get_layers(): # limit unnecessary calls to client if layer.visible and layer.id not in extract_ids: - photoshop_client.set_visible(layer.id, - False) + stub.set_visible(layer.id, False) if not layer.visible and layer.id in extract_ids: - photoshop_client.set_visible(layer.id, - True) + stub.set_visible(layer.id, True) save_options = [] if "png" in self.formats: @@ -50,16 +44,14 @@ class ExtractImage(pype.api.Extractor): save_options.append('jpg') file_basename = os.path.splitext( - photoshop_client.get_active_document_name() + stub.get_active_document_name() )[0] for extension in save_options: _filename = "{}.{}".format(file_basename, extension) files[extension] = _filename full_filename = os.path.join(staging_dir, _filename) - photoshop_client.saveAs(full_filename, - extension, - True) + stub.saveAs(full_filename, extension, True) representations = [] for extension, filename in files.items(): diff --git a/pype/plugins/photoshop/publish/extract_review.py b/pype/plugins/photoshop/publish/extract_review.py index 806b59341b..6fb50bba9f 100644 --- a/pype/plugins/photoshop/publish/extract_review.py +++ b/pype/plugins/photoshop/publish/extract_review.py @@ -4,10 +4,6 @@ import pype.api import pype.lib from avalon import photoshop -from pype.modules.websocket_server.clients.photoshop_client import ( - PhotoshopClientStub -) - class ExtractReview(pype.api.Extractor): """Produce a flattened image file from all instances.""" @@ -20,7 +16,7 @@ class ExtractReview(pype.api.Extractor): staging_dir = self.staging_dir(instance) self.log.info("Outputting image to {}".format(staging_dir)) - photoshop_client = PhotoshopClientStub() + stub = photoshop.stub() layers = [] for image_instance in instance.context: @@ -30,26 +26,22 @@ class ExtractReview(pype.api.Extractor): # Perform extraction output_image = "{}.jpg".format( - os.path.splitext(photoshop_client.get_active_document_name())[0] + os.path.splitext(stub.get_active_document_name())[0] ) output_image_path = os.path.join(staging_dir, output_image) with photoshop.maintained_visibility(): # Hide all other layers. - extract_ids = set([ll.id for ll in photoshop_client. + extract_ids = set([ll.id for ll in stub. get_layers_in_layers(layers)]) self.log.info("extract_ids {}".format(extract_ids)) - for layer in photoshop_client.get_layers(): + for layer in stub.get_layers(): # limit unnecessary calls to client if layer.visible and layer.id not in extract_ids: - photoshop_client.set_visible(layer.id, - False) + stub.set_visible(layer.id, False) if not layer.visible and layer.id in extract_ids: - photoshop_client.set_visible(layer.id, - True) + stub.set_visible(layer.id, True) - photoshop_client.saveAs(output_image_path, - 'jpg', - True) + stub.saveAs(output_image_path, 'jpg', True) ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") diff --git a/pype/plugins/photoshop/publish/extract_save_scene.py b/pype/plugins/photoshop/publish/extract_save_scene.py index c56e5418c9..63a4b7b7ea 100644 --- a/pype/plugins/photoshop/publish/extract_save_scene.py +++ b/pype/plugins/photoshop/publish/extract_save_scene.py @@ -1,10 +1,6 @@ import pype.api from avalon import photoshop -from pype.modules.websocket_server.clients.photoshop_client import ( - PhotoshopClientStub -) - class ExtractSaveScene(pype.api.Extractor): """Save scene before extraction.""" @@ -15,5 +11,4 @@ class ExtractSaveScene(pype.api.Extractor): families = ["workfile"] def process(self, instance): - photoshop_client = PhotoshopClientStub() - photoshop_client.save() + photoshop.stub().save() diff --git a/pype/plugins/photoshop/publish/increment_workfile.py b/pype/plugins/photoshop/publish/increment_workfile.py index af8ba0b6ae..eca2583595 100644 --- a/pype/plugins/photoshop/publish/increment_workfile.py +++ b/pype/plugins/photoshop/publish/increment_workfile.py @@ -2,9 +2,7 @@ import pyblish.api from pype.action import get_errored_plugins_from_data from pype.lib import version_up -from pype.modules.websocket_server.clients.photoshop_client import ( - PhotoshopClientStub -) +from avalon import photoshop class IncrementWorkfile(pyblish.api.InstancePlugin): @@ -27,7 +25,6 @@ class IncrementWorkfile(pyblish.api.InstancePlugin): ) scene_path = version_up(instance.context.data["currentFile"]) - photoshop_client = PhotoshopClientStub() - photoshop_client.saveAs(scene_path, 'psd', True) + photoshop.stub().saveAs(scene_path, 'psd', True) self.log.info("Incremented workfile to: {}".format(scene_path)) diff --git a/pype/plugins/photoshop/publish/validate_instance_asset.py b/pype/plugins/photoshop/publish/validate_instance_asset.py index aa8d2661ff..f05d9601dd 100644 --- a/pype/plugins/photoshop/publish/validate_instance_asset.py +++ b/pype/plugins/photoshop/publish/validate_instance_asset.py @@ -4,10 +4,6 @@ import pyblish.api import pype.api from avalon import photoshop -from pype.modules.websocket_server.clients.photoshop_client import ( - PhotoshopClientStub -) - class ValidateInstanceAssetRepair(pyblish.api.Action): """Repair the instance asset.""" @@ -27,12 +23,12 @@ class ValidateInstanceAssetRepair(pyblish.api.Action): # Apply pyblish.logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(failed, plugin) - photoshop_client = PhotoshopClientStub() + stub = photoshop.stub() for instance in instances: - data = photoshop_client.read(instance[0]) + data = stub.read(instance[0]) data["asset"] = os.environ["AVALON_ASSET"] - photoshop_client.imprint(instance[0], data) + stub.imprint(instance[0], data) class ValidateInstanceAsset(pyblish.api.InstancePlugin): diff --git a/pype/plugins/photoshop/publish/validate_naming.py b/pype/plugins/photoshop/publish/validate_naming.py index c612270802..2483adcb5e 100644 --- a/pype/plugins/photoshop/publish/validate_naming.py +++ b/pype/plugins/photoshop/publish/validate_naming.py @@ -2,10 +2,6 @@ import pyblish.api import pype.api from avalon import photoshop -from pype.modules.websocket_server.clients.photoshop_client import ( - PhotoshopClientStub -) - class ValidateNamingRepair(pyblish.api.Action): """Repair the instance asset.""" @@ -25,14 +21,14 @@ class ValidateNamingRepair(pyblish.api.Action): # Apply pyblish.logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(failed, plugin) - photoshop_client = PhotoshopClientStub() + stub = photoshop.stub() for instance in instances: self.log.info("validate_naming instance {}".format(instance)) name = instance.data["name"].replace(" ", "_") instance[0].Name = name - data = photoshop_client.read(instance[0]) + data = stub.read(instance[0]) data["subset"] = "image" + name - photoshop_client.imprint(instance[0], data) + stub.imprint(instance[0], data) return True From a00b11d31fe2e1c58dab6b124a1a62a2d377a768 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 26 Aug 2020 14:29:57 +0200 Subject: [PATCH 16/17] Added pulling websocket server port from environment variable WEBSOCKET_URL --- .../websocket_server/websocket_server.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index 6b730d4eb3..777bcf1f61 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -9,6 +9,7 @@ import os import sys import pyclbr import importlib +import urllib log = Logger().get_logger("WebsocketServer") @@ -28,19 +29,16 @@ class WebSocketServer(): self.qaction = None self.failed_icon = None self._is_running = False - default_port = 8099 WebSocketServer._instance = self self.client = None self.handlers = {} - try: - self.presets = config.get_presets()["services"]["websocket_server"] - except Exception: - self.presets = {"default_port": default_port, "exclude_ports": []} - log.debug(( - "There are not set presets for WebsocketServer." - " Using defaults \"{}\"" - ).format(str(self.presets))) + websocket_url = os.getenv("WEBSOCKET_URL") + if websocket_url: + parsed = urllib.parse.urlparse(websocket_url) + port = parsed.port + if not port: + port = 8099 # try default port self.app = web.Application() @@ -52,7 +50,7 @@ class WebSocketServer(): directories_with_routes = ['hosts'] self.add_routes_for_directories(directories_with_routes) - self.websocket_thread = WebsocketServerThread(self, default_port) + self.websocket_thread = WebsocketServerThread(self, port) def add_routes_for_directories(self, directories_with_routes): """ Loops through selected directories to find all modules and From 06359c6dc335779f251dc426af8d6bda5be76422 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 27 Aug 2020 19:22:15 +0200 Subject: [PATCH 17/17] Hound --- pype/modules/websocket_server/hosts/photoshop.py | 2 +- .../modules/websocket_server/stubs/photoshop_server_stub.py | 3 +-- pype/modules/websocket_server/websocket_server.py | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pype/modules/websocket_server/hosts/photoshop.py b/pype/modules/websocket_server/hosts/photoshop.py index ae72963b1b..cdfb9413a0 100644 --- a/pype/modules/websocket_server/hosts/photoshop.py +++ b/pype/modules/websocket_server/hosts/photoshop.py @@ -61,4 +61,4 @@ class Photoshop(WebSocketRoute): photoshop.execute_in_main_thread(partial_method) # Required return statement. - return "nothing" \ No newline at end of file + return "nothing" diff --git a/pype/modules/websocket_server/stubs/photoshop_server_stub.py b/pype/modules/websocket_server/stubs/photoshop_server_stub.py index f798a09b92..da69127799 100644 --- a/pype/modules/websocket_server/stubs/photoshop_server_stub.py +++ b/pype/modules/websocket_server/stubs/photoshop_server_stub.py @@ -164,8 +164,7 @@ class PhotoshopServerStub(): :return: full path with name """ res = self.websocketserver.call( - self.client.call - ('Photoshop.get_active_document_full_name')) + self.client.call('Photoshop.get_active_document_full_name')) return res diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index 777bcf1f61..4556dd0491 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -1,4 +1,4 @@ -from pype.api import config, Logger +from pype.api import Logger import threading from aiohttp import web @@ -37,8 +37,8 @@ class WebSocketServer(): if websocket_url: parsed = urllib.parse.urlparse(websocket_url) port = parsed.port - if not port: - port = 8099 # try default port + if not port: + port = 8099 # fallback self.app = web.Application()