• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

script to return or restore rotation of multiple selected objects

Community Beginner ,
Feb 11, 2025 Feb 11, 2025

Copy link to clipboard

Copied

Hello, there will be a script that restores the rotations to zero degrees "0°" of each selected object. I ask Chatgpt but unfortunately he doesn't get it. I have several objects selected with different rotation angles and doing it manually is very laborious.

TOPICS
Draw and design , Experiment , Feature request , Scripting

Views

343

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe
Community Expert ,
Feb 13, 2025 Feb 13, 2025

Copy link to clipboard

Copied

Hi @vectora98504848, to "unrotate" something—back to zero degrees rotation—means you must first derive the item's rotation amount. In Illustrator, page items don't have an absolute rotation amount, however it does (usually!) keep track of rotations in a "BBAccumRotation" tag assigned to the item. The other possibility is if the item is a placed image, or a text frame, then it may also have a matrix, and sometimes it is possible to derive a rotation value from that.

 

I've written a script to derive the rotation, and then unrotate the selected items. Let me know how it goes, but bear in mind that there will be certain objects for which a rotation value cannot be determined in this way. There are other approaches to the problem, but they are not general—we must know more about the type of item.

- Mark

 

 

/**
 * @file Unrotate Selected Items.js
 *
 * Makes an attempt to remove any recorded rotation
 * that can be derived from the selected items.
 * Finds rotation either in the item's BBAccumRotation
 * tag or the item's matrix.
 *
 * @author m1b
 * @version 2025-02-14
 * @discussion https://community.adobe.com/t5/illustrator-discussions/script-to-return-or-restore-rotation-of-multiple-selected-objects/m-p/15146692
 */
(function () {

    var doc = app.activeDocument;

    var items = getItems({
        from: doc.selection,
        getPageItems: true,
        getGroupItems: true,
    });

    if (0 === items.length)
        return alert('Please select some items to rotate and try again.');

    for (var i = 0; i < items.length; i++)
        unrotate(items[i]);

})();

/**
 * Attempt to return a page item to unrotated state.
 * Important: will only work if the item has a valid
 * BBAccumRotation tag or a matrix that is ammenable
 * to derivation of the rotation.
 * @author m1b
 * @version 2025-02-13
 * @param {PageItem} item - the item to unrotate.
 * @return {Number} - the amount of rotation, in degrees.
 */
function unrotate(item) {

    var accumRotation = undefined,
        rotationTag;

    if (item.hasOwnProperty('tags')) {

        // derive rotation from the BBAccumRotation tag
        rotationTag = getThing(item.tags, 'name', 'BBAccumRotation');

        if (rotationTag)
            accumRotation = rotationTag.value * 180 / Math.PI;

    }

    if (
        undefined == accumRotation
        && item.hasOwnProperty('matrix')
    )
        // derive rotation from the matrix
        accumRotation = getRotationFromMatrix(item.matrix);

    if (undefined == accumRotation)
        return;

    // rotate the item
    item.rotate(- accumRotation);

    if (rotationTag)
        // update tag
        rotationTag.value = 0;

    return accumRotation;

};

/**
 * Returns a thing with matching property.
 * If `key` is undefined, evaluate the object itself.
 * @author m1b
 * @version 2024-04-21
 * @param {Array|Collection} things - the things to look through.
 * @param {String} [key] - the property name (default: undefined).
 * @param {*} value - the value to match.
 */
function getThing(things, key, value) {

    for (var i = 0, obj; i < things.length; i++)
        if ((undefined == key ? things[i] : things[i][key]) == value)
            return things[i];

};

/**
 * Returns the rotation amount, in degrees,
 * of the given (not skewed) matrix.
 * @author m1b
 * @version 2023-10-20
 * @param {Matrix} matrix - an Illustrator Matrix.
 * @returns {Number}
 */
function getRotationFromMatrix(matrix) {

    if (!matrix.hasOwnProperty('mValueA'))
        throw new Error('getRotationFromMatrix: bad `matrix` supplied.');

    // scaling factors
    var scaleX = Math.sqrt(matrix.mValueA * matrix.mValueA + matrix.mValueC * matrix.mValueC);
    // scaleY = Math.sqrt(matrix.mValueB * matrix.mValueB + matrix.mValueD * matrix.mValueD);

    // rotation angle
    var radians = Math.acos(matrix.mValueA / scaleX),
        degrees = radians * (180 / Math.PI);

    return Math.round(degrees * 1000) / 1000;

};


