Skip to content

Commit

Permalink
feat(artlayer): improve layer kind property to support artboard layer
Browse files Browse the repository at this point in the history
Add new method to get layer kind and handle artboard layer type

Signed-off-by: longhao <[email protected]>
  • Loading branch information
loonghao committed Dec 29, 2024
1 parent b90d421 commit 01852a1
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 9 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
.idea/

# Vim / Notepad++ temp files
*~
.*/
*.egg-info
# PyInstaller output
build/
Expand All @@ -24,3 +22,4 @@ venv_python

# Docs
docs_src/_build/
/.windsurfrules
222 changes: 222 additions & 0 deletions examples/export_artboards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
"""Example of how to export artboards from a PSD file.
This script demonstrates how to:
1. Identify artboard layers in a PSD file
2. Export each artboard as a separate image
"""
import os.path
from pathlib import Path
from typing import List, Union

from photoshop import Session
from photoshop.api._artlayer import ArtLayer
from photoshop.api._layerSet import LayerSet
from photoshop.api.enumerations import LayerKind


def is_artboard(layer: Union[ArtLayer, LayerSet], ps) -> bool:
"""Check if a layer is an artboard.
Args:
layer: Photoshop layer object (ArtLayer or LayerSet)
ps: Photoshop session object
Returns:
bool: True if the layer is an artboard, False otherwise
"""
try:
# Get the active document
doc = ps.active_document

# Select the layer
doc.activeLayer = layer

# Check if it's an artboard by checking its bounds and artboard property
js_code = """
var layer = app.activeDocument.activeLayer;
try {
var ref = new ActionReference();
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
var desc = executeActionGet(ref);
var hasArtboard = desc.hasKey(stringIDToTypeID("artboardEnabled"));
hasArtboard && desc.getBoolean(stringIDToTypeID("artboardEnabled"));
} catch(e) {
false;
}
"""
result = ps.app.eval_javascript(js_code)
return result.lower() == "true"
except Exception as e:
print(f"Error checking layer {layer.name}: {str(e)}")
# Fallback to checking layer name
return "Artboard" in layer.name


def get_all_layers(doc) -> List[Union[ArtLayer, LayerSet]]:
"""Recursively get all layers from document, including nested layers.
Args:
doc: Photoshop document object
Returns:
List[Union[ArtLayer, LayerSet]]: List of all layers
"""
def _get_layers(layer_container) -> List[Union[ArtLayer, LayerSet]]:
layers = []

# Get art layers
for layer in layer_container.artLayers:
layers.append(layer)

# Get layer sets and their children
for layer_set in layer_container.layerSets:
layers.append(layer_set)
layers.extend(_get_layers(layer_set))

return layers

return _get_layers(doc)


def get_artboard_layers(doc, artboard_name: str) -> List[Union[ArtLayer, LayerSet]]:
"""Get all layers that belong to an artboard.
Args:
doc: Photoshop document object
artboard_name: Name of the artboard
Returns:
List[Union[ArtLayer, LayerSet]]: List of layers that belong to this artboard
"""
try:
# Get all layers in the document
all_layers = []
for layer in doc.artLayers:
all_layers.append(layer)
for layer in doc.layerSets:
all_layers.append(layer)

# Get the artboard layer
artboard = None
for layer in all_layers:
if layer.name == artboard_name:
artboard = layer
break

if not artboard:
return []

# Get all layers that belong to this artboard
artboard_layers = []
for layer in all_layers:
if layer.name != artboard_name:
try:
# Check if layer is visible and within artboard bounds
if layer.visible and isinstance(layer, ArtLayer):
artboard_layers.append(layer)
except Exception as e:
print(f"Error checking layer {layer.name}: {str(e)}")
continue

return artboard_layers
except Exception as e:
print(f"Error getting artboard layers: {str(e)}")
return []


def export_artboards(psd_path: str, output_dir: str) -> None:
"""Export all artboards in a PSD file as separate images.
Args:
psd_path (str): Path to the PSD file
output_dir (str): Directory to save the exported images
"""
with Session() as ps:
try:
# Open the PSD file
ps.app.open(os.path.abspath(psd_path))
doc = ps.active_document

# Create output directory if it doesn't exist
Path(output_dir).mkdir(parents=True, exist_ok=True)

