Chapter 9: The Dual Language Engine — FSL & Lua Side by Side
Welcome to Part II: FSL & Lua — Parametric & Algorithmic Gem Design.
In Part I, you saw how every action in the Interactive Slicer builds a gemstone visually. Under the hood, ProFacet’s 3D geometry kernel is driven by a dual-language scripting engine that natively executes both FSL (Faceting Specification Language) and Lua.
Why does ProFacet offer two languages instead of one?
- FSL (Faceting Specification Language) is designed to look and read like a traditional gem-cutting diagram. Each line (
P1 0 @ 41.5 : cp() x8) states a single tier's index, mast angle, meetpoint target, and symmetry fold in fewer characters than any general-purpose language—while still giving you variables, 3D vectors, functional array pipelines (map,filter,reduce), and+/-GPU optimization hints. - Lua (
-- lua script) is a full, industry-standard imperative programming language withforandwhileloops, mutable tables,if/elseif/elsebranching, and standardmath.*functions. Whenever you want to write a procedural algorithm—such as a spiral vortex crown, a parametric step-cut generator with variable tier counts, or a mathematical sculpture—Lua gives you unrestricted programming power connected to the exact same 3D cutting engine and Optimizer.
1. How ProFacet Detects FSL vs. Lua
You can write either language right inside the Spec Editor (</> Spec Editor). ProFacet automatically detects which language your script is written in:
- FSL scripts use C-style comments (
// comment) and declarative@/:tier lines (P1 0 @ 41.5 : cp() x8). - Lua scripts start with a Lua comment (
-- lua script) and use standard Lua function calls (TierAP("P1", 0, 41.5, cp(), "x8")).
[!TIP] Always Start Lua Scripts with
-- lua scriptPlacing-- lua scripton line 1 guarantees that both the Spec Editor and the interactive playgrounds in this book immediately activate Lua syntax highlighting and the Lua runtime.
2. Line-by-Line Comparison: The Same Gemstone in FSL & Lua
Click between FSL and Lua in the top bar of the live playground below and click Run (Ctrl+Enter / Cmd+Enter) to see how identically the two languages construct a Sapphire Round Brilliant:
// 1. Stone & Machine Metadata
name = "Sapphire Round Brilliant"
ri = 1.76 // Corundum (Sapphire / Ruby)
gear = 96 // 96-tooth index gear
color = "#38bdf8"
// 2. Pavilion & Girdle Tiers
P1 3 @ 43.00 +/- 1.0 : cp() xx8 // 16 mirrored pavilion breaks meeting cp()
G1 3 @ 90.00 : size xx8 // 16 vertical girdle facets at radius 1.0
P2 0 @ 41.50 +/- 1.5 : gp() x8 // 8 pavilion mains meeting the girdle
// 3. Crown & Table Tiers
C1 3 @ 42.00 +/- 2.0 : mp(P1, P2) + girdle xx8 // 16 crown breaks + girdle thickness
C2 0 @ 34.50 +/- 1.5 : gp() x8 // 8 crown mains meeting the girdle
C3 6 @ 22.00 +/- 1.5 : mp(C1) x8 // 8 star facets meeting C1
T 0 @ 0.00 : mp(C2) x1 // 1 flat table meeting C2
-- lua script
-- 1. Stone & Machine Metadata
name("Sapphire Round Brilliant")
ri(1.76)
gear(96)
color("#38bdf8")
-- 2. Pavilion & Girdle Tiers
TierAP("P1", 3, val(43.00, 1.0), cp(), "xx8")
TierAD("G1", 3, 90.00, size, "xx8")
TierAP("P2", 0, val(41.50, 1.5), gp(), "x8")
-- 3. Crown & Table Tiers
TierAP("C1", 3, val(42.00, 2.0), mp("P1", "P2"):add(girdle), "xx8")
TierAP("C2", 0, val(34.50, 1.5), gp(), "x8")
TierAP("C3", 6, val(22.00, 1.5), mp("C1"), "x8")
TierAP("T", 0, 0.00, mp("C2"), "x1")
3. Anatomy of a Cut Statement in Both Languages
In FSL:
C1 3 @ 42.00 +/- 2.0 : mp(P1, P2) + girdle xx8
────── ───── ────────────────── ─── ───────────────────── ─────
Tier Base Angle (°) + Opt Meetpoint Target + Sym
Name Index Search Hint Girdle Offset (Mir)
In Lua:
TierAP( "C1", 3, val(42.00, 2.0), mp("P1", "P2"):add(girdle), "xx8" )
───── ─── ───────────────── ─────────────────────────── ──────
Tier Base Angle (°) + Opt Meetpoint Target + Mirrored
Name Index Search Hint Girdle Offset Symmetry
4. The Complete FSL ↔ Lua Rosetta Stone
Keep this side-by-side cheat sheet handy as you read through Part II:
| Feature | FSL Syntax | Lua Syntax (-- lua script) |
|---|---|---|
| Comments | // Single-line comment | -- Single-line comment |
| Metadata Settings | name = "Star Ruby"ri = 1.76gear = 96color = "#e11d48" | name("Star Ruby")ri(1.76)gear(96)color("#e11d48") |
| Variables | pavAngle = 41.5girdle = Vector(0, 0, 0.04) | local pavAngle = 41.5local girdle = Point(0, 0, 0.04) |
| Optimizable Numbers | 41.5 +/- 1.542.0 +/- 2% | val(41.5, 1.5)val(42.0, "2%") |
| CAM Preforms | Round(43, 16)RectTr(1.4, 43, 0.28, 0) | Round(43, 16)RectTr(1.4, 43, 0.28, 0) |
| Style 1: Angle + Depth | P1 0 @ 41.8 : 0.25 x8 | TierAD("P1", 0, 41.8, 0.25, "x8") |
| Style 2: Angle + Point | P1 0 @ 41.5 : cp() x8 | TierAP("P1", 0, 41.5, cp(), "x8") |
| Style 3: Point-Pair | C1 0 : gp() + girdle : mp(C2) x8 | TierPP("C1", 0, gp():add(girdle), mp("C2"), "x8") |
| Mirrored Symmetry | P2 0 @ 43.0 : gp() x8 m 3(axis 0 with ±3 delta, or zero-axis shorthand P2 3 @ 43.0 : gp() xx8) | TierAP("P2", 0, 43.0, gp(), "x8 m 3")(or zero-axis shorthand TierAP("P2", 3, 43.0, gp(), "xx8")) |
| Explicit Tooth List | P3 [0, 24, 48] @ 42.0 : gp() | TierAP("P3", {0, 24, 48}, 42.0, gp()) |
| Frosted / Notes | G1 0 @ 90 : size x16 { frosted: true } | TierAD("G1", 0, 90, size, "x16", { frosted = true }) |
| Meetpoint Helpers | cp(), gp(), mp(P1, P2, !G1) | cp(), gp(), mp("P1", "P2", "!G1") |
| Edge Interpolation | ep(edge(C1:0, C1:6), 0.5) | ep(edge("C1:0", "C1:6"), 0.5) |
| 3D Primitives | Point(x, y, z), Vector(x, y, z), Plane(...) | Point(x, y, z), Vector(x, y, z), Plane(...) |
Inspect Stone (stone()) | s = stone(); print(s.tablePercentage) | local s = stone(); print(s.tablePercentage) |
| Assertions | assert(s.tablePercentage > 45, "Small table") | assert(s.tablePercentage > 45, "Small table") |
| Loops / Iteration | Math.range(0, 8, 1).forEach((i) => ...) | for i = 0, 7 do ... end |
5. Why Coding Your Cuts Changes Everything
When a gemstone is defined as a parametric script rather than a static list of vertices:
- One-Line Material Adaptation: You can compute the critical angle directly from
ri(crit = asin(1.0 / ri)) and express your Pavilion angles relative tocrit. Changeri = 1.54tori = 2.42and the entire Pavilion re-angles itself automatically! - Proportion-Locked Scaling: When you stretch an Oval from
1.25×to1.50×, tiers anchored withgp(),mp(),ep(), orsurface(u, v)automatically recompute their exact angles and depths so every meetpoint stays closed. - Instant Multi-Tier Generation: Use a 3-line FSL
.forEach()pipeline or a Luaforloop to generate a 10-tier Portuguese pavilion or a 64-facet geodesic dome without repeating yourself.
Next, let’s establish the exact 3D coordinate system, machine configuration settings, and symmetry rules in Chapter 10: Coordinate System, Machine Settings & Symmetry Rules.