/** ------------------------------------------------------------------- *
 *  GET ITEMS                                                           *
 * -------------------------------------------------------------------- *
 * @author m1b                                                          *
 * @version 2024-03-01                                                  *
 * -------------------------------------------------------------------- *
 * Collects page items from a `from` source, eg. a Document, Layer,     *
 * GroupItem, or Array. Will look inside group items up to `maxDepth`.  *
 * Search can be filtered using `filter` function. Note that the        *
 * filter function is evaluated last in the filtering process.          *
 * -------------------------------------------------------------------- *
 * Example 1. Get all items in document:                                *
 *                                                                      *
 *    var myItems = getItems({ from: app.activeDocument });             *
 *                                                                      *
 * -------------------------------------------------------------------- *
 * Example 2. Get all selected items except groups:                     *
 *                                                                      *
 *    var myItems = getItems({                                          *
 *      from: app.activeDocument.selection,                             *
 *      getGroupItems: false,                                           *
 *    });                                                               *
 *                                                                      *
 * -------------------------------------------------------------------- *
 * Example 3. Using `filter` function to choose item type:              *
 *                                                                      *
 *    var myItems = getItems({                                          *
 *      from: app.activeDocument,                                       *
 *      filter: function (item) {                                       *
 *        return (                                                      *
 *          'PathItem' === item.typename                                *
 *          || 'CompoundPathItem' === item.typename                     *
 *        );                                                            *
 *      }                                                               *
 *    });                                                               *
 *                                                                      *
 * -------------------------------------------------------------------- *
 * Example 4. Using `filter` function:                                  *
 *                                                                      *
 *    var myItems = getItems({                                          *
 *      from: app.activeDocument,                                       *
 *      filter: onlyPngLinks                                            *
 *    });                                                               *
 *                                                                      *
 *    function onlyPngLinks(item, depth) {                              *
 *       return (                                                       *
 *           'PlacedItem' === item.typename                             *
 *           && '.png' === item.file.name.slice(-4).toLowerCase()       *
 *       );                                                             *
 *    };                                                                *
 *                                                                      *
 * -------------------------------------------------------------------- *
 * Example 4. Using the `filter` function for custom collecting:        *
 *                                                                      *
 * This example bypasses the normal returned array and instead          *
 * captures items in an "external" array `itemsByDepth`.                *
 *                                                                      *
 *    var itemsByDepth = [];                                            *
 *                                                                      *
 *    function getItemsByDepth(item, depth) {                           *
 *      if (undefined == itemsByDepth[depth])                           *
 *        itemsByDepth[depth] = [];                                     *
 *      itemsByDepth[depth].push(item);                                 *
 *    };                                                                *
 *                                                                      *
 *    getItems({                                                        *
 *      from: app.activeDocument,                                       *
 *      filter: getItemsByDepth                                         *
 *    });                                                               *
 *                                                                      *
 * -------------------------------------------------------------------- *
 * @param {Object} options - parameters
 * @param {PageItem|Array<PageItem>|Document|Layer} options.from - the thing(s) to look in, eg. a selection.
 * @param {Function} [options.filter] - function that, given a found item, must return true (default: no filtering).
 * @param {Boolean} [options.getPageItems] - whether to include page items in returned items (default: true).
 * @param {Boolean} [options.getGroupItems] - whether to include GroupItems in returned items (default: true).
 * @param {Boolean} [options.getLayers] - whether to include Layers in returned items (default: false).
 * @param {Boolean} [options.getHiddenItems] - whether to include hidden items in returned items (default: true).
 * @param {Boolean} [options.getLockedItems] - whether to include locked items in returned items (default: true).
 * @param {Boolean} [options.getGuideItems] - whether to include guide items in returned items (default: false).
 * @param {Number} [options.maxDepth] - deepest folder level (recursion depth limit) (default: 99).
 * @param {Boolean} [options.returnFirstMatch] - whether to return only the first found item (default: false).
 * @param {Number} [depth] - the current depth (private).
 * @returns {Array|PageItem} - all the found items in a flat array, or the first found item if `returnFirstMatch`.
 */
