Copy link to clipboard
Copied
I've made a suite of javascript convenience functions to make it much easier to apply Live Effects in your scripts.
Get them here: AI Live Effects Functions. (Note that some of the functions don't work as expected: see my notes.)
I've had a lot of help from others on this site. Most of the information here has been compiled from knowledge shared by several other forum users, especially @CarlosCanto (with initial help from Adobe's Sanjay K) and @Silly-V.
Let me know what you think. - Mark
Edit: My original post was explaining the underlying process to work out the structure of the applyEffect XML parameter, but now that I've made these functions, most people won't need to worry about all that detail and should find the functions much easier to use. (For anyone interested, I've pasted the original notes in later as a reply.)
Get them here: AI Live Effects Functions. (Note that some of the functions don't work as expected: see my notes.)
Copy link to clipboard
Copied
Thanks. This is great, particularly the helper tool. I would like, when I have time, to go through the effects systematically and try each. Thanks again.
Copy link to clipboard
Copied
That is a good idea. You've got me thinking about maybe making a suite of functions that hide away the whole XML business and accept normal parameters. It wouldn't be difficult. I think I'll make a prototype for one or two effects and see what they are like.
Copy link to clipboard
Copied
Here's a few example functions, what do you think? I like them. I'll make some more, when I next get time. Feel free to make some too if you'd like!
function LE_Feather(item, radius) {
radius = radius || 10;
var xml = '<LiveEffect name="Adobe Fuzzy Mask"><Dict data="R Radius #Radius# "/></LiveEffect>'
.replace(/#Radius#/, radius);
item.applyEffect(xml);
}
function LE_Offset(item, offset, joinType, miterlimit) {
// joinTypes: 0 = Round, 1 = Bevel , 2 = Miter
offset = offset || 10;
joinType = joinType || 1;
miterlimit = miterlimit || 2;
var xml = '<LiveEffect name="Adobe Offset Path"><Dict data="R mlim #mLim# R ofst #ofst# I jntp #jntp# "/></LiveEffect>'
.replace(/#ofst#/, offset)
.replace(/#jntp#/, joinType)
.replace(/#mLim#/, miterlimit);
item.applyEffect(xml);
}
function LE_Rasterize(item, dpi, colorType, transparentBackground, paddingInPts, antialiasType, clipMask) {
// colorTypes: 0 = RGB, 1 = CMYK, 2 = Grayscale, 3 = Bitmap
// antialiasTypes: 0 = none, 1 = art optimizes, 2 = type optimized
offset = dpi || 72;
colorType = colorType || 0;
transparentBackground = transparentBackground || true;
paddingInPts = paddingInPts || 0;
antialiasType = antialiasType || 0;
clipMask = clipMask || 0;
var xml = '<LiveEffect name="Adobe Rasterize"><Dict data="I colr #colr# B alis #alis# I dpi. #dpi# B mask #mask# R padd #padd# I optn #optn# "/></LiveEffect>'
.replace(/#colr#/, colorType + (transparentBackground ? 4 : 0))
.replace(/#alis#/, antialiasType > 0 ? 1 : 0)
.replace(/#dpi#/, Math.round(dpi))
.replace(/#mask#/, clipMask ? 1 : 0)
.replace(/#padd#/, paddingInPts)
.replace(/#optn#/, antialiasType == 2 ? 16 : 0);
item.applyEffect(xml);
}
Copy link to clipboard
Copied
Cool. It's not particularly pretty, but here's a scribble function.
// select pathItem
// uncomment "stroked" and "filled" as applicable
function scribble() {
// selection[0].stroked = false;
// selection[0].filled = false;
xml = '<LiveEffect name="Adobe Scribble Fill"><Dict data="R Spacing #6 R Angle #0 R Scribbliness #4 R StrokeWidth #3 R EdgeOverlap #1 R ScribbleVariation #5 R SpacingVariation #7 R EdgeOverlapVariation #2"/></LiveEffect>'.replace(/#6/, arguments[6]*72).replace(/#0/, arguments[0]).replace(/#4/, arguments[4]/100).replace(/#3/, arguments[3]*72).replace(/#1/, arguments[1]*72).replace(/#5/, arguments[5]/100).replace(/#7/, arguments[7]*72).replace(/#2/, arguments[2]*72);
selection[0].applyEffect(xml);
}
scribble(30 /*angle (0-360)*/,
0 /*pathOverlap (-13.89-13.89 in)*/,
0.07 /*pathOverlapVariation (0-13.89 in)*/,
0.04 /*strokeWidth (0-13.89 in) */,
5 /*curviness (0-100%)*/,
1 /*curvinessVariation (0-100%)*/,
0.07 /*spacing (0-13.89 in)*/,
0.01 /*spacingVariation (0-13.89 in)*/);
Copy link to clipboard
Copied
Yeah! I like the way you've structured that. And I'd never have thought of throwing *too many* parameters at a function that wasn't defined to accept them. Javascript is very relaxed that way! That's given me some food for thought. Thanks. I'll be back! 🙂
Copy link to clipboard
Copied
you guys are rocking!
Copy link to clipboard
Copied
Hi @femkeblanco, I've rearranged your scribble code a little. I'm not too fussed about coding style so long as its consistent across all the functions. The changes I've made are more for handling things more generally.
Here's my initial thinking (feel free to improve!):
Well here's the scribble code, modifed as I described above. Let me know what you think. - Mark
function LE_Scribble(item, angle, pathOverlap, pathOverlapVariation, strokeWidth, curviness, curvinessVariation, spacing, spacingVariation) {
try {
if (item == undefined) throw new Error ('LE_Scribble: no item');
angle = angle != undefined ? angle : 30;
pathOverlap = pathOverlap != undefined ? pathOverlap : 0;
pathOverlapVariation = pathOverlapVariation != undefined ? pathOverlapVariation : 5;
strokeWidth = strokeWidth != undefined ? strokeWidth : 3;
curviness = curviness != undefined ? curviness : 5;
curvinessVariation = curvinessVariation != undefined ? curvinessVariation : 1;
spacing = spacing != undefined ? spacing : 5;
spacingVariation = spacingVariation != undefined ? spacingVariation : 0.5;
xml = '<LiveEffect name="Adobe Scribble Fill"><Dict data="R Spacing #7 R Angle #1 R Scribbliness #5 R StrokeWidth #4 R EdgeOverlap #2 R ScribbleVariation #6 R SpacingVariation #8 R EdgeOverlapVariation #3"/></LiveEffect>'
.replace(/#1/, angle)
.replace(/#2/, pathOverlap)
.replace(/#3/, pathOverlapVariation)
.replace(/#4/, strokeWidth)
.replace(/#5/, curviness / 100)
.replace(/#6/, curvinessVariation / 100)
.replace(/#7/, spacing)
.replace(/#8/, spacingVariation);
item.applyEffect(xml);
} catch (error) {
alert(error);
}
}
Copy link to clipboard
Copied
Great, as usual. Thanks for all the hard work.
Copy link to clipboard
Copied
I've got the bit between my teeth! I'm thinking of doing one filter every morning before work.
I've made a 'final' tweak to the design of the functions. I wanted to make the default values neater (this problem goes away in newer versions of javascript) so I've made a helper function called defaults. It's a shame to have an extra function required, but after a bit of pondering I think it's nicer. Anyway the defaults function can be put inside the individual function if user prefers. Feel free to show me a better way of course.
Here's two examples (and defaults function at end):
function LE_Scribble(item, angle, pathOverlap, pathOverlapVariation, strokeWidth, curviness, curvinessVariation, spacing, spacingVariation) {
try {
eval(
defaults([
'angle', 30,
'pathOverlap', 0,
'pathOverlapVariation', 5,
'strokeWidth', 3,
'curviness', 5,
'curvinessVariation', 1,
'spacing', 5,
'spacingVariation', 0.5
])
);
if (item == undefined) throw new Error('LE_Scribble: no item');
xml = '<LiveEffect name="Adobe Scribble Fill"><Dict data="R Spacing #7 R Angle #1 R Scribbliness #5 R StrokeWidth #4 R EdgeOverlap #2 R ScribbleVariation #6 R SpacingVariation #8 R EdgeOverlapVariation #3"/></LiveEffect>'
.replace(/#1/, angle)
.replace(/#2/, pathOverlap)
.replace(/#3/, pathOverlapVariation)
.replace(/#4/, strokeWidth)
.replace(/#5/, curviness / 100)
.replace(/#6/, curvinessVariation / 100)
.replace(/#7/, spacing)
.replace(/#8/, spacingVariation);
item.applyEffect(xml);
} catch (error) {
alert(error);
}
}
function LE_Offset(item, offset, joinType, miterlimit) {
// joinTypes: 0 = Round, 1 = Bevel , 2 = Miter
try {
eval(
defaults([
'offset', 10,
'joinType', 2,
'miterlimit', 4
])
)
if (item == undefined) throw new Error('LE_Offset: no item');
var xml = '<LiveEffect name="Adobe Offset Path"><Dict data="R mlim #3 R ofst #1 I jntp #2 "/></LiveEffect>'
.replace(/#1/, offset)
.replace(/#2/, joinType)
.replace(/#3/, miterlimit);
item.applyEffect(xml);
} catch (error) {
alert(error);
}
}
function defaults(arr) {
var defaults = '';
for (var i = 0; i < arr.length; i += 2) {
defaults += (arr[i] + '=' + arr[i] + '!=undefined?' + arr[i] + ':' + arr[i + 1] + ';');
}
return defaults;
}
Edit: removed extraneous parts of code examples, and added a line I forgot.
Copy link to clipboard
Copied
Here is my take on the default function. Using some regex we can get the argument names and going through the arguments object we can get the values. If an object is supplied which has some arguments inside with default values and if the actual argument is not defined, the object is left alone - otherwise an object's default value is overriden by the value of the argument.
In the first function the whole default-setting procedure is just in the function.
In the 2nd function, it uses a helper function that takes in a default object as well as the parent function and its arguments as the next parameters. This is very much like your approach, but it uses no string-building or eval.
#target illustrator
function test () {
function getCustomString (itemArg, userName, email, phone) {
var paramObj = {
userName : "Bob",
email : "bob@bob.com",
phone : null
};
var argNames = getCustomString.toString().match(/function\s[a-z0-9_]+\s?\((([a-z0-9_]\s?,?\s?)+)\)/i)[1].split(/\s?,\s?/g);
var thisArg, thisArgName;
for (var i = 0; i < arguments.length; i++) {
thisArg = arguments[i];
thisArgName = argNames[i];
if (!(thisArgName in paramObj)) {
continue;
}
if (typeof(thisArg) != "undefined") {
paramObj[thisArgName] = arguments[i];
}
}
var templateString = "User name: #userName#\nEmail: #email#\nPhone: #phone#";
for (var all in paramObj) {
templateString = templateString.replace("#" + all + "#", paramObj[all]);
}
return templateString;
}
alert(getCustomString("SomeItem", "Suzy", undefined, "000.222.2323"));
//==============================================================================================//
function setDefaults (defaultObj, func, args) {
var argNames = func.toString().match(/function\s[a-z0-9_]+\s?\((([a-z0-9_]\s?,?\s?)+)\)/i)[1].split(/\s?,\s?/g);
var thisArg, thisArgName;
for (var i = 0; i < args.length; i++) {
thisArg = args[i];
thisArgName = argNames[i];
if (!(thisArgName in defaultObj)) {
continue;
}
if (typeof(thisArg) != "undefined") {
defaultObj[thisArgName] = args[i];
}
}
return defaultObj;
}
function getCustomString_2 (itemArg, userName, email, phone) {
var paramObj = setDefaults(
{
userName : "Bob",
email : "bob@bob.com",
phone : null
},
arguments.callee,
arguments
);
var templateString = "User name: #userName#\nEmail: #email#\nPhone: #phone#";
for (var all in paramObj) {
templateString = templateString.replace("#" + all + "#", paramObj[all]);
}
return templateString;
}
alert(getCustomString_2("SomeItem", "Suzy", undefined, "000.222.2323"));
};
test();
Copy link to clipboard
Copied
Copy link to clipboard
Copied
Hi again @Silly-V , I've had a play, and implemented your second approach, re-jigged a bit for my learning benefit, and I think it'll be really good for this project. Your help is tremendous!
Here are the same examples from before, but with the new approach to defaults:
function LE_Scribble(item, angle, pathOverlap, pathOverlapVariation, strokeWidth, curviness, curvinessVariation, spacing, spacingVariation) {
try {
var defaults = {
angle: 30,
pathOverlap: 0,
pathOverlapVariation: 5,
strokeWidth: 3,
curviness: 5,
curvinessVariation: 1,
spacing: 5,
spacingVariation: 0.5
};
if (item == undefined) throw new Error('LE_Scribble: no item');
with (getArgsObject(arguments, arguments.callee, defaults)) {
xml = '<LiveEffect name="Adobe Scribble Fill"><Dict data="R Spacing #7 R Angle #1 R Scribbliness #5 R StrokeWidth #4 R EdgeOverlap #2 R ScribbleVariation #6 R SpacingVariation #8 R EdgeOverlapVariation #3"/></LiveEffect>'
.replace(/#1/, angle)
.replace(/#2/, pathOverlap)
.replace(/#3/, pathOverlapVariation)
.replace(/#4/, strokeWidth)
.replace(/#5/, curviness / 100)
.replace(/#6/, curvinessVariation / 100)
.replace(/#7/, spacing)
.replace(/#8/, spacingVariation);
}
item.applyEffect(xml);
} catch (error) {
alert(error);
}
}
function LE_Offset(item, offset, joinType, miterlimit) {
try {
var defaults = {
offset: 10,
joinType: 2, // joinTypes: 0 = Round, 1 = Bevel , 2 = Miter
miterlimit: 4
};
if (item == undefined) throw new Error('LE_Offset: no item');
with (getArgsObject(arguments, arguments.callee, defaults)) {
var xml = '<LiveEffect name="Adobe Offset Path"><Dict data="R mlim #3 R ofst #1 I jntp #2 "/></LiveEffect>'
.replace(/#1/, offset)
.replace(/#2/, joinType)
.replace(/#3/, miterlimit);
}
item.applyEffect(xml);
} catch (error) {
alert(error);
}
}
function getArgsObject(args, func, defaults) {
var argNames = func.toString().match(/function\s[a-z0-9_]+\s?\((([a-z0-9_]\s?,?\s?)+)\)/i)[1].split(/\s?,\s?/g);
for (var i = 0; i < args.length; i++) {
if ((argNames[i] in defaults) && (args[i] != undefined)) {
defaults[argNames[i]] = args[i];
}
}
return defaults;
}
I might have made the code look a bit obscure in places, but the important bits are super easy to understand.
Any comments welcome! I'm nearly there I think.
- Mark
Copy link to clipboard
Copied
Here is an example of a validation object. This technique will validate arguments and produce error messages when users put in bad values. The validation object contains methods and properties that it uses for its own functionality, but at the bottom are key-value objects which are keyed by the function name and have properties which are keyed by the function's argument names. Those property objects have some generic checking properties such as 'regex' and 'min', 'max', etc. Since a lot of these would be duplicated and this validation object can grow very big, some custom or elaborate validations such as the "email" field example could be extracted to stand-alone named functions which can then be assigned to the 'parsingFunction' property. Otherwise, the collection of per-property validations could be put into a separate object in its own file.
#target illustrator
function test () {
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(searchElement, fromIndex) {
var k;
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
var len = o.length >>> 0;
if (len === 0) {
return -1;
}
var n = +fromIndex || 0;
if (Math.abs(n) === Infinity) {
n = 0;
}
if (n >= len) {
return -1;
}
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
while (k < len) {
if (k in o && o[k] === searchElement) {
return k;
}
k++;
}
return -1;
};
};
const FruitTypes = {
APPLE : "apple",
BANANA : "banana"
};
const ShapeTypes = {
CIRCLE : 0,
SQUARE : 1,
TRIANGLE : 2
};
const StringKeyValidations = {
debugLevel : 0,
validationLog : [],
notifyMessage : function (str, func) {
this.validationLog.push(func.name + "() : " + str);
if (this.debugLevel == 1) {
$.writeln(str);
} else if (this.debugLevel == 2) {
alert(str);
}
},
getPropValues : function (obj) {
var arr = [];
for (var all in obj) {
arr.push(obj[all]);
}
return arr;
},
validateProperty : function (func, propName, val) {
var funcName = func.name;
var prop = this[funcName][propName];
var STRING = "string";
var NUMBER = "number";
var MIN = "min";
var MAX = "max";
var MAXLENGTH = "maxLength";
var MINLENGTH = "minLength";
var REGEX = "regex";
var PARSINGFUNCTION = "parsingFunction";
var thisValueType = typeof(val);
if (prop.type == STRING) {
if (thisValueType != STRING) {
this.notifyMessage("Value of '" + propName + "' is expected to be '" + STRING + "', got '" + thisValueType + "'", func);
return false;
}
if (MAXLENGTH in prop) {
var maxLength = prop[MAXLENGTH];
if (val.length > maxLength) {
this.notifyMessage("Length of '" + propName + "' is expected to be at most '" + maxLength + "', got '" + val.length + "'", func);
return false;
}
}
if (MINLENGTH in prop) {
var minLength = prop[MINLENGTH];
if (val.length < minLength) {
this.notifyMessage("Length of '" + propName + "' is expected to be at least '" + minLength + "', got '" + val.length + "'", func);
return false;
}
}
if (REGEX in prop) {
var regex = prop[REGEX];
if (!regex.test(val)) {
this.notifyMessage("'" + propName + "' does not pass regular expression test '" + regex + "'.", func);
return false;
}
}
} else if (prop.type == "number") {
if (thisValueType != NUMBER) {
this.notifyMessage("Value of '" + propName + "' is expected to be '" + NUMBER + "', got '" + thisValueType + "'", func);
return false;
}
if (MIN in prop) {
var min = prop[MIN];
if (val < min) {
this.notifyMessage("'" + propName + "' is expected to be at least '" + min + "', got '" + val + "'", func);
return false;
}
}
if (MAX in prop) {
var max = prop[MAX];
if (val > max) {
this.notifyMessage("'" + propName + "' is expected to be at most '" + max + "', got '" + val + "'", func);
return false;
}
}
} else if (typeof(prop.type) == "object") {
// enum check
var validValues = this.getPropValues(prop.type);
if (validValues.indexOf(val) == -1) {
this.notifyMessage("'" + propName + "' value '" + val + "' is not among expected values: '" + validValues.join(", ") + "'", func);
return false;
}
}
if (PARSINGFUNCTION in prop) {
var parsedResult = prop[PARSINGFUNCTION](val);
if (!parsedResult.status) {
this.notifyMessage(parsedResult.message, func);
return false;
}
}
return true;
},
//============================================================ validations for each function ================================================//
getCustomString_3 : {
userName : {
type : "string",
maxLength : 20
},
email : {
type : "string",
parsingFunction : function (str) {
var result = { status : true, message : "Success" };
var isValid = str.indexOf("@") > -1;
if (!isValid) {
result.message = ("This should be a valid email.");
result.status = false;
return result;
}
return result;
}
},
phone : {
type : "string",
regex : /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/i
}
},
getCustomString_4 : {
fruitChoice : {
type : FruitTypes
},
shapeChoice : {
type : ShapeTypes
},
someAmount : {
type : "number",
min : 1,
max : 100
}
}
};
function setDefaultsWithValidation (defaultObj, func, args) {
var argNames = func.toString().match(/function\s[a-z0-9_]+\s?\((([a-z0-9_]\s?,?\s?)+)\)/i)[1].split(/\s?,\s?/g);
var thisArg, thisArgName, isValid;
for (var i = 0; i < args.length; i++) {
thisArg = args[i];
thisArgName = argNames[i];
if (!(thisArgName in defaultObj)) {
continue;
}
if (typeof(thisArg) != "undefined") {
isValid = StringKeyValidations.validateProperty(func, thisArgName, thisArg);
if (!isValid) {
continue;
}
defaultObj[thisArgName] = args[i];
}
}
return defaultObj;
}
function getCustomString_3 (itemArg, userName, email, phone) {
var paramObj = setDefaultsWithValidation(
{
userName : "Bob",
email : "bob@bob.com",
phone : null
},
arguments.callee,
arguments
);
var templateString = "User name: #userName#\nEmail: #email#\nPhone: #phone#";
for (var all in paramObj) {
templateString = templateString.replace("#" + all + "#", paramObj[all]);
}
return templateString;
}
alert(getCustomString_3("SomeItem", "Suzy", undefined, "000.222.2323")); // ok
alert(getCustomString_3("SomeItem", "Suzy", "bad_email_with_no_at", "000.222.2323")); // validation error
alert(getCustomString_3("SomeItem", "Suzy", "suzy@aol.com", "000.2-----22.2323")); // validation error
function getCustomString_4 (itemArg, fruitChoice, shapeChoice, someAmount) {
var paramObj = setDefaultsWithValidation(
{
fruitChoice : "apple",
shapeChoice : 0,
someAmount : 1
},
arguments.callee,
arguments
);
var templateString = "Fruit Choice: #fruitChoice#\nShape Choice: #shapeChoice#\nSome Amount: #someAmount#";
for (var all in paramObj) {
templateString = templateString.replace("#" + all + "#", paramObj[all]);
}
return templateString;
}
alert(getCustomString_4("SomeItem", "banana", 2, 10)); // ok
alert(getCustomString_4("SomeItem", "watermelon", 5, -10)); // validation error
alert(StringKeyValidations.validationLog.join("\n"));
};
test();
Plus, the default object can now be moved into the validation object using some property such as ".default".
The functions can be augmented so that when it does not validate, the default value is pulled from the validation property object.
Copy link to clipboard
Copied
That's a nice example! I'll have a good look at that. My feeling is that a comprehensive validation system might be overkill for this little project, but I'll look into it. Again... thanks for the help; it'll be great to look through and try to understand. 🙂
Copy link to clipboard
Copied
Wow, what an in-depth tutorial! Great work, this is going to be one of the legendary posts.
Also, thank you for building that tool. It's super-useful and will help anyone get to using live effect strings in no-time!
Copy link to clipboard
Copied
Thanks! I did notice that when I searched the forum for Live effect script solutions there was quite a lot, but nowhere was a central topic, and it required careful reading of many threads to glean the knowledge. Yeah, I found the helper tool useful too. It isn't complete, as I'm sure there will be dictionary entries that it doesn't handle. Although it's already pretty good at parsing the basics. Let me know when it fails and I will update.
Copy link to clipboard
Copied
this is awesome Mark! thanks for putting together such comprehensive tutorial. Love the helper tool!
Copy link to clipboard
Copied
Thank you! I mean... thank you guys for passing on what you learned about this function. I'd honestly never come across it before. I really got into it! 🙂 Actually, I don't think I've got it out of my system yet either, lol. I'm talking to femkeblanco about making a set of functions to wrap all this xml stuff in, so no-one needs to reinvent the wheel.
Copy link to clipboard
Copied
that is a wonderful idea!!
Copy link to clipboard
Copied
@CarlosCanto , @Silly-V , @femkeblanco,
Well I have done it! Bit by bit, I made the functions. Now I hope anyone can use them (also improve them—quite a few don't work yet).
Overall I'm pretty happy with them. I'm sure there'll be bugs to fix etc. And I've only tested roughly on my version of Illustrator.
If you get the chance, have a look at the repo here. The notes on which functions worked and which didn't are here.
I was thinking of editing my initial post here to point people to the new functions—after all that's the whole point of this post. Do you think that would be make sense?
- Mark
Copy link to clipboard
Copied
Thank you for such a detailed explanation and repo for us to look at.
I have always wondered about the Live Effects but just pretended they did not exist for the most part!
Will have a read through the repo and see what I can learn 🙂
Copy link to clipboard
Copied
Thanks! I hope its useful. Really I'm just organising all the work others had previously done and shared to make it easier (I hope *much* easier) for people to use in their own scripts. If I've succeeded, it means I've pretty much made the topic of this post obsolete. I mean: I don't expect people to parse raw .ai file markup. Most people should try the functions first! So I'll need to edit the intial post.
(By the way, I've just added multi-item support.)
Copy link to clipboard
Copied
For anyone interested in the workings out..
How to derive the XML string parameter:
1. Make a blank document (in the color space you want), draw a square and apply the live effect manually, and configure it the way you want for your script.
2. Save an .ai document without compression
3. Open that uncompressed .ai document in a text editor
It looks like an impossible mess... at first.
4. Search for words that might match the Live Effect name
eg, 'roughen' or, alternatively, you can search for "(Adobe".
Cycle through the found instances until you see the one most promising.
5. Select text from /BasicFilter down until the end of the property lines
6. Use this quick-and-dirty helper tool I made.
7. Paste the text copied from step 4 into the entry field
8. The helper tool will parse that text and display the result on the right. Click "Copy XML to clipboard".
9. Paste the xml into your script! Your script might look something like this:
var xml = '<yourPastedStringHere>';
var item = app.activeDocument.selection[0];
item.applyEffect(xml);
Done!
Further Info
Here are some cheat sheets graphics:
Anatomy of a Live Effect XML string
There's still A LOT we don't know, so please share if you discover something, or even better add to the github repository!
- Mark
EDIT 2024-06-11: updated dead link to old bl.ocks.org gist.
Copy link to clipboard
Copied
Hi @ Synder37927574fian, I have fixed the link now. The old one went dead when github moved gists. You can examine the tool, but bear in mind that it isn't a proper XML parser; I just use a bunch of RegExp to parse what I needed. - Mark