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

# Custom rounded rectangle function

Community Expert ,
Jul 18, 2022 Jul 18, 2022

Copy link to clipboard

Copied

Ok... So just like any petulant, stubborn child.... I don't like being told what I can't do... And  I've decided i'm no longer going to simply accept that we can't set our own custom corner radii when creating a roundedRectangle... Only being able to change "horizontalRadius" and "verticalRadius" isn't enough, because unless you use the same value for each, the corners will not really be round and the shape won't resemble a rounded rectangle, but rather a bloated and/or stretched rounded rectangle.

To that end... Here's a function i just threw together that lets you create a rectangle at whatever dimensions you want, and lets you pass in an array of "corner radii" listed clockwise from top left to bottom left. So, if you need a rectangle at [0,0] and 200pt wide and 400pt tall with rounded corners of the radii [20,10,25,50] for whatever reason, you could create one with this function call:

``customRoundedRectangle(app.activeDocument, 0,0,200,400,[20,10,25,50]);``

the first argument is the "parent" object. The container in which you want the rectangle to be created. This could be the document, a layer, a group, a compound path.. (though i think this will need some refinement to properly handle making something inside a clipping mask.. otherwise the pathfinder logic won't work properly, i suspect.. I'll get to it). The next 4 arguments are the standard rectangle arguments (top, left, width, height). Then the last argument is the array of corner radii, again sorted clockwise from top left.

Here's the code:

**Updated 23 August, 2022**

Made all arguments optional. undefined arguments will get default values.

Removed the logic that set the fill color of the shape. now the shape will inherit the document default fill/stroke. and the shape is returned by the function so it can easily be styled after the fact.

Still need to add some logic to check for radii that are too large.. this seems to break things in interesting ways..

**End of Update**

``````//Custom Rounded Rectangle Generator
//The built in roundedRectangle() method only allows for changing
//"verticalRadius" and "horizontalRadius".. But if you use different
//values for these, the corners are not truly round... they'll be asymptotic
//and the values given will apply to both left and right sides or top and bottom
//so you can't change the corners individually
//This customRoundedRectangle() function allows for each corner to have a separate
//corner radius that is truly rounded and maintains the integrity of the rectangle's existing side

//author: William Dowling
//email: illustrator.dev.pro@gmail.com
//github: https://www.github.com/wdjsdev
//this project on github: https://github.com/wdjsdev/public_illustrator_scripts/blob/master/custom_rounded_rectangle.js
//Adobe Discussion Forum Post that initiated this: https://community.adobe.com/t5/illustrator-discussions/rounded-rectangle/td-p/13076613

//*******//

//Did you find this useful? Would you like to buy me a cup (or a pot) of coffee to say thanks?
//paypal.me/illustratordev
//<3

//Do you have some work to do, but you have more money than time/skill?
//Send me an email or a dm! I'll see what we can do to help each other..

//*******//

//arguments:
//[y] optional: y (top) coordinate of the rectangle (Number, in points)
//default value: 0

//[x] optional: x (left) coordinate of the rectangle  (Number, in points)
//default value: 0

//[width] optional: width of the rectangle (Number, in points)
//default value: 100

//[height] optional: height of the rectangle (Number, in points)
//default value: 100

//[radii] optional: array of 4 numbers or number (in points)
//array of radii clockwise from top left corner
//default value: [10,10,10,10]

//if only one number is given, it is applied to all corners

