Foundations
Welcome to Convex-λ, a functional geometry language designed for precision gemstone faceting. It combines declarative geometry definitions with functional programming concepts.
This chapter covers the fundamental building blocks of the language: syntax, comments, types, and variables.
Comments
Code should be documented. Convex-λ supports both line and block comments.
// This is a single line comment
// It ends at the end of the line
/*
This is a block comment.
It can span multiple lines.
Useful for describing complex logic.
*/
x = 10 // Comments can also appear after code
Types
Convex-λ has a simple but powerful type system.
Numbers
All numbers are double-precision floating point. Integers are treated as floats.
val_a = 42
val_b = 3.14159
val_c = -0.001
Strings
Text is enclosed in double quotes.
name = "Brilliant Cut"
note = "Set girdle width"
assert(name == "Brilliant Cut", "String equality")
Booleans
True or false logic values.
is_visible = true
has_finished = false
assert(is_visible, "Boolean check")
Arrays
Ordered lists of values. Arrays can hold mixed types, but typically hold homogenous data (like a list of Points).
// Array of numbers
fib = [1, 1, 2, 3, 5, 8]
assert(fib[5] == 8, "Array access")
// Array of mixed types (valid but rare)
mixed = [1, "two", true]
Structs
Key-value pairs for grouping data. Keys are identifiers.
config = {
speed: 100,
mode: "fast"
}
assert(config.speed == 100, "Struct access")
Variables & Assignments
There are four ways to handle values in Convex-λ, each with a specific purpose.
1. Internal Assignment =
Used for intermediate calculations. These values are stored in memory but not sent to the 3D viewer or output log.
width = 10
height = 20
// Calculated but not shown
area = width * height
assert(area == 200, "Area calc")
2. Render Assignment :=
This is the primary way to output data. It assigns a value to a variable AND renders it.
- If the value is Geometry (Plane, Point, Line), it is added to the "Stone" (the 3D model).
- If the value is Data (Number, String), it is printed to the Output Log.
// This calculates a value AND prints it to the Output
total_area := 10 * 20
// This creates a plane AND adds it to the 3D model
// We will cover geometry constructors later.
p1 := Plane(Vector(0,0,1), 5)
3. Debug Assignment ?=
Used for "Ghost" rendering. It shows the geometry in the viewer (usually as a wireframe) without adding it to the official intersection list (the Stone). This is useful for visualizing construction lines or helper planes.
// Show a guide plane (ghost) but don't cut the stone
guide_plane ?= Plane(Vector(1,0,0), 0)
4. The Underscore Pattern _
Often you want to inspect a value without creating a named variable. We use the underscore _ combined with := to log values directly to the output.
// Log a string
_ := "Starting calculation..."
// Inspect a calculation immediately
_ := 10 + 20 * 4
Configuration
Global configuration statements set up the environment. These are usually placed at the top of your file.
Gear(96) // Set index gear (default: 96)
RI(1.54) // Set Refractive Index (default: 1.54)
Mode("deg") // Set angle mode (default: "deg")
// Mode("index") is automatically set when Gear() is called.
Basic Arithmetic
Standard arithmetic operators apply to numbers.
a = 10 + 5 // 15
assert(a == 15, "+")
b = 10 - 5 // 5
assert(b == 5, "-")
c = 10 * 5 // 50
assert(c == 50, "*")
d = 10 / 5 // 2
assert(d == 2, "/")
e = 10 % 3 // 1 (Modulo)
assert(e == 1, "%")
f = 2 ^ 3 // 8 (Power)
assert(f == 8, "^")
The standard library provides common math functions.
import "std"
root = sqrt(16) // 4
assert(root == 4, "sqrt")
val = abs(-10) // 10
assert(val == 10, "abs")
s = sin(PI/2) // 1
assert(abs(s - 1) < 0.0001, "sin")
c = cos(0) // 1
assert(c == 1, "cos")
In the next chapter, we will explore the powerful operator system that allows these math operations to work on Vectors, Points, and Arrays.
Operators
Convex-λ is built on a rich operator system that often behaves differently depending on the types of the operands (Polymorphism). This allows for concise and expressive geometric logic.
This chapter details every operator with extensive examples.
Mathematical Operators
Addition +
Used for arithmetic addition, vector translation, and array concatenation.
1. Number + Number Standard addition.
sum = 10 + 5 // 15
assert(sum == 15, "Add Num")
2. Vector + Vector Component-wise addition.
v1 = Vector(1, 0, 0)
v2 = Vector(0, 1, 0)
v3 = v1 + v2 // Vector(1, 1, 0)
assert(v3 == Vector(1, 1, 0), "Add Vec")
3. Point + Vector Translates a point by a vector.
p = Point(0, 0, 0)
v = Vector(0, 0, 5)
p_new = p + v // Point(0, 0, 5)
assert(p_new == Point(0, 0, 5), "Point Translate")
4. Array + Array Concatenates two arrays.
arr1 = [1, 2]
arr2 = [3, 4]
arr3 = arr1 + arr2 // [1, 2, 3, 4]
assert(arr3.length == 4, "Array Concat")
5. Array + Scalar (Broadcast) Adds the scalar to every element of the array.
nums = [10, 20, 30]
result = nums + 5 // [15, 25, 35]
assert(result[0] == 15, "Array Broadcast +")
Subtraction -
Used for arithmetic subtraction, vector difference, and unary negation.
1. Number - Number
diff = 10 - 3 // 7
assert(diff == 7, "Sub Num")
2. Vector - Vector
v1 = Vector(1, 1, 1)
v2 = Vector(0, 0, 1)
v3 = v1 - v2 // Vector(1, 1, 0)
assert(v3 == Vector(1, 1, 0), "Sub Vec")
3. Unary Negation Negates numbers or vectors.
n = -10
v = -Vector(1, 0, 0) // Vector(-1, 0, 0)
assert(v.x == -1, "Negate Vec")
4. Array Subtraction (Broadcast)
arr = [10, 20, 30]
res = arr - 5 // [5, 15, 25]
assert(res[0] == 5, "Array Broadcast -")
Multiplication *
Used for scaling, dot products, symmetry composition, and intersection.
1. Number * Number
prod = 6 * 7 // 42
assert(prod == 42, "Mul Num")
2. Vector * Number Scales the vector.
v = Vector(1, 0, 0)
scaled = v * 5 // Vector(5, 0, 0)
assert(scaled.x == 5, "Scale Vec")
3. Plane * Plane (Intersection) Returns the Line where two planes intersect.
pl1 = Plane(Vector(1,0,0), 0) // YZ plane
pl2 = Plane(Vector(0,1,0), 0) // XZ plane
line = pl1 * pl2 // Line along Z axis
assert(line.dir.z == 1 || line.dir.z == -1, "Plane Intersect")
4. Line * Plane (Intersection) Returns the Point where a line hits a plane.
line = Line(Point(0,0,10), Vector(0,0,-1))
plane = Plane(Vector(0,0,1), 0)
hit = line * plane // Point(0,0,0)
assert(hit == Point(0,0,0), "Line Plane Hit")
5. Symmetry Group Composition Combines two symmetry operations.
// Rotate * Mirror composition
g = Rotate(Vector(0,0,1), 4) // * Mirror(...)
Division /
Used for arithmetic division and scaling.
1. Number / Number
quo = 10 / 2 // 5
assert(quo == 5, "Div Num")
2. Vector / Number Scales the vector by 1/N.
v = Vector(10, 0, 0)
scaled = v / 2 // Vector(5, 0, 0)
assert(scaled.x == 5, "Div Vec")
3. Point / Number Scales the point's coordinates (relative to origin).
p = Point(10, 10, 10)
scaled = p / 2 // Point(5, 5, 5)
assert(scaled.x == 5, "Div Point")
4. Array / Number (Broadcast)
arr = [10, 20, 30]
res = arr / 10 // [1, 2, 3]
assert(res[0] == 1, "Div Array")
5. Midpoint Calculation
Combining + and / to find a midpoint.
p1 = Point(0, 0, 0)
p2 = Point(10, 10, 10)
mid = (p1 + p2) / 2 // Point(5, 5, 5)
assert(mid.x == 5, "Midpoint")
Modulo / Cross Product %
Overloaded for remainder and vector cross product.
1. Number % Number Euclidean remainder.
rem = 10 % 3 // 1
assert(rem == 1, "Mod")
2. Negative Modulo Handles negative numbers correctly (Euclidean).
val = -10 % 3 // 2 (not -1)
assert(val == 2, "Euclidean Mod")
3. Array Broadcast
nums = [10, 11, 12]
mods = nums % 2 // [0, 1, 0]
assert(mods[1] == 1, "Mod Array")
Power / Distance ^
Overloaded for exponentiation and distance calculation.
1. Number ^ Number Exponentiation.
sq = 5 ^ 2 // 25
assert(sq == 25, "Pow")
2. Vector ^ Vector (Distance) Euclidean distance between two vectors tips.
v1 = Vector(0, 0, 0)
v2 = Vector(3, 4, 0)
dist = v1 ^ v2 // 5
assert(dist == 5, "Vec Dist")
3. Point ^ Point (Distance) Distance between two points.
p1 = Point(0, 0, 0)
p2 = Point(1, 1, 1)
dist = p1 ^ p2 // sqrt(3) ~ 1.732
assert(abs(dist - 1.732) < 0.001, "Point Dist")
4. Array Broadcast
nums = [1, 2, 3]
squares = nums ^ 2 // [1, 4, 9]
assert(squares[2] == 9, "Pow Array")
Projection >>
Projects a point onto a plane.
1. Point >> Plane Projects the point onto the plane surface along the normal.
pt = Point(0, 0, 10)
pl = Plane(Vector(0,0,1), 0) // XY Plane
// Projects pt onto pl
proj = pt >> pl
assert(proj.z == 0, "Projection")
2. Array >> Plane Projects multiple points.
pts = [Point(0,0,1), Point(0,0,2)]
pl = Plane(Vector(0,0,1), 0)
flat = pts >> pl
assert(flat[0].z == 0, "Array Projection")
Logical Operators
Returns true or false.
Comparison
==, !=, <, >, <=, >=.
1. Number Comparison
check = 10 > 5 // true
assert(check, "GT")
2. Equality Check
is_equal = 5 == 5 // true
assert(is_equal, "EQ")
3. Filtering Logic Used inside filters.
x = 10
assert(x > 0, "Check")
4. Boolean Logic
&& (AND), || (OR), ! (NOT).
x = 5
valid = (x > 0) && (x < 10)
assert(valid, "Logic")
5. Ternary Operator ? :
Conditional value selection.
x = 5
val = (x > 0) ? "Positive" : "Negative"
assert(val == "Positive", "Ternary")
Pipe Operators
Map / Apply |>
Passes the left operand as the first argument to the function on the right.
1. Function Application
x = 16 |> sqrt // same as sqrt(16) -> 4
assert(x == 4, "Pipe Func")
2. Array Mapping Applies the function to every element.
nums = [1, 4, 9]
roots = nums |> sqrt // [1, 2, 3]
assert(roots[1] == 2, "Pipe Map")
3. Symmetry Application Applies a symmetry group to geometry.
p = Point(1, 0, 0)
rot = Rotate(Vector(0,0,1), 4)
// Generates 4 points
sym_pts = p |> rot
assert(sym_pts.length == 4, "Symmetry Apply")
4. Chained Operations Reading left-to-right.
val = -16 |> abs |> sqrt
assert(val == 4, "Pipe Chain")
5. Lambda Application
nums = [1, 2, 3]
doubled = nums |> (x) => x * 2 // [2, 4, 6]
assert(doubled[2] == 6, "Pipe Lambda")
Filter ?|
Selects elements from an array that satisfy a condition.
1. Basic Filtering
nums = [1, 2, 3, 4, 5]
odds = nums ?| (x) => x % 2 != 0 // [1, 3, 5]
assert(odds.length == 3, "Filter Basic")
2. Geometric Filtering Find points above the equator.
pts = [Point(0,0,1), Point(0,0,-1)]
upper = pts ?| (p) => p.z > 0
assert(upper.length == 1, "Filter Geo")
3. Type Filtering (Advanced usage with type checks)
4. Range Filtering
nums = 1 .. 10
high = nums ?| (x) => x > 8 // [9, 10]
assert(high.length == 2, "Filter Range")
5. Combined with Map Filter then Map.
result = (1 .. 10) ?| ((x) => x > 5) |> (x) => x * 10
assert(result[0] == 60, "Filter Map")
Geometric Operators
Bind :
Binds a Normal Vector and a Point (or distance) to create a Plane.
1. Normal : Point
Creates a plane with normal n passing through point p.
n = Vector(0, 0, 1)
p = Point(0, 0, 5)
pl = n : p // Plane(0,0,1, dist=5)
assert(pl.distance == 5, "Bind Point")
2. Normal : Distance (Number)
Usually done via constructor Plane(n, d), but : works if overloaded (check specific implementation). Typically used with Points.
3. Array : Point Creates multiple planes passing through one point.
normals = [Vector(1,0,0), Vector(0,1,0)]
p = Point(0,0,0)
planes = normals : p
assert(planes.length == 2, "Bind Array Normal")
4. Normal : Array Creates parallel planes.
n = Vector(0,0,1)
pts = [Point(0,0,0), Point(0,0,10)]
planes = n : pts
assert(planes.length == 2, "Bind Array Point")
5. Auto-Centering If the point is the origin (0,0,0), it creates a center-point cut.
pl = Vector(1,1,1) : Point(0,0,0)
Sweep / Auto-Note ->
Similar to Bind, but implies a "cutting" action and often attaches automatic documentation notes (like "Meet A", "Set Girdle").
1. Normal -> Distance
Creates a plane at distance d.
pl = Vector(0,0,1) -> 5
assert(pl.distance == 5, "Sweep Dist")
2. Normal -> Point
Creates a plane through point p.
pl = Vector(0,0,1) -> Point(0,0,5)
assert(pl.distance == 5, "Sweep Point")
3. Auto-Note: Set Girdle
If the cut defines the girdle width, -> detects it.
girdle_pl = Vector(1,0,0) -> 10
// plane.notes is automatically set to "Set stone size" or "Set girdle width"
4. Auto-Note: Meet
If the target point was derived from an intersection, -> generates a "Meet ..." note.
pl1 = Plane(Vector(1,0,0), 1)
pl2 = Plane(Vector(0,1,0), 1)
pl3 = Plane(Vector(0,0,1), 1)
meet_pt = pl1 * pl2 * pl3
cut = Vector(1,1,1) -> meet_pt
// plane.notes = "Meet [Plane names...]"
5. Chaining
pl1 = Plane(Vector(1,0,0), 1)
pl2 = Plane(Vector(0,1,0), 1)
pl3 = Plane(Vector(0,0,1), 1)
final_plane = Vector(0,0,1) -> (pl1 * pl2 * pl3)
Contact *>
Finds the "Contact Point" or "Tangent Point". It searches a set of geometry for the point most extreme in the direction of a vector.
*1. Vector > Stone
Finds the vertex on the current stone that is furthest in direction v.
_ := Cube(2)
// Find the tip of the stone (highest Z)
tip = Vector(0,0,1) *> Stone
assert(tip.z == 1, "Contact Stone")
*2. Vector > Array
Finds the point in the array that maximizes dot product with v.
pts = [Point(0,0,0), Point(10,10,10)]
v = Vector(1,1,1)
best = v *> pts // Point(10,10,10)
assert(best.x == 10, "Contact Array")
3. Used for Meet-point Faceting "Cut until you touch this vertex".
_ := Cube(2)
target_v = Vector(0,0,1) *> Stone
new_cut = Vector(1,0,1) -> target_v
*4. Vector > Plane (Usually undefined unless bounded, behavior depends on implementation).
5. Finding Girdle Corners
_ := Cube(2)
corner = Vector(1,0,0) *> Stone
Range Operator ..
Creates an array of integers.
1. Basic Range
r = 1 .. 5 // [1, 2, 3, 4, 5]
assert(r.length == 5, "Range Len")
2. Loop Indexing Useful for creating sequences.
angles = 0 .. 3 |> (i) => i * 90
assert(angles[3] == 270, "Range Loop")
3. Creating Grids
x_vals = 0 .. 10
y_vals = 0 .. 10
4. With Filter
evens = (1 .. 10) ?| (x) => x % 2 == 0
assert(evens.length == 5, "Range Filter")
5. Negative Ranges
r = -3 .. 3 // [-3, -2, -1, 0, 1, 2, 3]
assert(r.length == 7, "Neg Range")
Functional Core
Convex-λ is a functional language. Functions are first-class citizens, meaning they can be assigned to variables, passed as arguments, and returned from other functions.
Lambda Expressions
Lambdas are anonymous functions defined using the => arrow syntax.
Basic Syntax
// Identity function
id = (x) => x
assert(id(5) == 5, "Identity")
// Squaring function
sq = (x) => x * x
assert(sq(4) == 16, "Square")
// Multiple arguments
add = (a, b) => a + b
assert(add(2,3) == 5, "Add")
Closures
Lambdas can "capture" variables from their surrounding scope. This is called a closure.
factor = 10
multiplier = (x) => x * factor
res = multiplier(5) // 50
assert(res == 50, "Closure")
Higher-Order Functions
These are functions that take other functions as input.
Map |>
Transforms every element in an array using a function.
nums = [1, 2, 3, 4]
// Square every number
squares = nums |> (x) => x * x
// Result: [1, 4, 9, 16]
assert(squares[3] == 16, "Map Square")
// Create Points from numbers
points = nums |> (z) => Point(0, 0, z)
// Result: [Point(0,0,1), Point(0,0,2), ...]
assert(points[0].z == 1, "Map Point")
Filter ?|
Selects elements that satisfy a boolean condition.
nums = 1..10
// Keep only evens
evens = nums ?| (n) => n % 2 == 0
// Result: [2, 4, 6, 8, 10]
assert(evens.length == 5, "Filter Even")
// Filter geometry
all_pts = [Point(0,0,1), Point(0,0,-1)]
upper_pts = all_pts ?| (p) => p.z > 0
assert(upper_pts.length == 1, "Filter Geo")
Fold
Reduces an array to a single value by iterating through it.
Syntax: arr.fold(reducer_lambda, initial_value)
nums = [1, 2, 3, 4]
// Sum
sum = nums.fold((acc, x) => acc + x, 0) // 10
assert(sum == 10, "Fold Sum")
// Product
prod = nums.fold((acc, x) => acc * x, 1) // 24
assert(prod == 24, "Fold Prod")
// Find Max
max_val = nums.fold((acc, x) => max(acc, x), 0)
assert(max_val == 4, "Fold Max")
Nested Lambdas & Currying
You can return functions from functions.
// A function that returns a function
make_adder = (n) => (x) => x + n
add_10 = make_adder(10)
res = add_10(5) // 15
assert(res == 15, "Curry")
Complex Chains
Functional programming allows you to build complex logic by chaining simple operations.
Example: Geometry Generation pipeline
- Generate range of angles.
- Filter out specific sectors.
- Map to Vectors (Normals).
- Bind to Planes.
// 1. Generate angles 0..360 in steps of 15
angles = (0..23) |> (i) => i * 15
// 2. Filter: Keep only angles in the first quadrant (0-90)
quad1 = angles ?| (a) => a >= 0 && a <= 90
// 3. Map: Create Normal vectors at 45 deg elevation
normals = quad1 |> (az) => Normal(45, az)
// 4. Map: Create Planes at distance 10
planes = normals |> (n) => Plane(n, 10)
// Render all
_ := planes
assert(planes.length == 7, "Chain Result")
Example: Nested Processing
Finding the average height of points in a point cloud.
cloud = [Point(1,1,5), Point(2,2,10), Point(3,3,15)]
// Extract Z values
heights = cloud |> (p) => p.z
// Sum them up
total_h = heights.fold((acc, h) => acc + h, 0)
// Calculate average
avg_h = total_h / heights.length
assert(avg_h == 10, "Avg Height")
Geometric Types
Convex-λ provides first-class support for 3D geometry.
Vector
Represents a direction and magnitude in 3D space. Vectors are location-independent.
Constructor
Vector(x, y, z)
v = Vector(1.0, 0.0, 0.0)
up = Vector(0, 0, 1)
assert(up.z == 1, "Vector Z")
Properties
.x,.y,.z: Coordinates
Helpers
Normal(elevation, azimuth): Creates a unit vector from spherical coordinates.elevation: 0 (Equator) to 90 (Pole).azimuth: 0 to 360.- Note: If
Mode("index")is active, elevation 0 is Pole, 90 is Equator.
// Standard Math Mode
n = Normal(90, 0) // Z axis (Pole)
assert(abs(n.z - 1) < 0.001, "Normal pole")
// Faceting Mode (after Gear())
Gear(96)
n_facet = Normal(0, 0) // Z axis (Pole)
assert(abs(n_facet.z - 1) < 0.001, "Facet Normal pole")
Point
Represents a specific location in 3D space.
Constructor
Point(x, y, z)
origin = Point(0, 0, 0)
p = Point(10, 5, -2)
assert(p.y == 5, "Point Y")
Properties
.x,.y,.z: Coordinates
Line
Represents an infinite line defined by an origin point and a direction vector.
Constructor
Line(Point, Vector): Point and Direction.Line(Point, Point): Two points (direction is p2 - p1).
l1 = Line(Point(0,0,0), Vector(0,0,1)) // Z axis
l2 = Line(Point(0,0,0), Point(1,1,1)) // Diagonal
Usage
Lines are typically the result of intersections (Plane * Plane).
Plane
Represents an infinite plane. This is the primary "cutting" tool in faceting.
Constructor
Plane(Normal, Distance): Normal vector and distance from origin.Plane(index, p1, p2): (Faceting mode only) Defines plane by gear index and two points.
// Plane facing X, at distance 5
p1 = Plane(Vector(1,0,0), 5)
assert(p1.distance == 5, "Plane Dist")
// Plane facing Z, passing through origin
p2 = Plane(Vector(0,0,1), 0)
Properties
.normal: The normal Vector..distance: The distance Number.
Symmetry Groups
Represents a geometric transformation or collection of transformations. Groups are applied using the pipe operator |>.
Constructor
Rotate(axis, order): Creates a rotational symmetry group.Mirror(plane): Creates a reflection symmetry.
// 4-fold rotation around Z
rot = Rotate(Vector(0,0,1), 4)
// Mirror across XY plane
mir = Mirror(Plane(Vector(0,0,1), 0))
// Composition using *
group = rot * mir
Arrays of Geometry
Most operations work on arrays of geometry (broadcasting).
ring_pts = [Point(1,0,0), Point(0,1,0), Point(-1,0,0), Point(0,-1,0)]
assert(ring_pts.length == 4, "Array length")
Helpers
Hull(points_array): Creates the convex hull (array of planes) from a set of points.
pts = [Point(1,0,0), Point(-1,0,0), Point(0,1,0), Point(0,-1,0), Point(0,0,1), Point(0,0,-1)]
octahedron = Hull(pts)
assert(octahedron.length == 8, "Octahedron faces")
Solid Modeling
In Convex-λ, a "Solid" or "Stone" is defined as the intersection of half-spaces defined by planes. You "cut" a stone by adding planes.
5 Ways to Make a Cube
Here are five different ways to define a Cube of size 2 (extending from -1 to 1).
1. The Built-in Function
The simplest way.
c1 = Cube(2)
_ := c1
assert(c1.length == 6, "Builtin Cube")
2. Explicit Planes
Defining all 6 faces manually.
planes = [
Plane(Vector( 1, 0, 0), 1), // Right
Plane(Vector(-1, 0, 0), 1), // Left
Plane(Vector( 0, 1, 0), 1), // Front
Plane(Vector( 0,-1, 0), 1), // Back
Plane(Vector( 0, 0, 1), 1), // Top
Plane(Vector( 0, 0,-1), 1) // Bottom
]
_ := planes
assert(planes.length == 6, "Explicit Planes")
3. Convex Hull of Points
Defining the 8 vertices and letting the Hull function calculate the planes.
// Helper to make code shorter
pm = [-1, 1]
// Generate all 8 points using functional mapping
points = pm |> (x) =>
pm |> (y) =>
pm |> (z) => Point(x, y, z)
// Note: This produces a nested array [[[Point...]]]
// but Hull flattens it automatically.
cube_hull = Hull(points)
_ := cube_hull
assert(cube_hull.length == 6, "Hull Cube")
4. Symmetry (Rotation)
Define one side face and rotate it 4 times, then add Top and Bottom.
// Side face (Right)
side = Plane(Vector(1,0,0), 1)
// Symmetry group: 4-fold rotation around Z
rotZ = Rotate(Vector(0,0,1), 4)
// Apply rotation
walls = side |> rotZ
// Add caps
caps = [
Plane(Vector(0,0,1), 1),
Plane(Vector(0,0,-1), 1)
]
full = walls + caps
_ := full
assert(full.length == 6, "Symm Cube")
5. Intersection of Slabs
A cube is the intersection of 3 perpendicular "slabs" (infinite strips).
// Function to make a slab (pair of parallel planes)
make_slab = (normal, width) => [
Plane(normal, width/2),
Plane(-normal, width/2)
]
slab_x = make_slab(Vector(1,0,0), 2)
slab_y = make_slab(Vector(0,1,0), 2)
slab_z = make_slab(Vector(0,0,1), 2)
slab_cube = slab_x + slab_y + slab_z
_ := slab_cube
assert(slab_cube.length == 6, "Slab Cube")
Platonic Solids
Tetrahedron
Using Hull of 4 points.
s = sqrt(2)
tetra_pts = [
Point(1, 0, -1/s),
Point(-1, 0, -1/s),
Point(0, 1, 1/s),
Point(0, -1, 1/s)
]
tetra = Hull(tetra_pts)
_ := tetra
assert(tetra.length == 4, "Tetrahedron")
Octahedron
Using the built-in or symmetry.
// Built-in
oct1 = Octahedron(2)
_ := oct1
assert(oct1.length == 8, "Octahedron Builtin")
// Or Intersecting Pyramids
pyramid_top = Plane(Vector(1,0,1), 1) |> Rotate(Vector(0,0,1), 4)
pyramid_bot = Plane(Vector(1,0,-1), 1) |> Rotate(Vector(0,0,1), 4)
oct2 = pyramid_top + pyramid_bot
_ := oct2
assert(oct2.length == 8, "Octahedron Symm")
Dodecahedron
Defined by 12 planes.
phi = (1 + sqrt(5)) / 2
// 1. A plane perpendicular to a vertex of a cube
p1 = Plane(Vector(0, 1, phi), 1)
// 2. Rotate it to cover all variations
// This requires complex icosahedral symmetry groups
// which we can simulate by permuting coordinates manually or using Hull.
Note: For complex solids like Dodecahedron and Icosahedron, calculating the Hull of their vertices is often easier.
Archimedean Solids
Truncated Cube
Start with a Cube, then cut off the corners.
// 1. Make the Cube
_ := Cube(2)
// 2. Cut corners
// A corner is at (1,1,1). The normal pointing at it is Vector(1,1,1).
corner_cut = Plane(Vector(1,1,1), 1.5)
// Apply 4-fold symmetry around Z, then Mirror Z
// Combine symmetry groups first
group = Rotate(Vector(0,0,1), 4) * Mirror(Plane(Vector(0,0,1), 0))
cuts = corner_cut |> group
_ := cuts
assert(cuts.length == 8, "Truncated Corners")
Faceting Setup
Welcome to Part 2: Faceting. While Part 1 focused on abstract geometry, Part 2 applies these concepts to the specific workflow of gemstone design.
Configuration
A faceting design always starts with the machine configuration.
Gear(96) // Set the index gear (96, 80, 64, etc.)
RI(1.76) // Set Refractive Index (e.g., Corundum)
Modes
Calling Gear(n) automatically switches the system to "index" mode.
- Angle: 0 to 90 degrees (elevation).
- 0° = Pole (Table)
- 90° = Equator (Girdle)
- Index: 0 to Gear Size (azimuth).
Note: This differs from standard math spherical coordinates used in Part 1.
The Faceting Coordinate System
When Gear is set, the Normal constructor adapts:
Normal(elevation_angle, gear_index)
// A facet on the table (top)
table = Normal(0, 0)
// A facet on the girdle at index 96 (or 0)
girdle_main = Normal(90, 96)
// A pavilion facet at 42 degrees, index 3
pavilion = Normal(42, 3)
Preforms
Before cutting facets, you usually establish a rough shape or "Preform".
Cylinder (Girdle)
A basic 16-sided cylinder preform.
// Define indices: 96 / 16 = 6
indices = (0..15) |> (i) => i * 6
// Create vertical planes (90 degrees) at distance 10
cylinder = indices |> (idx) => Normal(90, idx) -> 10
_ := cylinder
Cone (Pavilion)
Cutting a cone to establish a center point (CP).
// Cut cone at 42 degrees
cone = indices |> (idx) => Normal(42, idx) -> Point(0,0,0)
_ := cone
Symmetry helpers
In faceting, we often repeat cuts around the stone. While |> with Rotate works, standard loops and arrays are common.
// Standard 8-fold symmetry function
sym8 = (idx) => (0..7) |> (i) => (idx + i * (96/8)) % 96
// Generate all 8 indices for index 3
indices_3 = sym8(3)
// [3, 15, 27, 39, 51, 63, 75, 87]
Cutting & Tiering
This chapter covers the core of Meetpoint Faceting: placing facets precisely where they meet.
The Sweep Operator ->
In faceting, we rarely place planes at arbitrary coordinates. We "sweep" a plane from infinity until it hits a specific target.
Plane = Normal -> Target
1. Cutting to Centerpoint (CP)
The most common starting move. All pavilion mains usually meet at the culet (tip).
// Define symmetry indices (e.g., 96-index gear, 8-fold)
indices = [96, 12, 24, 36, 48, 60, 72, 84]
// Cut Mains at 42 degrees to Centerpoint
p1 := indices |> (i) => Normal(42, i) -> Point(0,0,0)
2. Cutting to Girdle Width
Establishing the size of the stone.
// Cut Girdle facets at 90 degrees, distance 5mm
girdle := indices |> (i) => Normal(90, i) -> 5
Note: The -> operator automatically detects this as a "Set stone size" operation and adds it to the facet notes.
3. Cutting to Meet (Meetpoint Faceting)
The heart of the process. We cut a new tier until it touches a vertex formed by previous tiers.
Example: Cutting Breaks
Assume we have p1 (Mains) and girdle tiers. We want to cut "Break" facets that meet where the Main and Girdle intersect.
-
Find the Meetpoint. Use the
*operator to intersect planes, or*>to find a vertex on the stone.// Find intersection of Main(96) and Girdle(96) // We access the specific planes from our arrays mp = p1[0] * girdle[0] * p1[1] // This defines a point where two mains and a girdle meetAlternatively, use
Contact (*>):// "Look" in the direction of index 6 (between 96 and 12) // The stone's current corner is there. target_v = Normal(0, 6) *> Stone -
Cut to the Meetpoint.
// Breaks at 45 degrees break_indices = [6, 18, 30, 42, 54, 66, 78, 90] p2 := break_indices |> (i) => Normal(45, i) -> target_vNote:
->automatically detects the meetpoint source and adds "Meet [Plane Names]" to the notes.
Tiering
A "Tier" is a group of facets (usually with same angle) cut in a symmetry pattern.
// Helper for 8-fold symmetry
sym8 = (start_idx) => (0..7) |> (k) => (start_idx + k*12) % 96
// Tier 1: Pavilion Mains
t1 := sym8(96) |> (i) => Normal(42, i) -> Point(0,0,0)
// Tier 2: Girdle
t2 := sym8(96) |> (i) => Normal(90, i) -> 5
// Tier 3: Breaks (meeting Mains & Girdle)
// Calculate one meetpoint
ref_mp = t1[0] * t2[0]
// Cut tier
t3 := sym8(6) |> (i) => Normal(45, i) -> ref_mp
Auto-Noting
The -> operator is smart. It analyzes the Target expression to generate cutting instructions.
-> Number: "Set stone size" or "Set girdle width" (if angle is 90).-> Point(0,0,0): "Cut to centerpoint".-> (P1 * P2): "Meet P1, P2".
This makes your code self-documenting for the final diagram generation.
Geometric Analysis
Part 2 concludes with advanced analysis. You can inspect the geometry you create to verify angles, measure distances, or optimize performance.
The Stone Variable
The Stone variable is a built-in structure that updates automatically whenever you use := with geometry.
// Setup
_ := Cube(2)
// Accessing Stone properties
_ := "Volume: " + Stone.volume
_ := "Area: " + Stone.area
_ := "Center: " + Stone.center
It also contains arrays of the raw geometry:
Stone.planes: All planes added.Stone.vertices: Calculated convex hull vertices.
Filtering Facets
You can use the functional skills from Part 1 to inspect specific parts of the stone.
Example: Counting Facets by Type
// Setup
_ := Cube(2)
// Helper to classify facets
is_pavilion = (p) => p.normal.z < -0.01
is_crown = (p) => p.normal.z > 0.01
is_girdle = (p) => abs(p.normal.z) <= 0.01
// Filter the Stone.planes array
pav_count = (Stone.planes ?| is_pavilion).length
crown_count = (Stone.planes ?| is_crown).length
_ := "Pavilion Facets: " + pav_count
_ := "Crown Facets: " + crown_count
Ray Casting
You can "shoot" a laser through the stone to measure thickness or light paths.
Operator: Line * Stone -> Returns sorted array of intersection points.
Example: Measuring Center Thickness
// Setup
_ := Cube(2)
// 1. Define a Ray straight down through the center
ray = Line(Point(0,0,10), Vector(0,0,-1))
// 2. Cast against the Stone
hits := ray * Stone
// 3. Analyze
// hits[0] is entry (Table), hits[1] is exit (Culet)
thickness = hits[0] ^ hits[1]
_ := "Center Thickness: " + thickness
Custom Analysis Tools
You can build your own tools using Blocks and Lambdas.
Example: Bounding Box
// Setup
_ := Cube(2)
BoundingBox = (points) => {
{
min: Point(
min(points |> (p) => p.x),
min(points |> (p) => p.y),
min(points |> (p) => p.z)
),
max: Point(
max(points |> (p) => p.x),
max(points |> (p) => p.y),
max(points |> (p) => p.z)
)
}
}
// Usage
box = BoundingBox(Stone.vertices)
_ := "Height: " + (box.max.z - box.min.z)
Simple Cut
A standard Round Brilliant style design.
// 1. Configuration
Gear(96)
RI(1.54)
// 2. Symmetry
sym = Rotate(Z, 8)
// 3. Pavilion (Bottom)
// Pavilion requires Planes (P), not just Vectors (n)
// Negative angle for Pavilion (Z < 0)
pavilion = Plane(Normal(-43.0, 4), 5) |> sym
// 4. Crown (Top)
// Positive angle for Crown (Z > 0)
crown = Plane(Normal(35.0, 4), 5) |> sym
// 5. Table
// Table is the top-most facet.
// Normal Z(0,0,1) points Up.
// Distance determines height. Must be > 0.
// Usually Table is higher than the Crown/Pavilion intersection height?
// Or we cut Crown/Pavilion first (infinite cone), then chop top with Table.
// If Crown distance is 5, tip is at Z ~ 5/cos(35) ~ 6.1.
// So Table should be at Z=6 or similar to create a facet.
table = Plane(Z, 6.0)
// 6. Assembly
main := pavilion + crown + table
Advanced Pavilion Design
This example demonstrates a multi-tier pavilion with color coding.
// 1. Configuration
Gear(96)
RI(1.54)
// Use standard Z-axis for 8-fold symmetry
sym = Rotate(Z, 8)
// Define a mirror plane across the Y axis (normal to Y)
// This creates 16-fold symmetry when combined with Rotate(Z,8)
mirror = Mirror(Plane(Y, 0))
// 2. Pavilion (Bottom)
// Pavilion angles are negative
P1 = Plane(Normal(-43.0, 0), 5) |> sym
P1 = P1 { color: "blue", notes: "Main Facets" }
// Tier 2: Star Facets
P2 = Plane(Normal(-41.0, 6), 5) |> sym
P2 = P2 { color: "cyan" }
// Tier 3: Lower Girdle
P3 = Plane(Normal(-44.0, 3), 5) |> sym * mirror
P3 = P3 { color: "navy" }
// 3. Crown (Top)
// Crown angles are positive
crown = Plane(Normal(35.0, 0), 5) |> sym
// 4. Table
table = Plane(Z, 6.0) // Flat top at height 6
// 5. Assembly
pavilion = P1 + P2 + P3
stone := pavilion + crown + table
Using Attributes
You can attach attributes to any geometry variable.
// Create P1 first (as above)
P1 = Plane(Normal(-43.0, 0), 5) |> sym
// Attach color
P1 = P1 { color: "blue" }
Gemstone Computations
You can write scripts to analyze geometry.
// 1. Setup Stone
sym = Rotate(Z, 8)
// Pavilion (Negative)
P1 = Plane(Normal(-42.0, 0), 5) |> sym
P2 = Plane(Normal(-41.0, 6), 5) |> sym
// Crown (Positive)
C1 = Plane(Normal(35.0, 0), 5) |> sym
// Table (Z+, Height 6)
Table = Plane(Z, 6.0)
Stone_Planes = P1 + P2 + C1 + Table
// Assign to "Stone" to render and compute hull
Stone := Stone_Planes
// 2. Extract Coordinates
// unique ensures we don't count duplicate vertices
Verts = Stone_Planes.unique.vertices
Xs = Verts |> (v) => v.x
Ys = Verts |> (v) => v.y
Zs = Verts |> (v) => v.z
// 3. Analysis Helpers
import "std"
// Custom fold to find max/min
get_max = (arr) => arr.fold((acc, x) => max(acc, x), -1000.0)
get_min = (arr) => arr.fold((acc, x) => min(acc, x), 1000.0)
min_x = get_min(Xs)
max_x = get_max(Xs)
// ... same for Y and Z
_ := [min_x, max_x]
Optical Checks
Calculate critical angles to check for windowing.
import "std"
// Calculate Critical Angle for RI 1.54
crit_angle = asin(1.0 / 1.54) * (180.0 / PI)
// Check if a pavilion angle works
is_windowing = 40.0 < crit_angle
_ := [crit_angle, is_windowing]
Analyzer
The Analyzer scores your gemstone design against professional-grade optical metrics. It runs 39 GPU-accelerated simulations across different viewing angles to measure brightness, contrast, and scintillation.
Before you start
- Run your code first so the interpreter outputs valid geometry.
- Clear all errors. The Analyzer stays disabled until the last run finishes without errors.
- The studio requires WebGPU support in your browser.
Run an analysis
- Open a design and click Analyzer in the side panel.
- When the run finishes, the metrics and charts update immediately.
Understand the results
- Metric cards show Average Brightness, Contrast Density, Contrast Intensity, Scintillation Score, Compactness, and Shannon Entropy. Each value is shown as a percentage relative to a reference brilliant.
- Average Brightness is the mean return light over every valid pixel in the sweep.
- Contrast Density measures the amount of contrast areas visible. It describes the spatial distribution of light and dark zones.
- Contrast Intensity measures the strength of the contrast in areas where it exists.
- Scintillation Score counts blink events between angles and normalises them by the number of pixels and tilt steps.
- Compactness reports how much of the stone's bounding box is filled by the actual volume.
- Shannon Entropy measures the information content or "richness" of the light distribution.
- Per-angle sparklines plot the same metrics for each of the 39 tilt angles so you can see where the stone rises or falls off.
Head Shadow Toggle
The Head Shadow checkbox controls whether the simulation includes a realistic head shadow—the occlusion caused by the viewer's head blocking some overhead light.
- Enabled (default): Simulates realistic viewing conditions.
- Disabled: Removes the head shadow for brighter overall scores, useful for comparing theoretical maximum brightness.
Toggle this setting and the Analyzer immediately re-runs.
Show Angle Images
Click 📷 Show Angle Images to see the actual rendered frames from all 39 viewing angles. A modal opens with a grid of images labeled by their tilt angle (e.g., L45° for 45° left tilt, 0° for upright view, T45° for 45° tilt).
This is invaluable for debugging:
- See exactly where brightness drops or spikes
- Identify problem areas like light leakage or dead zones
- Verify that geometry is correct across viewing angles
Optimizer
The Optimizer explores your design's parameter space to find configurations that maximize optical performance. It leverages WebGPU acceleration to evaluate thousands of variations in real-time.
Configuring the optimizer in code
In Convex-λ, you configure the optimizer directly in your source code by assigning an optimizer variable with a struct:
optimizer = {
algorithm: "de",
maxIterations: 200,
populationSize: 20,
weights: {
brightness: 0.4,
contrast: 0.3,
scintillation: 0.2,
compactness: 0.1
}
}
Configuration options
| Option | Type | Description |
|---|---|---|
algorithm | string | "de" (differential evolution), "ga" (genetic), "sa" (simulated annealing), "random" |
maxIterations | number | Maximum optimization iterations |
populationSize | number | Population size for evolutionary algorithms |
weights | struct | Weights for each metric (brightness, contrast, scintillation, etc.) |
crossoverRate | number | Crossover rate for DE/GA (0.0–1.0) |
differentialWeight | number | Differential weight for DE (typically 0.5–1.0) |
mutationRate | number | Mutation rate for GA |
initialTemp | number | Starting temperature for SA |
finalTemp | number | Ending temperature for SA |
coolingRate | number | Cooling rate for SA |
seed | number | Random seed for reproducibility |
Marking values as optimizable
Add hints after numbers to mark them as optimizable.
Syntax Note: The hint immediately follows the number. Do not enclose the hint in parentheses.
// Percentage range: +/- 10% of the value
angle = 45 +/- 10%
angle = 45 ± 10%
// Absolute range: between 3.2 and 3.6
depth = 3.4 3.2..3.6
// Plus/minus offset
width = 2.0 +/- 0.5
width = 2.0 ± 0.5
Hint formats
| Format | Meaning | Example |
|---|---|---|
value +/- N% | ±N% of value | 45 +/- 10% → 40.5 to 49.5 |
value ± N% | ±N% of value | 45 ± 10% → 40.5 to 49.5 |
value min..max | Explicit range | 3.4 3.2..3.6 |
value +/- delta | ±delta | 2.0 +/- 0.5 → 1.5 to 2.5 |
value ± delta | ±delta | 2.0 ± 0.5 → 1.5 to 2.5 |
If no hints are present, the engine returns "No optimizable parameters found".
Launch and monitor a run
- Run your code and clear every error.
- Press Start Optimization. The button switches to "Optimization Running".
- The progress panel shows:
- Current iteration and best score
- Improvement vs. initial design
- Live metrics (brightness, contrast, scintillation, entropy, compactness)
- Convergence chart
- Algorithm details (population, generation, evaluations)
- Parameter table showing initial, current, and best values
- Stop & Keep Best stops and preserves the strongest candidate so far.
Compare and apply
- The comparison panel shows side-by-side viewports labeled Original and Optimized.
- Replace Editor with Optimized swaps the editor contents with the optimized parameters.
- Save Optimized as New Version creates a new file while preserving the original.
Example
// Configure the optimizer
optimizer = {
algorithm: "de",
maxIterations: 150,
populationSize: 15,
weights: {
brightness: 0.5,
contrast: 0.3,
scintillation: 0.2
}
}
// Mark values as optimizable
pavilionAngle = 41.5 +/- 10% // ±10%
crownAngle = 34.0 30..38 // range 30-38
// Basic setup
Gear(96)
Sym := Rotate(Z, 8)
// Geometry
Pavilion := Plane(Normal(pavilionAngle), 10) |> Sym
Crown := Plane(Normal(crownAngle), 10) |> Sym
Stone := Pavilion + Crown
Diagram Generation
Convex-λ automatically generates cutting instructions (diagrams) for every script execution. This diagram includes the Pavilion and Crown tiers, index settings, and angles.
Metadata (Info Struct)
You can customize the diagram's header information (Author, Title, Footnote, etc.) by defining a special info variable in your script.
You can use either = or := assignment.
info = {
title: "My Brilliant Cut",
author: "Jane Doe",
date: "2024-03-20",
footnote: "Best cut in RI 1.76"
}
Supported Fields
| Field | Description |
|---|---|
title | The main title displayed at the top of the diagram. Defaults to the script filename if omitted. |
author | Displayed below the title. |
date | A date string displayed in the header. |
footnote | Small italic text displayed at the bottom of the diagram (useful for copyright, specific instructions, or material notes). |
Tier Sorting & Splitting
The diagram generator automatically organizes facets into "Pavilion" and "Crown" sections based on their geometric position.
- Crown: Facets facing generally "Up" (Z > 0).
- Pavilion: Facets facing generally "Down" (Z <= 0).
Tiers are listed in the order they appear in your code (or the order they are constructed), preserving your specific cutting sequence.
Gear & Indexes
The diagram automatically detects the gear setting from your code using the Gear(N) directive (e.g., Gear(96)).
- Index conversions are automatic.
- The diagram displays the specific gear number used (e.g., "Gear: 96").
Refractive Index (RI)
The diagram also displays the Refractive Index (RI) used for the stone, helping you verify that the design matches the intended material. This is taken from your RI(n) setting.
Printing
Click the Print button in the diagram panel to open a clean, printer-friendly version of the cutting instructions, including:
- Metadata headers
- Formatted tables for Pavilion and Crown
- Wireframe views (Top, Bottom, Side, Perspective)
Storage
Ultra IDE supports three storage backends for your Convex-λ projects. Each has different characteristics for file access, authentication, and synchronization.
At a Glance
| Feature | 📁 Local Storage | ☁️ Google Drive | 📦 Dropbox |
|---|---|---|---|
| Files live... | On your computer's disk | In your Google Drive cloud | In your Dropbox cloud |
| Authentication | None (browser permission) | Google sign-in | Dropbox sign-in |
| Synchronization | Instant (direct disk access) | On explicit save | On explicit save |
| Offline access | ✅ Full | ❌ Requires internet | ❌ Requires internet |
| External editing | ✅ Use any editor | ❌ Use Ultra IDE only | ❌ Use Ultra IDE only |
📁 Local Storage
When you use Local Storage, Ultra IDE works directly with files on your computer using your browser's File System Access API.
How it works
- Select a Working Folder on your disk (e.g.,
/Users/jon/Designs). - Ultra IDE scans this folder for
.cvfiles and displays them in the file tree. - When you edit and save, changes are written directly to disk—just like a native application.
- If you edit files with an external editor (VS Code, Sublime), Ultra IDE sees those changes on next open.
Synchronization behavior
Instant and automatic. Because files live on your local disk:
- Saving a file immediately updates the disk.
- The working folder is persisted across browser restarts.
- You can use Git, Dropbox desktop sync, or any other file-level tools alongside Ultra IDE.
Requirements
- Browser: Chrome, Edge, or Brave (uses File System Access API).
- Permission: You grant the browser one-time permission to read/write your working folder.
Note: Safari and Firefox do not support the File System Access API. Use Chrome-based browsers for local storage.
☁️ Google Drive
When you use Google Drive, Ultra IDE stores your project files in your Google Drive cloud storage.
How it works
- Sign in with your Google account when prompted.
- Select or create a project folder in your Drive (e.g.,
My Gems/Project1). - Ultra IDE downloads the project files into memory.
- When you save, changes are uploaded back to Google Drive.
Synchronization behavior
Explicit saves only. Cloud storage works differently from local disk:
- Files are not synced in real-time like Dropbox Sync or Google Drive desktop.
- When you open a project, Ultra IDE downloads a snapshot of your files.
- When you save, Ultra IDE uploads the changed files.
- If you modify files through the Google Drive web interface while the IDE is open, those changes won't appear until you reopen the project.
What this means for you
- Save frequently to push your work to the cloud.
- Don't edit the same files in both Ultra IDE and Google Drive simultaneously.
- Your files are safe in the cloud, accessible from any device with Ultra IDE.
📦 Dropbox
When you use Dropbox, Ultra IDE stores your project files in your Dropbox cloud storage.
How it works
- Sign in with your Dropbox account when prompted.
- Enter or select a project folder path (e.g.,
/Gemstones/MyDesign). - Ultra IDE downloads the project files into memory.
- When you save, changes are uploaded back to Dropbox.
Synchronization behavior
Explicit saves only. Identical to Google Drive:
- Files are downloaded once when you open the project.
- Saves upload changes to Dropbox.
- The Dropbox desktop app will sync those changes to your computer, but Ultra IDE doesn't monitor for external changes.
How It Works Behind the Scenes
Ultra IDE uses a multi-adapter architecture to support different storage backends:
┌─────────────────────────────────────────────┐
│ Ultra IDE │
│ (File Tree, Editor, Tabs, Run Code) │
└───────────────────┬─────────────────────────┘
│
┌─────────▼─────────┐
│ Integration Layer │
│ (file-system-integration.ts)
└─────────┬─────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Local │ │ Google │ │ Dropbox │
│ Adapter │ │ Drive │ │ Adapter │
│ │ │ Adapter │ │ │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
[Disk] [REST API] [REST API]
All adapters implement the same IFileSystemService interface, so the IDE doesn't need to know which backend is active—it just calls readFile(), writeFile(), and listFiles().
Common Questions
"I saved in Ultra IDE but my Dropbox desktop app doesn't show the change!"
Dropbox does sync the file—check the timestamp. But if you have the file open in another editor, you may need to reload it there to see the update.
"I edited a file on Google Drive web—why doesn't Ultra IDE show the change?"
Ultra IDE downloaded your project when you opened it. External changes made after that point aren't automatically detected. Close and reopen the project to refresh.
"Is my code stored on ProFacet servers?"
No. Ultra IDE is a client-side application. Your files go directly between your browser and:
- Your local disk (Local Storage), or
- Google's servers (Google Drive), or
- Dropbox's servers (Dropbox)
ProFacet never sees your design files.
"Which should I use?"
| Your situation | Recommended |
|---|---|
| I want files on my computer with Git | 📁 Local Storage |
| I work from multiple devices | ☁️ Google Drive or 📦 Dropbox |
| I share designs for others to view | ☁️ Google Drive (use Drive's sharing) |
| I need offline access | 📁 Local Storage |
| I'm on Safari/Firefox | ☁️ Cloud storage (local not supported) |
⚠️ Collaboration warning: Cloud storage is not designed for simultaneous editing. If two people edit the same file at the same time, the last person to save will overwrite the other's changes. For real collaboration, use Local Storage with Git.
BNF Grammar
This is the complete formal grammar for Convex-λ in Backus-Naur Form.
Program Structure
<program> ::= <statement>*
<statement> ::= <import>
| <export>
| <assignment>
| <config>
| <expression>
| <comment>
Import and Export
<import> ::= "import" <import_list> "from" <string>
<import_list> ::= <identifier> ("," <identifier>)* ["as" <identifier>]
<export> ::= "export" <identifier_list>
<identifier_list> ::= <identifier> ("," <identifier>)*
Assignments
<assignment> ::= <identifier> "=" <expression> # variable binding
| <identifier> ":=" <expression> # renderable output
Configuration
<config> ::= "Gear" "(" <number> ")"
| "Mode" "(" <string> ")"
| "RI" "(" <number> ")"
Expressions
<expression> ::= <ternary>
<ternary> ::= <logical_or> ("?" <expression> ":" <expression>)?
<logical_or> ::= <logical_and> ("||" <logical_and>)*
<logical_and> ::= <equality> ("&&" <equality>)*
<equality> ::= <comparison> (("==" | "!=") <comparison>)*
<comparison> ::= <term> (("<" | ">" | "<=" | ">=") <term>)*
<term> ::= <factor> (("+" | "-") <factor>)*
<factor> ::= <power> (("*" | "/" | "%") <power>)*
<power> ::= <unary> ("^" <power>)?
<unary> ::= ("!" | "-" | "?")? <pipe>
<pipe> ::= <postfix> (("|>" | "?|") <postfix>)*
<postfix> ::= <primary> <postfix_op>*
<postfix_op> ::= "." <identifier> # property access
| "(" <arg_list>? ")" # function call
| "[" <expression> "]" # index access
| ">>" <expression> # index shorthand
Primary Expressions
<primary> ::= <number>
| <string>
| <identifier>
| <array>
| <lambda>
| <fold>
| <constructor_call>
| "(" <expression> ")"
<array> ::= "[" (<expression> ("," <expression>)*)? "]"
<lambda> ::= <identifier> "=>" <expression>
| "(" <param_list>? ")" "=>" <expression>
<fold> ::= "fold" "(" <expression> "," <expression> "," <expression> ")"
Constructors
<constructor_call> ::= "n" "(" <expression> "," <expression> ")"
| "p" "(" <expression> "," <expression> "," <expression> ")"
| "v" "(" <expression> "," <expression> "," <expression> ")"
| "P" "(" <expression> "," <expression> ")"
| "l" "(" <expression> "," <expression> ")"
| "Rot" "(" <expression> "," <expression> ")"
| "Mir" "(" <expression> ")"
| "Hull" "(" <expression> ")"
Literals
<number> ::= <float> <opt_hint>?
<opt_hint> ::= <range_hint> | <delta_hint>
<range_hint> ::= <float> ".." <float>
<delta_hint> ::= ("+/-" | "±") <float> "%"?
<string> ::= '"' <char>* '"'
<identifier> ::= <letter> (<letter> | <digit> | "_")*
<letter> ::= "a".."z" | "A".."Z" | "_"
<digit> ::= "0".."9"
Comments
<comment> ::= "//" <any>* <newline>
| "/*" <any>* "*/"
Built-in Constants
<constant> ::= "PI" | "PHI" | "RI" | "true" | "false"
Operators Summary
| Category | Operators |
|---|---|
| Arithmetic | +, -, *, /, %, ^ |
| Comparison | ==, !=, <, >, <=, >= |
| Logical | &&, ||, ! |
| Assignment | =, := |
| Pipe | |>, `? |
| Geometric | :, ->, *> |
| Access | ., >>, [] |
| Lambda | => |
| Range | .. |
Core API Reference
Standard Library Functions
Built-in functions available in the default scope or via import "std".
Math
sin(rad),cos(rad),tan(rad)asin(v),acos(v),atan(v)sqrt(n),abs(n),pow(n, exp)min(a, b),max(a, b)floor(n),ceil(n),round(n)PI: Constant (3.14159...)
Collections
avg(array): Returns average of numbers.print(args...): Prints to console.Grid(start, step, count): Generates an arithmetic sequence.
Array Methods
array.fold(reducer, initial): Reduces array to single value.array.map(lambda): Transforms each element.array.filter(lambda): Selects elements.array.count/array.length: Number of elements.
import "std"
sum := (1..10).fold((acc, x) => acc + x, 0)
Geometry Constructors
Primitives
Vector(x, y, z): Creates a Vector.Point(x, y, z): Creates a Point.Normal(elev, index): Creates a normal Vector from machine angles (elevation, index). RequiresGear().Normal(elev, azim): Creates a normal Vector from spherical coordinates (elevation, azimuth).Plane(normal, dist): Creates a Plane.Line(origin, dir): Creates a Line.
Solids
Cube(size): Creates a Cube centered at origin.Octahedron(size): Creates an Octahedron.Hull(geometry...): computes the convex hull of the provided points/planes.
Symmetry
Rotate(axis, order): Creates a rotational symmetry group of ordernaroundaxis.Mirror(plane): Creates a reflection symmetry group acrossplane.
Global Configuration
Gear(n): Sets the index gear (e.g., 96). Enables Index Mode.RI(n): Sets the Refractive Index.Mode(s): Sets the angle mode ("deg" or "index").