function getItems(options, depth) {

    // defaults
    options = options || {};

    var found = [],
        depth = depth || 0,
        items = options.from;

    if (!options.initialized)
        // once-off initialization
        if (!initialize())
            return [];

    itemsLoop:
    for (var i = 0, item, len = items.length; i < len; i++) {

        item = items[i];

        if (
            false === excludeFilter(item)
            && true === includeFilter(item)
        ) {
            // item found!
            found.push(item);

            if (options.returnFirstMatch)
                break itemsLoop;
        }

        if (
            'GroupItem' !== item.constructor.name
            && 'Layer' !== item.typename
        )
            // only items with children from here
            continue itemsLoop;

        if (
            excludeHidden(item)
            || excludeLocked(item)
        )
            // don't look into excluded containers
            continue itemsLoop;

        if (depth >= options.maxDepth)
            // don't go deeper
            continue itemsLoop;

        // set up for the next depth
        options.from = item.pageItems;

        // look inside
        found = found.concat(getItems(options, depth + 1));

    }

    // this level done
    if (true == options.returnFirstMatch)
        return found[0];
    else
        return found;

    /**
     * Returns true when the item should be not be found.
     * @param {PageItem|Layer} item
     * @returns {Boolean}
     */
    function excludeFilter(item) {

        return (

            isAlreadyFound(item)

            // is hidden
            || excludeHidden(item)

            // is locked
            || excludeLocked(item)

            // is guide
            || (
                false === options.getGuideItems
                && true === item.guides
            )

            // is layer
            || (
                false === options.getLayers
                && 'Layer' === item.typename
            )

            // is group item
            || (
                false === options.getGroupItems
                && 'GroupItem' === item.typename
            )

            // is page item
            || (
                false === options.getPageItems
                && 'GroupItem' !== item.typename
                && undefined != item.uuid
            )

        );

    };

    /**
     * Returns true when the item should be included.
     * @param {PageItem|Layer} item
     * @returns {Boolean}
     */
    function includeFilter(item) {

        return (
            undefined == options.filter
            || options.filter(item, depth)
        );

    };

    /**
     * Returns true when the item should
     * be excluded because it is hidden.
     * @param {PageItem|Layer} item
     * @returns {Boolean}
     */
    function excludeHidden(item) {

        return (
            false === options.getHiddenItems
            && (
                true === item.hidden
                || false === item.visible
            )
        );

    };

    /**
     * Returns true when the item should
     * be excluded because it is locked.
     * @param {PageItem|Layer} item
     * @returns {Boolean}
     */
    function excludeLocked(item) {

        return (
            false === options.getLockedItems
            && true === item.locked
        );

    };

    /**
     * Returns true if item was already
     * found, and marks item as found,
     * to avoid finding same item twice.
     * @param {PageItem|Layer} item
     * @returns {Boolean}
     */
    function isAlreadyFound(item) {

        var uuid = item.hasOwnProperty('uuid')
            ? item.uuid
            : item.typename + item.zOrderPosition,

            isFound = !!options.isFound[uuid];

        options.isFound[uuid] = true;

        return isFound;

    }

    /**
     * Returns the initialised `options` object.
     * @returns {Object}
     */
    function initialize() {

        // make a new object, so we don't pollute the original
        options = {
            initialized: true,
            depth: 0,
            isFound: {},
            filter: options.filter,
            getPageItems: false !== options.getPageItems,
            getGroupItems: false !== options.getGroupItems,
            getLayers: true === options.getLayers,
            getHiddenItems: false !== options.getHiddenItems,
            getLockedItems: false !== options.getLockedItems,
            getGuideItems: true === options.getGuideItems,
            maxDepth: options.maxDepth,
            returnFirstMatch: options.returnFirstMatch,
        };

        if (
            undefined == options.maxDepth
            || !options.maxDepth instanceof Number
        )
            options.maxDepth = 99;

        // items is a single layer
        if ('Layer' === items.typename)
            items = [items];

        // items is a document
        else if ('Document' === items.constructor.name) {

            var layers = items.layers;
            items = [];

            for (var i = 0; i < layers.length; i++)
                items.push(layers[i]);

        }

        else if ('Array' !== items.constructor.name)
            items = [items];

        return items.length > 0;

    };

};

Edit 2025-02-14: added `getItems` function to recursively search inside groups.

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Feb 13, 2025 Feb 13, 2025