//[parent] optional: the parent container object inside which to create the rectangle
//default value: app.activeDocument
function customRoundedRectangle ( y, x, width, height, radii, parent )
{

//verify open document//
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
if ( !app.documents.length )
{
alert( "Please open a document and try again." );
return;
}

///////////////////////////////////////////////////////////////////////////
//sanitize inputs//
///////////////////////////////////////////////////////////////////////////

var errorInputs = [];
if ( radii && radii.constructor.toString().match( /array/i ) )
{
if ( radii.length == 1 )
{
//user probably just wanted to use one value for all corners
//but used an array with one element instead of just a number
//no problem. just use that value for all corners
}
else if ( radii.length !== 4 )
{
//user gave an array with more or less than 4 elements
\$.writeln( "You must provide 4 radii values or a single number for all radii." );
return;
}
}
else if ( typeof radii == "number" )
{
//user just gave one number for all radii
//use that number for all corners
}

//set some default values for any argument that is not provided:

if ( !parent || parent.toString().match( /document/i ) )
{
parent = app.activeDocument.layers[ 0 ];
}
parent.locked = false;
parent.visible = true;
parent.hidden = false;
y = y || 0;
x = x || 0;
width = width || 100;
height = height || 100;
radii = radii || [ 10, 10, 10, 10 ];

typeof y == "number" ? y : errorInputs.push( "y" );
typeof x == "number" ? x : errorInputs.push( "x" );
typeof width == "number" ? width : errorInputs.push( "width" );
typeof height == "number" ? height : errorInputs.push( "height" );

//make sure the parent is a group/layer/docdument
if ( !parent.typename.match( /group|layer|doc/i ) )
{
\$.writeln( "Can't make a rounded rectangle within a parent that isn't a group, layer, or document." );
return;
}

if ( errorInputs.length )
{
var errorString = "The following arguments were not valid:\n";
errorInputs.forEach( function ( arg )
{
errorString += arg + ", ";
} )
errorString += "\n Please ensure these arguments are numbers (int or float)."
return;
}

///////////////////////////////////////////////////////////////////////////
//finished sanitizing inputs//
///////////////////////////////////////////////////////////////////////////

//this is the function call that actually
//does the work. I wrapped the logic in a function
//to avoid variable scope issues and accessing
//Array.forEach before it was defined.
//But i still want the function logic to be at the
//top of the file so it's easier to read and
//the dependencies at the bottom and out of the way.
return makeRect( y, x, width, height, radii, parent );

}

///////////////////////////////////////////////////////////////////////////
//dependencies:
///////////////////////////////////////////////////////////////////////////

function makeRect ( y, x, width, height, radii, parent )
{
var doc = app.activeDocument;
var swatches = doc.swatches;
var resultingShape = null;

//make a temp sandbox layer to hold the rectangle
//and make for easy identification of the resulting rectangle
//at the end, we'll move it inside the parent
var sandboxLayer = doc.layers.add();

//draw the base rectangle
//for now, use whatever fill/stroke is currently selected
//todo: make appearance customizable
//more arguments?? i don't love it...
//maybe an "options" object? then the user/dev could have a list of preset options to easily pass in
//should support plain fill/stroke and graphic styles
var rect = sandboxLayer.pathItems.rectangle( y, x, width, height );
var rectData = { width: rect.width, height: rect.height, l: rect.left, t: rect.top, r: rect.left + rect.width, b: rect.top - rect.height };
var cp = sandboxLayer.compoundPathItems.add();
//create an ellipse for each radius
{
if ( radius === 0 )
{
//don't make an ellipse for this.. this corner should remain as is.
return;
}
var diameter = radius * 2;
var ellipse = cp.pathItems.ellipse( y, x, diameter, diameter );

//ellipse has been created to size, now move it to the correct corner
switch ( i )
{
case 0:
//this is the top left.. no need to move anything
break;
case 1:
//this is the top right corner
ellipse.left = rectData.r - diameter;
break;
case 2:
//this is the bottom right corner
ellipse.left = rectData.r - diameter;
ellipse.top = rectData.b + diameter;
break;
case 3:
//this is the bottom left corner
ellipse.top -= rectData.height - diameter;
}
} );

//load pathfinder actions into actions panel
createAction( "pathfinder", getActionString() );

doc.selection = null;
cp.selected = rect.selected = true;
app.doScript( "divide", "pathfinder" );

//look for the "corner"s of the rectangle...
//the part that's outside our desired radius
afc( doc.selection[ 0 ], "pageItems" ).forEach( function ( item )
{
var area = Math.abs( item.area );
var calcArea = item.width * item.height;
if ( area < ( calcArea * .6 ) )
{
item.remove();
}
} );

//all that's left should be the base rectangle and
//the ellipses for the corner radii. unite them all
//together into a single pathItem.
app.doScript( "unite", "pathfinder" );

//remove pathfinder actions from actions panel
removeAction( "pathfinder" );

resultingShape = sandboxLayer.pageItems[ 0 ];
resultingShape.moveToBeginning( parent );

