Skip to content

lib

isolated_layers_visibility(stub, layer_ids, all_layers=None)

Show only the specified layers and their ancestor paths, hiding all siblings.

Similar to isolated_instance_visibility but supports multiple layer IDs. All specified layers and their ancestors will be visible simultaneously.

Parameters:

Name Type Description Default
stub

PhotoshopServerStub

required
layer_ids

List of layer IDs to show (can be single layer or multiple)

required
all_layers

Optional list of PSItem layers (fetched if not provided)

None

Tracks original visibility and restores it on exit.

Source code in client/ayon_photoshop/api/lib.py
 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
@contextlib.contextmanager
def isolated_layers_visibility(stub, layer_ids, all_layers=None):
    """Show only the specified layers and their ancestor paths, hiding all siblings.

    Similar to isolated_instance_visibility but supports multiple layer IDs.
    All specified layers and their ancestors will be visible simultaneously.

    Args:
        stub: PhotoshopServerStub
        layer_ids: List of layer IDs to show (can be single layer or multiple)
        all_layers: Optional list of PSItem layers (fetched if not provided)

    Tracks original visibility and restores it on exit.
    """
    if all_layers is None:
        all_layers = stub.get_layers()

    # Normalize to list if single ID provided
    if not isinstance(layer_ids, (list, tuple, set)):
        layer_ids = [layer_ids]

    layers_by_id = {layer.id: layer for layer in all_layers}
    layers_by_parent = _get_layers_by_parent(all_layers)

    # Build paths from all target layers to top-level
    path_ids = set()
    for layer_id in layer_ids:
        current = layers_by_id.get(layer_id)
        if not current:
            continue  # Skip if layer not found
        while current:
            path_ids.add(current.id)
            current = layers_by_id.get(current.parents[-1]) if current.parents else None

    if not path_ids:
        yield  # No-op if no valid layers found
        return

    # Record original visibility and build change map
    original_visibility = {}
    visibility_changes = {}

    for layer_id in path_ids:
        layer = layers_by_id.get(layer_id)
        if not layer:
            continue
        parent_id = layer.parents[-1] if layer.parents else None
        for sibling in layers_by_parent.get(parent_id, []):
            # Record original state before any changes
            if sibling.id not in original_visibility:
                original_visibility[sibling.id] = sibling.visible
            # Path layers visible, siblings hidden
            visibility_changes[sibling.id] = sibling.id in path_ids

    try:
        if visibility_changes:
            stub.set_layers_visibility(visibility_changes)
        yield
    finally:
        # Restore original visibility state
        if original_visibility:
            stub.set_layers_visibility(original_visibility)

maintained_selection()

Maintain selection during context.

Source code in client/ayon_photoshop/api/lib.py
76
77
78
79
80
81
82
83
@contextlib.contextmanager
def maintained_selection():
    """Maintain selection during context."""
    selection = stub().get_selected_layers()
    try:
        yield selection
    finally:
        stub().select_layers(selection)

publish_in_test(log, close_plugin_name=None)

Loops through all plugins, logs to console. Used for tests. Args: log (Logger) close_plugin_name (Optional[str]): Name of plugin with responsibility to close application.

Source code in client/ayon_photoshop/api/lib.py
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
def publish_in_test(log, close_plugin_name=None):
    """Loops through all plugins, logs to console. Used for tests.
    Args:
        log (Logger)
        close_plugin_name (Optional[str]): Name of plugin with responsibility
            to close application.
    """

    # Error exit as soon as any error occurs.
    error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}"
    close_plugin = find_close_plugin(close_plugin_name, log)

    for result in pyblish.util.publish_iter():
        for record in result["records"]:
            # Why do we log again? pyblish logger is logging to stdout...
            log.info("{}: {}".format(result["plugin"].label, record.msg))

        if not result["error"]:
            continue

        # QUESTION We don't break on error?
        error_message = error_format.format(**result)
        log.error(error_message)
        if close_plugin:  # close host app explicitly after error
            context = pyblish.api.Context()
            try:
                close_plugin().process(context)
            except Exception as exp:
                print(exp)