Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

CommandValue syntaxWhat it controls
name = "Brilliant Oval"Quoted stringUpdates the project title used in the Viewer, Analyzer panels, and printable exports.
ri = 1.760Plain 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 stringA text note to describe the design.
tagsString listComma or space separated keywords (e.g., "round, brilliant" or "quartz green") used for searching in the file dialog.
absorptionNumberSets 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.
gearPositive integerSets the index gear tooth count for every later facet.
cw = trueBoolean (true for clockwise, false for counter-clockwise)Sets the dop rotation direction.
cube = 3.0Number interpreted as the cube’s edge lengthRebuilds 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 like let, const, or var are 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 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 C tier lives on the crown, a P tier 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 the size variable 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 xxN when 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:

  1. Angle + depthP1 0 @ 41.8 : 0.18
  2. Angle + pointC1 0 @ 32 : mp(P1, G1) . Aim the facet at a meet point or helper.
  3. Point pairC2 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 P default to the pavilion.
  • Names that start with C or T default 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.