Copy link to clipboard

Copied

Hello @m1b, thank you for providing the script, it works wonders for simple objects, but when they are grouped objects or objects with clipping masks, the same does not happen. Will there be? some way to solve it

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 13, 2025 Feb 13, 2025

Copy link to clipboard

Copied

Oh yes you are right! In that case I need to be a bit more sophisticated about getting the selected objects...

I have updated the script above. Please try it again.

- Mark

 

P.S. Note that Illustrator does not assign rotation values to GroupItems, per se, so the contents of the groups will (in most cases!) unrotate, but not the groups themselves.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Feb 13, 2025 Feb 13, 2025

Copy link to clipboard

Copied

but it looks like it's not handling groups and clipping masks correctly. The main issue is that the script is rotating items individually within groups and masks, instead of rotating the entire group or mask as a single object.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Feb 13, 2025 Feb 13, 2025

Copy link to clipboard

Copied

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 13, 2025 Feb 13, 2025

Copy link to clipboard

Copied

Hi @vectora98504848 @yep that's what I meant in my P.S. above. Sadly groups don't seem to have rotation tags or any way to derive a rotation value.

 

What do you think of the following idea: that for every group (a clipping group is also a group by the way) I read the rotation of the first path item and then unrotate the group by that amount, not doing anything to the contents beyond that? When I get a chance I'll try it out.

- Mark

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Feb 14, 2025 Feb 14, 2025

Copy link to clipboard

Copied

Bonjour Marc, je me suis permis (hier soir) de modifier votre fonction unrotate().

pour gérer les groupes, tracés transparents et les masques.

Cela semble fonctionner?

 

function unrotate(item) {
    var grp = false, trs = false;
    var accumRotation = undefined,
        rotationTag;

    if (item.hasOwnProperty('tags')) {
      if (item.typename == "GroupItem" || item.typename == "CompoundPathItem") {
        if (item.typename == "CompoundPathItem") {
          rotationTag = getThing(item.pathItems[0].tags, 'name', 'BBAccumRotation');
          trs = true;
        }
          else {
           rotationTag = getThing(item.pageItems[0].tags, 'name', 'BBAccumRotation');
          grp = true;
          }
      }
      else rotationTag = getThing(item.tags, 'name', 'BBAccumRotation');

    if (rotationTag)
      accumRotation = rotationTag.value * 180 / Math.PI;
    }

    if (
        undefined == accumRotation
        && item.hasOwnProperty('matrix')
    )
        // derive rotation from the matrix
        accumRotation = getRotationFromMatrix(item.matrix);

    if (undefined == accumRotation)
        return;

    // rotate the item
    item.rotate(- accumRotation);

    if (rotationTag)
        // update tag
          var pi;
          if (grp)
            for (var i = 0; i < item.pageItems.length; i++ ){
              pi = item.pageItems[i];
              rotationTag = getThing(pi.tags, 'name', 'BBAccumRotation');
              if (rotationTag != undefined) {rotationTag.value = 0;}
              }
            }
         if (trs) {
            pi = item.pathItems[0];
              rotationTag = getThing(pi.tags, 'name', 'BBAccumRotation');
              if (rotationTag != undefined) {rotationTag.value = 0;}
                rotationTag = getThing(item.tags, 'name', 'BBAccumRotation');
                if (rotationTag != undefined) {rotationTag.value = 0;}
         }
        if (!trs && !grp) rotationTag.value = 0;
};

 

René

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 15, 2025 Feb 15, 2025

Copy link to clipboard

Copied

@renél80416020 Wonderful! Thanks René! I'll add it in when I get a chance and OP can test it out. I didn't remember that compound path items also don't have BBAccumRotation tags, so that's for including that logic!

- Mark

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 17, 2025 Feb 17, 2025

Copy link to clipboard

Copied

Okay @vectora98504848 here is the script again, now with René's improved function. Let us know how it goes!

- Mark

 

/**
 * @file Unrotate Selected Items 2.js
 *
 * Makes an attempt to remove any recorded rotation
 * that can be derived from the selected items.
 * Finds rotation either in the item's BBAccumRotation
 * tag or the item's matrix.
 *
 * @author m1b
 * @version 2025-02-17
 * @discussion https://community.adobe.com/t5/illustrator-discussions/script-to-return-or-restore-rotation-of-multiple-selected-objects/m-p/15146692
 */
