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.
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
...
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!
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!
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!
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?
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!!
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.
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.
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;
}
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?
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;
}
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?
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]);
Copy link to clipboard
Copied
Beautiful! Thanks so much for all your help, you really helped streamline my workflow. 🙂