sandboxLayer.remove();
return resultingShape;
}

//array from container
//get all elements of the type "crit" inside the container
//and return as a standard javascript array
function afc ( container, crit )
{
var result = [];
var items;
if ( !crit || crit === "any" )
{
items = container.pageItems;
} else
{
items = container[ crit ];
}
for ( var x = 0; x < items.length; x++ )
{
result.push( items[ x ] )
}
return result;
}

//Array.forEach() prototype for easily looping arrays
//this helps eliminate scope issues because variables
//remain contained inside the function block. almost
//kind of imitating "let" in modern javascript.
//Plus, you don't need to worry about pre-declaring
//variables outside the loop to prevent mrap/parm errors.
//args:
//callback: the function to call for each element
//startPos: the index to start at (defaults to 0)
//inc: the increment to move by (defaults to 1)
Array.prototype.forEach = function ( callback, startPos, inc )
{
if ( !inc ) inc = 1;
if ( !startPos ) startPos = 0;
for ( var i = startPos; i < this.length; i += inc )
callback( this[ i ], i, this );
};

//create and load a new action
function createAction ( name, actionString )
{
var documentsPath = "~/Documents/Adobe_Script_Helpers/";
var dest = Folder( documentsPath );
if ( !dest.exists )
{
dest.create();
}

var actionFile = new File( decodeURI( dest + "/" + name + ".aia" ) );

actionFile.open( "width" );
actionFile.write( actionString );
actionFile.close();

}

//remove all instances of an action with a given name
function removeAction ( actionName )
{
var localValid = true;

while ( localValid )
{
try
{
app.unloadAction( actionName, "" );
}
catch ( e )
{
localValid = false;
}
}
}

