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

Scripting Live Effects

Community Expert ,
Jan 11, 2021 Jan 11, 2021

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.)

TOPICS
Scripting

Views

2.9K

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 , Feb 04, 2021 Feb 04, 2021

Get them here: AI Live Effects Functions. (Note that some of the functions don't work as expected: see my notes.)

Votes

Translate

Translate
Adobe
Guide ,
Jan 11, 2021 Jan 11, 2021

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.  

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 ,
Jan 12, 2021 Jan 12, 2021

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.

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 ,
Jan 12, 2021 Jan 12, 2021

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);
}

 

 

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
Guide ,
Jan 12, 2021 Jan 12, 2021

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)*/);

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 ,
Jan 12, 2021 Jan 12, 2021

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! 🙂

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 ,
Jan 12, 2021 Jan 12, 2021

Copy link to clipboard

Copied

you guys are rocking!

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 ,
Jan 12, 2021 Jan 12, 2021

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!):

  • Measurements in points (to match native methods). See this post I just made about handling units.
  • First parameter is the page item. This it to allow flexibility of usage, such as in loops.
  • Don't do anything else in the function except apply the effect (its easy to make wrapper functions to do extra things like set fill or stroke etc.)
  • After a bit of consideration, I think it's better to define the parameters explicitly, as it's more understandable and, again, to match convention of native methods. Having explicit variable names helps when the function is more complex and the parameters won't always match what xml needs (eg. the "colr" of LE_Rasterize is made up of colorType and transparentBackground, and without variable names we get: arg[1] + arg[3] == 0 ? 4 : 0;
  • I like your #1 #2 convention. My approach of repeating the key names added nothing.
  • The way you commented the function call was awesome! For complex functions users will want to copy and paste the calling code too.
  • I've added default values for each parameter, so that the function can be simply called LE_Scribble(item) and it will be applied with the defaults. I suggest choosing Illustrator's own defaults.

 

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);
    }
}

 

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
Guide ,
Jan 12, 2021 Jan 12, 2021

Copy link to clipboard

Copied

Great, as usual. Thanks for all the hard work. 

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 ,
Jan 13, 2021 Jan 13, 2021

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.

 

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
Valorous Hero ,
Jan 13, 2021 Jan 13, 2021

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();

 

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 ,
Jan 13, 2021 Jan 13, 2021

Copy link to clipboard

Copied

Hi @Silly-V this is a big help! Thanks. I'll have a play around...

 

- Mark

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 ,
Jan 14, 2021 Jan 14, 2021

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

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
Valorous Hero ,
Jan 13, 2021 Jan 13, 2021

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.

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 ,
Jan 13, 2021 Jan 13, 2021

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. 🙂

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
Valorous Hero ,
Jan 11, 2021 Jan 11, 2021

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!

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 ,
Jan 12, 2021 Jan 12, 2021

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.

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 ,
Jan 12, 2021 Jan 12, 2021

Copy link to clipboard

Copied

this is awesome Mark! thanks for putting together such comprehensive tutorial. Love the helper tool!

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 ,
Jan 12, 2021 Jan 12, 2021

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.

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 ,
Jan 12, 2021 Jan 12, 2021

Copy link to clipboard

Copied

that is a wonderful idea!!

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 ,
Jan 25, 2021 Jan 25, 2021

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

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
Participant ,
Jan 25, 2021 Jan 25, 2021

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 🙂

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 ,
Jan 25, 2021 Jan 25, 2021

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.)

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 ,
Feb 04, 2021 Feb 04, 2021

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 compressionScreen Shot 2021-01-12 at 2.08.20 pm.png

 

 

 

 

 

 

 

 

 

 

3. Open that uncompressed .ai document in a text editorScreen Shot 2021-01-12 at 2.04.16 pm.png

It looks like an impossible mess... at first.

 

 

 

 

4. Search for words that might match the Live Effect nameScreen Shot 2021-01-12 at 2.05.29 pm.png

 

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 Screen Shot 2021-01-12 at 2.07.03 pm.png

 

 

 

 

 

 

6. Use this quick-and-dirty helper tool I made.

7. Paste the text copied from step 4 into the entry field Screen Shot 2021-01-12 at 2.11.26 pm.png

 

 

 

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 stringAnatomy of a Live Effect XML string

 

anatomy-of-raw-ai-text.png

 dialog-to-raw-markup.png

 

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.

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 ,
Jun 11, 2024 Jun 11, 2024

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

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