Simple example of code and Console output while looping through two layers:
Any update maybe? Now thereās no way to actually correctly avoid this issue when multiple layers are active, but you want to rename just one. Same goes for color change via BatchPlay
Hello, I havenāt noticed the fact that renaming a layer with UXP code or changing a layer color with batchPlay has an impact on layer visibility. Parsing the PS layer stack is very useful.
Have a look here https://www.adobe.io/photoshop/uxp/guides/ps_basics#layer
and here https://www.adobe.io/photoshop/uxp/ps_reference/
I tend to build and keep the PS layer stack in sync with my code using:
let layerTree = require('photoshop').app.activeDocument.layerTree
To acess and rename the top most layer, you can do:
layerTree[0].name = "Renamed Layer"
layerTree[1].name
will return the name of the second layer below the top most layer
layerTree[2].name
will return the name of the third layer below the top most layer (if thereās one)
And so onā¦
Note 1: If PS stack changes dynamically / programatically, itās a good idea to keep and eye on the number of layers in the PS stack using:
layerTree.length
If you try to access a layer index that is not in the range of the object, PS will throw an error.
Note 2: layerTree
is an array of the PS layer stack. Which is great because you can parse it and get to know how many layers you have in the stack (object length), and some props too as blendmode, bounds, kind, linkedLayers, locked or not, name, opacity, parent, selected state, visible (boolean), docId, idā¦
Therefore:
layerTree[layerTree.length-1].name
will give you the name of the lowest PS layer.
To access the current layer, Iād use:
var currentDocument = app.activeDocument
var layers = currentDocument.activeLayers;
var currentLayer = layers[0];
Therefore:
currentLayer.name = "New Name";
renames the current selected PS layer
currentLayer.visible
will let you know if current layer is visible or not
currentLayer.visible = false
will mute the current layer
Note 3: To change a layer color, one can get tempted to use:
currentLayer.color = "blue"
Unfortunatelyā¦ this wonāt work.
Therefore, to change the ācurrent-opened-documentā ācurrent-layer-colorā (here blue), you have to use batchPlay as the color prop isnāt implemented in the layer object (yet?):
const batchPlay = require("photoshop").action.batchPlay;
batchPlay(
[{
"_obj": "set",
"_target": [{
"_ref": "layer",
"_enum": "ordinal",
"_value": "targetEnum"
}],
"to": {
"_obj": "layer",
"color": {
"_enum": "color",
"_value": "blue"
}
},
"_isCommand": true
}], {});
That for sure hasnāt got an impact on the current layer visibility.
Note 4:
You can replace āblueā by any other string that PS accepts.
Hereās the exhaustive list for color codes:
- ānoneā, āredā, āorangeā, āyellowColorā, āgrainā, āblueā, āvioletā or āgrayā
āyellowColorā is for Yellow, and āgrainā is for Green. Thereās no typo here thatās just how PS internally works in the case of some PS strings.
Hope this makes sense
After a bit of playing even just with the Console in UXP dev tools you will get more comfy as in what does what and how
I really appreciate your thorough response, but I didnāt find anything useful to me for solving the problem. All your notes are exactly what I do on my plugin, but that just doesnāt work.
Also this
is not true. Youāre getting here only first active layer and not all active layers.
Yes, Iām not completely new to JS, but Ps UXP is still full of bugs and thereāre no good solutions to avoid them. Like, if you have multiple active layers and loop through them to set color, Ps makes layer visible if it wasnāt. And there are plenty more such nuances
Indeed, in the case of multiple selected layers, youād get the lower āz-indexā PS stack layer.
If you parse the layer
object and check recursively for its props (selected and visibility), does changing colors breaks as well?
OK I think I managed to make my plugin work, but quite a huge part of script is just workarounds for visible/active issueā¦ My plugin heavily relies on layers activity and a bit on visibility, so almost every single action is wrapped into something like this:
Set needed layers active/visible states to var
v
Do some action on each layer
v
Restore active/visible states for each layer
I couldnāt find any other solution 3 months ago. And couldnāt find one now.
And itās not really possible to have this logic for each layer, because if you have multiple active layers, results of layers visibility are completely unpredictable for other active layers. Thatās why you have to do 3 loops instead of just one
It appears renaming a groupLayer or setting a color also expands it.
Whatās the deal with setting layer one or another property, messing up a bunch of other properties?
Neither renaming via batchPlay, nor renaming via the DOM classes expands the group for me:
Also, group expansion in the layer panel is no scriptable property after all, so you canāt programmatically change it. Maybe something in your code is causing the Interface to expand it as a side effect.
Maybe. Iām already pulling my hair out. It should be so simple, but Iām slowly getting furiousā¦
At least there was a way to add breakpoints and debug normally. Or is there?
You can debug & set breakpoints in the DevTools, canāt you?
I just never use it because I donāt know how well it harmonizes with bundlers like webpack etc.
Console.log all the way
I give upā¦ It either passes Adobeās review or Iām done with UXP until itās fixedā¦
Manipulating layers ends up in completely unpredictable and random behavior.
Thanks for the tip I can add breakpoints in Dev Dools, so I was going step by stepā¦
Have two active layers. First is invisible.
doc.activeLayers.forEach(layer => {
layersStates.push({
'layer': layer,
'selected': layer.selected,
'visible': layer.visible,
});
layer.selected = false;
});
After the second layer.selected = false;
, first one becomes visible. And this is completely random and happens not every time. Sometimes (as already mentioned countless times) changing layer name makes it visible, but again - not always. I have no idea how it was tested at Adobe when such simple and core features just simply doesnāt work. Or to be more clear - they work, but with lots of side effectsā¦
photoshop.app.activeDocument.activeLayers.forEach(layer => {
layer.selected = false;
});
works fine for me without the side-effects described by you
As you said - maybe something else has some influence, but thatās what I get debugging step by step to see exactly where things go wrong. And findings doesnāt make any sense.
Hello, I might be onto something with this. Iāve pushed my insvestigations a bit further and tried your code. Hereās what I found out. Itās true that itās confusing. And weird. Sharing this to see if it can be reproduced with these simple steps.
Kindly follow the steps as close as possible and in that particular order to trig the ābugā
-
In PS, create a blank doc, with a BG layer. Add two more blank layers.
-
in UXP Dev Tools Console, type this:
var currentDocument = app.activeDocument;
var layers = currentDocument.activeLayers;
var allLayers = currentDocument.layers; -
In PS Layer stack, MUTE All Layers
-
Select the Background Layer. We are going to rename the middle layer.
-
in the Console, type
allLayers[1].name = "L1"
-
Note the result in PS: BG and layer above wetre muted, and after rename, they become both un-muted! ~ Weird! Bug?
-
go back to step 3. In PS, mute all layers
-
select the layer that has been renamed at step 5, so now itās āL1ā
-
in the Console, type
allLayers[1].name = "LA"
-
note the result in PS: āLAā is un-muted! ~ Weird! Bug?
-
go to PS and mute layer āLAā
-
in the Console, type
allLayers[1].name = "LB"
-
notice the layer has been renamed āLBā, but itās visibility stay muted - thatās OK
-
go to PS and un-mute layer āLBā
-
in the Console, type
allLayers[1].name = "LC"
-
notice the layer has been renamed āLCā, but itās visibility stayed un-muted - thatās OK
We could carry on like that for a while, but this is the thing : it seems that the ābugā shows up when the command to rename a layer occurs while the layer that is to be renamed is not selected. And if you have namy layers selectedā¦ itāsā¦ hell indeed. More parsing ahead.
Itās interesting to note as well that using batchPlay to rename a layer will work just fine, because by nature it needs either the layer id
to target, or a layer name, or will target the current selected layer; therefore no impact on layer visibility (in this case) should appear whislt using batchPlay, again in this particlar case.
It would be interesting to know if someone would have the same results as in steps 6 and 10. Forgive me if thatās more confusing in a way, but these steps are the ones Iāve cornered so far. Hopefully itās replicable
I believe the issue to be the selection event itself. In order to do a rename or change the color, Ps needs to select the layer that youāre targeting, apply the change, and then reset the original selection. Turns out thereās a flag on the select
command called makeVisible
. If itās not specified, the default action is toā¦ select the layer and make it visible.
As far as I can tell, the JS DOM API isnāt taking this into account, and so changing a layerās name
can affect layer visibility when it tries to put things back to how they were before the call. If the layer that is to be changed was already selected, then thereās no need to change the selection in the first place, and so the issue never occurs.
Until the bug itself is fixed, as a workaround, you can use batchPlay
with makeVisible
(or you will have the exact same issue).
Iāve been able to change layer names & colors without impacting visibility or the current selection using the following code. Feel free to use it as you like. Hopefully it helps.
async function layerMagic() {
const app = require("photoshop").app
// get a layer's ID
const toLayerId = (layer => layer._id);
// get all layers in the current document
const allLayerIds = app.activeDocument.layers.map(toLayerId);
// actions required to select one or more layers. Note makeVisible here defaults to "false"
// so that the selection doesn't impact layer visibility! If this were omitted from the
// actions, changing the selection would also change the selected layers' visibility.
// Also note that to select multiple layers, one must use "addToSelection" for further
// selections, otherwise you'd only end up with the last layer as the selected layer.
const selectLayers = (ids, makeVisible = false) => ([
{_obj: "select", _target: [{ _ref: "layer", _id: ids[0]}], makeVisible},
...ids.slice(1).map(id => ({_obj: "select", _target: [{ _ref: "layer", _id: id}], makeVisible, selectionModifier: {_enum: "selectionModifierType", _value: "addToSelection"}}))
]);
// Sets all selected layer colors to the specified color value. Doesn't bother to validate
// that your color is valid.
const setSelectedLayerColor = color => [{_obj:"set", _target:[{_ref:"layer", _enum:"ordinal", _value:"targetEnum"}],to:{_obj:"layer",color:{_enum:"color", _value: color}}}];
// Sets the first selected layer's name to the specified value. If multiple layers are selected,
// then only the first layer in the selection is modified.
const setSelectedLayerName = name => [{_obj:"set", _target:[{_ref:"layer", _enum:"ordinal", _value:"targetEnum"}],to:{_obj:"layer",name}}];
const colorChoices = ["red", "green", "yellowColor", "blue", "violet", "gray", "orange"];
// create a list of random colors so that it's obvious the UI what's happening
const colors = allLayerIds.map(() => colorChoices[Math.floor(Math.random()*colorChoices.length)]);
// save the currently selected layer IDs. We need this to _restore_ the user's selection
// after we change all the layer colors and names.
const allSelectedLayerIds = app.activeDocument.activeLayers.map(toLayerId);
const actions = [
// for every layer in the document...
...allLayerIds.map((id, idx) => [
// select it...
...selectLayers([id]),
// set its color to the random value we've determined earlier
...setSelectedLayerColor(colors[idx]),
// change the name to include the id and a random number
...setSelectedLayerName(`Layer ID: ${id} - ${Math.floor(Math.random()*1000)}`)
]).flat(),
// and when done, re-select the originally selected layers
...selectLayers(allSelectedLayerIds)
];
// ... and go!
await app.batchPlay(actions, {modalBehavior: "wait"});
}
layerMagic();
Yep, happens exactly as you described each step. And as I mentioned itās not just rename, I had same experience while setting the color via batchPlay too
Looks like your script, @kerrishotts, does work fine. Iāll try to implement it later. Still not fully understand how it works Will take a closer look in the morning. Thank you
Great use of the āspread operatorā
LayerMagic
works as expected
So Iāve split up the layerMagic()
apart to console log what I get where. Please correct me if Iām wrong. All the magic happens just because of makeVisible: false
? I mean if it wasnāt there, then weād experience same weird behavior with layers visibility on renaming or changing color, right? If so, then I think I had it done correctly at some point in my plugin, but had no clue about makeVisible: false
and I thought I was going the wrong way again