Copy link to clipboard
Copied
Hi,
I've tried to work a little with photoshop scripting, and I tried to make a thing that removes groups if it fits some criteria.
The problem I am facing is - it's VERY VERY slow. Let's say we have a file that contains 10 groups, each group contains 10 sub-groups, and each of these groups contains 10 layers.
I made a recursion that checks if the first child layer is a group, if it is a group, it moves in (recursion), if it's not, then skip. At the end, remove group. (This is simplified, but it is basically the thing).
From my previous programming experience, I'd say this should run under a second without any issues. But what I've found out is, that it takes several seconds to remove one group.
This is just straight up unacceptable. Can anyone point me in the right direction on how to solve this? Or explain to me, why is such a popular app so so so badly made? (In this specific case)
These results show, that any photoshop call takes quite a long time. Any suggestions on how to improve this? I'd need it to improve dramatically in speed (at least 10x).
Second issue:
I've now noticed a second problem - the first group is marked visible immediatelly, so it doens't get removed. Any idea why this happens?
Intel core i7-7700, 32 GB RAM, Adobe Photoshop 2021
Recreate demo:
Photoshop file:
create new, printing, A4, remove layer, add empty layer, dulicate so you have 10 empty layers. Now group these 10 layers into one group, and duplicate 9 more times. Group these groups together and duplicate 9 more times.
So there are 10 top groups, each contains 10 sub groups, each sub group contains 10 empty groups.
My code:
var start_time = new Date().getTime();
var doc = activeDocument;
var objectString;
var textFile = File(doc.path + '/time.txt');
textFile.encoding = 'UTF-8';
textFile.open('a');
textFile.write("Initialization: ", new Date().getTime() - start_time, "\n");
loopTopGroups();
textFile.write("Total time: ", new Date().getTime() - start_time, "\n");
function loopTopGroups() {
var time_before = new Date().getTime();
var len = doc.layers.length;
var layers = doc.layers;
textFile.write("Get layers: ", new Date().getTime() - time_before, "\n");
for (var i = len - 1; i >= 0; i--) {
var currentLayer = layers[i];
if (!currentLayer.visible) {
loopGroups(currentLayer);
}
if (currentLayer.layers.length == 0) {
var time_before = new Date().getTime();
currentLayer.remove();
textFile.write("Out remove: ", new Date().getTime() - time_before, "\n");
}
}
}
function loopGroups(parentLayer) {
var time_before = new Date().getTime();
var len = parentLayer.layers.length;
var layers = parentLayer.layers;
textFile.write("Get inner layers: ", new Date().getTime() - time_before, "\n");
for (var i = len - 1; i >= 0; i--) {
var currentLayer = layers[i];
if (currentLayer.typename == 'LayerSet') {
if (currentLayer.layers[0].typename == 'LayerSet') {
loopGroups(currentLayer);
} else {
var time_before = new Date().getTime();
currentLayer.remove();
textFile.write("Remove: ", new Date().getTime() - time_before, "\n");
}
}
}
}
Output:
my final target was to have a script that removes not needed groups and ignores entire group if it's visible on top level (no recursion dive into these groups).
how to determine if the group is not needed - if it's first sub-layer is a layer and not a group, and the name of this group is not in a .txt file, remove.
By @Samuel_PFD
UPD 1*
#target photoshop
$.hiresTimer
var lr = new AM(),
namesOfGroupsFromTxtFile = ['test name 1', 'test name 2']
var lrs = getLayersCollection(),
exceptionN
...
After hours of pain and suffering, I made my own solution.
@jazz-y I really appreciate you helping (and your older posts where you explained some basics and how to start), without you, I wouldn't have been able to do anything.
I would have straight up used your solution, but it had some errors, and at that time, I had no knowledge of this magic API, so I wasn't capable of fixing it -> so I learned the basics and adapted them in my solution.
Again, huge thanks to you.
loopGroups(loadObjectsFile(
...
After some talk with my team, the target changed a little, to make it easier to work with.
Right now what the script is supposed to do - ignore groups that are visible on top level.
For the rest of the groups, find those that are in the objects.txt file. Remove everything else (except parent groups of groups to keep). It also deletes the top groups first to make it faster.
Huge thanks to this community, I wouldn't have been able to make it at all without you.
removeGroups(loadObjectsFile());
//
...
Copy link to clipboard
Copied
Regarding the delete all at once
I tried to do it in a for loop, but it didn't really work - said delete is not available at the moment.
function removeLayerByIDs(IDs) {
ref = new ActionReference();
for (ID in IDs) {
ref.putIdentifier(s2t('layer'), ID);
}
var d = new ActionDescriptor();
d.putReference(s2t('null'), ref);
executeAction(s2t('delete'), d);
}
Copy link to clipboard
Copied
I have not checked, but it seems the order is important here. if the parent group identifier is the first in the list, then it will be removed first. If the next on the list is the child group ID (which has already been deleted), then there is likely to be an error. That is why I decided not to bother in my script and first selected all the necessary layers, and then deleted them in one operation.
Copy link to clipboard
Copied
that could be possibly the issue here, I'll check right away.
I've added a check - if parent is not to be removed, only then add the ID.
This did not resolve the issue, and it still ended with the same error. (yes, I've tested if the exclude works properly, it does.)
if (!isParentInList(ID, toRemove)) {
toRemove.push(ID);
}
function isParentInList(ID, list) {
var parentID = getLayerPropertyByID(ID, 'parentLayerID');
if (arrayContains(list, parentID)) {
return true;
}
while (parentID != -1) {
ID = getLayerPropertyByID(ID, 'parentLayerID');
parentID = getLayerPropertyByID(ID, 'parentLayerID');
if (arrayContains(list, parentID)) {
return true;
}
}
return false;
}
Copy link to clipboard
Copied
You're going to have fast script if jazz-y will be willing to implement it to his code for you.
Copy link to clipboard
Copied
it could also be a lot faster if the one delete is faster than loads of smaller ones (which it should be), but for some reason it doesn't really want to work.
It should also be a lot similar to what you wrote @Kukurykus in your reply (the delete).
I'll copy it here for your convenience. Even after making sure, that the IDs array doesn't contain both child and parent objects, it still doesn't work.
if (!isParentInList(ID, toRemove)) {
toRemove.push(ID);
}
function removeLayerByIDs(IDs) {
ref = new ActionReference();
for (ID in IDs) {
ref.putIdentifier(s2t('layer'), ID);
}
var d = new ActionDescriptor();
d.putReference(s2t('null'), ref);
executeAction(s2t('delete'), d);
}
function isParentInList(ID, list) {
var parentID = getLayerPropertyByID(ID, 'parentLayerID');
if (arrayContains(list, parentID)) {
return true;
}
while (parentID != -1) {
ID = getLayerPropertyByID(ID, 'parentLayerID');
parentID = getLayerPropertyByID(ID, 'parentLayerID');
if (arrayContains(list, parentID)) {
return true;
}
}
return false;
}
Copy link to clipboard
Copied
Post the complete code again.
Have you removed the removal of layers from the loop? Is it possible that your script in each iteration tries to delete layers according to the same list of IDs?
Copy link to clipboard
Copied
@Kukurykus i still do not fully understand the task 🙂 Yes, in my script it is easy to replace selection + deletion with simple deletion, but my script does not produce the result that @Samuel_PFD needs
UPD: yes, my code becomes almost a third faster if I remove the layer selection operation and pass the list of IDs to the delete function (fortunately, it was written with this in mind).
function selectAndDelete(lr, list) {
if (list.length) {
// lr.deselect()
// lr.selectLayers(list)
lr.removeLayer(list)
}
}
Copy link to clipboard
Copied
Here is the latest version (it's the same as the small segments, but full).
So it adds all the IDs if their parent isn't there already, and their parent will get sorted first, and only tries to remove them once at the end.
removeGroups(loadObjectsFile());
// Load objects.txt file
function loadObjectsFile() {
var title = getProperty('document', 'title');
var full_path = String(getProperty('document', 'fileReference')).split('%20').join(' ');
var textFile = File(full_path.replace(title, 'objects.txt'));
textFile.encoding = 'UTF-8';
textFile.open('r');
return textFile.read().toLowerCase();
}
// Return if top group is visible
function isTopVisible(index) {
var parentID = getLayerPropertyByIndex(index, 'parentLayerID');
var ID = getLayerPropertyByIndex(index, 'layerID');
while (parentID != -1) {
ID = getLayerPropertyByID(ID, 'parentLayerID');
parentID = getLayerPropertyByID(ID, 'parentLayerID');
}
return getLayerPropertyByID(ID, 'visible');
}
// Return if any parent is in a list
function isParentInList(ID, list) {
var parentID = getLayerPropertyByID(ID, 'parentLayerID');
if (arrayContains(list, parentID)) {
return true;
}
while (parentID != -1) {
ID = getLayerPropertyByID(ID, 'parentLayerID');
parentID = getLayerPropertyByID(ID, 'parentLayerID');
if (arrayContains(list, parentID)) {
return true;
}
}
return false;
}
function arrayContains(array, element) {
for (var i = 0; i < array.length; i++) {
if (array[i] == element) {
return true;
}
}
return false;
}
function removeGroups(objectString) {
var start = getProperty('document', 'hasBackgroundLayer') ? 0 : 1;
var end = getProperty('document', 'numberOfLayers');
var toRemove = [];
// Loop through all layers
for (var i = end - 1; i >= start; i--) {
var ID = getLayerPropertyByIndex(i, 'layerID');
var section = getLayerPropertyByIndex(i, 'layerSection').value;
// It's a group
if (section == 'layerSectionStart') {
if (!isTopVisible(i)) {
var name = getLayerPropertyByIndex(i, 'name');
if (objectString.indexOf(String(name).toLowerCase()) == -1) {
if (!isParentInList(ID, toRemove)) {
toRemove.push(ID);
alert(ID);
}
else {
alert("Ignore");
}
}
}
}
}
removeLayerByIDs(toRemove);
}
function s2t(s) { return stringIDToTypeID(s); }
function t2s(t) { return typeIDToStringID(t); }
function getProperty(obj, property) {
var ref = new ActionReference();
ref.putProperty(s2t('property'), s2t(property));
ref.putEnumerated(s2t(obj), s2t('ordinal'), s2t('targetEnum'));
return getDescValue(executeActionGet(ref), s2t(property));
}
function getLayerPropertyByIndex(index, property) {
ref = new ActionReference();
ref.putProperty(s2t('property'), s2t(property))
ref.putIndex(s2t('layer'), index);
var desc = executeActionGet(ref);
return getDescValue(desc, s2t(property));
}
function getLayerPropertyByID(ID, property) {
ref = new ActionReference();
ref.putProperty(s2t('property'), s2t(property))
ref.putIdentifier(s2t('layer'), ID);
var desc = executeActionGet(ref);
return getDescValue(desc, s2t(property));
}
function removeLayerByIDs(IDs) {
ref = new ActionReference();
for (ID in IDs) {
ref.putIdentifier(s2t('layer'), ID);
}
var d = new ActionDescriptor();
d.putReference(s2t('null'), ref);
executeAction(s2t('delete'), d);
}
function getDescValue(descriptor, property) {
switch (descriptor.getType(property)) {
case DescValueType.OBJECTTYPE: return { type: t2s(descriptor.getObjectType(property)), value: descriptor.getObjectValue(property) };
case DescValueType.LISTTYPE: return descriptor.getList(property);
case DescValueType.REFERENCETYPE: return descriptor.getReference(property);
case DescValueType.BOOLEANTYPE: return descriptor.getBoolean(property);
case DescValueType.STRINGTYPE: return descriptor.getString(property);
case DescValueType.INTEGERTYPE: return descriptor.getInteger(property);
case DescValueType.LARGEINTEGERTYPE: return descriptor.getLargeInteger(property);
case DescValueType.DOUBLETYPE: return descriptor.getDouble(property);
case DescValueType.ALIASTYPE: return descriptor.getPath(property);
case DescValueType.CLASSTYPE: return descriptor.getClass(property);
case DescValueType.UNITDOUBLE: return (descriptor.getUnitDoubleValue(property));
case DescValueType.ENUMERATEDTYPE: return { type: t2s(descriptor.getEnumerationType(property)), value: t2s(descriptor.getEnumerationValue(property)) };
default: return 0;
};
}
Copy link to clipboard
Copied
You are using the "for...in" loop incorrectly (generally not the best choice for arrays). As an argument to the function, you pass not the value of an element of the IDs array, but its index.
function removeLayerByIDs(IDs) {
ref = new ActionReference();
for (ID in IDs) {
ref.putIdentifier(s2t('layer'), IDs[ID]);
}
var d = new ActionDescriptor();
d.putReference(s2t('null'), ref);
executeAction(s2t('delete'), d);
}
Copy link to clipboard
Copied
oh wow ...
that's a really stupid mistake. well, first time working with .js though.
Implemented it properly now, and it works about 3x faster, which is amazing.
Thanks for the help. I'll get back to the script tomorrow, I'll clean it up, add some comments and stuff, and post it as a new answer with a description what it's supposed to do.
Good day to you all
Copy link to clipboard
Copied
So that was the culprID 😉
Copy link to clipboard
Copied
After some talk with my team, the target changed a little, to make it easier to work with.
Right now what the script is supposed to do - ignore groups that are visible on top level.
For the rest of the groups, find those that are in the objects.txt file. Remove everything else (except parent groups of groups to keep). It also deletes the top groups first to make it faster.
Huge thanks to this community, I wouldn't have been able to make it at all without you.
removeGroups(loadObjectsFile());
// Load objects.txt file, to lowercase and some clean-up
function loadObjectsFile() {
var title = getObjectProperty('document', 'title');
var full_path = String(getObjectProperty('document', 'fileReference')).split('%20').join(' ');
var textFile = File(full_path.replace(title, 'objects.txt'));
textFile.encoding = 'UTF-8';
textFile.open('r');
return textFile.read().toLowerCase();
}
// Hide all groups the script will work with
function hideAll(start, end) {
var toDo = [];
for (var i = end - 1; i >= start; i--) {
var section = getLayerPropertyByIndex(i, 'layerSection').value;
// It's a group
if (section == 'layerSectionStart') {
var ID = getLayerPropertyByIndex(i, 'layerID');
// Ignore groups that are visible on top level
if (!isTopVisible(ID)) {
toDo.push(ID);
}
}
}
// Hide all at once
if (toDo.length != 0) {
executeActionOnLayers('hide', toDo);
}
}
// Show all groups that should be kept (but not top level), return top level groups to keep
function showTarget(start, end, objectString) {
var toDo = [];
var topGroups = [];
for (var i = end - 1; i >= start; i--) {
var section = getLayerPropertyByIndex(i, 'layerSection').value;
// It's a group
if (section == 'layerSectionStart') {
var ID = getLayerPropertyByIndex(i, 'layerID');
if (!isTopVisible(ID)) {
var name = getLayerPropertyByIndex(i, 'name');
if (objectString.indexOf(String(name).toLowerCase()) != -1) {
if (!arrayContains(toDo, ID)) {
toDo.push(ID);
}
var parents = getParentGroups(ID);
topGroups.push(parents[parents.length - 1]);
for (j in parents) {
if (!arrayContains(toDo, parents[j])) {
toDo.push(parents[j]);
}
}
}
}
}
}
// Show all at once
if (toDo.length != 0) {
executeActionOnLayers('show', toDo);
}
return topGroups;
}
// Delete all groups that are not visible, keep top level array groups
function deleteRest(start, end, topGroups) {
var toDo = [];
// Loop through all layers
for (var i = end - 1; i >= start; i--) {
var section = getLayerPropertyByIndex(i, 'layerSection').value;
// It's a group
if (section == 'layerSectionStart') {
var ID = getLayerPropertyByIndex(i, 'layerID');
var visible = getLayerPropertyByID(ID, 'visible');
// Only push if a parent group is not in list, and group is hidden
if ((!isTopVisible(ID) || isParentInList(ID, topGroups)) && !visible) {
toDo.push(ID);
}
}
}
// Delete all at once
if (toDo.length != 0) {
executeActionOnLayers('delete', toDo);
}
}
// Remove all groups that are not in the objectString
function removeGroups(objectString) {
var start = getObjectProperty('document', 'hasBackgroundLayer') ? 0 : 1;
var end = getObjectProperty('document', 'numberOfLayers');
hideAll(start, end);
var topGroups = showTarget(start, end, objectString);
deleteRest(start, end, topGroups);
}
// Return if top group is visible
function isTopVisible(ID) {
var parentID = getLayerPropertyByID(ID, 'parentLayerID');
while (parentID != -1) {
ID = getLayerPropertyByID(ID, 'parentLayerID');
parentID = getLayerPropertyByID(ID, 'parentLayerID');
}
return getLayerPropertyByID(ID, 'visible');
}
// Return if any parent is in a list
function isParentInList(ID, list) {
if (list.length == 0) {
return false;
}
var parentID = getLayerPropertyByID(ID, 'parentLayerID');
if (arrayContains(list, parentID)) {
return true;
}
while (parentID != -1) {
ID = getLayerPropertyByID(ID, 'parentLayerID');
parentID = getLayerPropertyByID(ID, 'parentLayerID');
if (arrayContains(list, parentID)) {
return true;
}
}
return false;
}
// Return IDs of all parent groups
function getParentGroups(ID) {
var parentID = getLayerPropertyByID(ID, 'parentLayerID');
var parents = [];
while (parentID != -1) {
parents.push(parentID);
ID = getLayerPropertyByID(ID, 'parentLayerID');
parentID = getLayerPropertyByID(ID, 'parentLayerID');
}
return parents;
}
// Return if array contains an element
function arrayContains(array, element) {
for (var i = 0; i < array.length; i++) {
if (array[i] == element) {
return true;
}
}
return false;
}
function s2t(s) { return stringIDToTypeID(s); }
function t2s(t) { return typeIDToStringID(t); }
function getObjectProperty(obj, property) {
var ref = new ActionReference();
ref.putProperty(s2t('property'), s2t(property));
ref.putEnumerated(s2t(obj), s2t('ordinal'), s2t('targetEnum'));
return getDescValue(executeActionGet(ref), s2t(property));
}
function getLayerPropertyByIndex(index, property) {
ref = new ActionReference();
ref.putProperty(s2t('property'), s2t(property))
ref.putIndex(s2t('layer'), index);
var desc = executeActionGet(ref);
return getDescValue(desc, s2t(property));
}
function getLayerPropertyByID(ID, property) {
ref = new ActionReference();
ref.putProperty(s2t('property'), s2t(property))
ref.putIdentifier(s2t('layer'), ID);
var desc = executeActionGet(ref);
return getDescValue(desc, s2t(property));
}
function executeActionOnLayers(action, IDs) {
ref = new ActionReference();
for (i in IDs) {
ref.putIdentifier(s2t('layer'), IDs[i]);
}
var d = new ActionDescriptor();
d.putReference(s2t('null'), ref);
executeAction(s2t(action), d);
}
function getDescValue(descriptor, property) {
switch (descriptor.getType(property)) {
case DescValueType.OBJECTTYPE: return { type: t2s(descriptor.getObjectType(property)), value: descriptor.getObjectValue(property) };
case DescValueType.LISTTYPE: return descriptor.getList(property);
case DescValueType.REFERENCETYPE: return descriptor.getReference(property);
case DescValueType.BOOLEANTYPE: return descriptor.getBoolean(property);
case DescValueType.STRINGTYPE: return descriptor.getString(property);
case DescValueType.INTEGERTYPE: return descriptor.getInteger(property);
case DescValueType.LARGEINTEGERTYPE: return descriptor.getLargeInteger(property);
case DescValueType.DOUBLETYPE: return descriptor.getDouble(property);
case DescValueType.ALIASTYPE: return descriptor.getPath(property);
case DescValueType.CLASSTYPE: return descriptor.getClass(property);
case DescValueType.UNITDOUBLE: return (descriptor.getUnitDoubleValue(property));
case DescValueType.ENUMERATEDTYPE: return { type: t2s(descriptor.getEnumerationType(property)), value: t2s(descriptor.getEnumerationValue(property)) };
default: return 0;
};
}
Copy link to clipboard
Copied
It's appropriate to mark correct answer of user (jazz-y) that helped you due to your original request, than the solution to somehow changed problem, but still with code based on originally provided.
Copy link to clipboard
Copied
That's a good point, however that code wasn't working for me due to something.
If it's fixed, I will mark that instead.
Copy link to clipboard
Copied
If you mean the code from Aug 12, 2021 there's no your comment to where you say what that 'something' is, to let correct it.
Copy link to clipboard
Copied
testing it now, it didn't show the initial error I got, I think that was due to me making it into a .jsx instead of .js (which I did the first time).
Now it removes everything, I tried to check if it's case sensitive, but it removed everything in both cases, so I'm not sure what the mistake/error is now.
It also producec the couldn't execute delete at the end (after removing pretty much everything), again not sure what the issue is there.
Copy link to clipboard
Copied
You asked why the script you use is slow and how to accelearate its work. Later you additionally mentioned of names of groups in text file, but did not posted its structure.
jazz-y explained why it's slow, said how to speed it up and made correct code due to your description (10 groups conatining 10 subgroups with 10 layers each). As a bonus he created array of names that imitiated .txt file. The script process skipped visible groups and removed all invisible ones unless the name of their subfolders were part of array (then it deleted only other subgroups within their top groups).
I tested it originally and now again and it works as should, so that's sure only his answer should be marked as correct solution.
Copy link to clipboard
Copied
It really makes no difference to me which answer will be marked as correct - the most important thing is that the @Samuel_PFD understood why his code was slow and figured out with our help how to make it faster.
My code works well for a test case. Its code copes with the task in a real environment. Both answers are correct.
Copy link to clipboard
Copied
Personally no matter indeed, but not for the forum order 😉
Copy link to clipboard
Copied
Why is java script files are slow, I think may be some issue.
I found the answar but not understand :- .jsx script extremely slow
There, DOM is slower than AM. I can't indentify which code is DOM or AM
Could you give the example of DOM or AM For Understand. I have searched on google ,but didnot get the answar.
Copy link to clipboard
Copied
Copy link to clipboard
Copied
There, I can't determine which code is AM Code, Could you give example of AM Code For Understanding
I can't identify to AM Code or DOM Code
Copy link to clipboard
Copied
Photoshop CS6 scripting guide pages 73 - 81
get active layer id in with DOM:
var id = activeDocument.activeLayer.id
get active layer id in with AM:
(r = new ActionReference()).putProperty(stringIDToTypeID('property'), stringIDToTypeID('layerID'));
r.putEnumerated(stringIDToTypeID('layer'), stringIDToTypeID('ordinal'), stringIDToTypeID('targetEnum'));
var id = executeActionGet(r).getInteger(stringIDToTypeID('layerID'));
Copy link to clipboard
Copied
Thanku so much I am happy for your answar
My Doubt is clear now. why is DOM slow in PS Script
Copy link to clipboard
Copied
In the Contents look for ActionDescriptor, ActionList, ActionReference and the Application methods: charIdToTypeId, executeAction, ExecuteActionGet, stringIdToTypeId, typeIdToCharId, typeIdToStringId. They are used in Action Manager.