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

Need a script that can copy the width of one object and paste that width onto another object

Community Beginner ,
Sep 16, 2024 Sep 16, 2024

Copy link to clipboard

Copied

Hello, my job requires a lot of scaling one object to match another object.

It would be super convenient to have my workflow look like this:

1. Select first object, run script to copy width.

2. Select second object, run script to paste width. Now they are the same width.

 

I imagine they would be two separate scripts. Could I possibly have help with this? 

I'm aware that scripts like MatchObject exists but it's not what I'm looking for. I don't want to have to constantly select settings in a dialogue box. Assigning scripts to F1 and F2 would make it just a couple button presses.

 

Thanks in advance.

TOPICS
Scripting

Views

459

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

correct answers 1 Correct answer

Community Expert , Sep 17, 2024 Sep 17, 2024

So below are two scripts...

 

The first script gets the "visible" bounds of the first selected object and saves them as an environment variable. 

// scale_get.jsx

// get the "visibleBounds" of the first item of the app selection
var sourceItem = app.activeDocument.selection[0];

// get the "visible" bounds of the sourceItem
// https://ai-scripting.docsforadobe.dev/scripting/positioning.html#art-item-bounds
// to use a different type of bounds update the line below
var sourceBounds = sourceItem.vis
...

Votes

Translate

Translate
Adobe
Community Beginner ,
Sep 16, 2024 Sep 16, 2024

Copy link to clipboard

Copied

I also want to add that it's important that the height stays porportional to the width. Any help appreciated!

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 ,
Sep 17, 2024 Sep 17, 2024

Copy link to clipboard

Copied

So below are two scripts...

 

The first script gets the "visible" bounds of the first selected object and saves them as an environment variable. 

// scale_get.jsx

// get the "visibleBounds" of the first item of the app selection
var sourceItem = app.activeDocument.selection[0];

// get the "visible" bounds of the sourceItem
// https://ai-scripting.docsforadobe.dev/scripting/positioning.html#art-item-bounds
// to use a different type of bounds update the line below
var sourceBounds = sourceItem.visibleBounds;

// save sourceBounds to an environment variable so it can be retrieved by second script
$.setenv("scaleTargetBounds", sourceBounds.toSource());

 

After running the first script, make sure to select the objects you want to scale (multiple objects are allowed), then run this second script.

// scale_apply.jsx

// get source bounds from first script
var sourceBounds = eval($.getenv("scaleTargetBounds"));

// iterate over each selected object and scale to match source object width
var target, targetBounds, scaleFactor, scaleMatrix;
for (var i = 0; i < app.activeDocument.selection.length; i++) {
  target = app.activeDocument.selection[i];
  // if you change the type of bounds in the first script, you need to change her as well
  targetBounds = target.visibleBounds;
  // calculate the scale factor between the source and current target widths
  scaleFactor =
    ((sourceBounds[2] - sourceBounds[0]) / (targetBounds[2] - targetBounds[0])) * 100;
  // generate a scale matrix
  scaleMatrix = app.getScaleMatrix(scaleFactor, scaleFactor);

  // apply the matrix using the transform method
  // https://ai-scripting.docsforadobe.dev/jsobjref/PageItem.html#pageitem-transform
  target.transform(
    scaleMatrix,
    true, // change positions
    true, // change fill patterns
    true, // change fill gradients
    true, // change stroke patterns
    true, // change stroke width
    Transformation.CENTER // anchor point https://ai-scripting.docsforadobe.dev/jsobjref/scripting-constants.html#transformation
  );
}

 

Please note, you may want to use a different type of bounds other than "visible". There are comments in the scripts of where you need to make the change as well as links to the documentation. There are also options to scale the properties of the target objects (patterns, gradients, strokes) that you may want to read the docs on. You can also choose an anchor point for the scaling transformation.

 

Try these out and let me know if you have any questions? Cheers!

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 ,
Sep 17, 2024 Sep 17, 2024

Copy link to clipboard

Copied

Firstly I just want to say thank you for the scripts! I tried them out and noticed something a little strange. When I use the script to copy the width of a box and then use the next script to paste it onto some artwork, the width is a little off.

Width of box copied: 3.5in

Width of artwork after I paste width: 3.5139in

Any idea why this may be? Thanks!

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 ,
Sep 17, 2024 Sep 17, 2024

Copy link to clipboard

Copied

Following up with that this issue seems to happen if the shape copied is a stroke instead of a filled in shape. It's including the part of the stroke that goes outside the bounding box instead of just the bounding box. Is there a way to make the width copied account for just the bounding box?

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 ,
Sep 17, 2024 Sep 17, 2024

Copy link to clipboard

Copied

Sorry, following up one more time to say that your link answered my question (changing visibleBounds to geometricBounds)

Marking as correct answer, thank you!!

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 ,
Sep 19, 2024 Sep 19, 2024

Copy link to clipboard

Copied

