Skip to main content
Participant
January 15, 2025
Question

create script to move on the Y axis using artborad as a reference

  • January 15, 2025
  • 2 replies
  • 203 views

 

I can't create a java script to align all objects to the base of the artboard because it always gives an error on the Y axis. I've tried several approaches but so far nothing
Does anyone have an example script how to align multiple objects to the base?
on the x axis it works perfectly to center without losing the relative distance between the objects, however I have not had success in relation to the Y axis, either to measure the size of the grouping, to center or to move


 

 

2 replies

m1b
Community Expert
Community Expert
January 15, 2025

Hi @Luiz Vigraf there are some complexities to this issue, for example: getting the actual bounds of an item is difficult in many cases, such as text or clipping masked items. I have written a function to help with this (see in script below), which makes things much easier, and made writing this script today super quick. I also wrote a function to get the most likely artboard for a given page item, which will be handy, too. I included an option for left justifying the items (you might not need this, but I did it for fun—turn it off by editing `settings.justifyLeft` to false in the script.

 

Let me know if it's useful.

- Mark

 

 

 

/**
 * @file Move Selected Items To Bottom Of Artboard.js
 *
 * Moves the selected items to the bottom of its artboard,
 * with the option to left justify with space between.
 *
 * @author m1b
 * @version 2025-01-16
 * @discussion https://community.adobe.com/t5/illustrator-discussions/create-script-to-move-on-the-y-axis-using-artborad-as-a-reference/m-p/15091849
 */
(function () {

    var settings = {
        justifyLeft: true,
        spaceBetween: 0,
    };

    var doc = app.activeDocument,
        items = doc.selection,
        xPositions = {};

    if (settings.justifyLeft)
        items.sort(sortByLeft);

    for (var i = 0; i < items.length; i++) {

        var item = items[i],
            bounds = getItemBoundsIllustrator(item, false),
            artboard = getArtboardWithMostOverlap(doc.artboards, bounds);

        if (!artboard)
            // item not on any artboard
            continue;

        var artboardBounds = artboard.artboardRect;

        // keep track of x position per artboard
        xPositions[artboard.name] = undefined == xPositions[artboard.name] ? artboardBounds[0] : xPositions[artboard.name];

        var dx = settings.justifyLeft ? xPositions[artboard.name] - bounds[0] : 0,
            dy = artboardBounds[3] - bounds[3];

        // advance x position for this artboard
        xPositions[artboard.name] += bounds[2] - bounds[0] + settings.spaceBetween;

        // move the item into position
        item.translate(dx, dy, true, true, true, true);

    }

})();

/**
 * Returns the artboard that most overlaps the given bounds.
 * Will return nothing if no overlap at all.
 * @author m1b
 * @version 2025-01-16
 * @param {Array<Artboard>|Artboards} artboards - the artboards to search.
 * @param {Array<Number>} bounds - the bounds to check against [L, T, R, B].
 * @returns {Artboard?} - the artboard with the most overlap.
 */
function getArtboardWithMostOverlap(artboards, bounds) {

    if (
        undefined == artboards
        || 0 === artboards.length
    )
        return;

    var maxOverlap = 0,
        bestArtboard = undefined;

    for (var i = 0; i < artboards.length; i++) {

        var artboard = artboards[i];
        var artboardBounds = artboard.artboardRect; // [L, T, R, B]

        // calculate the overlap
        var overlapL = Math.max(bounds[0], artboardBounds[0]),
            overlapT = Math.min(bounds[1], artboardBounds[1]),
            overlapR = Math.min(bounds[2], artboardBounds[2]),
            overlapB = Math.max(bounds[3], artboardBounds[3]);

        // overlap area
        var overlapWidth = Math.max(0, overlapR - overlapL),
            overlapHeight = Math.max(0, overlapT - overlapB),
            overlapArea = overlapWidth * overlapHeight;

        if (overlapArea > maxOverlap) {
            // the largest overlapping artboard so far
            maxOverlap = overlapArea;
            bestArtboard = artboard;
        }

    }

    return bestArtboard;

};

/**
 * Sort by left bounds.
 * @author m1b
 * @version 2025-01-16
 * @param {PageItem} a - item to sort.
 * @param {PageItem} b - item to sort.
 * @returns {Number} - the sort code.
 */
function sortByLeft(a, b) {

    return (

        // left bounds value rounded to 3 decimal places
        Math.round((a.visibleBounds[0] - b.visibleBounds[0]) * 1000) / 1000

        // same left, so we'll sort by top bounds value
        || a.visibleBounds[1] - b.visibleBounds[1]

    );

};

// from here down is just code to get a page item's bounds

/**
 * Returns bounds of item(s).
 * @author m1b
 * @version 2024-03-10
 * @param {PageItem|Array<PageItem>} item - an Illustrator PageItem or array of PageItems.
 * @param {Boolean} [geometric] - if false, returns visible bounds.
 * @param {Array} [bounds] - private parameter, used when recursing.
 * @returns {Array} - the calculated bounds.
 */
function getItemBoundsIllustrator(item, geometric, bounds) {

    var newBounds = [],
        boundsKey = geometric ? 'geometricBounds' : 'visibleBounds';

    if (undefined == item)
        return;

    if (
        item.typename == 'GroupItem'
        || item.constructor.name == 'Array'
    ) {

        var children = item.typename == 'GroupItem' ? item.pageItems : item,
            contentBounds = [],
            isClippingGroup = (item.hasOwnProperty('clipped') && item.clipped == true),
            clipBounds;

        for (var i = 0, child; i < children.length; i++) {

            child = children[i];

            if (
                child.hasOwnProperty('clipping')
                && true === child.clipping
            )
                // the clipping item
                clipBounds = child.geometricBounds;

            else
                contentBounds.push(getItemBoundsIllustrator(child, geometric, bounds));

        }

        newBounds = combineBounds(contentBounds);

        if (isClippingGroup)
            newBounds = intersectionOfBounds([clipBounds, newBounds]);

    }

    else if (
        'TextFrame' === item.constructor.name
        && TextType.AREATEXT !== item.kind
    ) {

        // get bounds of outlined text
        var dup = item.duplicate().createOutline();
        newBounds = dup[boundsKey];
        dup.remove();

    }

    else if (item.hasOwnProperty(boundsKey)) {

        newBounds = item[boundsKey];

    }

    // `bounds` will exist if this is a recursive execution
    bounds = (undefined == bounds)
        ? bounds = newBounds
        : bounds = combineBounds([newBounds, bounds]);

    return bounds;

};

/**
 * Returns the combined bounds of all bounds supplied.
 * Works with Illustrator or Indesign bounds.
 * @author m1b
 * @version 2024-03-09
 * @param {Array<bounds>} boundsArray - an array of bounds [L, T, R, B] or [T, L , B, R].
 * @returns {bounds?} - the combined bounds.
 */
function combineBounds(boundsArray) {

    var combinedBounds = boundsArray[0],
        comparator;

    if (/indesign/i.test(app.name))
        comparator = [Math.min, Math.min, Math.max, Math.max];

    else if (APP_IS_ILLUSTRATOR)
        comparator = [Math.min, Math.max, Math.max, Math.min];

    // iterate through the rest of the bounds
    for (var i = 1; i < boundsArray.length; i++) {

        var bounds = boundsArray[i];

        combinedBounds = [
            comparator[0](combinedBounds[0], bounds[0]),
            comparator[1](combinedBounds[1], bounds[1]),
            comparator[2](combinedBounds[2], bounds[2]),
            comparator[3](combinedBounds[3], bounds[3]),
        ];

    }

    return combinedBounds;

};

/**
 * Returns the overlapping rectangle
 * of two or more rectangles.
 * NOTE: Returns undefined if ANY
 * rectangles do not intersect.
 * @author m1b
 * @version 2024-09-05
 * @param {Array<bounds>} arrayOfBounds - an array of bounds [L, T, R, B] or [T, L , B, R].
 * @returns {bounds?} - intersecting bounds.
 */
function intersectionOfBounds(arrayOfBounds) {

    var comparator;

    if (/indesign/i.test(app.name))
        comparator = [Math.max, Math.max, Math.min, Math.min];

    else if (APP_IS_ILLUSTRATOR)
        comparator = [Math.max, Math.min, Math.min, Math.max];

    // sort a copy of array
    var bounds = arrayOfBounds
        .slice(0)
        .sort(function (a, b) { return b[0] - a[0] || a[1] - b[1] });

    // start with first bounds
    var intersection = bounds.shift(),
        b;

    // compare each bounds, getting smaller
    while (b = bounds.shift()) {

        // if doesn't intersect, bail out
        if (!boundsDoIntersect(intersection, b))
            return;

        intersection = [
            comparator[0](intersection[0], b[0]),
            comparator[1](intersection[1], b[1]),
            comparator[2](intersection[2], b[2]),
            comparator[3](intersection[3], b[3]),
        ];

    }

    return intersection;

};

/**
 * Returns true if the two bounds intersect.
 * @author m1b
 * @version 2024-03-10
 * @param {Array} bounds1 - bounds array.
 * @param {Array} bounds2 - bounds array.
 * @param {Boolean} [TLBR] - whether bounds arrays are interpreted as [t, l, b, r] or [l, t, r, b] (default: based on app).
 * @returns {Boolean}
 */
function boundsDoIntersect(bounds1, bounds2, TLBR) {

    if (undefined == TLBR)
        TLBR = (/indesign/i.test(app.name));

    return !(

        TLBR

            // TLBR
            ? (
                bounds2[0] > bounds1[2]
                || bounds2[1] > bounds1[3]
                || bounds2[2] < bounds1[0]
                || bounds2[3] < bounds1[1]
            )

            // LTRB
            : (
                bounds2[0] > bounds1[2]
                || bounds2[1] < bounds1[3]
                || bounds2[2] < bounds1[0]
                || bounds2[3] > bounds1[1]
            )
    );

};

Edit 2025-01-16: improved variable name.

 

Kurt Gold
Community Expert
Community Expert
January 15, 2025

Can you share your script that works perfectly with the x axis?