(function () {

    var doc = app.activeDocument;

    var items = getItems({
        from: doc.selection,
        getPageItems: true,
        getGroupItems: true,
    });

    if (0 === items.length)
        return alert('Please select some items to rotate and try again.');

    for (var i = 0; i < items.length; i++)
        unrotate2(items[i]);

})();

/**
 * Attempt to return a page item to unrotated state.
 * Modified by renél80416020 to apply rotation
 * to group items and compound path items correctly.
 * Important: will only work if the item has a valid
 * BBAccumRotation tag or a matrix that is ammenable
 * to derivation of the rotation.
 * @author m1b and renél80416020
 * @version 2025-02-14
 * @param {PageItem} item - the item to unrotate.
 * @return {Number} - the amount of rotation, in degrees.
 */
function unrotate2(item) {

    var grp = false, trs = false;

    var accumRotation = undefined,
        rotationTag;

    if (item.hasOwnProperty('tags')) {

        if (item.typename == "GroupItem" || item.typename == "CompoundPathItem") {
            if (item.typename == "CompoundPathItem") {
                rotationTag = getThing(item.pathItems[0].tags, 'name', 'BBAccumRotation');
                trs = true;
            }

            else {
                rotationTag = getThing(item.pageItems[0].tags, 'name', 'BBAccumRotation');
                grp = true;
            }
        }
        else rotationTag = getThing(item.tags, 'name', 'BBAccumRotation');

        if (rotationTag)
            accumRotation = rotationTag.value * 180 / Math.PI;
    }

    if (
        undefined == accumRotation
        && item.hasOwnProperty('matrix')
    )
        // derive rotation from the matrix
        accumRotation = getRotationFromMatrix(item.matrix);

    if (undefined == accumRotation)
        return;

    // rotate the item
    item.rotate(- accumRotation);

    if (rotationTag) {
        // update tag
        var pi;

        if (grp)
            for (var i = 0; i < item.pageItems.length; i++) {
                pi = item.pageItems[i];
                rotationTag = getThing(pi.tags, 'name', 'BBAccumRotation');
                if (rotationTag != undefined) { rotationTag.value = 0; }
            }

        if (trs) {
            pi = item.pathItems[0];
            rotationTag = getThing(pi.tags, 'name', 'BBAccumRotation');
            if (rotationTag != undefined) { rotationTag.value = 0; }
            rotationTag = getThing(item.tags, 'name', 'BBAccumRotation');
            if (rotationTag != undefined) { rotationTag.value = 0; }
        }

        if (!trs && !grp)
            rotationTag.value = 0;

    }

};

/**
 * Returns a thing with matching property.
 * If `key` is undefined, evaluate the object itself.
 * @author m1b
 * @version 2024-04-21
 * @param {Array|Collection} things - the things to look through.
 * @param {String} [key] - the property name (default: undefined).
 * @param {*} value - the value to match.
 */
function getThing(things, key, value) {

    for (var i = 0, obj; i < things.length; i++)
        if ((undefined == key ? things[i] : things[i][key]) == value)
            return things[i];

};

/**
 * Returns the rotation amount, in degrees,
 * of the given (not skewed) matrix.
 * @author m1b
 * @version 2023-10-20
 * @param {Matrix} matrix - an Illustrator Matrix.
 * @returns {Number}
 */
function getRotationFromMatrix(matrix) {

    if (!matrix.hasOwnProperty('mValueA'))
        throw new Error('getRotationFromMatrix: bad `matrix` supplied.');

    // scaling factors
    var scaleX = Math.sqrt(matrix.mValueA * matrix.mValueA + matrix.mValueC * matrix.mValueC);
    // scaleY = Math.sqrt(matrix.mValueB * matrix.mValueB + matrix.mValueD * matrix.mValueD);

    // rotation angle
    var radians = Math.acos(matrix.mValueA / scaleX),
        degrees = radians * (180 / Math.PI);

    return Math.round(degrees * 1000) / 1000;

};

