jduncan
Community Expert
jduncan
Community Expert
Activity
‎Mar 07, 2025
08:42 AM
So, after digging around a bit, I can get the blend mode to show up in the exported SVG xml by using setting the export option `optimizeForSVGViewer` to `true`. Unfortunately, the attribute comes as `adobe-blending-mode` instead of `mix-blend-mode` so it doesn't actually work. Also, the exported SVG xml isn't valid either. In line 3 (see below), Adobe include a few "&" characters which are invalid.
My best guess, is that when using File > Export Selection, Adobe uses a more moden SVG export method than the one that is used when calling the `exportFile` method from ExtendScript. If you inspect the two types of exported xml code you'll see that they are completely different.
Unfortunately, this may be a dead end for you...
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 9.03 Build 55982) -->
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" viewBox="0 0 157.84 184.76" style="enable-background:new 0 0 157.84 184.76;" xml:space="preserve">
<g style="enable-background:new ;">
<g id="Layer_1-2">
<circle style="fill:#ED1C24;stroke:#231F20;stroke-miterlimit:10;adobe-blending-mode:difference;enable-background:new ;" cx="56.7" cy="56.7" r="56.2"/>
<circle style="fill:#FFF200;stroke:#231F20;stroke-miterlimit:10;adobe-blending-mode:difference;enable-background:new ;" cx="86.4" cy="113.32" r="70.94"/>
</g>
</g>
</svg>
... View more
‎Mar 07, 2025
07:57 AM
The dialog that pops up when you manually do File > Export Selection... (see attached).
... View more
‎Mar 06, 2025
07:36 PM
There are a handful of export options that would need to match you setting from the Export Selection dialog. Can you share a screenshot of your Asset Export dialog and also share a PDF of the base file you are exporting from?
... View more
‎Mar 05, 2025
10:47 AM
If you are using the `app.activeDocument.exportFile()` method, it will export at the current document size. You could try fitting the current artboard to the selected artwork and then exporting (see below). FYI, this will get weird with multiple artboards, so you would need to account for that edge case (and probably a few more).
(function () {
// get the active document
try {
var doc = app.activeDocument;
} catch (e) {
alert("No active document.\n" + e);
return;
}
var fp = Folder.desktop + "/export.svg";
var ab = doc.artboards.getActiveArtboardIndex();
doc.fitArtboardToSelectedArt(ab);
var exportOptions = new ExportOptionsSVG();
exportOptions.embedRasterImages = true;
var type = ExportType.SVG;
var fileSpec = new File(fp);
doc.exportFile(fileSpec, type, exportOptions);
})();
... View more
‎Feb 17, 2025
06:56 AM
2 Upvotes
Adding these my spreadsheet and next release of Ai Command Palette. Thanks as always @sttk3! Appreciate the clarification @Sergey Osokin and @krasnovpro!
... View more
‎Feb 05, 2025
03:58 AM
Not from within the original script that calls the VariableImporter script... You could edit the VariableImporter script to skip any dialogs and just hardcode the values you want it to use. I'm not that familiar with it but it shouldn't be that hard.
... View more
‎Jan 26, 2025
08:55 AM
1 Upvote
You should just be able to evaluate the script file. Here's a helper function I use for executing other scripts from inside of a script. Let me know if this works for you? Cheers!
function runExternalScript(filePath) {
f = new File(filePath);
if (!f.exists) {
alert("Script Not Found!\nThe script file was not found at the path\n" + filePath);
} else {
$.evalFile(f);
}
}
... View more
‎Jan 17, 2025
08:55 AM
1 Upvote
I already had a script that performs something similar to what you described (for sign production as well) so I simplified it and added defaults that you can easily adjust to change the crosshair color, width, height, and weight.
A few things to note:
The script requires you to select the objects (circles) you want to mark the center of
The script doesn't check to ensure any of the selected objects are actual circles so it will place a center mark on anything that is selected.
You need to adjust the defaults to match your requirements
The script utilizes unit values (docs) so you can enter things like ".125 in", "3 cm", "4 pt", for the default values.
All crosshair marks are grouped by their parts and placed on a separate layer.
Let me know if this does what you need or if you have any questions/issues? Cheers!
// Draw a crosshair mark at the center of each selected object.
(function () {
// get the active document
try {
var doc = app.activeDocument;
} catch (e) {
alert("No active document.\n" + e);
return;
}
// get the document selection
var sel = doc.selection;
if (!selection.length) {
alert("Empty selection.\nMake a selection and rerun the script.");
return;
}
// setup script defaults
var marksLayerName = "Center Crosshair Marks";
var crosshairStrokeWeight = ".0625 in";
var crosshairWidth = ".5 in";
var crosshairHeight = ".25 in";
var crosshairStrokeColor = new RGBColor();
crosshairStrokeColor.red = 255;
crosshairStrokeColor.green = 0;
crosshairStrokeColor.blue = 0;
// convert default unit values to points
var crosshairStrokeWeight = UnitValue(crosshairStrokeWeight).as("pt");
var crosshairWidth = UnitValue(crosshairWidth).as("pt");
var crosshairHeight = UnitValue(crosshairHeight).as("pt");
// create a layer to hold the crosshair marks
var crosshairLayer = createLayer(marksLayerName);
// draw center crosshair mark for each selected object
var bounds, centerX, centerY;
for (var i = 0; i < sel.length; i++) {
bounds = sel[i].geometricBounds; // [left, top, right, bottom]
var centerX = bounds[0] + (bounds[2] - bounds[0]) / 2;
var centerY = -(bounds[1] - (bounds[1] - bounds[3]) / 2);
drawCrosshair(
crosshairLayer,
centerX,
centerY,
crosshairWidth,
crosshairHeight,
crosshairStrokeColor,
crosshairStrokeWeight
);
}
/**
* ************************************
* HELPER FUNCTIONS
* ************************************
*/
function createLayer(name) {
var layer;
try {
layer = doc.layers.getByName(name);
} catch (e) {
layer = doc.layers.add();
layer.name = name;
}
return layer;
}
function drawCrosshair(layer, x, y, width, height, strokeColor, strokeWeight) {
// make a group to hold the parts
var crosshair = layer.groupItems.add();
// draw x-line
var xLine = crosshair.pathItems.add();
xLine.setEntirePath([
[x - width / 2, -y],
[x + width / 2, -y],
]);
xLine.strokeColor = strokeColor;
xLine.stroked = true;
xLine.strokeWidth = strokeWeight;
xLine.filled = false;
// draw y-line
var yLine = crosshair.pathItems.add();
yLine.setEntirePath([
[x, -y + height / 2],
[x, -y - height / 2],
]);
yLine.strokeColor = strokeColor;
yLine.stroked = true;
yLine.strokeWidth = strokeWeight;
yLine.filled = false;
}
})();
... View more
‎Dec 17, 2024
01:17 PM
No, f15 doesn't work for me. Everything up to f14 works on my extended keyboard, but not f15. Maybe a third-party app (i.e. Keyboard Maestro) could catch the event and fire a script that runs the action for you.
... View more
‎Dec 16, 2024
02:37 PM
Your script works fine for me with single and multiple PDFs dropped onto your tkinter window. Maybe try individually dropping each of the PDFs you have selected when you "drop several files" and see if you still get the error. PDF files can be finicky, especially when trying to read them using another library. I have a few Python scripts that process PDF files and every now and then I run across certain PDF files that break my otherwise working scripts. Let us know what you find out. Cheers!
... View more
‎Dec 13, 2024
01:23 PM
There was no need for them to uncheck the stroke checkbox as it doesn't apply to the blend graphic they had created. This is why you get the same result no matter if the box is checked or not. They probably did it out of habit or didn't understand how expand works with blend objects. If you use Object > Blend > Expand, you will notice there are no options, each step object in the blend is expanded as a whole.
If you want to better understand how expand works, following the same tutorial, if you run Object > Expand a second time and check to expand strokes, you will see where each fill and each stroke becomes a separate object.
... View more
‎Dec 05, 2024
01:40 PM
1 Upvote
That is great to hear. If you don't mind, could you mark the answer as correct? Cheers!
... View more
‎Dec 03, 2024
06:48 AM
So, if I test both of the document sizes you listed above with the default settings of the script, everything works just fine. I think your issue is with the dialog inputs and the script calculation. For example, if I only change `min` to 5, the script will create a path item... But as you can see from the path properties in Ai, something wonky is happening...   If you inspect the path item properties in ExtendScript you can see it has 0 path points and this is why nothing is showing in my example. [PathItem ]
area: 0
controlBounds: 8334,-8334,-24432,24432
geometricBounds: 8334,-8334,-24432,24432
height: -32766
hidden: false
left: 8334
length: 0
pathPoints:
length = 0
position: 8334,-8334
selected: true
top: -8334
visibleBounds: 8334,-8334,-24432,24432
width: -32766 Since I don't know your actual inputs, this is all speculation on my part. If you want to share your dialog inputs I can look into it further. Cheers!
... View more
‎Nov 18, 2024
02:44 PM
2 Upvotes
So, in your code above, when you find a clipped groupItem you set `var clippingMask = null;` and then you find the first pathItem that is `clipping` so the value of `clippingMask` is no longer `null`. Then when you check the second page item in your clipped grouped, you haven't reset clippingMask to null so your `if (clippingMask)` check still evaluates to true, firing the alert again for the second time. To fix your code, you can simply move the `var clippingMask = null;` line inside of the `for (var j = 0; j < item.pageItems.length; j++)` loop. You can simplify the code quite a bit like below to get the same result. PLEASE NOTE, there are some wonky edge cases with clipping masks so this may not catch everything single situation. var newLayer = app.activeDocument.layers[0];
var arrayNoFill = [];
var g, s;
for (var i = 0; i < newLayer.groupItems.length; i++) {
g = newLayer.groupItems[i];
if (!g.clipped) continue;
for (var j = 0; j < g.pageItems.length; j++) {
s = g.pageItems[j];
if (s.clipping && !s.filled) {
arrayNoFill.push(s);
}
}
}
alert("Found " + arrayNoFill.length + " unfilled clipping mask(s).");
... View more
‎Nov 11, 2024
11:03 AM
`File.fsName` works for me on a Mac. absoluteURI: /G-DRIVE/test/asu.jpg
fsName: /Volumes/G-DRIVE/test/asu.jpg
fullName: /G-DRIVE/test/asu.jpg
... View more
‎Nov 10, 2024
08:44 PM
Ok, this is what I thought you were describing. The script is working as expected, it is just that the requirements the second time you run it (after adding the new layer) have changed. You could certainly adjust the script to account for this occurrence but it would be (imho) overly complex for its original job. My suggestion would be to either just create the new layer inside of the parent layer of the already merged group, or drag your new layer into the parent after you create and finish working with it. Let me know if you are interested in the more complex version and I'm sure we can work through it here. Cheers!
... View more
‎Nov 08, 2024
09:25 PM
I'm not sure I follow... Are you running the script which "merges" common named layers then independently creating a new layer in the file with same common name as one of the groups, then running the script again? Could you share your file here as a pdf or even do a screen recording so I can follow your steps?
... View more
‎Nov 08, 2024
07:42 PM
There were a few issues with the way you were trying to do things so I wrote some code to accomplish the task you are attempting. A few things to note... There are numerous ways to do this but I tried to follow your initial logic so you can understand the steps There is no real need to store if a layer is locked or hidden since you can just check the property on the actual layer The script below first groups similar layers by name, then does the work of moving them var doc = app.activeDocument;
var groups = groupLayersByName();
// sanity check
for (var i = 0; i < groups.length; i++) {
$.writeln(
"Group " + (i + 1) + "\nName: " + groups[i][0].name + "\nCount: " + groups[i].length
);
}
moveGroupedLayersToNewLayer(groups);
function groupLayersByName() {
var groups = [];
var group, cur, found, comp;
// iterate over each layer
for (var i = 0; i < doc.layers.length; i++) {
cur = doc.layers[i];
found = false;
// iterate over groups of layer with same names
for (var j = 0; j < groups.length; j++) {
group = groups[j];
comp = group[0];
// check if the current layer name matches the group
if (cur.name === comp.name) {
found = true;
group.push(cur);
break;
}
}
// if the current layer didn't match any groups create a new group
if (!found) groups.push([cur]);
}
return groups;
}
function moveGroupedLayersToNewLayer(groups) {
var group, commonName, parent, layer;
// iterate over commong layer groups
// please note the iteration is going in reverse to
// try and keep the inital layer similar (not exact because of method)
for (var i = groups.length - 1; i >= 0; i--) {
group = groups[i];
// skip groups with only one layer
if (group.length === 1) continue;
commonName = group[0].name;
// create a new layer to hold common layers
parent = doc.layers.add();
parent.name = commonName;
// iterate over common layer and move each into new parent layer
// going in reverse again to keep layer order similar
var locked, visible;
for (var j = group.length - 1; j >= 0; j--) {
layer = group[j];
// get layer locked and visible status
locked = layer.locked;
visible = layer.visible;
// unlock and make layer visible for move
layer.locked = false;
layer.visible = true;
// move the layer into the parent layer
layer.move(parent, ElementPlacement.INSIDE);
// reset the layer locked and visible status
layer.locked = locked;
layer.visible = visible;
}
}
} And, to answer your question about a boolean inside of an array, you can certainly store and retrieve a boolean value from an array. There were multiple reasons you were getting tripped up but about the boolean specifically, you were storing an array of an array of the boolean. // you were doing something like
var isLocked = [false}
// then adding that to another array like
{...lockStatus: [isLocked]} // which really equals [[false]]
// so to access the boolean value you would need to go two levels deep
var boolValue = lockStatus[0][0]
... View more
‎Nov 07, 2024
07:48 AM
1 Upvote
You can place the `swatches.getByName()` call in a try block and then check to see if there was an error. I would suggest doing the same thing for the call to `layers.getByName()`. In the code below, if the layer isn't found, the script will create it. You can do something similar for the spot color if you want. Let me know if this works for you? Cheers! // grab reference to the current document
var doc = app.activeDocument;
// deselect all items
doc.selection = null;
// grab reference to the work layer
var layerName = "Dimensions";
var layer;
try {
layer = doc.layers.getByName(layerName);
} catch (e) {
alert("'" + layerName + "' layer not found so it will be created now.");
layer = doc.layers.add();
layer.name = layerName;
}
// grab reference to the "Dimension" color
var colorName = "Dimension";
var color;
try {
color = doc.swatches.getByName(colorName).color;
} catch (e) {
alert("'" + colorName + "' swatch not found!");
// I would typically create the spot color here
color = null;
}
// check to see if the color was found, if so move any matching items
if (color != null) {
// find items stroked with "Dimension" color and move to layer
doc.defaultStrokeColor = color;
app.executeMenuCommand("Find Stroke Color menu item");
moveSelectedItemsToLayer(layer, ElementPlacement.PLACEATEND);
// find items filled with "Dimension" color and move to layer
doc.defaultFillColor = color;
app.executeMenuCommand("Find Fill Color menu item");
moveSelectedItemsToLayer(layer, ElementPlacement.PLACEATEND);
}
// Move any selected item to `layer` at `insertLocation`.
function moveSelectedItemsToLayer(layer, insertLocation) {
var _selectedItems = app.selection;
if (_selectedItems.length == 0) return; // no need to continue
for (var i = _selectedItems.length - 1; i >= 0; i--) {
_selectedItems[i].move(layer, insertLocation);
_selectedItems[i].selected = false;
}
app.redraw();
doc.selection = null;
}
... View more
‎Oct 30, 2024
08:46 AM
1 Upvote
Try this... (function () {
var doc = app.activeDocument;
try {
var _spotColor = doc.spots.getByName("W");
var spotWhite = new SpotColor();
spotWhite.spot = _spotColor;
} catch (e) {
alert("Spot color 'W' not found.\n" + e);
return;
}
var item = doc.selection[0];
item.fillColor = spotWhite;
})();
... View more
‎Oct 29, 2024
11:59 AM
Understood... Was just going for the simplest option first.
... View more
‎Oct 29, 2024
10:16 AM
Why not outline all strokes and unite everything into a single compound shape using the Pathfinder palette? This would only work with single-color designs, like the one in the blog post mentioned.
... View more
‎Oct 23, 2024
07:03 PM
@renél80416020, you have a keen eye and are definitely right. This has come up before to be honest but I wasn't sure how to handle it... I should have picked a different name for the function initially but by the time I realized that it might be confusing (my definition of visible bounds doesn't match the official API) others had found the function on Github and were referencing it by the name `getVisibleBounds` so I didn't want to create confusion by changing it. It is really silly actually, since the function deals exclusively with geometric bounds. It probably should have been called something like `getGeometricBoundsOfClippedItems()`. I was kind of new to scripting when I wrote this function but a long-time Ai user, so to me, the size of an item is the size of the path (excluding any strokes) like displayed in the transform palette. So the reason I choose `visible` was to say the potential visible parts of a clipped collection of items. A fix would be simple so maybe I'll make another utility function that does just that... Thanks for the feedback, I really appreciate it!
... View more
‎Oct 21, 2024
03:01 PM
2 Upvotes
@renél80416020, yes this works perfectly for simple objects where the mask is the first item that encompasses all. The main purpose of the function I linked above is to cover more edge cases. For example, if the selected object is a group that includes a mask along with other objects (like the attached file), the helper function still gives the correct info so that you can calculate a proper translation matrix. Your script probably covers most people's needs, I just needed more coverage for a few more involved scripts I was working on. Cheers!
... View more
‎Oct 21, 2024
10:14 AM
1 Upvote
Correct the position property (and same for the multiple bounds properties). The function linked above returns an array of the provided object bounds [left, top, right, bottom] accounting for the mask. Running the script below in the provided file (with linked function included) presents a dialog with the info from the screenshot. As you can see the hidden ovals are included in the info from the API, whereas the custom function returns what I think you are looking for... var o = app.activeDocument.selection[0];
alert(
"Placement Info\nPosition: " +
o.position +
"\nGeometric Bounds: " +
o.geometricBounds +
"\nVisible Bounds: " +
o.visibleBounds +
"\ngetVisibleBounds: " +
getVisibleBounds(o)
);
... View more
‎Oct 21, 2024
08:05 AM
1 Upvote
Hey, here's a handy function for getting the bounds of objects that accounts for clipping masks and compound paths. Let me know if you have any questions? GetVisibleBounds: https://github.com/joshbduncan/illustrator-scripts/blob/ada54de802cb950f711f2b78ae93e231a426998c/jsx/utils/GetVisibleBounds.jsxinc
... View more
‎Oct 21, 2024
06:59 AM
1 Upvote
This is nice! Thanks for sharing @m1b!
... View more
‎Oct 14, 2024
12:05 PM
1 Upvote
@sttk3, you are the BEST! Commands have been added to my sheet and AiCommandPalette v0.11.3. Thanks for your great work!
... View more
‎Oct 02, 2024
07:15 AM
1 Upvote
Try the `evalFile()` method on the global Dollar ($) object. Documentation. $.evalFile(f)
... View more
‎Sep 30, 2024
09:02 AM
Awesome, I'll get these added to my script. Thanks as always @sttk3!
... View more