function getActionString ()
{
//pathfinder action string. this will create an
//action set containing all of the pathfinder options.
//this is much more reliable than app.executeMenuCommand()
//which uses "live pathfinder" effects which don't always work
//and need to be expanded.
var pathfinderActionString = [
"/version 3",
"/name [ 10",
"	7061746866696e646572",
"]",
"/isOpen 1",
"/actionCount 10",
"/action-1 {",
"	/name [ 5",
"		756e697465",
"	]",
"	/keyIndex 0",
"	/colorIndex 0",
"	/isOpen 0",
"	/eventCount 1",
"	/event-1 {",
"		/internalName (ai_plugin_pathfinder)",
"		/localizedName [ 10",
"			5061746866696e646572",
"		]",
"		/isOpen 0",
"		/isOn 1",
"		/hasDialog 0",
"		/parameterCount 1",
"		/parameter-1 {",
"			/key 1851878757",
"			/showInPalette 4294967295",
"			/type (enumerated)",
"			/name [ 3",
"				416464",
"			]",
"			/value 0",
"		}",
"	}",
"}",
"/action-2 {",
"	/name [ 11",
"		6d696e75735f66726f6e74",
"	]",
"	/keyIndex 0",
"	/colorIndex 0",
"	/isOpen 0",
"	/eventCount 1",
"	/event-1 {",
"		/internalName (ai_plugin_pathfinder)",
"		/localizedName [ 10",
"			5061746866696e646572",
"		]",
"		/isOpen 0",
"		/isOn 1",
"		/hasDialog 0",
"		/parameterCount 1",
"		/parameter-1 {",
"			/key 1851878757",
"			/showInPalette 4294967295",
"			/type (enumerated)",
"			/name [ 8",
"				5375627472616374",
"			]",
"			/value 3",
"		}",
"	}",
"}",
"/action-3 {",
"	/name [ 9",
"		696e74657273656374",
"	]",
"	/keyIndex 0",
"	/colorIndex 0",
"	/isOpen 0",
"	/eventCount 1",
"	/event-1 {",
"		/internalName (ai_plugin_pathfinder)",
"		/localizedName [ 10",
"			5061746866696e646572",
"		]",
"		/isOpen 0",
"		/isOn 1",
"		/hasDialog 0",
"		/parameterCount 1",
"		/parameter-1 {",
"			/key 1851878757",
"			/showInPalette 4294967295",
"			/type (enumerated)",
"			/name [ 9",
"				496e74657273656374",
"			]",
"			/value 1",
"		}",
"	}",
"}",
"/action-4 {",
"	/name [ 7",
"		6578636c756465",
"	]",
"	/keyIndex 0",
"	/colorIndex 0",
"	/isOpen 1",
"	/eventCount 1",
"	/event-1 {",
"		/internalName (ai_plugin_pathfinder)",
"		/localizedName [ 10",
"			5061746866696e646572",
"		]",
"		/isOpen 0",
"		/isOn 1",
"		/hasDialog 0",
"		/parameterCount 1",
"		/parameter-1 {",
"			/key 1851878757",
"			/showInPalette 4294967295",
"			/type (enumerated)",
"			/name [ 7",
"				4578636c756465",
"			]",
"			/value 2",
"		}",
"	}",
"}",
"/action-5 {",
"	/name [ 6",
"		646976696465",
"	]",
"	/keyIndex 0",
"	/colorIndex 0",
"	/isOpen 1",
"	/eventCount 1",
"	/event-1 {",
"		/internalName (ai_plugin_pathfinder)",
"		/localizedName [ 10",
"			5061746866696e646572",
"		]",
"		/isOpen 0",
"		/isOn 1",
"		/hasDialog 0",
"		/parameterCount 1",
"		/parameter-1 {",
"			/key 1851878757",
"			/showInPalette 4294967295",
"			/type (enumerated)",
"			/name [ 6",
"				446976696465",
"			]",
"			/value 5",
"		}",
"	}",
"}",
"/action-6 {",
"	/name [ 4",
"		7472696d",
"	]",
"	/keyIndex 0",
"	/colorIndex 0",
"	/isOpen 1",
"	/eventCount 1",
"	/event-1 {",
"		/internalName (ai_plugin_pathfinder)",
"		/localizedName [ 10",
"			5061746866696e646572",
"		]",
"		/isOpen 0",
"		/isOn 1",
"		/hasDialog 0",
"		/parameterCount 1",
"		/parameter-1 {",
"			/key 1851878757",
"			/showInPalette 4294967295",
"			/type (enumerated)",
"			/name [ 4",
"				5472696d",
"			]",
"			/value 7",
"		}",
"	}",
"}",
"/action-7 {",
"	/name [ 5",
"		6d65726765",
"	]",
"	/keyIndex 0",
"	/colorIndex 0",
"	/isOpen 1",
"	/eventCount 1",
"	/event-1 {",
"		/internalName (ai_plugin_pathfinder)",
"		/localizedName [ 10",
"			5061746866696e646572",
"		]",
"		/isOpen 0",
"		/isOn 1",
"		/hasDialog 0",
"		/parameterCount 1",
"		/parameter-1 {",
"			/key 1851878757",
"			/showInPalette 4294967295",
"			/type (enumerated)",
"			/name [ 5",
"				4d65726765",
"			]",
"			/value 8",
"		}",
"	}",
"}",
"/action-8 {",
"	/name [ 4",
"		63726f70",
"	]",
"	/keyIndex 0",
"	/colorIndex 0",
"	/isOpen 1",
"	/eventCount 1",
"	/event-1 {",
"		/internalName (ai_plugin_pathfinder)",
"		/localizedName [ 10",
"			5061746866696e646572",
"		]",
"		/isOpen 0",
"		/isOn 1",
"		/hasDialog 0",
"		/parameterCount 1",
"		/parameter-1 {",
"			/key 1851878757",
"			/showInPalette 4294967295",
"			/type (enumerated)",
"			/name [ 4",
"				43726f70",
"			]",
"			/value 9",
"		}",
"	}",
"}",
"/action-9 {",
"	/name [ 7",
"		6f75746c696e65",
"	]",
"	/keyIndex 0",
"	/colorIndex 0",
"	/isOpen 1",
"	/eventCount 1",
"	/event-1 {",
"		/internalName (ai_plugin_pathfinder)",
"		/localizedName [ 10",
"			5061746866696e646572",
"		]",
"		/isOpen 0",
"		/isOn 1",
"		/hasDialog 0",
"		/parameterCount 1",
"		/parameter-1 {",
"			/key 1851878757",
"			/showInPalette 4294967295",
"			/type (enumerated)",
"			/name [ 7",
"				4f75746c696e65",
"			]",
"			/value 6",
"		}",
"	}",
"}",
"/action-10 {",
"	/name [ 10",
"		6d696e75735f6261636b",
"	]",
"	/keyIndex 0",
"	/colorIndex 0",
"	/isOpen 1",
"	/eventCount 1",
"	/event-1 {",
"		/internalName (ai_plugin_pathfinder)",
"		/localizedName [ 10",
"			5061746866696e646572",
"		]",
"		/isOpen 0",
"		/isOn 1",
"		/hasDialog 0",
"		/parameterCount 1",
"		/parameter-1 {",
"			/key 1851878757",
"			/showInPalette 4294967295",
"			/type (enumerated)",
"			/name [ 10",
"				4d696e7573204261636b",
"			]",
"			/value 4",
"		}",
"	}",
"}"
].join( "\n" );

return pathfinderActionString;
}

