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

Using Lua for Gemstone Design

ProFacet offers two powerful scripting languages for designing gemstones: the purpose-built Facet Specification Language (FSL) and the versatile, widely-adopted Lua language.

Why Lua?

While FSL is optimized specifically for geometric cutting and terse tier declarations, Lua brings the full power of a general-purpose programming language. With Lua, you gain access to:

  • Standard Control Structures: for loops, while loops, and complex if/elseif/else branching.
  • Rich Data Structures: Lua tables are incredibly flexible, serving as arrays, dictionaries, and objects.
  • Familiar Syntax: If you already know Lua (popular in game scripting and embedded systems), you can start designing immediately without learning a new syntax.
  • Extensive Mathematical Capabilities: The standard math library provides everything you need for complex geometric calculations.

Getting Started with Lua

ProFacet automatically detects whether a design script is written in FSL or Lua using a scoring-based heuristic that looks for language-specific patterns (e.g. -- comments and local for Lua vs // comments and @ tier syntax for FSL).

The most reliable way to ensure Lua detection is to start your script with a Lua comment:

-- lua script

Since FSL scripts use C-style comments (//), starting with -- is a clean, unambiguous signal.

Core Concepts in Lua

Designing a stone in Lua follows the same core concepts as FSL, but using standard Lua function calls instead of FSL's specialized syntax. The ProFacet Lua environment provides global functions and types corresponding to FSL's built-in commands.

Vectors and Points

The core building blocks are available as global constructors:

-- lua script
local p1 = Point(1, 0, 0)
local p2 = Point(0, 1, 0)

local v = Vector(0, 0, 1)

-- Normal vectors are automatically normalized upon creation
local n = Normal(1, 1, 1)

Coordinate Getters

You can access the individual coordinates of a Point, Vector, Normal, or the lazy points returned by mp() or cp(). These coordinate fields are read-only:

local p = Point(1.2, 3.4, 5.6)
print("X coordinate:", p.x) -- Prints 1.2
print("Y coordinate:", p.y) -- Prints 3.4
print("Z coordinate:", p.z) -- Prints 5.6

Making Cuts

In Lua, cutting commands are functions, mirroring the core FSL facet commands. These functions expect specific string arguments for things like symmetry (e.g., "x8"), sides ("crown", "pavilion"), or height modifiers ("up", "down").

Here are the primary tier functions available in the global Lua namespace:

TierAD (Angle & Distance)

Cuts a tier defined by an angle and distance/height.

-- TierAD(name, index, angle, height_or_distance, symmetry, [options])
TierAD("P1", 96, 45.0, 0.5, "x8")

-- With optional table for notes or frosted status
TierAD("P2", 0, 42.0, 0.4, "x4", { notes = "Pre-form", frosted = true })

TierAP (Angle & Point)

Cuts a tier defined by an angle that passes through a specific 3D point.

local my_point = Point(0, 0, -1)
-- TierAP(name, index, angle, point, symmetry, [options])
TierAP("P3", 12, 40.0, my_point, "x8")

TierPP (Point & Point)

Cuts a tier defined by passing through points.

local pA = Point(1, 0, 0)
local pB = Point(0, 1, 0)
-- TierPP(name, index, p1, p2, symmetry, [options])
TierPP("C1", 0, pA, pB, "x4")

Lazy Point Evaluation

Just like in FSL, ProFacet Lua handles point calculations lazily. Functions like mp(), cp(), gp(), gpl(), ep(), edge(), prz(), and fred() do not evaluate to raw 3D coordinates immediately. Instead, they return a Lazy Point object (LuaLazyPoint).

Why Lazy Evaluation?

  1. Deferred Geometry: The facets or meetpoints reference geometry that may not have been cut yet, or whose positions will change during optimization. Lazy points record the relationship or formula rather than a static coordinate.
  2. Dynamic Resolution: When the design is run, the engine resolves the actual 3D coordinates of the lazy point at the exact moment a facet is cut or the point is checked.
  3. Lazy Arithmetic: You can perform math operations on lazy points directly. For example:
    -- Adds an offset vector to a lazy meetpoint
    local offset_mp = mp("C1", "C2") + Vector(0, 0, 0.04)
    
    This builds a compound lazy expression that is only computed when resolved.

Forcing Evaluation

If you need a concrete Point from a lazy expression, use the resolve() function. This materializes a lazy point into a real Point that works everywhere:

-- Materialize lazy points into concrete Points
local ur = resolve(mp("C1", { index = 40, angle = 90 }))
local ll = resolve(mp("C1", { index = 10, angle = 90 }))

-- ur and ll are now concrete Points
print("Upper right:", ur)
print("Lower left:", ll)

resolve() accepts a LuaLazyPoint, Point, or Vector and always returns a concrete Point. If the input is already a Point, it passes through unchanged.

You can also access individual coordinates, which forces evaluation implicitly:

local lazy_pt = mp("C1", "C2")

-- Accessing fields forces immediate evaluation of the coordinates
local x_val = lazy_pt.x
local y_val = lazy_pt.y
local z_val = lazy_pt.z

print("Evaluated coordinates:", x_val, y_val, z_val)

Point Helpers Reference

In addition to mp() and cp(), the ProFacet Lua environment registers the full suite of geometric point helpers from FSL:

HelperDescription
cp()Center point. Returns (0, 0, 1) for crown (up) and (0, 0, -1) for pavilion (down).
mp(t1, t2, ..., [options])Meetpoint helper. E.g. mp("C1", "C2") or mp("C1", "G1", "!C2"). You can pass an options table: mp("C1", { index = 12, angle = 42 }).
gp(options)Girdle Point helper. Scans the vertical girdle at the appropriate height and returns the vertex in front of a tooth. E.g. gp({ index = 12, side = "Up" }).
gpl(facet_ref)Girdle Point Line. Finds the exact girdle point for a specific facet. E.g. gpl("G1:0").
edge(facet_ref_1, facet_ref_2)Returns a sorted 2-element array of Points [pt1, pt2] representing the endpoints of the intersection edge. E.g. local e = edge("C1:1", "C1:7").
ep(edge_array, t) or ep(p1, p2, t)Edge Interpolation. Returns the point at fraction t (from 0.0 to 1.0) along the edge or between two points. E.g. ep(edge("C1:1", "C1:7"), 0.5) or ep(p1, p2, 0.25).
prz(pt, [facet_ref])Project Z. Projects pt vertically onto the gem surface or onto the infinite plane of the specified facet (e.g. prz(my_pt, "C1:0")).
fred(p1, p2, p3, p4) or fred(p1, p2, edge)Fred Point. Intersects line p1-p2 with line p3-p4 (or edge) in XY projection, and returns the 3D point on the target edge at that crossing.
resolve(pt)Materializes a lazy point into a concrete Point. Accepts LuaLazyPoint, Point, or Vector. Use when you need a resolved point for custom math or passing to functions that require concrete points.
grid(x_n, y_n, x, y, [pt1, pt2])Grid subdivision helper. Divides the bounding box (or box between pt1 and pt2) into x_n by y_n divisions and returns the vertex at column x, row y.
surface(u, v, [pt1, pt2], [options])Projects a fractional u, v coordinate (0..1) vertically onto the gem's surface. Can override side: surface(0.5, 0.5, { side = "down" }).

Gemstone Property Getters/Setters

Rather than using global variables, ProFacet Lua provides unified getter/setter functions for gemstone and machine properties. If called with no arguments, they act as getters and return the current value. If called with a single argument, they act as setters and update the property:

FunctionTypeDescription
name([str])StringGet/set the gemstone design name
ri([num])NumberGet/set the material Refractive Index (RI)
color([str])StringGet/set the rendering color (as a hex code, e.g. "#ff0000")
notes([str])StringGet/set notes/metadata
tags([str])StringGet/set design tags
absorption([num])NumberGet/set material absorption coefficient
gear([num])IntegerGet/set index gear (e.g. 96, 120)
cw([bool])BooleanGet/set index gear direction (true = Clockwise, false = Counter-clockwise)

Example Usage:

-- Get the current refractive index
local current_ri = ri()
print("Refractive index is:", current_ri)

-- Change the refractive index and gemstone name
ri(2.45)
name("My New Gem Design")

You can also use standard Lua print() to output values to the console during evaluation:

print("Hello from the Lua engine!")
print("Calculation result:", 5 * math.pi)

Optimization Support

The Lua integration includes full support for the ProFacet Optimizer. Just like in FSL, you can define ranges for your numeric values, and the optimizer will search for the best performing cut.

To define an optimizable range in Lua, use the val() function instead of a raw number. The function supports several ways to define ranges:

  • Explicit Bounds: val(initial, min, max)
  • Relative Delta: val(initial, delta) (ranges from initial - delta to initial + delta)
  • Relative Delta String: val(initial, "+/-", delta) (ranges from initial - delta to initial + delta)
  • Percentage Delta: val(initial, "+/- %", percentage) (ranges from initial * (1 - percentage/100) to initial * (1 + percentage/100))
-- lua script

-- The angle 42.0 can vary between 40.0 and 44.0 degrees during optimization
local p_angle = val(42.0, 2.0)

TierAD("P1", 0, p_angle, 0.5, "x8")

When the file is run normally in the studio, val(42.0, 2.0) evaluates directly to 42.0. When run inside the optimizer, the engine will explore the defined space automatically.


3D Geometry API

ProFacet Lua provides objects and methods to handle vector math, projections, rotations, and plane intersections.

Point, Vector, and Normal Methods

All coordinates and vectors constructed using Point(x,y,z), Vector(x,y,z), and Normal(x,y,z) support the following methods:

  • p:dot(other) — Computes the dot product with another point/vector.
  • p:cross(other) — Computes the cross product (returns a Vector).
  • p:normalize() — Returns a new unit-length vector/normal.
  • p:length() / p:magnitude() — Returns the magnitude of the vector.
  • p:lengthSquared() — Returns the squared magnitude of the vector.
  • p:distance(other) — Calculates the distance between two points.
  • p:angle(other) — Calculates the angle (in degrees) to another vector.
  • p:angleSigned(other, axis) — Calculates the signed angle (in degrees) relative to a reference axis.
  • p:lerp(other, t) — Linearly interpolates between two vectors by factor t.
  • p:slerp(other, t) — Spherical linear interpolation.
  • p:reflect(plane_or_point_or_vector) — Reflects the vector across a plane, point, or normal vector.
  • p:rotate(axis, angle) — Rotates the vector around a zero-centered axis by angle (in degrees).
  • p:rotateAround(pivot, axis, angle)(Point only) Rotates a point around an arbitrary axis passing through pivot.
  • p:project(other) — Projects the vector onto another vector.
  • p:midpoint(other) — Returns the midpoint between two vectors.
  • p:clampLength(max) — Clamps the vector magnitude to max.
  • p:toArray() — Converts the vector to a standard, 1-indexed Lua table {x, y, z}.

Mathematical Methods (No Operator Overloading)

Due to engine-level constraints in the Piccolo Lua virtual machine, standard mathematical operators (like +, -, *, /, ==) are not supported directly on custom geometric objects.

Instead, you must call their respective methods directly:

  • Addition: p:add(v) or p:add(p2)
  • Subtraction: p:sub(v) or p:sub(p2) (returns a Vector if subtracting two points)
  • Multiplication: v:mul(2.5) (scalar scaling)
  • Division: v:div(2.0) (scalar division)
  • Negation: v:unm() (unary negation)

To compare coordinates for equality, you must compare their individual coordinates manually (e.g., p1.x == p2.x and p1.y == p2.y and p1.z == p2.z).


Planes (Plane)

You can define and query 3D planes using the Plane primitive constructor:

  • Plane(origin, normal) — Defines a plane passing through origin with a given normal vector.
  • Plane(p1, p2, p3) — Defines a plane passing through three non-collinear points.

Properties:

  • plane.origin — Returns the plane's origin point.
  • plane.normal — Returns the plane's normal vector.

Methods:

  • plane:distance(point) — Returns the signed perpendicular distance from the plane to point.
  • plane:project(point) — Projects point orthogonally onto the plane.
  • plane:intersect(ray_origin, ray_direction) — Calculates the intersection of a ray with the plane. Returns a Point, or nil if the ray is parallel to the plane.

Machine & State Commands

To configure the faceting machine and cutting state:

  • cube(size) — Resets the gemstone polyhedron to a starting cube of the given size.
  • stone(sides, angle, height, symmetry) — Resets the gemstone polyhedron to a preform cylinder/cone.
  • setSide(side_str) — Explicitly changes the active side context to "Up" (or "Crown") or "Down" (or "Pavilion"). E.g. setSide("Down").

CAM Outline System Functions

The Centerpoint-Angle Method (CAM) outlines defined in the CAM Outlines Guide are fully supported as built-in functions in the Lua environment. You can use them to generate standard girdle and preform geometries in a single call:

  • Round(angle, sym) — Generates a circular preform. Defaults: angle = 43, sym = 10.
  • Octagon(angle) — Generates an octagonal preform. Default: angle = 43.
  • Heptagon(angle) — Generates a heptagonal preform. Requires gear 84. Default: angle = 43.
  • Pentagon(angle) — Generates a pentagonal preform. Requires gear 120. Default: angle = 43.
  • Hexagon(angle) — Generates a hexagonal preform. Default: angle = 43.
  • HexTr(angle, truncation) — Truncated hexagon. Default: truncation = 0.3.
  • HexTrMir(angle, truncation, offset) — Mirrored truncated hexagon. Defaults: truncation = 0.3, offset = 2.
  • Rect(lwr, angle) — Rectangle outline with length-to-width ratio lwr. Defaults: lwr = 1.5, angle = 45.
  • RectTr(lwr, angle, truncation, offset) — Truncated rectangle. Defaults: truncation = 0.3, offset = 0.
  • RectDblTr(lwr, angle, first_truncation, second_truncation, offset) — Double-truncated rectangle.
  • SquareTr(angle, truncation) — Truncated square. Default: truncation = 0.3.
  • SquareTrMir(angle, truncation, offset) — Mirrored truncated square. Default: offset = 4.
  • SquareCushTr(cushion, angle, truncation) — Cushioned truncated square. E.g. SquareCushTr(2, 45, 0.40).
  • Shield(angle) — Mirrored triangular shield outline. Default: angle = 45.
  • TriTr(truncation, angle) — Truncated triangle. Default: truncation = 0.25.
  • TriTrMir(truncation, angle, offset) — Mirrored truncated triangle. Default: offset = 4.
  • TriCushTr(truncation, angle, cushion) — Cushioned truncated triangle.
  • PentCushTr(offset, angle, truncation) — Cushioned truncated pentagon.
  • TriCurved(angle, c1, c2) — Curved trilliant preform.