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

Commit

Permalink
#654 - Fix Layer name is not propagating
Browse files Browse the repository at this point in the history
Fixed saving namespace into Headline
Fixed switch asset and update version
Added delete layer by id
  • Loading branch information
kalisp committed Oct 20, 2020
1 parent 987a06d commit 987a6c3
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 987a6c3

Please sign in to comment.