Search code examples
jsxphotoshop-script

How do I crop and export specific group layers inside other group layers with a known name?


first time posting here! I apologize beforehand in case this post is missing information, or if it's an eyesore!

So, I have a bunch of layers, structured kinda like this as an example:

+ graphics (group, unique)
+ - + Figure_A (group, unique)
+ - + - + Figure_A_arm (group, unique)
+ - + - + - + Figure_A_arm_Variation1 (group, unique)
+ - + - + - + - + frame1_arm (group, not unique)
+ - + - + - + - + - + FigureGraphic (linked layer, not unique, has a mask)
+ - + - + - + - + frame2_arm (group, not unique)
+ - + - + - + - + - + FigureGraphic (linked layer, not unique, has a mask)
+ - + - + - + - + frame2_arm (group, not unique)
+ - + - + - + - + - + FigureGraphic (linked layer, not unique, has a mask)

(unique/not unique means if the name of the layer is unique or not)

I'm trying to get the pixels of the latest in the hierarchy of the groups, with the document cropped before an automated export. The main problem is, there are a lot of functions I can't figure out, even after poking at it for two weeks non stop. I've dug a lot of snippets in the script but it's turning into a mess that doesn't work. I am including a pseudo-code with what I'm trying to do. Ideally I'd write the functions written in there, but after googling for two weeks, I can't get a lot of them clear. A lot of functions in Photoshop scripting seem quite obscure and I can't figure them out at all...

I've been writing functions by taking code snippets from these places:

Export to PNG: Photoshop CS6 Export Nested Layers to PNG?

Select all child layers: http://www.joonas.me/posts/2018/01/30/select-child-layers-photoshop-script/

Get selected layer IDs: https://forums.adobe.com/thread/2256708

Select layer by ID: https://forums.adobe.com/thread/2140075

I've been trying but I am finding it difficult to implement them to do what I need, and sometimes I get errors or variables become 'undefined', triggering an error.

// Here's some 'pseudocode' with how I'm trying to make it work
cropAndSave("Figure_A_arm_Variation1");
cropAndSave("Figure_A_arm_Variation2");
cropAndSave("Figure_A_arm_Variation3");
cropAndSave("Figure_A_leg_Variation1");
cropAndSave("Figure_A_leg_Variation2");
cropAndSave("Figure_A_leg_Variation3");
cropAndSave("Figure_B_arm_Variation1");
cropAndSave("Figure_B_arm_Variation2");
cropAndSave("Figure_B_arm_Variation3");
//etc

function cropAndSave(nameOfGroup)
{
    //nameOfGroup is the group layer we are looking for

    //Duplicate the document so we don't mess with the original
    var workingDoc = app.activeDocument.duplicate();

    // Path we want to export to
    var path="C:/test/"+nameOfGroup+"/";

    // Look up for this group layer in the document
    setActiveLayerSet(nameOfGroup);

    // Make a list (array?) with the ids of each layer set/group inside nameOfGroup
    // This shouldn't include anything inside each group, only the direct groups inside nameOfGroup
    var listOfLayers = getListOfChildrenObjects();

    // Loop the process for each sub-object
    for( var i = 0; i < listOfLayers.length ; i++ )
    {
        var name = getLayerNameById(listOfLayers.i); //I don't know if this is the correct way to ask for the i element of a list
        var bounds = []; //We'll be using this for the cropping
        if(groupName=="Figure_A_arm_Variation1") //Checking which set we're doing
        {
            if(name=="frame1_arm") bounds = [1px, 2px, 3px, 4px]; //just a test value
            else if(name=="frame2_arm") bounds = [5px, 6px, 7px, 8px]; //just a test value
            else if(name=="frame3_arm") bounds = [9px, 10px, 11px, 12px]; //just a test value
            else if(name=="frame1_leg") bounds = [13px, 14px, 15px, 16px]; //just a test value
            // and so on
        }
        else if(groupName=="Figure_B_arm_Variation2") //one of the many possible groups
        {
            if(name=="frame1_arm") bounds = [17px, 18px, 19px, 20px];
            //etc
        }
        // Duplicate this since we will be doing more drastic changes
        var secondaryDoc = workingDoc.duplicate();

        // Select the layer we're working with
        selectLayerByID(listOfLayers.i);

        // Do the cropping
        if(bounds.length > 2) secondaryDoc.crop(bounds); //Each bounds array has 4 elements, check if that's the case to make sure we haven't input any weird values, then crop
        else alert('No bounds set up for layer '+name+' in group '+groupName); //in this case it should be cancelled but I'm not sure how to stop the script from executing

        // Condense the group 'name', which is the one we're working with, into a single layer for easier management
        condenseGroup();

        // Remove all the layers that aren't the one we're working with
        deleteAllLayersAndGroupsExceptSelected();

        // Save as PNG
        var pngFile = new File(path+name);
        pngSaveOptions = new PNGSaveOptions();
        pngSaveOptions.embedColorProfile = true;
        pngSaveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
        pngSaveOptions.matte = MatteType.NONE; pngSaveOptions.quality = 1;
        activeDocument.saveAs(pngFile, pngSaveOptions, false, Extension.LOWERCASE);

        // Close this duplicate without saving the PSD
        secondaryDoc.close(SaveOptions.DONOTSAVECHANGES);
    }
    workingDoc.close(SaveOptions.DONOTSAVECHANGES); 
}

