Copy link to clipboard
Copied
How to select a specified layer with a script? For the same effect as clicking with the mouse, this layer must be highlighted.
I have tried the following methods, which are not always feasible:
app.activeDocument.activeLayer=app.activeDocument.layers[0]
app.activeDocument.layers[0].hasSelectedArtwork=true
When running the script, if a layer (highlighted) has been selected, they are feasible, but when running the script, if the specified layer had selected but not highlighted ( the mouse focus is not on the layer panel ), they are not feasible (or they have run correctly and selected the specified layer, but the layer is not highlighted).
So, the focus of my question is how to highlight select layers, for the same effect as clicking with the mouse. I do this mainly to run the "Duplicate Layer" command in the layer panel menu. If the layer is not highlighted, the "Duplicate Layer" command is not available even though the layer is already selected.
Looking forward to your response and many Thx for help and ideas.
The highlighting of a layer is a big problem because with ExtendScript there's no way to do so. However, what you can do is cause a condition which brings this about. Some sitations could be, adding a new layer for example. Its' behavior is to be automatically highlighted. Another nuance of this behavior is that if a layer happened to be deleted, the layer below it becomes highlighted. So we could apparently use enough redraws during a script which adds and removes a new layer, to cause the high
...Oh, this just IN! A brand-new technique which uses more actions to highlight any layer.
What it does is put a temporary path item to your desired layer and selects that item, then runs an action which uses "Locate Object" from the layers' panel to bring a highlighted selection to that item. Apparently the behavior of Illustrator is to automatically highlight the parent layer of an object that has recently exited the Isolation Mode!
This actually works really conveniently for this situation, so m
...Copy link to clipboard
Copied
The highlighting of a layer is a big problem because with ExtendScript there's no way to do so. However, what you can do is cause a condition which brings this about. Some sitations could be, adding a new layer for example. Its' behavior is to be automatically highlighted. Another nuance of this behavior is that if a layer happened to be deleted, the layer below it becomes highlighted. So we could apparently use enough redraws during a script which adds and removes a new layer, to cause the highlighting to both appear and stay. However there is a little issue with not being able to highlight a layer whose index is not 0 in the case of there not being a highlight on any layer in the first place, unless beforehand one ensures to highlight the layer at the very top and then do so to their intended layer.
This is an experimentation method which highlights my layers according to the code, let me know if it ends up working for you.
/**
* 0-based index Highlights a layer by adding and removing a temporary layer.
* @Param {number} layerIndex
*/
function highlightLayerAtIndex (layerIndex) {
var doc = app.activeDocument;
if (layerIndex >= doc.layers.length) {
throw new Error("The layer index (" + layerIndex + ") is out of bounds (0-" + (doc.layers.length - 1) + ")");
}
var indexLayer = doc.layers[layerIndex];
var tempLayer = doc.layers.add();
app.redraw();
tempLayer.move(indexLayer, ElementPlacement.PLACEBEFORE);
app.redraw();
tempLayer.remove();
app.redraw();
}
// Ensure a highlight for any scenario.
highlightLayerAtIndex(0);
// Highlights the 4th layer.
highlightLayerAtIndex(3);
This is de facto really undo-heavy, as in how the app.redraw() causes an undo point each time it is used.
Nevertheless, when used along with an action which duplicates a layer, this will create the duplication per Illustrator rules and appends " copy" after the name. It then also removes the highlighting. -- not a problem for us, as the same highlight method can surely rehighlight it.
Copy link to clipboard
Copied
Thank you @Silly-V for helping me out on this. This is worked within the scope of doc.layers.
I tried to highlight a sub layer (sub layer under layer 0 ) with the following code, but it didn't work! How can it be changed to highlight a sub layer?
function highlightLayerAtIndex (layerIndex) {
var doc = app.activeDocument;
if (layerIndex >= doc.layers[0].layers.length) {
throw new Error("The layer index (" + layerIndex + ") is out of bounds (0-" + (doc.layers[0].layers.length - 1) + ")");
}
var indexLayer = doc.layers[0].layers[layerIndex];
var tempLayer = doc.layers[0].layers.add();
app.redraw();
tempLayer.move(indexLayer, ElementPlacement.PLACEBEFORE);
app.redraw();
tempLayer.remove();
app.redraw();
}
highlightLayerAtIndex(0); // This also highlights the first layer in the document(doc.layers[0]), Not doc.layers[0].layers[0]
Many Thx for help and ideas.
Copy link to clipboard
Copied
Yea, this is where I think the magic sort of ... stops. The issue is of course that the automatic behavior of Illustrator has stopped accommodating ourthese-here work-arounds.
Well, here's what I think: if the objective is to trigger some Illustrator action which specifically works on layers in a highlighted state at least we can move the things from one layer into a top-level layer, then run the procedure to highlight and do whatever-else such as duplicate, then move the new duplicated layer to the new spot.
Maybe this will still be applicable to you?
Copy link to clipboard
Copied
Oh, this just IN! A brand-new technique which uses more actions to highlight any layer.
What it does is put a temporary path item to your desired layer and selects that item, then runs an action which uses "Locate Object" from the layers' panel to bring a highlighted selection to that item. Apparently the behavior of Illustrator is to automatically highlight the parent layer of an object that has recently exited the Isolation Mode!
This actually works really conveniently for this situation, so much so that this method and action is probably usable for all-time.
/**
* Highlights any layer by inserting a temporary item into it and using an action which enters and exists the isolation mode.
* Afterwards, this item is removed.
* @Param {Layer} targetLayer
*/
function highlightLayerInContainer (targetLayer) {
var someTempPath = targetLayer.pathItems.rectangle(0, 0, 200, 200);
someTempPath.name = "TEMP [for highlighting]";
app.activeDocument.selection = null;
someTempPath.selected = true;
app.doScript("target layer", "Test Set 2");
someTempPath.remove();
}
highlightLayerInContainer(app.activeDocument.layers.getByName("Layer 5").layers.getByName("Layer 7"));
// app.doScript("DupeLayer", "Test Set 2"); // * Do custom processing on the layer when it is highlighted.
/version 3
/name [ 10
54657374205365742032
]
/isOpen 1
/actionCount 2
/action-1 {
/name [ 9
447570654c61796572
]
/keyIndex 0
/colorIndex 0
/isOpen 1
/eventCount 1
/event-1 {
/useRulersIn1stQuadrant 0
/internalName (ai_plugin_Layer)
/localizedName [ 5
4c61796572
]
/isOpen 1
/isOn 1
/hasDialog 0
/parameterCount 2
/parameter-1 {
/key 1836411236
/showInPalette -1
/type (integer)
/value 1
}
/parameter-2 {
/key 1851878757
/showInPalette -1
/type (ustring)
/value [ 19
4475706c69636174652053656c656374696f6e
]
}
}
}
/action-2 {
/name [ 12
746172676574206c61796572
]
/keyIndex 0
/colorIndex 0
/isOpen 1
/eventCount 3
/event-1 {
/useRulersIn1stQuadrant 0
/internalName (ai_plugin_Layer)
/localizedName [ 5
4c61796572
]
/isOpen 0
/isOn 1
/hasDialog 0
/parameterCount 2
/parameter-1 {
/key 1836411236
/showInPalette -1
/type (integer)
/value 19
}
/parameter-2 {
/key 1851878757
/showInPalette -1
/type (ustring)
/value [ 13
4c6f63617465204f626a656374
]
}
}
/event-2 {
/useRulersIn1stQuadrant 0
/internalName (ai_plugin_Layer)
/localizedName [ 5
4c61796572
]
/isOpen 0
/isOn 1
/hasDialog 0
/parameterCount 1
/parameter-1 {
/key 1836411236
/showInPalette -1
/type (integer)
/value 24
}
}
/event-3 {
/useRulersIn1stQuadrant 0
/internalName (ai_plugin_Layer)
/localizedName [ 5
4c61796572
]
/isOpen 0
/isOn 1
/hasDialog 0
/parameterCount 1
/parameter-1 {
/key 1836411236
/showInPalette -1
/type (integer)
/value 25
}
}
}
Copy link to clipboard
Copied
Thanks @Silly-V
An error occurred when I ran this script, as shown in the following figure:
Do you perhaps have an idea what I may be overlooking?
Copy link to clipboard
Copied
You did save the action part as a .aia file and loaded it, no?
Make sure to save this part as .aia and load in the actions panel. Then the part of the script which is
app.doScript("target layer", "Test Set 2");
will work. But that action text cannot be part of your javascript.
Copy link to clipboard
Copied
This is more than I could have asked for, thank you so much!! @Silly-V ,
I simply added a loop, it worked like a charm:). You made my day! Thank you for helping me out on this.
for (var i=1;i<199;i++) {
highlightLayerInContainer(app.activeDocument.layers[0].layers[0]); //target layer
app.doScript("DupeLayer", "Test Set 2");
app.activeDocument.layers[0].layers[0].name=i+1 //target layer's name is "1", set dupeLayer name is "2" ......and so on
}
If you have a way to keep the names of all copied sub layers consistent with the source layer, or if you have a way to change the names of all copied sub layers to be consistent with the source layer, instead of having the word "copy", it will solve the problem in my following post, many thanks in advance for your time and advice!
Copy link to clipboard
Copied
It can be a bit of a mess, but at least we are confined to only Layer objects, and a recursive traversal can yield to you such information as nested layer names and indexes.
You can make a 'mirror' object to put instructions about layers into. This object would be a 'node'. A node can have information about it as well as most importantly - other nodes. And those nodes can have their own information and other nodes and so on. Once you have the snapshot of what the original names are you could recursively traverse this tree and assign new names based on that.
Here is some code which has a simple recursive function and traversal example. This example starts out assuming I have "Layer 4" which contains a nested "Layer 5". This Layer 4 is copied, so it gets named "Layer 4 copy" and the nested layer is "Layer 5 copy". Now we can run the code and edit the names however one wishes.
Note: JSDoc is handled badly by these forums, it bakes in some poor soul's user-name when you go @ type or @ param (without the spaces). I wonder if those couple of people get awesome forum emails each time I paste such snippets up!
/**
* @typedef LayerNodeTree
* @property {string} name
* @property {LayerNodeTree[]} layers
*/
/**
* Collects layer information into a node tree.
* @Param {Document | Layer} entryContainer - Where to start the node tree.
*/
function getNestedLayerInfo (entryContainer) {
/** @TyPe {LayerNodeTree} */
var node = {
name : (entryContainer instanceof Document) ? "DOCUMENT" : entryContainer.name,
layers : [],
};
var thisLayer;
for (var i = 0; i < entryContainer.layers.length; i++) {
thisLayer = entryContainer.layers[i];
node.layers.push(getNestedLayerInfo(thisLayer));
}
return node;
}
/**
* Processes a layer node tree with arbitrary processing.
* @Param {LayerNodeTree} nodeTree
* @Param {Layer | Document} newContainer
*/
function processLayerTree (nodeTree, newContainer) {
// alert(nodeTree.name + " : " + newRootContainer.name);
if (newContainer instanceof Layer && nodeTree.name == newContainer.name.replace(/ copy$/, "")) {
newContainer.name = newContainer.name.replace(/ copy$/, " legit!");
}
var thisLayer;
for (var i = 0; i < newContainer.layers.length; i++) {
thisLayer = newContainer.layers[i];
processLayerTree(nodeTree.layers[i], thisLayer);
}
}
var layerInfo = getNestedLayerInfo(app.activeDocument.layers.getByName("Layer 4"));
// alert(layerInfo.toSource());
processLayerTree(layerInfo, app.activeDocument.layers.getByName("Layer 4 copy"));
Copy link to clipboard
Copied
It seems a little difficult to understand, because my knowledge is limited, but anyway, thanks for your time and advice! 👍👍👍 @Silly-V
Copy link to clipboard
Copied
Keep in mind that what makes the recursive methods work is a [data structure] that contains a property which is an array of.. the [data structure].
So a layer will always have the .layers property, and each object in the array-like object that is layer.layers is also a Layer. Therefore a Layer object contains a collection of Layer objects, each one contains its own and so on. You could write numerous loops an keep track of a crazy number of arrays in custom objects, but it will never truly work when you do not know the amount of nested items and they're dynamic. So you never know what you're gonna get and no matter how many loops are written, the users can always have just one more layer in some layer. Therefore, the answer to this conundrum is a function that can run itself.
var node = {
name : (entryContainer instanceof Document) ? "DOCUMENT" : entryContainer.name,
layers : [],
};
This part creates the node object, as you see it has a property that isn't an array and it's symbolic for any node's random properties that one may create as needed - in this case just the layer's name. But the array one is a special property because it will end up containing some nested node objects.
var thisLayer;
for (var i = 0; i < entryContainer.layers.length; i++) {
thisLayer = entryContainer.layers[i];
node.layers.push(getNestedLayerInfo(thisLayer));
}
This part here goes over the Illustrator Layer's .layers collection and uses the same function that we're in right now, to process those nested layers. So our original "var node = ..." object will have a .layers array of our custom node objects. If there were no nested layers in the variable "entryContainer", this same loop still works by iterating 0 times and pushing nothing to the node's .layers array because it doesn't get a chance to run and the recursion stops. However, if there was just one layer, the line node.layers.push(getNestedLayerInfo(thisLayer)) will run, and that one layer actually may have 20 layers in its .layers.
Finally, the node object is returned from the recursive function:
return node;
The logic is actually simple if you think of it in terms of this one function working on just the one layer. An Illustrator layer is passed in, we create an object that features its name and starts an empty array for its layers. Then it goes through any layers and runs itself, but we can stop thinking about what nesting level it's at and just proceed to the return and be out of it mental-wise. The only question is: when this function ran through the layer's array, is it going to process a layer? Yes, of course. And how would it do so? Well, we go to the top and see that a Layer object is our parameter, and yes indeed we are processing a layer.. check. Then we create an object that features its name and starts an emty array for its layers. Does the Layer object that is being worked on have a .layers property that is an array that can be full of other Layer objects only? Check. So is this function going to work on any layer? Yep, it sure will!
Copy link to clipboard
Copied
Thank you for your explanation @Silly-V . Now I seem to have some ideas. I'll still take some time to study...