Adobe Illustrator is a powerhouse for vector graphics, but creating precise geometric shapes like triangles often requires manual calculations or drawing tools that aren’t always straightforward.
Enter Triangle Maker (Unified UI), a script that simplifies the process by allowing you to generate triangles using mathematical inputs such as sides and angles.
Based on earlier scripts by Carlos Canto and enhanced by Copilot, this tool supports two primary methods: Side, Angle, Angle (SAA) and Side, Side, Angle (SSA).
In this post, we’ll explore what Triangle Maker does, how to install and use it, and why it’s a game-changer for designers working on logos, diagrams, or architectural illustrations.
What is Triangle Maker (Unified UI)?
Triangle Maker is a JavaScript script for Adobe Illustrator that automates the creation of triangles based on trigonometric principles. Unlike the basic triangle tool,
which requires you to drag and adjust manually, this script lets you input specific measurements and angles to generate accurate shapes instantly.
It supports two construction methods:
Side, Angle, Angle (SAA): Specify one side length and two angles to define the triangle.
Side, Side, Angle (SSA): Provide two side lengths and one angle between them.
The script also includes features like unit conversion (points, millimeters, inches, pixels), positioning options (origin, artboard center, or custom coordinates),
and basic styling (stroke and fill). A preview mode lets you see the triangle before committing, ensuring precision without trial and error.
How to Install and Use Triangle Maker
Installation
Download the script file (typically saved as a .jsx file).
Place it in your Illustrator Scripts folder (usually located at C:\Program Files\Adobe\Adobe Illustrator [version]\Presets\Scripts\ on Windows or Applications/Adobe Illustrator [version]/Presets/Scripts/ on macOS).
Restart Illustrator or run it via File > Scripts > Other Script.
Using the Script
Open a Document: Ensure you have an Illustrator document open.
Run the Script: Execute it from the Scripts menu. A dialog box will appear.
Select Triangle Type: Choose SAA or SSA from the dropdown.
Choose Units: Select pt, mm, in, or px.
Enter Inputs:
For SAA: Side length, Angle 1, Angle 2 (sum must be < 180°).
For SSA: Side 1, Side 2, Angle (between 0° and 180°).
Set Positioning: Place at origin, center on artboard, or specify custom X/Y coordinates.
Style Options: Toggle stroke (with width) and fill.
Preview: Enable preview to see a live outline before creating.
Create: Click “Create” to add the triangle to your artboard.
The script handles errors gracefully, alerting you if inputs are invalid (e.g., angles don’t form a valid triangle). It also preserves Illustrator’s coordinate system, with Y+ pointing up.
Pro Tips for Designers
Unit Flexibility: Use pixels for web graphics or millimeters for print—conversions are automatic.
Precision Work: Ideal for technical drawings where exact angles and sides are crucial.
Styling Integration: Apply gradients or patterns after creation using Illustrator’s tools.
Batch Creation: Run multiple times for complex compositions, like tessellations.
Benefits for Illustrators and Designers
Triangle Maker saves time and reduces guesswork in creating mathematically accurate triangles. It’s perfect for:
Geometric Art: Build patterns or mandalas with precise angles.
Diagrams and Charts: Illustrate mathematical concepts or data visualizations.
Logos and Icons: Design symmetrical or angled elements quickly.
Architectural Sketches: Prototype structures with accurate proportions.
With its unified UI and preview feature, it’s user-friendly for beginners while powerful for pros. The script’s error handling prevents common pitfalls, making it reliable for professional workflows.
Common Use Cases and Examples
Imagine designing a logo with an equilateral triangle—input 100pt sides and 60° angles in SAA mode. Or, for a right-angled triangle in SSA, specify sides 50pt and 70pt with a 90° angle.
The script ensures the output is spot-on, ready for further editing or export.
In conclusion, Triangle Maker (Unified UI) bridges the gap between math and design in Illustrator. If you’re tired of imprecise shapes, this script is worth adding to your toolkit.
Experiment with it on your next project and share your creations in the comments!
Disclaimer: Test scripts in a non-production environment. Backup your work before running
/**
* Triangle Maker (Unified UI)
* - Side, Angle, Angle (SAA)
* - Side, Side, Angle (SSA)
*
* Based on scripts by Carlos Canto (11/21/13)
* Consolidated and enhanced by Copilot
*
* Notes:
* - Units: pt (Illustrator native), mm, in, px (assumed 1 px = 1 pt at 72ppi)
* - Positioning: at origin, center on artboard, or custom X/Y
* - Preview mode: toggles live preview before creating the final triangle
* - Basic styling: stroke on/off, width, fill on/off
*/
(function () {
if (app.documents.length === 0) {
alert("Please open a document before running Triangle Maker.");
return;
}
var doc = app.activeDocument;
// ========= Utilities =========
function degToRad(deg) { return deg * Math.PI / 180; }
// Returns [x, y] from a side length and angle (degrees) from the +X axis
function getPointFromSideAngle(side, angleDeg) {
var r = degToRad(angleDeg);
var x = Math.cos(r) * side;
var y = Math.sin(r) * side; // Illustrator y+ is up (consistent with trig)
return [x, y];
}
// Given two angles, return the third interior angle
function getThirdAngle(a1, a2) { return 180 - a1 - a2; }
// Compliment used by original approach
function getTempAngle(thirdAngle) { return 90 - thirdAngle; }
// Returns the opposite side in the complimentary Right Angle Triangle
function getOpposite(adjacent, angleDeg) {
return Math.tan(degToRad(angleDeg)) * adjacent;
}
function mmToPt(mm) { return mm * 72 / 25.4; }
function inToPt(i) { return i * 72; }
function pxToPt(px) { return px; } // assume 1 px = 1 pt
function toPoints(val, unit) {
if (isNaN(val)) return NaN;
switch (unit) {
case 'pt': return val;
case 'mm': return mmToPt(val);
case 'in': return inToPt(val);
case 'px': return pxToPt(val);
default: return val;
}
}
function centerPath(pathItem) {
var wc = doc.width / 2;
var hc = doc.height * -0.5; // Illustrator coordinate system requires negative half height
pathItem.position = [wc, hc];
}
function setStyle(pathItem, style) {
// Stroke
pathItem.stroked = style.strokeEnabled;
if (style.strokeEnabled) {
pathItem.strokeWidth = style.strokeWidth;
// default: black stroke
var black = new GrayColor();
black.gray = 100;
pathItem.strokeColor = black;
}
// Fill
pathItem.filled = style.fillEnabled;
if (style.fillEnabled) {
// default: none or white fill
var white = new GrayColor();
white.gray = 0;
pathItem.fillColor = white;
} else {
var noFill = new NoColor();
pathItem.fillColor = noFill;
}
pathItem.closed = true;
}
function removeIfExists(item) {
try { if (item && item.remove) item.remove(); } catch (_) {}
}
// Build triangle via SAA: inputs side, angle1, angle2
function buildTriangleSAA(side, angle1, angle2) {
if (!(angle1 + angle2 < 180)) {
throw new Error("For SAA, angle1 + angle2 must be less than 180.");
}
var p0 = [0, 0];
var p1 = getPointFromSideAngle(side, angle1);
// Original construction:
// Drop a vertical from p1 to base (y=0), solve horizontal distance via right triangle
var C = getThirdAngle(angle1, angle2);
var teta = getTempAngle(C);
var adj = p1[1]; // y from p0 to p1
var opo = getOpposite(adj, teta);
var p2x = p1[0] + opo;
var p2 = [p2x, 0];
return [p0, p1, p2];
}
// Build triangle via SSA: inputs side1 (base), side2 (from p0 at 'angle'), angle
function buildTriangleSSA(side1, side2, angle) {
if (!(angle > 0 && angle < 180)) {
throw new Error("For SSA, angle must be between 0 and 180 degrees (non-inclusive).");
}
var p0 = [0, 0];
var p1 = [side1, 0];
var p2 = getPointFromSideAngle(side2, angle);
return [p0, p1, p2];
}
function drawTriangle(points, opts) {
var p = doc.pathItems.add();
p.setEntirePath(points);
if (opts.positioning.center) {
centerPath(p);
} else if (opts.positioning.custom) {
p.position = [opts.positioning.x, opts.positioning.y];
}
setStyle(p, opts.style);
return p;
}
// ========= UI =========
var dlg = new Window("dialog", "Triangle Maker");
dlg.alignChildren = "fill";
// Type
var typePanel = dlg.add("panel", undefined, "Triangle Type");
typePanel.orientation = "row";
typePanel.margins = 10;
typePanel.alignChildren = ["left", "top"];
var typeDropdown = typePanel.add("dropdownlist", undefined, [
"Side, Angle, Angle (SAA)",
"Side, Side, Angle (SSA)"
]);
typeDropdown.selection = 0;
// Units
var unitsPanel = dlg.add("panel", undefined, "Units");
unitsPanel.orientation = "row";
unitsPanel.margins = 10;
var unitsDropdown = unitsPanel.add("dropdownlist", undefined, ["pt", "mm", "in", "px"]);
unitsDropdown.selection = 0;
// Inputs container
var inputsStack = dlg.add("group");
inputsStack.orientation = "stack";
inputsStack.alignChildren = "fill";
// SAA Inputs
var saaGroup = inputsStack.add("panel", undefined, "Inputs: SAA (Side, Angle1, Angle2)");
saaGroup.orientation = "row";
saaGroup.margins = 10;
var saaCol1 = saaGroup.add("group");
saaCol1.orientation = "column";
saaCol1.alignChildren = ["left", "center"];
var sideSAAGroup = saaCol1.add("group");
sideSAAGroup.add("statictext", undefined, "Side:");
var sideSAAField = sideSAAGroup.add("edittext", undefined, "120");
sideSAAField.characters = 8;
var angle1Group = saaCol1.add("group");
angle1Group.add("statictext", undefined, "Angle 1 (deg):");
var angle1Field = angle1Group.add("edittext", undefined, "30");
angle1Field.characters = 8;
var angle2Group = saaCol1.add("group");
angle2Group.add("statictext", undefined, "Angle 2 (deg):");
var angle2Field = angle2Group.add("edittext", undefined, "57");
angle2Field.characters = 8;
// SSA Inputs
var ssaGroup = inputsStack.add("panel", undefined, "Inputs: SSA (Side1, Side2, Angle)");
ssaGroup.orientation = "row";
ssaGroup.margins = 10;
var ssaCol1 = ssaGroup.add("group");
ssaCol1.orientation = "column";
ssaCol1.alignChildren = ["left", "center"];
var side1Group = ssaCol1.add("group");
side1Group.add("statictext", undefined, "Side 1:");
var side1Field = side1Group.add("edittext", undefined, "100");
side1Field.characters = 8;
var side2Group = ssaCol1.add("group");
side2Group.add("statictext", undefined, "Side 2:");
var side2Field = side2Group.add("edittext", undefined, "120");
side2Field.characters = 8;
var angleSSAGroup = ssaCol1.add("group");
angleSSAGroup.add("statictext", undefined, "Angle (deg):");
var angleSSAField = angleSSAGroup.add("edittext", undefined, "30");
angleSSAField.characters = 8;
// Positioning
var posPanel = dlg.add("panel", undefined, "Positioning");
posPanel.orientation = "column";
posPanel.margins = 10;
posPanel.alignChildren = ["left", "top"];
var originRadio = posPanel.add("radiobutton", undefined, "Place at origin (0, 0)");
var centerRadio = posPanel.add("radiobutton", undefined, "Center on artboard");
var customPosGroup = posPanel.add("group");
var customRadio = customPosGroup.add("radiobutton", undefined, "Custom position:");
customPosGroup.add("statictext", undefined, "X:");
var posXField = customPosGroup.add("edittext", undefined, "0");
posXField.characters = 6;
customPosGroup.add("statictext", undefined, "Y:");
var posYField = customPosGroup.add("edittext", undefined, "0");
posYField.characters = 6;
centerRadio.value = true;
// Style
var stylePanel = dlg.add("panel", undefined, "Style");
stylePanel.orientation = "column";
stylePanel.margins = 10;
var strokeGroup = stylePanel.add("group");
var strokeCheckbox = strokeGroup.add("checkbox", undefined, "Stroke");
strokeCheckbox.value = true;
strokeGroup.add("statictext", undefined, "Width:");
var strokeWidthField = strokeGroup.add("edittext", undefined, "1");
strokeWidthField.characters = 4;
var fillGroup = stylePanel.add("group");
var fillCheckbox = fillGroup.add("checkbox", undefined, "Fill");
fillCheckbox.value = false;
// Preview + buttons
var btns = dlg.add("group");
btns.alignment = "right";
var previewCheckbox = btns.add("checkbox", undefined, "Preview");
var createBtn = btns.add("button", undefined, "Create", { name: "ok" });
var cancelBtn = btns.add("button", undefined, "Cancel", { name: "cancel" });
// Toggle inputs stack visibility
function updateInputsVisibility() {
var isSAA = typeDropdown.selection && typeDropdown.selection.index === 0;
saaGroup.visible = isSAA;
ssaGroup.visible = !isSAA;
dlg.layout.layout(true);
}
typeDropdown.onChange = function () {
updateInputsVisibility();
refreshPreviewIfEnabled();
};
updateInputsVisibility();
// Track preview path to clean/redraw
var previewPath = null;
function getOptions() {
var units = unitsDropdown.selection.text;
var positioning = {
center: centerRadio.value,
custom: customRadio.value,
x: toPoints(parseFloat(posXField.text), units),
y: toPoints(parseFloat(posYField.text), units)
};
if (customRadio.value && (isNaN(positioning.x) || isNaN(positioning.y))) {
throw new Error("Custom position X and Y must be valid numbers.");
}
var style = {
strokeEnabled: strokeCheckbox.value,
strokeWidth: Math.max(0, parseFloat(strokeWidthField.text) || 0),
fillEnabled: fillCheckbox.value
};
return {
units: units,
positioning: positioning,
style: style
};
}
function buildPointsFromUI() {
var isSAA = typeDropdown.selection.index === 0;
var units = unitsDropdown.selection.text;
if (isSAA) {
var side = toPoints(parseFloat(sideSAAField.text), units);
var a1 = parseFloat(angle1Field.text);
var a2 = parseFloat(angle2Field.text);
if (isNaN(side) || side <= 0) throw new Error("SAA: Side must be a positive number.");
if (isNaN(a1) || isNaN(a2)) throw new Error("SAA: Angles must be valid numbers.");
if (!(a1 + a2 < 180)) throw new Error("SAA: Angle1 + Angle2 must be less than 180.");
return buildTriangleSAA(side, a1, a2);
} else {
var s1 = toPoints(parseFloat(side1Field.text), units);
var s2 = toPoints(parseFloat(side2Field.text), units);
var ang = parseFloat(angleSSAField.text);
if (isNaN(s1) || s1 <= 0) throw new Error("SSA: Side1 must be a positive number.");
if (isNaN(s2) || s2 <= 0) throw new Error("SSA: Side2 must be a positive number.");
if (isNaN(ang) || !(ang > 0 && ang < 180)) throw new Error("SSA: Angle must be between 0 and 180.");
return buildTriangleSSA(s1, s2, ang);
}
}
function buildPreviewStyle() {
return {
strokeEnabled: true,
strokeWidth: 0.75,
fillEnabled: false
};
}
function refreshPreviewIfEnabled() {
if (!previewCheckbox.value) return;
try {
var points = buildPointsFromUI();
// Remove existing preview
removeIfExists(previewPath);
// Draw preview with preview style and chosen positioning
var opts = getOptions();
var previewOpts = {
positioning: opts.positioning,
style: buildPreviewStyle()
};
previewPath = drawTriangle(points, previewOpts);
previewPath.name = "Triangle_Maker_Preview";
} catch (e) {
// Clean any stale preview and ignore errors while typing
removeIfExists(previewPath);
previewPath = null;
}
}
// Wire up change handlers for live preview
previewCheckbox.onClick = refreshPreviewIfEnabled;
// Generic onChange helpers
function addLivePreviewHandlers(edit) {
edit.onChanging = refreshPreviewIfEnabled;
edit.onChange = refreshPreviewIfEnabled;
}
// SAA fields
addLivePreviewHandlers(sideSAAField);
addLivePreviewHandlers(angle1Field);
addLivePreviewHandlers(angle2Field);
// SSA fields
addLivePreviewHandlers(side1Field);
addLivePreviewHandlers(side2Field);
addLivePreviewHandlers(angleSSAField);
// Units and positioning and style
unitsDropdown.onChange = refreshPreviewIfEnabled;
originRadio.onClick = refreshPreviewIfEnabled;
centerRadio.onClick = refreshPreviewIfEnabled;
customRadio.onClick = refreshPreviewIfEnabled;
addLivePreviewHandlers(posXField);
addLivePreviewHandlers(posYField);
strokeCheckbox.onClick = refreshPreviewIfEnabled;
fillCheckbox.onClick = refreshPreviewIfEnabled;
addLivePreviewHandlers(strokeWidthField);
// Create action
createBtn.onClick = function () {
try {
app.executeMenuCommand('preview'); // ensure document is in a state to draw (no-op safeguard)
} catch (_) {}
try {
var points = buildPointsFromUI();
var opts = getOptions();
// Remove preview if present
removeIfExists(previewPath);
previewPath = null;
var finalPath = drawTriangle(points, opts);
finalPath.name = "Triangle_Maker_" + (typeDropdown.selection.index === 0 ? "SAA" : "SSA");
dlg.close(1);
} catch (e) {
alert(e.message);
}
};
cancelBtn.onClick = function () {
// Clean preview on cancel
removeIfExists(previewPath);
dlg.close(0);
};
dlg.center();
dlg.show();
})();