Using APL for Gemstone Design
β οΈ Experimental: APL support is experimental and under active development. The engine, syntax, and available functions may change without notice. If you encounter issues, please report them β and remember, this is a playground for the mathematically adventurous.
ProFacet supports three scripting languages for designing gemstones: FSL (the purpose-built spec language), Lua (a general-purpose scripting language), and APL β a mathematical notation turned programming language.
Why APL?
APL is not for everyone. It's a notation designed by mathematicians, for mathematicians. Where FSL and Lua offer convenience functions like mp(), cp(), and TierAP(), APL gives you raw linear algebra primitives and expects you to build everything yourself.
If that sounds appealing, read on. If you prefer a guided experience, stick with FSL or Lua.
What APL brings:
- Concise mathematical notation β A complete gemstone design in 4 lines
- Array-oriented thinking β Planes, vertices, and transforms are all matrices
- No magic β Every computation is explicit; you see exactly what the math does
- Live playground β Run APL directly in the handbook with instant 3D preview
Getting Started
In the interactive playground (found throughout this handbook), click the APL tab. The playground wraps your code with an auto-injected gemstone engine that provides the core functions. Your code runs between the header (which defines the engine) and the footer (which exports the stone to the 3D viewer).
Tutorial: Building Cuts Step by Step
Step 1: A Single Horizontal Cut
The simplest possible cut β a single horizontal table facet:
'Table' cut (0 0 1) at 0.09
That's it. Three numbers:
0β angle: 0Β° (horizontal)0β index: gear position 0 (doesn't matter for horizontal, but required)1β direction: up (crown side)
at 0.09 sets the depth. This cuts one flat plane off the top of the rough stone.
Step 2: A Single Angled Cut
Now tilt that facet and aim it downward:
'P1' cut (37.5 0 Β―1) at Β―1.0
- Angle
37.5Β°β the facet tilts inward - Index
0β at gear position 0 - Direction
Β―1β pavilion (down)
Notice Β―1 β that's APL's high minus (negative sign), distinct from the subtraction operator -. This creates a single angled facet on the pavilion side.
Step 3: Adding Symmetry
One facet is lonely. Let's repeat it 8 times around the stone:
'P1' cut (37.5 (0 sym 8) Β―1) at Β―1.0
The only change: scalar 0 β 0 sym 8. The sym function generates 8 evenly-spaced gear indices around the stone. With Gear β 96 (the default), 0 sym 8 produces indices 0 12 24 36 48 60 72 84.
Step 4: Adding Mirrors
For a brilliant cut, each facet has a mirror reflection:
'P1' cut (37.5 (mir 3 sym 8) Β―1) at Β―1.0
mir adds reflected indices: mir 3 sym 8 produces 16 indices (8 original + 8 mirrored). Index 3 instead of 0 ensures the mirrors don't overlap β 0 mirrors onto itself.
Step 5: Offset Indices
Not all facets start at index 0. For example, star facets on the crown sit between the main facets:
'P1' cut (37.5 (mir 0 sym 8) Β―1) at Β―1.0
'G1' cut (90 (mir 0 sym 8) Β―1) at 1.0
'C1' cut (25 (mir 3 sym 8) 1) at 0.5
Notice 3 sym 8 β the 3 offsets the indices so these crown facets sit rotated relative to the pavilion mains. The girdle (G1 at 90Β°) gives the stone its outline.
Step 6: Meetpoints
Instead of specifying a fixed depth, you can cut to where existing facets meet:
β Round Brilliant β 4 lines, zero variables
'P1' cut (37.5 (mir 3 sym 8) Β―1) at Β―1.0
'G1' cut (90 (mir 3 sym 8) Β―1) at 1.0
'C1' cut (25 (mir 3 sym 8) 1) at (zOff05 V on tier 'P1')
'Table' cut (0 (0 sym 1) 1) at 0.09
The crown (C1) cuts to a meetpoint instead of a fixed depth:
V on tier 'P1'β find vertices that touch the pavilion tierzOff05β add a small girdle offset (0.05) to those verticesatdetects the vertex matrix and auto-finds the meetpoint
The tier function looks up a tier's planes by name β so V on tier 'P1' finds vertices touching the pavilion mains without needing to save them to a variable first.
Reading the Cut Syntax
Each line follows this pattern:
'NAME' cut (ANGLE (INDICES) DIRECTION) TARGET
Assignment is optional β only save the result if you need it later (e.g., for V on).
Breaking down a pavilion cut:
'P1' cut (37.5 (mir 3 sym 8) Β―1) at Β―1.0
β β β β β β
β β β β β ββ depth = 1.0 (pavilion depth)
β β β β βββββ at = target (depth, point, or vertices)
β β β ββββββββββ Β―1 = pavilion direction (down)
β β βββββββββββββββββββββββββ mir 3 sym 8 = 16 indices (8-fold + mirrors)
β βββββββββββββββββββββββββββββββ 37.5Β° tilt angle
A crown cut with meetpoint:
'C1' cut (25 (mir 3 sym 8) 1) at (zOff05 V on tier 'P1')
β β β β β
β β β β βββ zOff05 offsets the vertices, at auto-finds meetpoint
β β β ββββββ 1 = crown direction (up)
β β βββββββββββββββββββββ 16 mirrored indices
β βββββββββββββββββββββββββ 25Β° crown angle
Settings
The engine provides three variables you can set at the top of your code:
Gear β 96 β Number of gear teeth (default 96)
Name β 'My Stone' β Name of the design
RI β 1.54 β Refractive index (default 1.54, quartz)
Gear controls the angular resolution of the indexing system. The standard is 96 teeth. All indices run from 0 to Gear-1, and functions like sym and mir use Gear for modular arithmetic.
Cutting Notes
Use note to attach instructions to any tier. Notes appear in the printable cutting diagram.
'P1' note 'Cut pavilion mains first'
'G1' note 'Polish to 600 grit'
'C1' note 'Meet P1 and G1 at girdle'
Notes are separate from cut β add them anywhere after the tier has been created. Each tier can have one note; if you call note again on the same tier, both entries are included.
Tips for APL Users
βIO β 0is set automatically β all indexing is zero-based- The gear has
Gearteeth (default 96) β indices run from 0 toGear-1 - Direction is
Β―1for pavilion,1for crown Vis a global variable holding all current verticesStoneis a global NΓ4 matrix of all planes- Tier names are the left argument β
'P1' cut ...names the tier directly; usetier 'P1'to reference it later
How It Works
Unlike FSL and Lua, the APL engine does not call into ProFacet's Rust/WASM engine. Instead, it implements the entire gemstone computation in pure APL β half-space intersection, vertex solving via Cramer's rule, and meetpoint projection. This means:
- No built-in geometry helpers β There is no
mp()orcp()magic function. The engine provides lower-level primitives that you compose. - Everything is a matrix β A stone is an NΓ4 matrix where each row is
[nx ny nz d](a half-space plane). Vertices are an MΓ3 matrix. - You build the cuts β The
cutfunction adds planes to the stone and recomputes vertices. You control the angle, symmetry, and depth directly.
The APL Gemstone Engine
The following code is automatically injected before your APL code runs. It's shown here in full so you can understand every function available to you.
Index Generation
β Generate gear indices with symmetry
β Usage: base_index sym fold_count
β Example: 3 sym 8 β 3 15 27 39 51 63 75 87
sym β { Gear | βΊ + (GearΓ·β΅) Γ β³β΅ }
β Mirror indices (adds reflections)
β Usage: mir base_indices
β Example: mir 3 sym 8 β 3 15 27 39 51 63 75 87 93 81 69 57 45 33 21 9
mir β { βͺ Gear | β΅ , Gear - β΅ }
sym generates evenly-spaced indices around the gear. mir adds mirror reflections. Together, mir 3 sym 8 produces 16 indices for an 8-fold symmetric cut with mirrors.
Normal Generation
β NGen: Generate 3D normal vectors from (angle, indices, direction)
β Returns an NΓ3 matrix of unit normals
β R β NGen args;t;I;d;A;T
(t I d) β args
A β β 2 Γ I Γ· Gear β Gear index β radians
T β β t Γ· 180 β Tilt angle β radians
R β β (3 , β’I) β΄ ((1βT)Γ2βA) , ((1βT)Γ1βA) , ((β’I)β΄dΓ2βT)
β
NGen converts (angle, gear_indices, direction) into 3D unit normals. The direction d is Β―1 for pavilion (pointing down) or 1 for crown (pointing up).
Vertex Computation (Cramer's Rule)
β 3Γ3 determinant for batched matrices
det3v β { +/ β΅[;0;] Γ (β΅[;1;1 2 0]Γβ΅[;2;2 0 1]) - β΅[;1;2 0 1]Γβ΅[;2;1 2 0] }
β Generate all 3-element combinations from 0..n-1
β R β comb3 n;all
all β β (n n n) β€ β³ n*3
R β all βΏβ¨ (all[;0] < all[;1]) β§ all[;1] < all[;2]
β
β Solve for all vertices of a stone (intersection of 3 half-spaces)
β R β getVertices S; ...
β For every combination of 3 planes, solve the 3Γ3 linear system
β using Cramer's rule. Keep only vertices that satisfy ALL planes.
...
β
getVertices finds every point where exactly 3 planes intersect, then filters to keep only those inside the stone (satisfying all half-space constraints). This is the core of the slicer.
Meetpoint and Cut Functions
β firstContact: find the vertex closest to a plane normal
firstContact β { β΅[ (0ββ β΅ +.Γ βΊ) ; ] }
β V on T: filter vertices that lie on tier T
β R β V on T
β Returns vertices from V that touch the planes in tier T
β
β args mp V: compute meetpoint (closest vertex to the cutting direction)
β R β args mp V
R β (NGen args)[0;] firstContact V
β
β args at target: compute plane distances from cut parameters
β Handles three target types:
β scalar β depth (e.g., at 1.0)
β 3-vec β meetpoint (e.g., at pt)
β matrix β vertices (auto-finds meetpoint)
β 2-vec β angle solve between two points
β R β args at rarg
β Returns NΓ4 matrix: [nx ny nz d] for each cutting plane
β
β CM operator: "Cut to Meetpoint" with transform
β Usage: args (transform CM) V
β R β args (LO CM) V;pt
pt β LO args mp V β Find meetpoint, apply transform (e.g., zOff05)
R β args at pt β Compute plane distances from that point
β
The Cut Function
β cut: Add named planes to the stone and recompute vertices
β Left arg is the tier name, right arg is the plane matrix
β tier β nm cut planes
TierCount β TierCount + 1
TierNames β TierNames , βnm
TierMap β TierMap , (β’planes) β΄ TierCount
Stone β Stone βͺ planes β Append new planes
β Keep existing vertices that satisfy new constraints
kept β V βΏβ¨ β§/ 1eΒ―5 β₯ (V +.Γ β planes[;0 1 2]) -β€1 planes[;3]
β Solve for new vertices involving the new planes
newV β (old_count) solveNew Stone
V β urows (kept βͺ newV) β Merge and deduplicate
tier β planes
β
β tier: look up a tier's planes by name
β R β tier nm
R β (TierMap = TierNames β³ βnm) βΏ Stone
β
cut is the main workhorse. The left argument names the tier (e.g., 'P1'), and the right argument is the NΓ4 plane matrix. It appends half-space planes to Stone, filters existing vertices, solves for new intersections, and updates the global vertex set V.
tier looks up a previously cut tier by name β e.g., tier 'P1' returns the plane matrix for tier P1. This lets you reference tiers without storing them in variables: V on tier 'P1'.
Cutting Notes
β note: attach a cutting instruction to a tier
β 'P1' note 'Cut pavilion mains first'
β nm note txt
β Appends to TierNoteNames and TierNotes vectors
β
note is separate from cut β call it any time after the tier exists. Notes are exported in the JSON output and appear in ProFacet's printable cutting diagram.
Utility Functions
β zOff05: Add a 0.05 girdle offset to a meetpoint
β R β zOff05 X
R β X + 0 0 0.05
β
β id: Identity (pass-through, used as default transform)
β R β id X
R β X
β
Initial State
β The rough: a 2Γ2Γ2 cube defined by 6 half-spaces
Rough β 6 4 β΄ 0 0 1 2 0 0 Β―1 2 1 0 0 2 Β―1 0 0 2 0 1 0 2 0 Β―1 0 2
Stone β Rough
V β getVertices Stone
TierCount β 0
TierMap β 6 β΄ 0
TierNames β β'Rough'
CutLog β 0 3 β΄ 0
Your code starts with a cube (Rough) and carves it down with cut calls.
CAM Outline Functions
The APL engine includes all ProFacet CAM (CenterpointβAngle Method) outline functions. These generate girdle preforms with a single function call, just like FSL and Lua. All functions are prefixed with apl to avoid name collisions. Internally, they call cut directly.
Usage
β Simple shapes β single argument
aplRound (43 8) β Round with angle 43Β°, 8-fold symmetry
aplHexagon 43 β Hexagonal outline
aplOctagon 43 β Octagonal outline
aplShield 44 β Shield/kite shape
β Truncated shapes β (angle truncation) or (truncation angle)
aplSquareTr (45 0.35) β Truncated square
aplHexTr (42 0.3) β Truncated hexagon
aplTriTr (0.35 44) β Truncated triangle
β Mirrored variants β add offset parameter
aplSquareTrMir (42 0.5 4) β Mirrored truncated square
aplHexTrMir (42 0.3 2) β Mirrored truncated hexagon
aplTriTrMir (0.25 42 4) β Mirrored truncated triangle
β Cushioned shapes
aplSquareCushTr (2 45 0.40) β Cushioned truncated square
aplTriCushTr (0.30 44 2) β Cushioned truncated triangle
aplPentCushTr (1 33 0.3) β Cushioned truncated pentagon
β Curved shapes
aplTriCurved (45 2 4) β Curved trilliant
aplSquareCurved (45 2 4) β Curved square
β Rectangles
aplRect (1.6 45) β Rectangle (lwr angle)
aplRectTr (1.6 45 0.30 5) β Truncated rectangle
aplRectDblTr (1.6 45 0.60 0.50) β Double-truncated rectangle
β Oval
aplOval (42 1.35 6) β Oval (angle lwr segmentsPerQuad)
Full Reference
| APL Function | FSL/Lua Equivalent | Parameters |
|---|---|---|
aplRound | Round | (angle symmetry) |
aplHexagon | Hexagon | angle |
aplOctagon | Octagon | angle |
aplPentagon | Pentagon | angle |
aplHeptagon | Heptagon | angle |
aplShield | Shield | angle |
aplHexTr | HexTr | (angle truncation) |
aplHexTrMir | HexTrMir | (angle truncation offset) |
aplSquareTr | SquareTr | (angle truncation) |
aplSquareTrMir | SquareTrMir | (angle truncation offset) |
aplTriTr | TriTr | (truncation angle) |
aplTriTrMir | TriTrMir | (truncation angle offset) |
aplRect | Rect | (lwr angle) |
aplRectTr | RectTr | (lwr angle truncation offset) |
aplRectDblTr | RectDblTr | (lwr angle t1 t2) |
aplSquareCushTr | SquareCushTr | (cushion angle truncation) |
aplSquareCushTrMir | SquareCushTrMir | (cushion angle truncation offset) |
aplTriCushTr | TriCushTr | (truncation angle cushion) |
aplTriCushTrMir | TriCushTrMir | (truncation angle cushion offset) |
aplTriCurved | TriCurved | (angle c1 c2) |
aplSquareCurved | SquareCurved | (angle c1 c2) |
aplPentCushTr | PentCushTr | (offset angle truncation) |
aplPentTr | PentTr | (angle truncation) |
aplPentTrMir | PentTrMir | (truncation angle offset) |
aplPentCurved | PentCurved | (angle c1 c2) |
aplPentCushTrMir | PentCushTrMir | (offset angle truncation mir_offset) |
aplHexCurved | HexCurved | (angle c1 c2) |
aplHexCushTr | HexCushTr | (offset angle truncation) |
aplHexCushTrMir | HexCushTrMir | (offset angle truncation mir_offset) |
aplHeptTr | HeptTr | (angle truncation) |
aplHeptTrMir | HeptTrMir | (truncation angle offset) |
aplHeptCurved | HeptCurved | (angle c1 c2) |
aplHeptCushTr | HeptCushTr | (offset angle truncation) |
aplHeptCushTrMir | HeptCushTrMir | (offset angle truncation mir_offset) |
aplOctTr | OctTr | (angle truncation) |
aplOctTrMir | OctTrMir | (angle truncation offset) |
aplOctCurved | OctCurved | (angle c1 c2) |
aplOctCushTr | OctCushTr | (offset angle truncation) |
aplOctCushTrMir | OctCushTrMir | (offset angle truncation mir_offset) |
aplOval | Oval | (angle lwr segmentsPerQuad) |
Helper Functions
The CAM functions use these lower-level helpers which are also available for custom designs:
| Function | Description |
|---|---|
tier | Look up tier planes by name: tier 'P1' β for use in V on tier 'P1' |
edge | (βtier 'PF1') edge (βtier 'G1') β find shared edge vertices |
ep | e ep 0.5 β interpolate along edge at ratio |
Comparison: FSL vs Lua vs APL
| Feature | FSL | Lua | APL |
|---|---|---|---|
| Syntax style | Declarative spec | Imperative script | Mathematical notation |
| Learning curve | Low | Medium | High |
| Built-in geometry | Full | Full | Full (via apl* functions) + raw primitives |
| Control flow | Functional only | Full (loops, if/else) | Dfns, guards, tradfns |
| Target audience | Designers | Programmers | Mathematicians |
| Engine | Rust/WASM | Rust/WASM | Pure APL |
Further Reading
- Quick Start β Build a complete stone step-by-step (with APL playground)
- CAM Outlines β All CAM outline functions with live APL playgrounds
- Functional Programming β FSL, Lua, and APL side-by-side examples
- Using Lua β The Lua scripting reference