//shorter function name if you'd rather use that
//use any name that makes sense to you. custRoundRect.. whatever. It's yours to use
//in whatever way you feel improves your process.
function crr ( y, x, width, height, radii, parent )
{
return customRoundedRectangle( y, x, width, height, radii, parent );
}

//sample function calls:
//parent as activeDocument
// customRoundedRectangle(0, 0, 200, 400, [20, 10, 25, 50],app.activeDocument);

//parent as specific groupItem inside specific layer
//you could also of course set a variable to the parent you want to use
//and then simply pass that variable as the argument.
// customRoundedRectangle(0, 0, 200, 400, [20, 10, 25, 50],app.activeDocument.layers[1].groupItems[0]);

//undefined parent. this will just default to activeDocument
//feel free to adjust the code on line 19 to use whatever default values you'd like
// customRoundedRectangle(0, 0, 200, 400, [20, 10, 25, 50]);

//in fact, all arguments are optional. the following will create a rounded rectangle
//at position [0,0] with a width/height of 100 and radii of 10 on each corner.
// customRoundedRectangle();

//test function to see if the script is working
function testRoundedRectangle ()
{
Array.prototype.forEach = function ( callback, startPos, inc )
{
if ( !inc ) inc = 1;
if ( !startPos ) startPos = 0;
for ( var i = startPos; i < this.length; i += inc )
callback( this[ i ], i, this );
};
var doc = app.activeDocument;
var w = h = 400;
var testData = {
"no args": [ undefined, undefined, undefined, undefined, undefined, undefined ],
"explicit to activeDocument": [ 450, 0, w, h, [ 20, 10, 25, 50 ], doc ],
"explicit to groupItem": [ 900, 0, w, h, [ 100, 50, 0, 20 ], doc.layers[ 0 ].groupItems[ 0 ] ],
"radii int": [ 2250, 200, w, h, 100, doc ],
}

var inputs;
// var args;
var rect;

for ( var key in testData )
{
\$.writeln( "testing: " + key );
inputs = [];
// args = testData[ key ];
testData[ key ].forEach( function ( arg )
{
inputs.push( arg );
} )
rect = customRoundedRectangle( inputs[ 0 ], inputs[ 1 ], inputs[ 2 ], inputs[ 3 ], inputs[ 4 ], inputs[ 5 ] );
if ( rect )
{
rect.name = key;
}
}
}

testRoundedRectangle();
``````

TOPICS
Scripting , Tools

Views

495

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
12 Replies 12
Community Expert ,
Jul 19, 2022 Jul 19, 2022

Copy link to clipboard

Copied

Hi William, thanks for sharing!

I've got an error though, r.forEach is not a function on line 67. Moving the function definition above the function call takes care of the error. I don't remember having to do that before. Can you restart illustrator and give it another try?

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 Expert ,
Jul 19, 2022 Jul 19, 2022

Copy link to clipboard

Copied

hmmm. strange..? I thought javascript treated functions as priority and "hoists" them to the top when the interpreter runs regardless of where it is in the code..

I've never had that problem before... (definitely had it with variables.. but not functions..) perhaps prototypes work differently than regular functions? I'll reorganize it and update the post and the github page.

