Copy link to clipboard
Copied
Hi guys,
I have a custom logic-heavy export script that needs to hide layers before it begins execution. And I need to use logic to figure out which layers I need to hide, so using commands like Select All Layers and then Hide Selected Layers won't cut it here.
I have a working version of the script that uses recursive DOM traversal and hides the necessary layers that way, but the performance is ridiculously slow. Some of the files I need this to run on are 1000+ layers, and it can literally take a minute to simply loop through all layers.
By using Script Listener, I have found some code that does pretty much exactly what I need:
var idhide = stringIDToTypeID( "hide" );
var desc877 = new ActionDescriptor();
var idnull = stringIDToTypeID( "null" );
var list111 = new ActionList();
var ref384 = new ActionReference();
var idlayer = stringIDToTypeID( "layer" );
ref384.putName( idlayer, "Layer 1" );
ref384.putName( idlayer, "Layer 2" );
ref384.putName( idlayer, "Layer 3" );
ref384.putName( idlayer, "Layer 4" );
list111.putReference( ref384 );
desc877.putList( idnull, list111 );
executeAction( idhide, desc877, DialogModes.NO );
I can basically use this to take a list of layer names and hide them all instantly. Marvellous! Except layer names are not unique, so this falls apart completely if any two layers share the same name, and it's a complete deal breaker for my case.
I need a version of the same code that uses layer IDs instead of names, but I have no idea how to construct it or where to look for reference (I presume it doesn't exist). I have found some functions in Photoshop that generate code that looks very similar to what I'm looking for, i.e. selecting a bunch of layers creates this snippet:
...
var idlayerID = stringIDToTypeID( "layerID" );
var list110 = new ActionList();
list110.putInteger( 238 );
list110.putInteger( 239 );
list110.putInteger( 242 );
list110.putInteger( 250 );
desc872.putList( idlayerID, list110 );
...
But I haven't been able to come up with a way to use this mode of passing layers to the "hide" command. And even in the "select" command it doesn't seem to work despite appearing like this in the Script Listener's log.
Any ideas?
visiblityByIDList(getAllLayersIDs(), 'hide');
// IDList - array of layer IDs
// mode - 'hide' or 'show'
function visiblityByIDList(IDList, mode) {
var s2t = stringIDToTypeID,
r = new ActionReference();
do { r.putIdentifier(s2t('layer'), IDList.shift()) } while (IDList.length)
var d = new ActionDescriptor();
d.putReference(s2t('target'), r);
executeAction(s2t(mode), d, DialogModes.NO);
}
// just for example
function getAllLayersIDs() {
s2t = stringIDToTypeID;
...
Copy link to clipboard
Copied
How do you know which layer ID should be visible and which layer ID should not be visible. I can see where when there may duplicate duplicate layer names that layer name can not be used for controlling visibility. I would think Relative stack location may use for targeting layers that visibility should be changed. Layer id are unique in a document however the layer position in the will change with the additions and deletion and moving of layers in the stack. How do you know what content the layer with ID x has or where in the stack the layer with ID x is. What logic do you use to determine that layer with ID x visibility should be on or off. How do yor manage a 1000+ layer stack. Perhapse you should look into using layer comps. Layer comps
Copy link to clipboard
Copied
The exporter I'm writing is used for preparing game assets. The logic emerges from the way assets in the file are organized. It's usually a complex scene with a lot of nested groups for objects and scene depths and categories and characters. Some objects have multiple timeline animations that need to be exported at different framerates, some exist in several variants dictated by combinations of layers within a group, some have arbitrary boundaries associated with them, etc. I'm using layer names to describe all this behavior and then I parse all of it inside the script, and by that point I have a complete model of everything I need to know about the document, including the right layer IDs. I'm just lacking a quick way of feeding it into Photoshop's API.
The 1000+ layers is not really a problem for artistic workflow because there's rhyme and reason to it and everything is organized in numerous groups (i.e. I can have "scene/level 5/background/objects/furniture/table" + 30 more furniture items in the same file, it adds up quickly). This is really just a domain specific thing I guess 🙂 Layer comps can't deal with this. I've actually implemented my own version of this that operates at group level — and that's another thing that I would love to teach how to hide layers quickly, but no luck so far. It all works beautifully, it's just horribly sluggish.
Copy link to clipboard
Copied
visiblityByIDList(getAllLayersIDs(), 'hide');
// IDList - array of layer IDs
// mode - 'hide' or 'show'
function visiblityByIDList(IDList, mode) {
var s2t = stringIDToTypeID,
r = new ActionReference();
do { r.putIdentifier(s2t('layer'), IDList.shift()) } while (IDList.length)
var d = new ActionDescriptor();
d.putReference(s2t('target'), r);
executeAction(s2t(mode), d, DialogModes.NO);
}
// just for example
function getAllLayersIDs() {
s2t = stringIDToTypeID;
(ref = new ActionReference()).putProperty(s2t('property'), p = s2t('numberOfLayers'));
ref.putEnumerated(s2t('document'), s2t('ordinal'), s2t('targetEnum'));
var len = executeActionGet(ref).getInteger(p),
lrs = [];
for (var i = 1; i <= len; i++) {
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('layerID'));
r.putIndex(s2t('layer'), i);
lrs.push(executeActionGet(r).getInteger(p))
}
return lrs
}
Copy link to clipboard
Copied
I'm not sure what the criteria are for determining which layer should be visible or not.
As I understand from your last snippet, the criteria are based on layer selection.
This function will handle only selected layers or all layers if nothing is selected.
function setVisibility(visible){
// visible: 'show' or 'hide'
selection = false;
desc = new ActionDescriptor();
desc2 = new ActionDescriptor();
ref = new ActionReference();
ref2 = new ActionReference();
ref3 = new ActionReference();
ref.putProperty( stringIDToTypeID('property'), stringIDToTypeID('targetLayersIDs') );
ref.putEnumerated( stringIDToTypeID('document'), stringIDToTypeID('ordinal'), stringIDToTypeID('targetEnum') );
ref2.putIndex(stringIDToTypeID("layer"), 1); // ignores background
desc.putReference(stringIDToTypeID("null"), ref2);
if ( !executeActionGet(ref).hasKey(stringIDToTypeID('targetLayersIDs')) ) {
executeAction(stringIDToTypeID("selectAllLayers"), desc, DialogModes.NO); // select all layers
selection = true
}
list = executeActionGet(ref).getList( stringIDToTypeID('targetLayersIDs') );
for ( var i = 0; i < list.count; i++ ) {
ref3.putIdentifier(stringIDToTypeID('layer'), list.getReference( i ).getIdentifier());
desc2.putReference(stringIDToTypeID('target'), ref3);
executeAction(stringIDToTypeID(visible), desc2, DialogModes.NO);
}
if (selection) executeAction(stringIDToTypeID("selectNoLayers"), desc, DialogModes.NO); // select no layers
}
Copy link to clipboard
Copied
This is just an example of using a function. The snippet collects the IDs of all layers in the document, regardless of the selection.
By the way, just one change will speed up your code 5-6 times 🙂
change:
for ( var i = 0; i < list.count; i++ ) {
ref3.putIdentifier(stringIDToTypeID('layer'), list.getReference( i ).getIdentifier());
desc2.putReference(stringIDToTypeID('target'), ref3);
executeAction(stringIDToTypeID(visible), desc2, DialogModes.NO);
}
to:
for ( var i = 0; i < list.count; i++ ) {
ref3.putIdentifier(stringIDToTypeID('layer'), list.getReference( i ).getIdentifier());
desc2.putReference(stringIDToTypeID('target'), ref3);
}
executeAction(stringIDToTypeID(visible), desc2, DialogModes.NO);
(this is especially noticeable on documents with a large number of layers)
Also, if we are talking about selection of layers on the palette, there is no point in wasting time getting their ID, just execute "hide" or "show" for the active layer (and the command will automatically apply to all selected layers)
ref3 = new ActionReference();
desc2 = new ActionDescriptor();
ref3.putEnumerated(stringIDToTypeID('layer'), stringIDToTypeID('ordinal'), stringIDToTypeID('targetEnum'));
desc2.putReference(stringIDToTypeID('target'), ref3);
executeAction(stringIDToTypeID('hide'), desc2, DialogModes.NO);
Copy link to clipboard
Copied
That's a really nice touch @jazz-y , thank you for that.
with this,
ref3.putIdentifier(stringIDToTypeID('layer'), list.getReference( i ).getIdentifier());
, I was planing to return a list of IDs as per the author's request, but I changed my mind and just ignored the "wasting of time" factor. Interesting that, with your suggestion, it gets all the selected layers. I didn't know that.
Based on your feedback I think this is a much cleaner coding:
function setVisibility(visible){
// visible: 'show' or 'hide'
selection = false;
desc = new ActionDescriptor();
desc2 = new ActionDescriptor();
ref = new ActionReference();
ref2 = new ActionReference();
ref3 = new ActionReference();
ref.putProperty( stringIDToTypeID('property'), stringIDToTypeID('targetLayersIDs') );
ref.putEnumerated( stringIDToTypeID('document'), stringIDToTypeID('ordinal'), stringIDToTypeID('targetEnum') );
ref2.putIndex(stringIDToTypeID("layer"), 1); // ignores background
desc.putReference(stringIDToTypeID("null"), ref2);
if ( !executeActionGet(ref).hasKey(stringIDToTypeID('targetLayersIDs')) ) {
executeAction(stringIDToTypeID("selectAllLayers"), desc, DialogModes.NO); // select all layers
selection = true
}
ref3.putEnumerated(stringIDToTypeID('layer'), stringIDToTypeID('ordinal'), stringIDToTypeID('targetEnum'));
desc2.putReference(stringIDToTypeID('target'), ref3);
executeAction(stringIDToTypeID(visible), desc2, DialogModes.NO);
if (selection) executeAction(stringIDToTypeID("selectNoLayers"), desc, DialogModes.NO); // select no layers
}
Copy link to clipboard
Copied
The criteria in my case are calculated by relatively complex logic inside the script, and in the end I basically have a list of IDs of layers I need to hide, so jazz-y's solution is on point. The snippet was just a breadcrumb I found in the SL output that pointed to the possibility of what I was after. But your code is also interesting and will be helpful in other cases, thank you!
Copy link to clipboard
Copied
This is incredible! Thank you so much, it works like magic