Quick question, is there a way to make the second script paste properly to objects with a clipping mask? It seems to take the mask into account of the geometric bounds. I'd only like it to apply to the geometric bounds of the object we can see after a clipping mask is applied.

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 ,
Sep 19, 2024 Sep 19, 2024

Copy link to clipboard

Copied

Final question I have is about scaling strokes. I have it in my Illustrator preferences for stroke to scale, but for some reason stroke doesn't scale when using the scripts. It stays the same thickness.

If I can fix the stroke thickness and clipping mask issues, I think I'll be golden.

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 ,
Sep 20, 2024 Sep 20, 2024

Copy link to clipboard

Copied

So, here's an updated script that scales target object(s) strokes proportionally to the scale factor. The updated script also accounts for clipping masks on the target objects when scaling.

 

PLEASE NOTE, the first script doesn't account for a "source" object that may be clipped. If this is not a case you need to cover then you should be set. If there is a chance you may need to get the "true visible bounds" for the source object then that script would need to be updated as well to use the `getVisibleBounds()` function.

 

Take it for a spin and let me know if you have any issues. Cheers!

 

// scale_apply.jsx

// get source bounds from first script
var sourceBounds = eval($.getenv("scaleTargetBounds"));

// iterate over each selected object and scale to match source object width
var target, targetBounds, scaleFactor, strokeScaleFactor, scaleMatrix;
for (var i = 0; i < app.activeDocument.selection.length; i++) {
  target = app.activeDocument.selection[i];

  // get the true visible bounds of the target object (accounting for clipping masks)
  targetBounds = getVisibleBounds(target);

  // calculate the scale factor between the source and current target widths
  scaleFactor =
    ((sourceBounds[2] - sourceBounds[0]) / (targetBounds[2] - targetBounds[0])) * 100;
  strokeScaleFactor =
    (sourceBounds[2] - sourceBounds[0]) / (targetBounds[2] - targetBounds[0]);

  // generate a scale matrix
  scaleMatrix = app.getScaleMatrix(scaleFactor, scaleFactor);

  // apply the matrix using the transform method
  // https://ai-scripting.docsforadobe.dev/jsobjref/PageItem.html#pageitem-transform
  target.transform(
    scaleMatrix,
    true, // change positions
    true, // change fill patterns
    true, // change fill gradients
    true, // change stroke patterns
    strokeScaleFactor, // change stroke width
    Transformation.CENTER // anchor point https://ai-scripting.docsforadobe.dev/jsobjref/scripting-constants.html#transformation
  );
}

/**
 * Determine the actual "visible" bounds for an object if clipping mask or compound path items are found.
 * @Param {PageItem} o A single Adobe Illustrator pageItem.
 * @Returns {Array} Object bounds [left, top, right, bottom].
 */
function getVisibleBounds(o) {
  var bounds, clippedItem, sandboxItem, sandboxLayer;
  var curItem;

  // skip guides (via william dowling @ github.com/wdjsdev)
  if (o.guides) {
    return undefined;
  }

  if (o.typename == "GroupItem") {
    // if the group has no pageItems, return undefined
    if (!o.pageItems || o.pageItems.length == 0) {
      return undefined;
    }
    // if the object is clipped
    if (o.clipped) {
      // check all sub objects to find the clipping path
      for (var i = 0; i < o.pageItems.length; i++) {
        curItem = o.pageItems[i];
        if (curItem.clipping) {
          clippedItem = curItem;
          break;
        } else if (curItem.typename == "CompoundPathItem") {
          if (!curItem.pathItems.length) {
            // catch compound path items with no pathItems (via William Dowling @ github.com/wdjsdev)
            sandboxLayer = app.activeDocument.layers.add();
            sandboxItem = curItem.duplicate(sandboxLayer);
            app.activeDocument.selection = null;
            sandboxItem.selected = true;
            app.executeMenuCommand("noCompoundPath");
            sandboxLayer.hasSelectedArtwork = true;
            app.executeMenuCommand("group");
            clippedItem = app.activeDocument.selection[0];
            break;
          } else if (curItem.pathItems[0].clipping) {
            clippedItem = curItem;
            break;
          }
        }
      }
      if (!clippedItem) {
        clippedItem = o.pageItems[0];
      }
      bounds = clippedItem.geometricBounds;
      if (sandboxLayer) {
        // eliminate the sandbox layer since it's no longer needed
        sandboxLayer.remove();
        sandboxLayer = undefined;
      }
    } else {
      // if the object is not clipped
      var subObjectBounds;
      var allBoundPoints = [[], [], [], []];
      // get the bounds of every object in the group
      for (var i = 0; i < o.pageItems.length; i++) {
        curItem = o.pageItems[i];
        subObjectBounds = getVisibleBounds(curItem);
        for (var j = 0; j < subObjectBounds.length; j++) {
          allBoundPoints[j].push(subObjectBounds[j]);
        }
      }
      // determine the groups bounds from it sub object bound points
      bounds = [
        Math.min.apply(Math, allBoundPoints[0]),
        Math.max.apply(Math, allBoundPoints[1]),
        Math.max.apply(Math, allBoundPoints[2]),
        Math.min.apply(Math, allBoundPoints[3]),
      ];
    }
  } else {
    bounds = o.geometricBounds;
  }
  return bounds;
}

 

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 ,
Sep 20, 2024 Sep 20, 2024

Copy link to clipboard

Copied

Thank you for this! This script worked like a charm. If I did want the source to take a clipping mask into account, does using getVisibleBounds mean it would go back to including stroke width that's outside the bounding box?

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 ,
Sep 20, 2024 Sep 20, 2024

Copy link to clipboard

Copied

So, I can see where the name would be confusing, but my custom `getVisibleBounds()` function actually uses geometric bounds. I have updated the first script so that it will also work with clipped items while ignoring the stroke size in the scale calculation. Let me know if this works for you?

 

// scale_get.jsx

// get the "visibleBounds" of the first item of the app selection
var sourceItem = app.activeDocument.selection[0];

// get the "visible" bounds of the sourceItem
// https://ai-scripting.docsforadobe.dev/scripting/positioning.html#art-item-bounds
// to use a different type of bounds update the line below
var sourceBounds = getVisibleBounds(sourceItem);

// save sourceBounds to an environment variable so it can be retrieved by second script
$.setenv("scaleTargetBounds", sourceBounds.toSource());

/**
 * Determine the actual "visible" bounds for an object if clipping mask or compound path items are found.
 * @Param {PageItem} o A single Adobe Illustrator pageItem.
 * @Returns {Array} Object bounds [left, top, right, bottom].
 */
function getVisibleBounds(o) {
  var bounds, clippedItem, sandboxItem, sandboxLayer;
  var curItem;

  // skip guides (via william dowling @ github.com/wdjsdev)
  if (o.guides) {
    return undefined;
  }

  if (o.typename == "GroupItem") {
    // if the group has no pageItems, return undefined
    if (!o.pageItems || o.pageItems.length == 0) {
      return undefined;
    }
    // if the object is clipped
    if (o.clipped) {
      // check all sub objects to find the clipping path
      for (var i = 0; i < o.pageItems.length; i++) {
        curItem = o.pageItems[i];
        if (curItem.clipping) {
          clippedItem = curItem;
          break;
        } else if (curItem.typename == "CompoundPathItem") {
          if (!curItem.pathItems.length) {
            // catch compound path items with no pathItems (via William Dowling @ github.com/wdjsdev)
            sandboxLayer = app.activeDocument.layers.add();
            sandboxItem = curItem.duplicate(sandboxLayer);
            app.activeDocument.selection = null;
            sandboxItem.selected = true;
            app.executeMenuCommand("noCompoundPath");
            sandboxLayer.hasSelectedArtwork = true;
            app.executeMenuCommand("group");
            clippedItem = app.activeDocument.selection[0];
            break;
          } else if (curItem.pathItems[0].clipping) {
            clippedItem = curItem;
            break;
          }
        }
      }
      if (!clippedItem) {
        clippedItem = o.pageItems[0];
      }
      bounds = clippedItem.geometricBounds;
      if (sandboxLayer) {
        // eliminate the sandbox layer since it's no longer needed
        sandboxLayer.remove();
        sandboxLayer = undefined;
      }
    } else {
      // if the object is not clipped
      var subObjectBounds;
      var allBoundPoints = [[], [], [], []];
      // get the bounds of every object in the group
      for (var i = 0; i < o.pageItems.length; i++) {
        curItem = o.pageItems[i];
        subObjectBounds = getVisibleBounds(curItem);
        for (var j = 0; j < subObjectBounds.length; j++) {
          allBoundPoints[j].push(subObjectBounds[j]);
        }
      }
      // determine the groups bounds from it sub object bound points
      bounds = [
        Math.min.apply(Math, allBoundPoints[0]),
        Math.max.apply(Math, allBoundPoints[1]),
        Math.max.apply(Math, allBoundPoints[2]),
        Math.min.apply(Math, allBoundPoints[3]),
      ];
    }
  } else {
    bounds = o.geometricBounds;
  }
  return bounds;
}

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 ,
Sep 20, 2024 Sep 20, 2024

Copy link to clipboard

Copied

This script worked beautifully! Thank you so much! These scripts for copying and pasting width are exactly what I need. If I wanted to make another version of the script that pastes the height, is it just a matter of replacing a few words in the second script?

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 ,
Sep 20, 2024 Sep 20, 2024

Copy link to clipboard

Copied

You would need to adjust the bounds array elements used in the scale factor calculation to the below.

 

// calculate the scale factor between the source and current target widths
scaleFactor =
  ((sourceBounds[3] - sourceBounds[1]) / (targetBounds[3] - targetBounds[1])) * 100;
strokeScaleFactor =
  (sourceBounds[3] - sourceBounds[1]) / (targetBounds[3] - targetBounds[1]);

 

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 ,
Sep 21, 2024 Sep 21, 2024

Copy link to clipboard

Copied

LATEST

Beautiful! Thanks so much for all your help, you really helped streamline my workflow. 🙂

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