Thanks for letting me know. 😃

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 Expert ,
Jul 19, 2022 Jul 19, 2022

Copy link to clipboard

Copied

Ok. I wrapped up the main logic in a function and then placed that function call at the bottom to ensure everything is declared and defined before the logic runs.

I wanted to keep the main logic at the top for readability, and then just kind of bury the dependencies below. In my own workflow, all those dependencies exist in a master "utilities container" that i include in every script, so these kinds of scope issues are never a problem because all this logic simply gets added to the beginning of every script. But unfortunately that's not a super portable solution, so I combined everything so this function would be self sufficient. And now i learned something new. haha. or relearned something i learned before and forgot becasue it didn't affect me.

I appreciate you, Carlos.

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 Expert ,
Jul 19, 2022 Jul 19, 2022

Copy link to clipboard

Copied

yeah that's what I thought, that functions could be anywhere but I guess not. I think functions inside functions don't follow that rule?

now we know I think 🙂

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 Expert ,
Jul 19, 2022 Jul 19, 2022

Copy link to clipboard

Copied

Hmm. ok. Some quick google fu and a bit of reading has properly edified me. functions and variables are hoisted at runtime... expressions and properties are not.

I guess technically Array.prototype.forEach = function(){//do thing}; is a PROPERTY of the array object, even though it looks/acts like a function.

If i had instead just created a standalone function like this: function forEach(array,myFunc){for(items in array){myFunc(cur item from array)}} then it would be properly hoisted and preclude this type of error. But personally i prefer the prototype and i have no issue using wrapper functions to avoid these situations. Plus, that's an extra argument i gotta type out. who's got time for that?! 😜 (the answer to this question? github copilot... wow.)

I have several other improvements to this on the way as well. default values available so that every argument is actually optional. argument sanitization to ensure no runtime errors when the main logic runs. a temporary sandbox layer to use for creating the shapes and doing all the pathfinder stuff just to ensure we don't accidentally mess with any existing artwork. plus a tiny testing framework for experimenting with any possible arguments.

stay tuned

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Guide ,
Jul 19, 2022 Jul 19, 2022

Copy link to clipboard

Copied

I think the problem was that forEach is a not global function, but a property of the prototype object. And like any property, it will not work before it is defined.  That is why polyfills are best put at the top.

``````var object1 = {};
object1.function1 = function () {alert("");};
object1.function1();  // will work

var object2 = {};
object2.function1();  // will not work
object2.function1 = function () {alert("");};``````

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 Expert ,
Jul 19, 2022 Jul 19, 2022

Copy link to clipboard

Copied

exactly right. prototype functions are properties and not functions as i assumed. But i think your example is a better demonstration of the fact that expressions aren't hoisted. writing functions like you've done here makes them technically expressions instead of properties or functions.

I intentionally wanted to bury the dependencies so that when you open the code, the meaningful code is right there at the top. I know i find it frustrating when I open up some code i didn't write and i have to search around to figure out where the actual logic occurs.

Using a simple wrapper function for the main logic and putting the function call at the end, after all the dependencies, did the trick nicely and i still get my way in terms of having the main logic front and center.

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Guide ,
Jul 19, 2022 Jul 19, 2022

Copy link to clipboard

Copied

They are assignment expressions or expression statements.  But I know what you mean.  The only things that gets hoisted is a declaration statement.  I.e.

``````var a;
function b() {};``````

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 Expert ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

Hi @Disposition_Dev, just saw this. Thanks for sharing! Great script.

- Mark

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 Expert ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

Hey, thanks Mark. I appreciate your kind words. I have more improvements in
the pipeline. But unfortunately (or fortunately depending on how you look
at it) at the moment I have more freelance stuff than I know what to do
with. So I gotta prioritize the paid gigs over the handouts.😋

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 Expert ,
Aug 23, 2022 Aug 23, 2022

Copy link to clipboard

Copied

bumping for update exposure. I've made this better.. i think. You let me know if you think it's better too.

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Guide ,
Aug 23, 2022 Aug 23, 2022

Copy link to clipboard

Copied

LATEST

@Disposition_Dev  Not that I am competing with you, but here's a roundedRectangle() function I wrote.  It has the same syntax as rectangle(), but with an added corner radius argument.