[Bug] Changing layer name, changes layer to visible

Simple example of code and Console output while looping through two layers:

Setting layer color using batchPlay(), also makes layer visible :frowning:

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 :wink: that’s just how PS internally works in the case of some PS strings.

Hope this makes sense :wink:
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 :wink:

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 :smile:

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 :thinking:

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”

  1. In PS, create a blank doc, with a BG layer. Add two more blank layers.

  2. in UXP Dev Tools Console, type this:
    var currentDocument = app.activeDocument;
    var layers = currentDocument.activeLayers;
    var allLayers = currentDocument.layers;

  3. In PS Layer stack, MUTE All Layers

  4. Select the Background Layer. We are going to rename the middle layer.

  5. in the Console, type allLayers[1].name = "L1"

  6. Note the result in PS: BG and layer above wetre muted, and after rename, they become both un-muted! ~ Weird! Bug?

  7. go back to step 3. In PS, mute all layers

  8. select the layer that has been renamed at step 5, so now it’s “L1”

  9. in the Console, type allLayers[1].name = "LA"

  10. note the result in PS: “LA” is un-muted! ~ Weird! Bug?

  11. go to PS and mute layer “LA”

  12. in the Console, type allLayers[1].name = "LB"

  13. notice the layer has been renamed “LB”, but it’s visibility stay muted - that’s OK

  14. go to PS and un-mute layer “LB”

  15. in the Console, type allLayers[1].name = "LC"

  16. 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 :slight_smile:

1 Like

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();
1 Like

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 :slight_smile: Will take a closer look in the morning. Thank you

Great use of the “spread operator” :+1:
LayerMagic works as expected :slight_smile:

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