Statements
Every line in an .fsl file starts with a command, variable assignment, or tier name. The parser treats newlines as whitespace, so you are free to split long commands across multiple lines or use indentation for clarity. If you want to chain multiple statements on the same line, you must use a semicolon ; to separate them. This chapter explains what each statement does and why you would reach for it.
configuration (gear, ri, etc.) – project-wide settings
Use configuration assignments (gear, ri, etc.) to update metadata (name = "...", ri = ...) or machine configuration (gear = ...). The change applies immediately and carries forward to later lines.
| Command | Value syntax | What it controls |
|---|---|---|
name = "Brilliant Oval" | Quoted string | Updates the project title used in the Viewer, Analyzer panels, and printable exports. |
ri = 1.760 | Plain number or optimizable literal (for example 1.76 +/- 5%) | Defines the refractive index the Renderer, Analyzer, and Optimizer use for light calculations. |
color = "#ffd166" | Hex literal (#ffd166) | Sets the simulated material color in raytraces. |
notes = "..." | Quoted string | A text note to describe the design. |
tags | String list | Comma or space separated keywords (e.g., "round, brilliant" or "quartz green") used for searching in the file dialog. |
absorption | Number | Sets the absorption strength for the visual raytracer. Values around 1 look quite natural, but it depends on the color as well; higher values darken the stone. |
gear | Positive integer | Sets the index gear tooth count for every later facet. |
cw = true | Boolean (true for clockwise, false for counter-clockwise) | Sets the dop rotation direction. |
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. |
Assignment – save a value for later
pt = mp(P1) stores a point or number in a variable named pt so you can reuse it further down. Inside functions, the variable disappears when the function finishes; elsewhere, it is available until the end of the file.
Note: FSL uses direct assignment (
x = 10). Keywords likelet,const, orvarare not supported.
Need a list of numbers? Wrap them in brackets to build an array literal: angles = [41.8, 43.0, 32.2]. Every element can be any numeric expression—including optimizable literals such as 41.8 +/- 0.1—and the interpreter remembers the evaluated values at the moment the line runs. Read a value back with zero-based indexing (pavilion = angles[0]), and the interpreter will flag an error immediately if you stray outside the array’s bounds.
The edge(...) helper returns an array containing two points: the start and end of an edge. You can assign this array to a variable and access the points using zero-based indexing.
name = "Edge points"
gear = 96
P1 0 @ 41.8 : 0.18 x8
e = edge(P1:0, P1:12)
start_point = e[0]
end_point = e[1]
print("Start:", start_point)
print("End:", end_point)
Those bindings behave like any other point variable—you can aim facets at them, drop debug markers, or pass them into functions.
show – drop a marker in the viewer
show(mp(P1)) drops a marker using the default highlight color (#ff0000). You can supply a custom color as a second argument: show(mp(P1), "cyan") or show(mp(P1), "#ffe422").
The function accepts any number of points and colors. If a color string follows a point, it applies to that point.
name = "Show markers"
P1 0 @ 41.8 : cp() x8
// Mark the meet point in red
show(mp(P1), "red")
If you need to verify an edge, you can display its endpoints:
name = "Show Edge"
P1 0 @ 41.8 : cp() x8
// P1:0 and P1:12 are adjacent facets (steps of 12)
e = edge(P1:0, P1:12)
show(e[0], "orange")
show(e[1], "orange")
print – log values to the console
print statements are the quickest way to inspect a calculation. The interpreter prints the evaluated values to the browser/CLI console. It accepts multiple arguments and joins them with spaces. Examples:
name = "Debug example"
gear = 96
P1 0 @ 41.8 : 0.18 x8
angle = 41.8
print("entry point:", mp(P1))
print("angle offset:", Math.toRadians(angle) - 0.08)
print(ep(edge(P1:0, P1:12), 0.5))
Open your browser’s developer console (or the CLI output when running the interpreter directly) to see the output.
return – halt execution
Use return; at the top level 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/print, inspect the partial stone, and keep experiments below the return; line without running them.
name = "Return demo"
P1 0 @ 41.8 : 0.18 x8
return;
P2 0 @ 43 : 0.18 x8 // skipped
Anything after return; is parsed but skipped at runtime.
Lambda Functions & Blocks
Define anonymous functions using elegant arrow syntax:
// Single parameter
double = (x) => x * 2
result = double(5) // 10
print("Double 5 is", result)
// Multiple parameters with block body
calc = (a, b) => {
temp = a + b
temp * 2
}
value = calc(3, 4) // 14
print("Calc result:", value)
Blocks can also be used as expressions:
angle = {
base = 40
offset = 1.8
base + offset // last expression is the value
}
print("Calculated angle:", angle)
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 { notes: "note", frosted: true }
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(P1, G1)), or thesizevariable for 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). - { notes: "...", frosted: true } – 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 the 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(P1, G1). Aim the facet at a meet point or helper. - Point pair —
C2 0 : mp(P1, G1) : mp(C1). Define the plane using two points instead of an angle.Note: This dual-meetpoint solver searches for candidates near a default angle of 15°. If your target meetpoints are significantly steeper or shallower (and thus filtered out), pass an explicit search angle to the first meetpoint helper:
mp(P1, { angle: 45 }).
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 variables (gear, ri, name, 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
CorTdefault 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 explicitly defaults to the crown. 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:
name = "Tier naming rules"
gear = 96
size = 1.0
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 : mp(P1) + girdle x8 // Crown cut using base index 3
P2 [4, 7, 11] @ 34 : gp() // Explicit index set; symmetry skipped, base index is 4 for mp/gp
If you 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.