/** ------------------------------------------------------------------- *
 *  GET ITEMS                                                           *
 * -------------------------------------------------------------------- *
 * @author m1b                                                          *
 * @version 2024-03-01                                                  *
 * -------------------------------------------------------------------- *
 * Collects page items from a `from` source, eg. a Document, Layer,     *
 * GroupItem, or Array. Will look inside group items up to `maxDepth`.  *
 * Search can be filtered using `filter` function. Note that the        *
 * filter function is evaluated last in the filtering process.          *
 * -------------------------------------------------------------------- *
 * Example 1. Get all items in document:                                *
 *                                                                      *
 *    var myItems = getItems({ from: app.activeDocument });             *
 *                                                                      *
 * -------------------------------------------------------------------- *
 * Example 2. Get all selected items except groups:                     *
 *                                                                      *
 *    var myItems = getItems({                                          *
 *      from: app.activeDocument.selection,                             *
 *      getGroupItems: false,                                           *
 *    });                                                               *
 *                                                                      *
 * -------------------------------------------------------------------- *
 * Example 3. Using `filter` function to choose item type:              *
 *                                                                      *
 *    var myItems = getItems({                                          *
 *      from: app.activeDocument,                                       *
 *      filter: function (item) {                                       *
 *        return (                                                      *
 *          'PathItem' === item.typename                                *
 *          || 'CompoundPathItem' === item.typename                     *
 *        );                                                            *
 *      }                                                               *
 *    });                                                               *
 *                                                                      *
 * -------------------------------------------------------------------- *
 * Example 4. Using `filter` function:                                  *
 *                                                                      *
 *    var myItems = getItems({                                          *
 *      from: app.activeDocument,                                       *
 *      filter: onlyPngLinks                                            *
 *    });                                                               *
 *                                                                      *
 *    function onlyPngLinks(item, depth) {                              *
 *       return (                                                       *
 *           'PlacedItem' === item.typename                             *
 *           && '.png' === item.file.name.slice(-4).toLowerCase()       *
 *       );                                                             *
 *    };                                                                *
 *                                                                      *
 * -------------------------------------------------------------------- *
 * Example 4. Using the `filter` function for custom collecting:        *
 *                                                                      *
 * This example bypasses the normal returned array and instead          *
 * captures items in an "external" array `itemsByDepth`.                *
 *                                                                      *
 *    var itemsByDepth = [];                                            *
 *                                                                      *
 *    function getItemsByDepth(item, depth) {                           *
 *      if (undefined == itemsByDepth[depth])                           *
 *        itemsByDepth[depth] = [];                                     *
 *      itemsByDepth[depth].push(item);                                 *
 *    };                                                                *
 *                                                                      *
 *    getItems({                                                        *
 *      from: app.activeDocument,                                       *
 *      filter: getItemsByDepth                                         *
 *    });                                                               *
 *                                                                      *
 * -------------------------------------------------------------------- *
 * @param {Object} options - parameters
 * @param {PageItem|Array<PageItem>|Document|Layer} options.from - the thing(s) to look in, eg. a selection.
 * @param {Function} [options.filter] - function that, given a found item, must return true (default: no filtering).
 * @param {Boolean} [options.getPageItems] - whether to include page items in returned items (default: true).
 * @param {Boolean} [options.getGroupItems] - whether to include GroupItems in returned items (default: true).
 * @param {Boolean} [options.getLayers] - whether to include Layers in returned items (default: false).
 * @param {Boolean} [options.getHiddenItems] - whether to include hidden items in returned items (default: true).
 * @param {Boolean} [options.getLockedItems] - whether to include locked items in returned items (default: true).
 * @param {Boolean} [options.getGuideItems] - whether to include guide items in returned items (default: false).
 * @param {Number} [options.maxDepth] - deepest folder level (recursion depth limit) (default: 99).
 * @param {Boolean} [options.returnFirstMatch] - whether to return only the first found item (default: false).
 * @param {Number} [depth] - the current depth (private).
 * @returns {Array|PageItem} - all the found items in a flat array, or the first found item if `returnFirstMatch`.
 */