So what I'm trying to get out of it is, while having a specific group structure, make it select a group within the hierarchy, and save the contents of each sub-group as PNG after cropping the document to a set of dimensions, but I'm having difficulties finding the correct way and functions to do it.

[Edit] Requested images are posted here:

This is the Layer structure. Each "Figure_X_bodypart_VariationY" has a set of sub-groups in it. I want to process each of these subgroups by cropping the document to specific bounds that are coded into the script for each one of them.

I have the PSD structured like this, the open folder has a mask, (which is the current selection to show which pixels belong to that layer). I want the script to select a rectangle specified for that one layer, like this.

After that, I want the script to duplicate the document (so it doesn't mess with the original), then crop using the bounds I've given in the script for that group. After that, the script should remove all the other groups so only this one remains, and save it as PNG, then close the duplicated document, and proceed to the next sub-object.

After all sub-objects are done, continue with each "Figure_X_bodypart_VariationY" type groups


Solution

  • Input:

    enter image description here

    Output:

    enter image description here

    var exportPath = "/C/test-export",
        bounds = {
            child1: [
                44,
                92,
                338,
                363
            ],
            child2: [
                263,
                62,
                536,
                315
            ],
            child3: [
                160,
                269,
                458,
                497
            ]
        };
    
    cropAndExport("figureA");
    
    function cropAndExport(groupName)
    {
        var doc = activeDocument;
    
        var masterClone = cloneDoc(); // master clone of the document
    
        masterClone.activeLayer = masterClone.layers.getByName(groupName); // selecting a group by name
        var groupChildren = masterClone.activeLayer.layers; // getting children of the group
    
        for (var i = 0; i < groupChildren.length; i++)
        {
            masterClone.activeLayer = groupChildren[i];
            var clone = cloneDoc();
            mergeDown(); // merging the group
            toggleVisibility(); //hiding all the other groupChildren
            rectSelection(bounds[groupChildren[i].name]); //you can use ifs to load bounds you want here, like if (groupChildren[i].name == 'child1') rectSelection(boundChild1);
            cropToSelection()
            savePng24(groupName, groupChildren[i].name, exportPath)
            clone.close(SaveOptions.DONOTSAVECHANGES);
        };
    
        masterClone.close(SaveOptions.DONOTSAVECHANGES);
    
        /////////////////////////////////////////////////////////////////////////////////////
        // functions
        /////////////////////////////////////////////////////////////////////////////////////
    
        function savePng24(nameX, nameY, exportFolder)
        {
            var pngOpts = new ExportOptionsSaveForWeb;
            pngOpts.format = SaveDocumentType.PNG
            pngOpts.PNG8 = false;
            pngOpts.transparency = true;
            activeDocument.exportDocument(new File(exportFolder + "/" + nameX + "_" + nameY + ".png"), ExportType.SAVEFORWEB, pngOpts);
        }; // end of savePng24()
    
        function cloneDoc()
        {
            var desc2 = new ActionDescriptor();
            var ref1 = new ActionReference();
            ref1.putEnumerated(cTID('Dcmn'), cTID('Ordn'), cTID('Frst'));
            desc2.putReference(cTID('null'), ref1);
            desc2.putString(cTID('Nm  '), 'clone');
            executeAction(cTID('Dplc'), desc2, DialogModes.NO);
            return activeDocument;
        }; // end of cloneDoc()
    
        function mergeDown()
        {
            var desc11 = new ActionDescriptor();
            executeAction(cTID('Mrg2'), desc11, DialogModes.NO);
        }; // end of mergeDown()
    
        function toggleVisibility()
        {
            var desc102 = new ActionDescriptor();
            var list5 = new ActionList();
            var ref18 = new ActionReference();
            ref18.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
            list5.putReference(ref18);
            desc102.putList(cTID('null'), list5);
            desc102.putBoolean(cTID('TglO'), true);
            executeAction(cTID('Shw '), desc102, DialogModes.NO);
        }; // end of toggleVisibility()
    
        function rectSelection(data)
        {
            var desc25 = new ActionDescriptor();
            var ref1 = new ActionReference();
            ref1.putProperty(cTID('Chnl'), cTID('fsel'));
            desc25.putReference(cTID('null'), ref1);
            var desc26 = new ActionDescriptor();
            desc26.putUnitDouble(cTID('Left'), cTID('#Pxl'), data[0]);
            desc26.putUnitDouble(cTID('Top '), cTID('#Pxl'), data[1]);
            desc26.putUnitDouble(cTID('Rght'), cTID('#Pxl'), data[2]);
            desc26.putUnitDouble(cTID('Btom'), cTID('#Pxl'), data[3]);
            desc25.putObject(cTID('T   '), cTID('Rctn'), desc26);
            executeAction(cTID('setd'), desc25, DialogModes.NO);
        }; // end of rectSelection()
    
        function cropToSelection()
        {
            var desc33 = new ActionDescriptor();
            desc33.putBoolean(cTID('Dlt '), true);
            executeAction(cTID('Crop'), desc33, DialogModes.NO);
        }; // end of cropToSelection()
    
    
        function cTID(s)
        {
            return app.charIDToTypeID(s);
        };
    
        function sTID(s)
        {
            return app.stringIDToTypeID(s);
        };
    
    
    }