Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #657 from pypeclub/bugfix/654-layer-name-is-not-pr…
Browse files Browse the repository at this point in the history
…opagating-to-metadata-in-photoshop

Photoshop: Fix Layer name was not propagating correctly
  • Loading branch information
mkolar authored Oct 22, 2020
2 parents 27cffd1 + e557e4c commit 4315d2f
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 42 deletions.
102 changes: 63 additions & 39 deletions pype/modules/websocket_server/stubs/photoshop_server_stub.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ def __init__(self):
def open(self, path):
"""
Open file located at 'path' (local).
:param path: <string> file path locally
:return: None
Args:
path(string): file path locally
Returns: None
"""
self.websocketserver.call(self.client.call
('Photoshop.open', path=path)
Expand All @@ -32,9 +33,10 @@ def open(self, path):
def read(self, layer, layers_meta=None):
"""
Parses layer metadata from Headline field of active document
:param layer: <namedTuple Layer("id":XX, "name":"YYY")
:param layers_meta: full list from Headline (for performance in loops)
:return:
Args:
layer: <namedTuple Layer("id":XX, "name":"YYY")
layers_meta: full list from Headline (for performance in loops)
Returns:
"""
if layers_meta is None:
layers_meta = self.get_layers_metadata()
Expand All @@ -44,22 +46,26 @@ def read(self, layer, layers_meta=None):
def imprint(self, layer, data, all_layers=None, layers_meta=None):
"""
Save layer metadata to Headline field of active document
:param layer: <namedTuple> Layer("id": XXX, "name":'YYY')
:param data: <string> json representation for single layer
:param all_layers: <list of namedTuples> - for performance, could be
Args:
layer (namedtuple): Layer("id": XXX, "name":'YYY')
data(string): json representation for single layer
all_layers (list of namedtuples): for performance, could be
injected for usage in loop, if not, single call will be
triggered
:param layers_meta: <string> json representation from Headline
layers_meta(string): json representation from Headline
(for performance - provide only if imprint is in
loop - value should be same)
:return: None
Returns: None
"""
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_meta and layers_meta[str(layer.id)]:
layers_meta[str(layer.id)].update(data)
if data:
layers_meta[str(layer.id)].update(data)
else:
layers_meta.pop(str(layer.id))
else:
layers_meta[str(layer.id)] = data

Expand All @@ -83,7 +89,7 @@ def get_layers(self):
"""
Returns JSON document with all(?) layers in active document.
:return: <list of namedtuples>
Returns: <list of namedtuples>
Format of tuple: { 'id':'123',
'name': 'My Layer 1',
'type': 'GUIDE'|'FG'|'BG'|'OBJ'
Expand All @@ -97,8 +103,9 @@ def get_layers(self):
def get_layers_in_layers(self, layers):
"""
Return all layers that belong to layers (might be groups).
:param layers: <list of namedTuples>
:return: <list of namedTuples>
Args:
layers <list of namedTuples>:
Returns: <list of namedTuples>
"""
all_layers = self.get_layers()
ret = []
Expand All @@ -116,7 +123,7 @@ def get_layers_in_layers(self, layers):
def create_group(self, name):
"""
Create new group (eg. LayerSet)
:return: <namedTuple Layer("id":XX, "name":"YYY")>
Returns: <namedTuple Layer("id":XX, "name":"YYY")>
"""
ret = self.websocketserver.call(self.client.call
('Photoshop.create_group',
Expand All @@ -128,7 +135,7 @@ def create_group(self, name):
def group_selected_layers(self, name):
"""
Group selected layers into new LayerSet (eg. group)
:return: <json representation of Layer>
Returns: <json representation of Layer>
"""
res = self.websocketserver.call(self.client.call
('Photoshop.group_selected_layers',
Expand All @@ -139,17 +146,18 @@ def group_selected_layers(self, name):
def get_selected_layers(self):
"""
Get a list of actually selected layers
:return: <list of Layer('id':XX, 'name':"YYY")>
Returns: <list of Layer('id':XX, 'name':"YYY")>
"""
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: <list of Layer('id':XX, 'name':"YYY")>
:return: None
Selects specified layers in Photoshop by its ids
Args:
layers: <list of Layer('id':XX, 'name':"YYY")>
Returns: None
"""
layer_ids = [layer.id for layer in layers]

Expand All @@ -161,7 +169,7 @@ def select_layers(self, layers):
def get_active_document_full_name(self):
"""
Returns full name with path of active document via ws call
:return: <string> full path with name
Returns(string): full path with name
"""
res = self.websocketserver.call(
self.client.call('Photoshop.get_active_document_full_name'))
Expand All @@ -171,7 +179,7 @@ def get_active_document_full_name(self):
def get_active_document_name(self):
"""
Returns just a name of active document via ws call
:return: <string> file name
Returns(string): file name
"""
res = self.websocketserver.call(self.client.call
('Photoshop.get_active_document_name'))
Expand All @@ -181,26 +189,27 @@ def get_active_document_name(self):
def is_saved(self):
"""
Returns true if no changes in active document
:return: <boolean>
Returns: <boolean>
"""
return self.websocketserver.call(self.client.call
('Photoshop.is_saved'))

def save(self):
"""
Saves active document
:return: None
Returns: None
"""
self.websocketserver.call(self.client.call
('Photoshop.save'))

def saveAs(self, image_path, ext, as_copy):
"""
Saves active document to psd (copy) or png or jpg
:param image_path: <string> full local path
:param ext: <string psd|jpg|png>
:param as_copy: <boolean>
:return: None
Args:
image_path(string): full local path
ext: <string psd|jpg|png>
as_copy: <boolean>
Returns: None
"""
self.websocketserver.call(self.client.call
('Photoshop.saveAs',
Expand All @@ -211,9 +220,10 @@ def saveAs(self, image_path, ext, as_copy):
def set_visible(self, layer_id, visibility):
"""
Set layer with 'layer_id' to 'visibility'
:param layer_id: <int>
:param visibility: <true - set visible, false - hide>
:return: None
Args:
layer_id: <int>
visibility: <true - set visible, false - hide>
Returns: None
"""
self.websocketserver.call(self.client.call
('Photoshop.set_visible',
Expand All @@ -224,7 +234,7 @@ def get_layers_metadata(self):
"""
Reads layers metadata from Headline from active document in PS.
(Headline accessible by File > File Info)
:return: <string> - json documents
Returns(string): - json documents
"""
layers_data = {}
res = self.websocketserver.call(self.client.call('Photoshop.read'))
Expand All @@ -234,31 +244,45 @@ def get_layers_metadata(self):
pass
return layers_data

def import_smart_object(self, path):
def import_smart_object(self, path, layer_name):
"""
Import the file at `path` as a smart object to active document.
Args:
path (str): File path to import.
layer_name (str): Unique layer name to differentiate how many times
same smart object was loaded
"""
res = self.websocketserver.call(self.client.call
('Photoshop.import_smart_object',
path=path))
path=path, name=layer_name))

return self._to_records(res).pop()

def replace_smart_object(self, layer, path):
def replace_smart_object(self, layer, path, layer_name):
"""
Replace the smart object `layer` with file at `path`
layer_name (str): Unique layer name to differentiate how many times
same smart object was loaded
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))
layer_id=layer.id,
path=path, name=layer_name))

def delete_layer(self, layer_id):
"""
Deletes specific layer by it's id.
Args:
layer_id (int): id of layer to delete
"""
self.websocketserver.call(self.client.call
('Photoshop.delete_layer',
layer_id=layer_id))

def close(self):
self.client.close()
Expand All @@ -267,8 +291,8 @@ def _to_records(self, res):
"""
Converts string json representation into list of named tuples for
dot notation access to work.
:return: <list of named tuples>
:param res: <string> - json representation
Returns: <list of named tuples>
res(string): - json representation
"""
try:
layers_data = json.loads(res)
Expand Down
54 changes: 51 additions & 3 deletions pype/plugins/photoshop/load/load_image.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from avalon import api, photoshop
import os
import re

stub = photoshop.stub()

Expand All @@ -13,10 +15,13 @@ class ImageLoader(api.Loader):
representations = ["*"]

def load(self, context, name=None, namespace=None, data=None):
layer_name = self._get_unique_layer_name(context["asset"]["name"],
name)
with photoshop.maintained_selection():
layer = stub.import_smart_object(self.fname)
layer = stub.import_smart_object(self.fname, layer_name)

self[:] = [layer]
namespace = namespace or layer_name

return photoshop.containerise(
name,
Expand All @@ -27,19 +32,62 @@ def load(self, context, name=None, namespace=None, data=None):
)

def update(self, container, representation):
""" Switch asset or change version """
layer = container.pop("layer")

context = representation.get("context", {})

namespace_from_container = re.sub(r'_\d{3}$', '',
container["namespace"])
layer_name = "{}_{}".format(context["asset"], context["subset"])
# switching assets
if namespace_from_container != layer_name:
layer_name = self._get_unique_layer_name(context["asset"],
context["subset"])
else: # switching version - keep same name
layer_name = container["namespace"]

path = api.get_representation_path(representation)
with photoshop.maintained_selection():
stub.replace_smart_object(
layer, api.get_representation_path(representation)
layer, path, layer_name
)

stub.imprint(
layer, {"representation": str(representation["_id"])}
)

def remove(self, container):
container["layer"].Delete()
"""
Removes element from scene: deletes layer + removes from Headline
Args:
container (dict): container to be removed - used to get layer_id
"""
layer = container.pop("layer")
stub.imprint(layer, {})
stub.delete_layer(layer.id)

def switch(self, container, representation):
self.update(container, representation)

def _get_unique_layer_name(self, asset_name, subset_name):
"""
Gets all layer names and if 'name' is present in them, increases
suffix by 1 (eg. creates unique layer name - for Loader)
Args:
name (string): in format asset_subset
Returns:
(string): name_00X (without version)
"""
name = "{}_{}".format(asset_name, subset_name)
names = {}
for layer in stub.get_layers():
layer_name = re.sub(r'_\d{3}$', '', layer.name)
if layer_name in names.keys():
names[layer_name] = names[layer_name] + 1
else:
names[layer_name] = 1
occurrences = names.get(name, 0)

return "{}_{:0>3d}".format(name, occurrences + 1)

0 comments on commit 4315d2f

Please sign in to comment.