# Get all layers including nested ones
all_layers = get_all_layers(doc)
print(f"Found {len(all_layers)} total layers:")
for layer in all_layers:
layer_type = "LayerSet" if isinstance(layer, LayerSet) else "ArtLayer"
is_ab = "Artboard" if is_artboard(layer, ps) else "Regular Layer"
print(f"Layer: {layer.name} ({layer_type} - {is_ab})")

# Iterate through all layers
for layer in all_layers:
if is_artboard(layer, ps):
print(f"\nProcessing artboard: {layer.name}")

# Export artboard using JavaScript
output_path = os.path.abspath(str(Path(output_dir) / f"{layer.name}.png"))
# Convert Windows path to JavaScript path format
js_path = output_path.replace("\\", "/")

js_code = """
function exportArtboard(filePath, artboardName) {
var doc = app.activeDocument;
var artboard = null;
// Find the artboard
for (var i = 0; i < doc.layers.length; i++) {
if (doc.layers[i].name === '%s') {
artboard = doc.layers[i];
break;
}
}
if (!artboard) return;
// Save the current state
var docState = doc.activeHistoryState;
try {
// Hide all layers
for (var i = 0; i < doc.layers.length; i++) {
doc.layers[i].visible = false;
}
// Show only the artboard and its contents
artboard.visible = true;
// Save as PNG
var pngFile = new File('%s');
var pngOptions = new PNGSaveOptions();
pngOptions.interlaced = false;
pngOptions.compression = 9;
pngOptions.transparency = true;
doc.saveAs(pngFile, pngOptions, true, Extension.LOWERCASE);
// Restore the document state
doc.activeHistoryState = docState;
} catch (e) {
// Restore the document state in case of error
doc.activeHistoryState = docState;
throw e;
}
}
exportArtboard('%s', '%s');
""" % (layer.name, js_path, js_path, layer.name)

ps.app.eval_javascript(js_code)
print(f"Exported {layer.name} to {output_path}")

except Exception as e:
print(f"Error processing PSD file: {str(e)}")
raise


if __name__ == "__main__":
# Example usage
current_dir = os.path.dirname(os.path.abspath(__file__))
psd_path = os.path.join(current_dir, "files", "artboard_example.psd") # Use absolute path
output_dir = os.path.join(current_dir, "output", "artboards") # Use absolute path
export_artboards(psd_path, output_dir)
Binary file added examples/files/artboard_example.psd
Binary file not shown.
35 changes: 28 additions & 7 deletions photoshop/api/_artlayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,36 @@ def isBackgroundLayer(self, value):

@property
def kind(self):
"""Sets the layer kind (such as ‘text layer’) for an empty layer.
Valid only when the layer is empty and when `isBackgroundLayer` is
false. You can use the ‘kind ‘ property to make a background layer a
normal layer; however, to make a layer a background layer, you must
set `isBackgroundLayer` to true.
"""Get the layer kind.
Returns:
LayerKind: The kind of this layer.
"""
return LayerKind(self.app.kind)
try:
js_code = f"""
function getLayerKindByIndex(index) {{
var ref = new ActionReference();
ref.putIndex(charIDToTypeID('Lyr '), index);
var desc = executeActionGet(ref);
if (desc.hasKey(stringIDToTypeID('artboard'))) {{
return 25; // ArtboardLayer
}}
if (desc.hasKey(stringIDToTypeID('textKey'))) {{
return 2; // TextLayer
}}
return 1; // NormalLayer
}}
getLayerKindByIndex({self.itemIndex});
"""
result = self.eval_javascript(js_code)
print(f"Layer kind result for {self.name}: {result}")
return LayerKind(int(result))
except Exception as e:
print(f"Error getting layer kind for {self.name}: {str(e)}")
return LayerKind.NormalLayer

@kind.setter
def kind(self, layer_type):
Expand Down
2 changes: 2 additions & 0 deletions photoshop/api/enumerations.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ class LayerCompressionType(IntEnum):


class LayerKind(IntEnum):
"""The kind of a layer."""
BlackAndWhiteLayer = 22
BrightnessContrastLayer = 9
ChannelMixerLayer = 12
Expand All @@ -554,6 +555,7 @@ class LayerKind(IntEnum):
ThresholdLayer = 15
Vibrance = 23
VideoLayer = 21
ArtboardLayer = 25 # Add new type for Artboard


class LayerType(IntEnum):
Expand Down

0 comments on commit 01852a1

Please sign in to comment.