Skip to content

Instantly share code, notes, and snippets.

@cwgw
Last active June 6, 2023 22:30
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cwgw/5d4aa43350c7df9babb1d9f6ee459232 to your computer and use it in GitHub Desktop.
Save cwgw/5d4aa43350c7df9babb1d9f6ee459232 to your computer and use it in GitHub Desktop.
A simple script for Adobe Illustrator CC that draws Metatron's Cube to your active artboard.
/**
* Title: Metatron's Cube
* Author: Charlie Wright
*
* A simple script for Adobe Illustrator CC that draws Metatron's Cube to your active artboard.
* To use, select File > Scripts > Other Script... then navigate to and open this file.
*
* For Illustrator scripting reference, see the official documentation at:
* https://www.adobe.com/devnet/illustrator/scripting.html
*/
var MetatronsCube = function() {
/* if there is no open document, then bail early */
if (app.documents.length < 1) {
alert("You must have an open document to use this script.");
return null;
}
/* get options */
var options = getUserInput();
/* make it! */
if (options) {
return drawCube(options);
}
/**
* getUserInput
*
* Prompts user to provide parameters with which to draw the cube.
*
* @return {Object} Options object formatted for use by drawCube()
*/
function getUserInput() {
/* create a new dialog window for our inputs */
var w = new Window("dialog", "Metatron's Cube")
/* input labels and default values */
var inputs = {
majorRadius: {
label: 'Major Radius',
defaultValue: 90,
},
minorRadius: {
label: 'Minor Radius',
defaultValue: 45,
},
iterationCount: {
label: 'Depth',
defaultValue: 2,
},
sideCount: {
label: 'Sides',
defaultValue: 6,
},
};
/* add an input group, label and text field for each input item */
for (item in inputs) {
if (inputs.hasOwnProperty(item)) {
var inputGroup = w.add("group");
inputGroup.alignment = "right";
inputGroup.add("statictext", undefined, inputs[item].label+":");
/* save the edittext control so we can retrieve its value later */
inputs[item].value = inputGroup.add("edittext", undefined, inputs[item].defaultValue);
inputs[item].value.characters = 12;
}
}
/* add "OK" and "Cancel" buttons to the dialog */
var submitGroup = w.add("group");
submitGroup.alignment = "right";
var cancel = submitGroup.add("button", undefined, "Cancel");
var submit = submitGroup.add("button", undefined, "OK")
/* make OK button focused as is convention */
submit.active = true;
/* on "OK" return user input */
if (w.show() === 1) {
var params = {};
/* parse and cleanup input values */
for (item in inputs) {
if (inputs.hasOwnProperty(item)) {
if (inputs[item].value && inputs[item].value.text) {
params[item] = parseInt(inputs[item].value.text);
}
}
}
return params;
}
return false
}
/**
* getArtboardCenterPoint
*
* Returns the coords of the center of a given artboard
*
* The property artboardRect is an array-like object of the form [ x1, y1, x2, y2 ]
* where (x1,y1) represents the top-left corner of the artboard and (x2,y2) the
* bottom-right corner.
*
* @param {Object} artboard An Illustrator document artboard
*
* @return {array} A coordinate pair of the form [ x, y ]
*/
function getArtboardCenterPoint(artboard) {
var rect = artboard.artboardRect;
return [
(rect[2] - rect[0]) / 2 + rect[0],
(rect[3] - rect[1]) / 2 + rect[1]
];
}
/**
* drawCube
*
* Draw the thing!
*
* @param {Object} o Options
* @param {Object} o.doc Illustrator Document
* @param {Array} o.center Coords to be used as the center of the cube
* @param {Number} o.majorRadius The primary unit of sizing for the cube (how far apart the circles are).
* @param {Number} o.minorRadius The secondary unit of sizing for the cube (how big the circles are).
* @param {Number} o.sideCount The number of sides of the base polygon. This is
* traditionally six, but feel free to change it.
* @param {Number} o.iterationCount The depth of the cube. Traditionally, Metatron's cube is
* drawn as two concentric hexagons with an ellipse at each
* vertex (and one in the center). Wanna see it with three
* or four or n concentric hexagons? Why not?
*
* @return {Object} A GroupItem containing the new Metatron's cube, its
* constituent parts separated into sub-groups.
*/
function drawCube(o) {
var options = {};
options.doc = o.doc || app.activeDocument;
options.center = o.center || getArtboardCenterPoint(options.doc.artboards[options.doc.artboards.getActiveArtboardIndex()]);
options.majorRadius = o.majorRadius || 90;
options.minorRadius = o.minorRadius || options.majorRadius / 2;
options.iterationCount = o.iterationCount || 2;
options.sideCount = o.sideCount || 6;
/* at the document level, turn off fill and turn on stroke */
options.doc.defaultFilled = false;
options.doc.defaultStroked = true;
/* the group that will hold everything */
var rootGroup = options.doc.groupItems.add();
rootGroup.name = 'MetatronsCube';
/* the group of original polygons (hexagons) */
var polyGroup = rootGroup.groupItems.add();
polyGroup.name = 'BaseShapes';
/* the group of ellipses */
var ellipseGroup = rootGroup.groupItems.add();
ellipseGroup.name = 'Ellipses';
/* the group of lines */
var lineGroup = rootGroup.groupItems.add();
lineGroup.name = 'Lines';
/* an array for all of our vertices */
var points = [];
/* create a bunch of ellipses */
for (var k = options.iterationCount; k > 0; k--) {
/* start with a polygon with sides equal to options.sides, radius equal to options.majorRadius and center at position options.center */
var poly = polyGroup.pathItems.polygon(options.center[0], options.center[1], options.majorRadius * k, options.sideCount);
/* create a group within ellipseGroup to hold the ellipses at each iteration */
var ellipseSubGroup = ellipseGroup.groupItems.add();
/* at each vertex in poly, create an ellipse with diameter equal to options.minorRadius */
for (var l = 0; l < poly.pathPoints.length; l++) {
var point = poly.pathPoints[l].anchor;
/* args for the ellipse method are (top, left, width, height) */
var ellipse = ellipseSubGroup.pathItems.ellipse(
point[1] + (options.minorRadius),
point[0] - (options.minorRadius),
options.minorRadius * 2,
options.minorRadius * 2
);
ellipse.rotate((360 / options.sideCount) * (l + 1));
/* save this vertex for later */
points.push(poly.pathPoints[l])
}
}
/* add one last ellipse at the center */
ellipseGroup.pathItems.ellipse(
options.center[1] + (options.minorRadius),
options.center[0] - (options.minorRadius),
options.minorRadius * 2,
options.minorRadius * 2
);
/* instantiate the var that will point to each of our groups of lines */
var lineSubGroup;
/* draw a line between each point in our points array and every other point in the array */
for (var i = 0; i < points.length; i++) {
/* create a new group within lineGroup for each major iteration */
lineSubGroup = i % options.sideCount === 0 ? lineGroup.groupItems.add() : lineSubGroup;
for (var j = points.length - 1; j > i; j--) {
/* the PathItem method setEntirePath accepts an array of coordinate pairs ([x, y]) and connects them with a line */
lineSubGroup.pathItems.add().setEntirePath([
[ points[i].anchor[0], points[i].anchor[1] ],
[ points[j].anchor[0], points[j].anchor[1] ]
]);
}
}
rootGroup.rotate(360 / (options.sideCount * 2));
/* let's select the whole thing for ease of use */
rootGroup.selected = true;
return rootGroup;
}
}()
@katieredman
Copy link

This was a dope find. Thanks for sharing your work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment