Statements
Every line in an .fsl file starts with a keyword or tier name. You can keep that one-statement-per-line style or add optional semicolons to chain a couple of statements on the same line—the parser treats ; the same way it treats a newline. This chapter explains what each statement does, why you would reach for it, and where it appears in the example at the end.
set – project-wide settings
Use set to update metadata (set name, set ri) or machine configuration (set gear, set girdle). The change applies immediately and carries forward to later lines.
| Command | Value syntax | What it controls |
|---|---|---|
set name "Brilliant Oval" | Quoted string | Updates the project title used in the viewer, analyzer panels, and printable exports. |
set ri 1.760 | Plain number or optimizable literal (for example [1.76, 1.70, 1.80]) | Defines the refractive index the renderer, analyzer, and optimizer use for light calculations. |
set color #ffd166 | Hex literal (#fff, #ffaa33), named color (red, cyan, ...). | Sets the simulated material color in raytraces. |
set absorption 0.6 | Number from 0.0–1.0 | Controls how strongly the gem colour tints the interactive WebGL preview. |
set gear 96 cw | Positive integer followed by optional cw or ccw | Sets the index gear tooth count and dop rotation direction for every later facet. |
set girdle 4.5 | Number representing a percentage of the stone’s average width | Adjusts how thick the girdle is when interpreting +/- girdle offsets. |
set cube 3.0 | Number interpreted as the cube’s edge length | Rebuilds the starting blank as a cube of the given size. Useful when you need extra “rough” after running out of material mid-cut. |
let – save a value for later
let pt = mp(G1) stores a point or number in a variable named pt so you can reuse it further down. Inside macros, the variable disappears when the macro finishes; elsewhere, it is available until the end of the file.
Need a list of numbers? Wrap them in braces to build an array literal: let angles = {41.8, 43.0, 32.2}. Every element can be any numeric expression—including optimizable literals such as [41.8]—and the interpreter remembers the evaluated values at the moment the line runs. Read a value back with zero-based indexing (let pavilion = angles[0]), and the interpreter will flag an error immediately if you stray outside the array’s bounds.
The edge(...) helper is a special case because it always returns two points: the start and end of an edge. Because of this, you must provide two variable names to store these two points. Attempting to assign both points to a single variable, like let edge_ref = edge(...), will result in an error.
The correct syntax provides a variable for each point: let point_a, point_b = edge(...).
This is useful when you need both endpoints of an edge. If you only need one of the points, you can use an underscore (_) to discard the one you don't need. For example:
set name "Edge destructuring"
set gear 96
P1 0 @ 41.8 : 0.18 x8
let start_point, end_point = edge(P1:0, P1:12)
let keep_this_one, _ = edge(P1:12, P1:24)
Those bindings behave like any other point variable—you can aim facets at them, drop debug markers, or pass them into macros.
show – drop a marker (or edge) in the viewer
show mp(P1) drops a marker using the default highlight colour (#ffd166). Add up or down (right after show) if you want to force the side, and optionally supply a colour: either one of the named options (cyan, orange, etc.) or a hex literal such as #ffe422 or #fff. The web studio keeps a small picker next to the editor so you can drop in hex values without memorising them.
Need to highlight a segment instead of a single point? Feed show edge(Tier:Index, Tier:Index) and the viewer/printable exports draw the coloured line. Just like the let statement, show does not accept edge variables directly—call edge(...) inline or reuse the destructured point pair.
debug – log a value or point to the console
debug statements are the quickest way to inspect a calculation without editing macros or sprinkling show markers everywhere. The interpreter prints the evaluated value to the browser/CLI console together with useful metadata such as the current side and symmetry. Examples:
set name "Debug example"
set gear 96
P1 0 @ 41.8 : 0.18 x8
let angle = 41.8
debug mp(P1) "entry point"
debug (deg2rad(angle) - 5.0), "angle offset"
debug ep(mp(P1), mp(P1) at (12), 0.5)
If you omit the label, the log uses a default one. When the value resolves to a number, the interpreter reports the raw value plus convenient conversions (abs, mod360, deg, rad, and gear turns). When the value resolves to a point, it logs the Cartesian coordinates, length, and azimuth. Open your browser’s developer console (or the CLI output when running the interpreter directly) to read the [FSLDbg] … lines.
stop – freeze execution for inspection
Drop stop on its own line to halt the interpreter immediately while keeping everything it has done so far: gemstone geometry, variables, debug markers, and the cut log. It is a handy escape hatch when debugging—pair it with show/debug, inspect the partial stone, and keep experiments below the stop line without running them.
set name "Stop demo"
P1 0 @ 41.8 : 0.18 x8
stop
P2 0 @ 43 : 0.18 x8 // skipped
Anything after stop is parsed but skipped at runtime, even when stop fires inside a macro body.
Macros – reuse a favourite sequence
define wraps statements so you can replay them with a single command. Every parameter is named ($Macro(tier = "C1")), and defaults live either in the header or inline using ${param = ...}.
They are an advanced feature, often used for complex gem outlines.
Facet commands – the main event
When a line starts with a tier name (P1, Star, etc.) you are cutting a facet. Read it like a simple recipe:
Tier [up|down] index @ angle : target xN ["note"] frosted
Go left to right:
- Tier – the label that will appear in the printable diagram.
- up / down – crown or pavilion. Leave it off when the tier already implies the side (for example, a
Ctier lives on the crown, aPtier is pavilion). - index @ angle – which tooth on the gear and which angle to lock in.
- : target – how far to cut. This can be a depth / distance (
: 0.18), a meet point (: mp(G1)), or the special wordsizefor auto-depth on 90° girdle cuts. The distance is measured between the cutting plane and the center of the stone (0,0,0). - xN – how many times to echo the facet around the dop. Use
xxNwhen you want mirror-image pairs (for example, left/right star facets). - "note" / frosted – optional add-ons: custom printable text or mark a tier as frosted.
A pavilion example:
P1 0 @ 41.8 : 0.18 x8
P1— name in the legend. The side is auto detected to be the pavilion.0 @ 41.8— tooth 0, 41.8° on the dial.: 0.18— stop when at 0.18 units from the center of teh stone.x8— repeat eight times (every 45°).
Once you remember the pattern, you only need to choose which target style fits:
- Angle + depth —
P1 0 @ 41.8 : 0.18 - Angle + point —
C1 0 @ 32 : mp(G1). Aim the facet at a meet point or helper. - Point pair —
C2 0 : mp(G1) : mp(C2). Define the plane using two points instead of an angle.
Tier naming, sides, and base index rules
Every tier label must be a single string with no whitespace inside it, and it has to start with an alphanumeric character. Reserved keywords (set, let, show, etc.) are off limits as tier names. The optional up/down flag tells the interpreter which side of the stone to use, but most of the time the engine can infer it:
- Names that start with
Pdefault to the pavilion. - Names that start with
Cdefault to the crown. - If you omit the side and the previous cut specified one, that side carries forward.
That’s why a table named T typically doesn’t need up—it inherits the current crown context. You can always override the inference by writing the side explicitly.
The base_index argument is zero-based and must stay below gear / symmetry. Until you dig into the Symmetry section later in this guide, use this mental model:
- With a 96 index gear and 4-fold symmetry, the valid base index range is
0 .. (96 / 4 - 1)→0 .. 23. - You can also provide an explicit index set using an array literal, for example
{4, 7, 11}, in place of the single base index. When you do that, symmetry is ignored entirely and the interpreter cuts exactly the indices in the set, in the order given. The first element is treated as the “base” index for meetpoint/girdle searches.
Some quick examples:
set name "Tier naming rules"
set gear 96
P1 1 @ 41.4 : 0.18 x8 // Pavilion cut using base index 1
G2 1 @ 90 : size x8 // Girdle tier; inherits the pavilion side from P1
C1 up 3 @ 32 : 0.12 x8 // Crown cut using base index 3
P2 {4, 7, 11} @ 43 : gp // Explicit index set; symmetry skipped, base index is 4 for mp/gp
Need to force a non-standard label onto the pavilion? Drop down right after the tier name—X8 down 5 @ 44 : 0.10 x2 guarantees the interpreter stays on the lower side even if the previous cut was on the crown.