function getItems(options, depth) {

    // defaults
    options = options || {};

    var found = [],
        depth = depth || 0,
        items = options.from;

    if (!options.initialized)
        // once-off initialization
        if (!initialize())
            return [];

    itemsLoop:
    for (var i = 0, item, len = items.length; i < len; i++) {

        item = items[i];

        if (
            false === excludeFilter(item)
            && true === includeFilter(item)
        ) {
            // item found!
            found.push(item);

            if (options.returnFirstMatch)
                break itemsLoop;
        }

        if (
            'GroupItem' !== item.constructor.name
            && 'Layer' !== item.typename
        )
            // only items with children from here
            continue itemsLoop;

        if (
            excludeHidden(item)
            || excludeLocked(item)
        )
            // don't look into excluded containers
            continue itemsLoop;

        if (depth >= options.maxDepth)
            // don't go deeper
            continue itemsLoop;

        // set up for the next depth
        options.from = item.pageItems;

        // look inside
        found = found.concat(getItems(options, depth + 1));

    }

    // this level done
    if (true == options.returnFirstMatch)
        return found[0];
    else
        return found;

    /**
     * Returns true when the item should be not be found.
     * @param {PageItem|Layer} item
     * @returns {Boolean}
     */
    function excludeFilter(item) {

        return (

            isAlreadyFound(item)

            // is hidden
            || excludeHidden(item)

            // is locked
            || excludeLocked(item)

            // is guide
            || (
                false === options.getGuideItems
                && true === item.guides
            )

            // is layer
            || (
                false === options.getLayers
                && 'Layer' === item.typename
            )

            // is group item
            || (
                false === options.getGroupItems
                && 'GroupItem' === item.typename
            )

            // is page item
            || (
                false === options.getPageItems
                && 'GroupItem' !== item.typename
                && undefined != item.uuid
            )

        );

    };

    /**
     * Returns true when the item should be included.
     * @param {PageItem|Layer} item
     * @returns {Boolean}
     */
    function includeFilter(item) {

        return (
            undefined == options.filter
            || options.filter(item, depth)
        );

    };

    /**
     * Returns true when the item should
     * be excluded because it is hidden.
     * @param {PageItem|Layer} item
     * @returns {Boolean}
     */
    function excludeHidden(item) {

        return (
            false === options.getHiddenItems
            && (
                true === item.hidden
                || false === item.visible
            )
        );

    };

    /**
     * Returns true when the item should
     * be excluded because it is locked.
     * @param {PageItem|Layer} item
     * @returns {Boolean}
     */
    function excludeLocked(item) {

        return (
            false === options.getLockedItems
            && true === item.locked
        );

    };

    /**
     * Returns true if item was already
     * found, and marks item as found,
     * to avoid finding same item twice.
     * @param {PageItem|Layer} item
     * @returns {Boolean}
     */
    function isAlreadyFound(item) {

        var uuid = item.hasOwnProperty('uuid')
            ? item.uuid
            : item.typename + item.zOrderPosition,

            isFound = !!options.isFound[uuid];

        options.isFound[uuid] = true;

        return isFound;

    }

    /**
     * Returns the initialised `options` object.
     * @returns {Object}
     */
    function initialize() {

        // make a new object, so we don't pollute the original
        options = {
            initialized: true,
            depth: 0,
            isFound: {},
            filter: options.filter,
            getPageItems: false !== options.getPageItems,
            getGroupItems: false !== options.getGroupItems,
            getLayers: true === options.getLayers,
            getHiddenItems: false !== options.getHiddenItems,
            getLockedItems: false !== options.getLockedItems,
            getGuideItems: true === options.getGuideItems,
            maxDepth: options.maxDepth,
            returnFirstMatch: options.returnFirstMatch,
        };

        if (
            undefined == options.maxDepth
            || !options.maxDepth instanceof Number
        )
            options.maxDepth = 99;

        // items is a single layer
        if ('Layer' === items.typename)
            items = [items];

        // items is a document
        else if ('Document' === items.constructor.name) {

            var layers = items.layers;
            items = [];

            for (var i = 0; i < layers.length; i++)
                items.push(layers[i]);

        }

        else if ('Array' !== items.constructor.name)
            items = [items];

        return items.length > 0;

    };

};

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Feb 17, 2025 Feb 17, 2025

Copy link to clipboard

Copied

Hi @m1b I get this error, line 17

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 17, 2025 Feb 17, 2025

Copy link to clipboard

Copied

Oh sorry @vectora98504848 I somehow didn't copy half of the script! I have fixed it now. - Mark

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Feb 18, 2025 Feb 18, 2025

Copy link to clipboard

Copied

LATEST

Thanks bro, I like Illustrator and even more the scripts help a lot but I'm still not at your level to program. 😉

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines