Skip to content

ws_stub

Stub handling connection from server to client. Used anywhere solution is calling client methods.

PSItem

Bases: object

Object denoting layer or group item in PS. Each item is created in PS by any Loader, but contains same fields, which are being used in later processing.

Source code in client/ayon_photoshop/api/ws_stub.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@attr.s
class PSItem(object):
    """
        Object denoting layer or group item in PS. Each item is created in
        PS by any Loader, but contains same fields, which are being used
        in later processing.
    """
    # metadata
    id = attr.ib()  # id created by AE, could be used for querying
    name = attr.ib()  # name of item
    group = attr.ib(default=None)  # item type (footage, folder, comp)
    parents = attr.ib(factory=list)
    visible = attr.ib(default=True)
    type = attr.ib(default=None)
    # all imported elements, single for
    members = attr.ib(factory=list)
    long_name = attr.ib(default=None)
    color_code = attr.ib(default=None)  # color code of layer
    instance_id = attr.ib(default=None)

    @property
    def clean_name(self):
        """Returns layer name without publish icon highlight

        Returns:
            (str)
        """
        return (self.name.replace(PhotoshopServerStub.PUBLISH_ICON, '')
                         .replace(PhotoshopServerStub.LOADED_ICON, ''))

clean_name property

Returns layer name without publish icon highlight

Returns:

Type Description

(str)

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

Source code in client/ayon_photoshop/api/ws_stub.py
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
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
    """
    PUBLISH_ICON = '\u2117 '
    LOADED_ICON = '\u25bc'

    def __init__(self):
        self.websocketserver = WebServerTool.get_instance()
        self.client = self.get_client()

    @staticmethod
    def get_client():
        """
            Return first connected client to WebSocket
            TODO implement selection by Route
        :return: <WebSocketAsync> client
        """
        clients = WebSocketAsync.get_clients()
        client = None
        if len(clients) > 0:
            key = list(clients.keys())[0]
            client = clients.get(key)

        return client

    def open(self, path):
        """Open file located at 'path' (local).

        Args:
            path(string): file path locally
        Returns: 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.

        Args:
            layer: (PSItem)
            layers_meta: full list from Headline (for performance in loops)
        Returns:
            (dict) of layer metadata stored in PS file

        Example:
            {
                'id': 'pyblish.avalon.container',
                'loader': 'ImageLoader',
                'members': ['64'],
                'name': 'imageMainMiddle',
                'namespace': 'Hero_imageMainMiddle_001',
                'representation': '6203dc91e80934d9f6ee7d96',
                'schema': 'openpype:container-2.0'
            }
        """
        if layers_meta is None:
            layers_meta = self.get_layers_metadata()

        for layer_meta in layers_meta:
            layer_id = layer_meta.get("uuid")  # legacy
            if layer_meta.get("members"):
                layer_id = layer_meta["members"][0]
            if str(layer.id) == str(layer_id):
                return layer_meta
        print("Unable to find layer metadata for {}".format(layer.id))

    def imprint(self, item_id, data, all_layers=None, items_meta=None):
        """Save layer metadata to Headline field of active document

        Stores metadata in format:
        [{
            "active":true,
            "productName":"imageBG",
            "productType":"image",
            "id":"ayon.create.instance",
            "folderPath":"Town",
            "uuid": "8"
        }] - for created instances
        OR
        [{
            "schema": "openpype:container-2.0",
            "id": "ayon.create.instance",
            "name": "imageMG",
            "namespace": "Jungle_imageMG_001",
            "loader": "ImageLoader",
            "representation": "5fbfc0ee30a946093c6ff18a",
            "members": [
                "40"
            ]
        }] - for loaded instances

        Args:
            item_id (str):
            data(string): json representation for single layer
            all_layers (list of PSItem): for performance, could be
                injected for usage in loop, if not, single call will be
                triggered
            items_meta(string): json representation from Headline
                           (for performance - provide only if imprint is in
                           loop - value should be same)
        Returns: None
        """
        if not items_meta:
            items_meta = self.get_layers_metadata()

        # json.dumps writes integer values in a dictionary to string, so
        # anticipating it here.
        item_id = str(item_id)
        is_new = True
        result_meta = []
        for item_meta in items_meta:
            if ((item_meta.get('members') and
                 item_id == str(item_meta.get('members')[0])) or
                    item_meta.get("instance_id") == item_id):
                is_new = False
                if data:
                    item_meta.update(data)
                    result_meta.append(item_meta)
            else:
                result_meta.append(item_meta)

        if is_new:
            result_meta.append(data)

        # Ensure only valid ids are stored.
        if not all_layers:
            all_layers = self.get_layers()
        layer_ids = [layer.id for layer in all_layers]
        cleaned_data = []

        for item in result_meta:
            if item.get("members"):
                if int(item["members"][0]) not in layer_ids:
                    continue

            cleaned_data.append(item)

        payload = json.dumps(cleaned_data, indent=4)
        self.websocketserver.call(
            self.client.call('Photoshop.imprint', payload=payload)
        )

    def get_layers(self):
        """Returns JSON document with all(?) layers in active document.

        Returns: <list of PSItem>
                    Format of tuple: { 'id':'123',
                                     'name': 'My Layer 1',
                                     'type': 'GUIDE'|'FG'|'BG'|'OBJ'
                                     'visible': 'true'|'false'
        """
        res = self.websocketserver.call(
            self.client.call('Photoshop.get_layers')
        )

        return self._to_records(res)

    def get_layer(self, layer_id):
        """
            Returns PSItem for specific 'layer_id' or None if not found
        Args:
            layer_id (string): unique layer id, stored in 'uuid' field

        Returns:
            (PSItem) or None
        """
        layers = self.get_layers()
        for layer in layers:
            if str(layer.id) == str(layer_id):
                return layer

    def get_layers_in_layers(self, layers):
        """Return all layers that belong to layers (might be groups).

        Args:
            layers <list of PSItem>:

        Returns:
            <list of PSItem>
        """
        parent_ids = set([lay.id for lay in layers])

        return self._get_layers_in_layers(parent_ids)

    def get_layers_in_layers_ids(self, layers_ids, layers=None):
        """Return all layers that belong to layers (might be groups).

        Args:
            layers_ids <list of Int>
            layers <list of PSItem>:

        Returns:
            <list of PSItem>
        """
        parent_ids = set(layers_ids)

        return self._get_layers_in_layers(parent_ids, layers)

    def _get_layers_in_layers(self, parent_ids, layers=None):
        if not layers:
            layers = self.get_layers()

        all_layers = layers
        ret = []

        for layer in all_layers:
            parents = set(layer.parents)
            if len(parent_ids & parents) > 0:
                ret.append(layer)
            if layer.id in parent_ids:
                ret.append(layer)

        return ret

    def create_group(self, name):
        """Create new group (eg. LayerSet)

        Returns:
            <PSItem>
        """
        enhanced_name = self.PUBLISH_ICON + name
        ret = self.websocketserver.call(
            self.client.call('Photoshop.create_group', name=enhanced_name)
        )
        # create group on PS is asynchronous, returns only id
        return PSItem(id=ret, name=name, group=True)

    def group_selected_layers(self, name):
        """Group selected layers into new LayerSet (eg. group)

        Returns:
            (Layer)
        """
        enhanced_name = self.PUBLISH_ICON + name
        res = self.websocketserver.call(
            self.client.call(
                'Photoshop.group_selected_layers', name=enhanced_name
            )
        )
        res = self._to_records(res)
        if res:
            rec = res.pop()
            rec.name = rec.name.replace(self.PUBLISH_ICON, '')
            return rec
        raise ValueError("No group record returned")

    def get_selected_layers(self):
        """Get a list of actually selected layers.

        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):
        """Selects specified layers in Photoshop by its ids.

        Args:
            layers: <list of Layer('id':XX, 'name':"YYY")>
        """
        layers_id = [str(lay.id) for lay in layers]
        self.websocketserver.call(
            self.client.call(
                'Photoshop.select_layers',
                layers=json.dumps(layers_id)
            )
        )

    def get_active_document_full_name(self):
        """Returns full name with path of active document via ws call

        Returns(string):
            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

        Returns(string):
            file name
        """
        return self.websocketserver.call(
            self.client.call('Photoshop.get_active_document_name')
        )

    def is_saved(self):
        """Returns true if no changes in active document

        Returns:
            <boolean>
        """
        return self.websocketserver.call(
            self.client.call('Photoshop.is_saved')
        )

    def save(self):
        """Saves active document"""
        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

        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',
                image_path=image_path,
                ext=ext,
                as_copy=as_copy
            )
        )

    def set_visible(self, layer_id, visibility):
        """Set layer with 'layer_id' to 'visibility'

        Args:
            layer_id: <int>
            visibility: <true - set visible, false - hide>
        Returns: None
        """
        self.websocketserver.call(
            self.client.call(
                'Photoshop.set_visible',
                layer_id=layer_id,
                visibility=visibility
            )
        )

    def hide_all_others_layers(self, layers):
        """hides all layers that are not part of the list or that are not
        children of this list

        Args:
            layers (list): list of PSItem - highest hierarchy
        """
        extract_ids = set([ll.id for ll in self.get_layers_in_layers(layers)])

        self.hide_all_others_layers_ids(extract_ids)

    def hide_all_others_layers_ids(self, extract_ids, layers=None):
        """hides all layers that are not part of the list or that are not
        children of this list

        Args:
            extract_ids (list): list of integer that should be visible
            layers (list) of PSItem (used for caching)
        """
        if not layers:
            layers = self.get_layers()
        for layer in layers:
            if layer.visible and layer.id not in extract_ids:
                self.set_visible(layer.id, False)

    def get_layers_metadata(self):
        """Reads layers metadata from Headline from active document in PS.
        (Headline accessible by File > File Info)

        Returns:
            (list)
            example:
                {"8":{"active":true,"productName":"imageBG",
                      "productType":"image","id":"ayon.create.instance",
                      "folderPath":"/Town"}}
                8 is layer(group) id - used for deletion, update etc.
        """
        res = self.websocketserver.call(self.client.call('Photoshop.read'))
        layers_data = []
        try:
            if res:
                layers_data = json.loads(res)
        except json.decoder.JSONDecodeError:
            raise ValueError("{} cannot be parsed, recreate meta".format(res))
        # format of metadata changed from {} to [] because of standardization
        # keep current implementation logic as its working
        if isinstance(layers_data, dict):
            for layer_id, layer_meta in layers_data.items():
                if layer_meta.get("schema") != "openpype:container-2.0":
                    layer_meta["members"] = [str(layer_id)]
            layers_data = list(layers_data.values())
        return layers_data

    def import_smart_object(self, path, layer_name, as_reference=False):
        """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
            as_reference (bool): pull in content or reference
        """
        enhanced_name = self.LOADED_ICON + layer_name
        res = self.websocketserver.call(
            self.client.call(
                'Photoshop.import_smart_object',
                path=path,
                name=enhanced_name,
                as_reference=as_reference
            )
        )
        rec = self._to_records(res).pop()
        if rec:
            rec.name = rec.name.replace(self.LOADED_ICON, '')
        return rec

    def replace_smart_object(self, layer, path, layer_name):
        """Replace the smart object `layer` with file at `path`

        Args:
            layer (PSItem):
            path (str): File to import.
            layer_name (str): Unique layer name to differentiate how many times
                same smart object was loaded
        """
        enhanced_name = self.LOADED_ICON + layer_name
        self.websocketserver.call(
            self.client.call(
                'Photoshop.replace_smart_object',
                layer_id=layer.id,
                path=path,
                name=enhanced_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 rename_layer(self, layer_id, name):
        """Renames specific layer by it's id.

        Args:
            layer_id (int): id of layer to delete
            name (str): new name
        """
        self.websocketserver.call(
            self.client.call(
                'Photoshop.rename_layer',
                layer_id=layer_id,
                name=name
            )
        )

    def remove_instance(self, instance_id):
        cleaned_data = []

        for item in self.get_layers_metadata():
            inst_id = item.get("instance_id") or item.get("uuid")
            if inst_id != instance_id:
                cleaned_data.append(item)

        payload = json.dumps(cleaned_data, indent=4)

        self.websocketserver.call(
            self.client.call('Photoshop.imprint', payload=payload)
        )

    def get_extension_version(self):
        """Returns version number of installed extension."""
        return self.websocketserver.call(
            self.client.call('Photoshop.get_extension_version')
        )

    def close(self):
        """Shutting down PS and process too.

            For webpublishing only.
        """
        # TODO change client.call to method with checks for client
        self.websocketserver.call(self.client.call('Photoshop.close'))

    def _to_records(self, res):
        """Converts string json representation into list of PSItem for
        dot notation access to work.

        Args:
            res (string): valid json

        Returns:
            <list of PSItem>
        """
        try:
            layers_data = json.loads(res)
        except json.decoder.JSONDecodeError:
            raise ValueError("Received broken JSON {}".format(res))
        ret = []

        # convert to AEItem to use dot donation
        if isinstance(layers_data, dict):
            layers_data = [layers_data]
        for d in layers_data:
            # currently implemented and expected fields
            ret.append(PSItem(
                d.get('id'),
                d.get('name'),
                d.get('group'),
                d.get('parents'),
                d.get('visible'),
                d.get('type'),
                d.get('members'),
                d.get('long_name'),
                d.get("color_code"),
                d.get("instance_id")
            ))
        return ret

close()

Shutting down PS and process too.

For webpublishing only.

Source code in client/ayon_photoshop/api/ws_stub.py
530
531
532
533
534
535
536
def close(self):
    """Shutting down PS and process too.

        For webpublishing only.
    """
    # TODO change client.call to method with checks for client
    self.websocketserver.call(self.client.call('Photoshop.close'))

create_group(name)

Create new group (eg. LayerSet)

Returns:

Type Description

Source code in client/ayon_photoshop/api/ws_stub.py
262
263
264
265
266
267
268
269
270
271
272
273
def create_group(self, name):
    """Create new group (eg. LayerSet)

    Returns:
        <PSItem>
    """
    enhanced_name = self.PUBLISH_ICON + name
    ret = self.websocketserver.call(
        self.client.call('Photoshop.create_group', name=enhanced_name)
    )
    # create group on PS is asynchronous, returns only id
    return PSItem(id=ret, name=name, group=True)

delete_layer(layer_id)

Deletes specific layer by it's id.

Parameters:

Name Type Description Default
layer_id int

id of layer to delete

required
Source code in client/ayon_photoshop/api/ws_stub.py
485
486
487
488
489
490
491
492
493
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)
    )

get_active_document_full_name()

Returns full name with path of active document via ws call

Returns(string): full path with name

Source code in client/ayon_photoshop/api/ws_stub.py
318
319
320
321
322
323
324
325
326
327
328
def get_active_document_full_name(self):
    """Returns full name with path of active document via ws call

    Returns(string):
        full path with name
    """
    res = self.websocketserver.call(
        self.client.call('Photoshop.get_active_document_full_name')
    )

    return res

get_active_document_name()

Returns just a name of active document via ws call

Returns(string): file name

Source code in client/ayon_photoshop/api/ws_stub.py
330
331
332
333
334
335
336
337
338
def get_active_document_name(self):
    """Returns just a name of active document via ws call

    Returns(string):
        file name
    """
    return self.websocketserver.call(
        self.client.call('Photoshop.get_active_document_name')
    )

get_client() staticmethod

Return first connected client to WebSocket
TODO implement selection by Route

:return: client

Source code in client/ayon_photoshop/api/ws_stub.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@staticmethod
def get_client():
    """
        Return first connected client to WebSocket
        TODO implement selection by Route
    :return: <WebSocketAsync> client
    """
    clients = WebSocketAsync.get_clients()
    client = None
    if len(clients) > 0:
        key = list(clients.keys())[0]
        client = clients.get(key)

    return client

get_extension_version()

Returns version number of installed extension.

Source code in client/ayon_photoshop/api/ws_stub.py
524
525
526
527
528
def get_extension_version(self):
    """Returns version number of installed extension."""
    return self.websocketserver.call(
        self.client.call('Photoshop.get_extension_version')
    )

get_layer(layer_id)

Returns PSItem for specific 'layer_id' or None if not found

Args: layer_id (string): unique layer id, stored in 'uuid' field

Returns:

Type Description

(PSItem) or None

Source code in client/ayon_photoshop/api/ws_stub.py
205
206
207
208
209
210
211
212
213
214
215
216
217
def get_layer(self, layer_id):
    """
        Returns PSItem for specific 'layer_id' or None if not found
    Args:
        layer_id (string): unique layer id, stored in 'uuid' field

    Returns:
        (PSItem) or None
    """
    layers = self.get_layers()
    for layer in layers:
        if str(layer.id) == str(layer_id):
            return layer

get_layers()

Returns JSON document with all(?) layers in active document.

<list of PSItem>

Type Description

Format of tuple: { 'id':'123', 'name': 'My Layer 1', 'type': 'GUIDE'|'FG'|'BG'|'OBJ' 'visible': 'true'|'false'

Source code in client/ayon_photoshop/api/ws_stub.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def get_layers(self):
    """Returns JSON document with all(?) layers in active document.

    Returns: <list of PSItem>
                Format of tuple: { 'id':'123',
                                 'name': 'My Layer 1',
                                 'type': 'GUIDE'|'FG'|'BG'|'OBJ'
                                 'visible': 'true'|'false'
    """
    res = self.websocketserver.call(
        self.client.call('Photoshop.get_layers')
    )

    return self._to_records(res)

get_layers_in_layers(layers)

Return all layers that belong to layers (might be groups).

Parameters:

Name Type Description Default
layers <list of PSItem>
required

Returns:

Type Description

Source code in client/ayon_photoshop/api/ws_stub.py
219
220
221
222
223
224
225
226
227
228
229
230
def get_layers_in_layers(self, layers):
    """Return all layers that belong to layers (might be groups).

    Args:
        layers <list of PSItem>:

    Returns:
        <list of PSItem>
    """
    parent_ids = set([lay.id for lay in layers])

    return self._get_layers_in_layers(parent_ids)

get_layers_in_layers_ids(layers_ids, layers=None)

Return all layers that belong to layers (might be groups).

Parameters:

Name Type Description Default
layers <list of PSItem>
None

Returns:

Type Description

Source code in client/ayon_photoshop/api/ws_stub.py
232
233
234
235
236
237
238
239
240
241
242
243
244
def get_layers_in_layers_ids(self, layers_ids, layers=None):
    """Return all layers that belong to layers (might be groups).

    Args:
        layers_ids <list of Int>
        layers <list of PSItem>:

    Returns:
        <list of PSItem>
    """
    parent_ids = set(layers_ids)

    return self._get_layers_in_layers(parent_ids, layers)

get_layers_metadata()

Reads layers metadata from Headline from active document in PS. (Headline accessible by File > File Info)

Returns:

Name Type Description

(list)

example

{"8":{"active":true,"productName":"imageBG", "productType":"image","id":"ayon.create.instance", "folderPath":"/Town"}} 8 is layer(group) id - used for deletion, update etc.

Source code in client/ayon_photoshop/api/ws_stub.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
def get_layers_metadata(self):
    """Reads layers metadata from Headline from active document in PS.
    (Headline accessible by File > File Info)

    Returns:
        (list)
        example:
            {"8":{"active":true,"productName":"imageBG",
                  "productType":"image","id":"ayon.create.instance",
                  "folderPath":"/Town"}}
            8 is layer(group) id - used for deletion, update etc.
    """
    res = self.websocketserver.call(self.client.call('Photoshop.read'))
    layers_data = []
    try:
        if res:
            layers_data = json.loads(res)
    except json.decoder.JSONDecodeError:
        raise ValueError("{} cannot be parsed, recreate meta".format(res))
    # format of metadata changed from {} to [] because of standardization
    # keep current implementation logic as its working
    if isinstance(layers_data, dict):
        for layer_id, layer_meta in layers_data.items():
            if layer_meta.get("schema") != "openpype:container-2.0":
                layer_meta["members"] = [str(layer_id)]
        layers_data = list(layers_data.values())
    return layers_data

get_selected_layers()

Get a list of actually selected layers.

Returns:

Source code in client/ayon_photoshop/api/ws_stub.py
294
295
296
297
298
299
300
301
302
def get_selected_layers(self):
    """Get a list of actually selected layers.

    Returns: <list of Layer('id':XX, 'name':"YYY")>
    """
    res = self.websocketserver.call(
        self.client.call('Photoshop.get_selected_layers')
    )
    return self._to_records(res)

group_selected_layers(name)

Group selected layers into new LayerSet (eg. group)

Returns:

Type Description

(Layer)

Source code in client/ayon_photoshop/api/ws_stub.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
def group_selected_layers(self, name):
    """Group selected layers into new LayerSet (eg. group)

    Returns:
        (Layer)
    """
    enhanced_name = self.PUBLISH_ICON + name
    res = self.websocketserver.call(
        self.client.call(
            'Photoshop.group_selected_layers', name=enhanced_name
        )
    )
    res = self._to_records(res)
    if res:
        rec = res.pop()
        rec.name = rec.name.replace(self.PUBLISH_ICON, '')
        return rec
    raise ValueError("No group record returned")

hide_all_others_layers(layers)

hides all layers that are not part of the list or that are not children of this list

Parameters:

Name Type Description Default
layers list

list of PSItem - highest hierarchy

required
Source code in client/ayon_photoshop/api/ws_stub.py
390
391
392
393
394
395
396
397
398
399
def hide_all_others_layers(self, layers):
    """hides all layers that are not part of the list or that are not
    children of this list

    Args:
        layers (list): list of PSItem - highest hierarchy
    """
    extract_ids = set([ll.id for ll in self.get_layers_in_layers(layers)])

    self.hide_all_others_layers_ids(extract_ids)

hide_all_others_layers_ids(extract_ids, layers=None)

hides all layers that are not part of the list or that are not children of this list

Parameters:

Name Type Description Default
extract_ids list

list of integer that should be visible

required
Source code in client/ayon_photoshop/api/ws_stub.py
401
402
403
404
405
406
407
408
409
410
411
412
413
def hide_all_others_layers_ids(self, extract_ids, layers=None):
    """hides all layers that are not part of the list or that are not
    children of this list

    Args:
        extract_ids (list): list of integer that should be visible
        layers (list) of PSItem (used for caching)
    """
    if not layers:
        layers = self.get_layers()
    for layer in layers:
        if layer.visible and layer.id not in extract_ids:
            self.set_visible(layer.id, False)

import_smart_object(path, layer_name, as_reference=False)

Import the file at path as a smart object to active document.

Parameters:

Name Type Description Default
path str

File path to import.

required
layer_name str

Unique layer name to differentiate how many times same smart object was loaded

required
as_reference bool

pull in content or reference

False
Source code in client/ayon_photoshop/api/ws_stub.py
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
def import_smart_object(self, path, layer_name, as_reference=False):
    """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
        as_reference (bool): pull in content or reference
    """
    enhanced_name = self.LOADED_ICON + layer_name
    res = self.websocketserver.call(
        self.client.call(
            'Photoshop.import_smart_object',
            path=path,
            name=enhanced_name,
            as_reference=as_reference
        )
    )
    rec = self._to_records(res).pop()
    if rec:
        rec.name = rec.name.replace(self.LOADED_ICON, '')
    return rec

imprint(item_id, data, all_layers=None, items_meta=None)

Save layer metadata to Headline field of active document

Stores metadata in format: [{ "active":true, "productName":"imageBG", "productType":"image", "id":"ayon.create.instance", "folderPath":"Town", "uuid": "8" }] - for created instances OR [{ "schema": "openpype:container-2.0", "id": "ayon.create.instance", "name": "imageMG", "namespace": "Jungle_imageMG_001", "loader": "ImageLoader", "representation": "5fbfc0ee30a946093c6ff18a", "members": [ "40" ] }] - for loaded instances

Parameters:

Name Type Description Default
item_id str
required
data(string)

json representation for single layer

required
all_layers list of PSItem

for performance, could be injected for usage in loop, if not, single call will be triggered

None
items_meta(string)

json representation from Headline (for performance - provide only if imprint is in loop - value should be same)

required

Returns: None

Source code in client/ayon_photoshop/api/ws_stub.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def imprint(self, item_id, data, all_layers=None, items_meta=None):
    """Save layer metadata to Headline field of active document

    Stores metadata in format:
    [{
        "active":true,
        "productName":"imageBG",
        "productType":"image",
        "id":"ayon.create.instance",
        "folderPath":"Town",
        "uuid": "8"
    }] - for created instances
    OR
    [{
        "schema": "openpype:container-2.0",
        "id": "ayon.create.instance",
        "name": "imageMG",
        "namespace": "Jungle_imageMG_001",
        "loader": "ImageLoader",
        "representation": "5fbfc0ee30a946093c6ff18a",
        "members": [
            "40"
        ]
    }] - for loaded instances

    Args:
        item_id (str):
        data(string): json representation for single layer
        all_layers (list of PSItem): for performance, could be
            injected for usage in loop, if not, single call will be
            triggered
        items_meta(string): json representation from Headline
                       (for performance - provide only if imprint is in
                       loop - value should be same)
    Returns: None
    """
    if not items_meta:
        items_meta = self.get_layers_metadata()

    # json.dumps writes integer values in a dictionary to string, so
    # anticipating it here.
    item_id = str(item_id)
    is_new = True
    result_meta = []
    for item_meta in items_meta:
        if ((item_meta.get('members') and
             item_id == str(item_meta.get('members')[0])) or
                item_meta.get("instance_id") == item_id):
            is_new = False
            if data:
                item_meta.update(data)
                result_meta.append(item_meta)
        else:
            result_meta.append(item_meta)

    if is_new:
        result_meta.append(data)

    # Ensure only valid ids are stored.
    if not all_layers:
        all_layers = self.get_layers()
    layer_ids = [layer.id for layer in all_layers]
    cleaned_data = []

    for item in result_meta:
        if item.get("members"):
            if int(item["members"][0]) not in layer_ids:
                continue

        cleaned_data.append(item)

    payload = json.dumps(cleaned_data, indent=4)
    self.websocketserver.call(
        self.client.call('Photoshop.imprint', payload=payload)
    )

is_saved()

Returns true if no changes in active document

Returns:

Type Description

Source code in client/ayon_photoshop/api/ws_stub.py
340
341
342
343
344
345
346
347
348
def is_saved(self):
    """Returns true if no changes in active document

    Returns:
        <boolean>
    """
    return self.websocketserver.call(
        self.client.call('Photoshop.is_saved')
    )

open(path)

Open file located at 'path' (local).

Parameters:

Name Type Description Default
path(string)

file path locally

required

Returns: None

Source code in client/ayon_photoshop/api/ws_stub.py
72
73
74
75
76
77
78
79
80
81
def open(self, path):
    """Open file located at 'path' (local).

    Args:
        path(string): file path locally
    Returns: None
    """
    self.websocketserver.call(
        self.client.call('Photoshop.open', path=path)
    )

read(layer, layers_meta=None)

Parses layer metadata from Headline field of active document.

Parameters:

Name Type Description Default
layer

(PSItem)

required
layers_meta

full list from Headline (for performance in loops)

None

Returns: (dict) of layer metadata stored in PS file

Example

{ 'id': 'pyblish.avalon.container', 'loader': 'ImageLoader', 'members': ['64'], 'name': 'imageMainMiddle', 'namespace': 'Hero_imageMainMiddle_001', 'representation': '6203dc91e80934d9f6ee7d96', 'schema': 'openpype:container-2.0' }

Source code in client/ayon_photoshop/api/ws_stub.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def read(self, layer, layers_meta=None):
    """Parses layer metadata from Headline field of active document.

    Args:
        layer: (PSItem)
        layers_meta: full list from Headline (for performance in loops)
    Returns:
        (dict) of layer metadata stored in PS file

    Example:
        {
            'id': 'pyblish.avalon.container',
            'loader': 'ImageLoader',
            'members': ['64'],
            'name': 'imageMainMiddle',
            'namespace': 'Hero_imageMainMiddle_001',
            'representation': '6203dc91e80934d9f6ee7d96',
            'schema': 'openpype:container-2.0'
        }
    """
    if layers_meta is None:
        layers_meta = self.get_layers_metadata()

    for layer_meta in layers_meta:
        layer_id = layer_meta.get("uuid")  # legacy
        if layer_meta.get("members"):
            layer_id = layer_meta["members"][0]
        if str(layer.id) == str(layer_id):
            return layer_meta
    print("Unable to find layer metadata for {}".format(layer.id))

rename_layer(layer_id, name)

Renames specific layer by it's id.

Parameters:

Name Type Description Default
layer_id int

id of layer to delete

required
name str

new name

required
Source code in client/ayon_photoshop/api/ws_stub.py
495
496
497
498
499
500
501
502
503
504
505
506
507
508
def rename_layer(self, layer_id, name):
    """Renames specific layer by it's id.

    Args:
        layer_id (int): id of layer to delete
        name (str): new name
    """
    self.websocketserver.call(
        self.client.call(
            'Photoshop.rename_layer',
            layer_id=layer_id,
            name=name
        )
    )

replace_smart_object(layer, path, layer_name)

Replace the smart object layer with file at path

Parameters:

Name Type Description Default
layer PSItem
required
path str

File to import.

required
layer_name str

Unique layer name to differentiate how many times same smart object was loaded

required
Source code in client/ayon_photoshop/api/ws_stub.py
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
def replace_smart_object(self, layer, path, layer_name):
    """Replace the smart object `layer` with file at `path`

    Args:
        layer (PSItem):
        path (str): File to import.
        layer_name (str): Unique layer name to differentiate how many times
            same smart object was loaded
    """
    enhanced_name = self.LOADED_ICON + layer_name
    self.websocketserver.call(
        self.client.call(
            'Photoshop.replace_smart_object',
            layer_id=layer.id,
            path=path,
            name=enhanced_name
        )
    )

save()

Saves active document

Source code in client/ayon_photoshop/api/ws_stub.py
350
351
352
353
354
def save(self):
    """Saves active document"""
    self.websocketserver.call(
        self.client.call('Photoshop.save')
    )

saveAs(image_path, ext, as_copy)

Saves active document to psd (copy) or png or jpg

Parameters:

Name Type Description Default
image_path(string)

full local path

required
ext

required
as_copy

required

Returns: None

Source code in client/ayon_photoshop/api/ws_stub.py
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
def saveAs(self, image_path, ext, as_copy):
    """Saves active document to psd (copy) or png or jpg

    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',
            image_path=image_path,
            ext=ext,
            as_copy=as_copy
        )
    )

select_layers(layers)

Selects specified layers in Photoshop by its ids.

Parameters:

Name Type Description Default
layers

required
Source code in client/ayon_photoshop/api/ws_stub.py
304
305
306
307
308
309
310
311
312
313
314
315
316
def select_layers(self, layers):
    """Selects specified layers in Photoshop by its ids.

    Args:
        layers: <list of Layer('id':XX, 'name':"YYY")>
    """
    layers_id = [str(lay.id) for lay in layers]
    self.websocketserver.call(
        self.client.call(
            'Photoshop.select_layers',
            layers=json.dumps(layers_id)
        )
    )

set_visible(layer_id, visibility)

Set layer with 'layer_id' to 'visibility'

Parameters:

Name Type Description Default
layer_id

required
visibility

required

Returns: None

Source code in client/ayon_photoshop/api/ws_stub.py
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
def set_visible(self, layer_id, visibility):
    """Set layer with 'layer_id' to 'visibility'

    Args:
        layer_id: <int>
        visibility: <true - set visible, false - hide>
    Returns: None
    """
    self.websocketserver.call(
        self.client.call(
            'Photoshop.set_visible',
            layer_id=layer_id,
            visibility=visibility
        )
    )