LUMA
Architecture

Selection System

Tag expressions, spatial attributes, and circle fitting algorithm

The selection system translates abstract fixture queries into concrete fixture lists with spatial metadata. It is the key mechanism that enables venue-portable patterns.

Selection Expression Parser

File: src-tauri/src/services/groups.rs

Selection expressions support boolean algebra over fixture group names. Each token in the expression is matched against the snake_case names of groups in the current venue. A fixture matches a token if it belongs to a group with that name.

all                          -- all fixtures in the venue
front_wash                   -- fixtures in the "front_wash" group
front_wash & left_truss      -- fixtures in both groups
left_truss | right_truss     -- fixtures in either group
floor_ring & ~dj_booth       -- floor ring fixtures not in the DJ booth group
strobes > front_wash         -- prefer strobes, fall back to front wash

Operators

OperatorNameDescription
&ANDBoth conditions must match
|OREither condition matches
^XORExactly one condition matches
~NOTNegate the following condition
>FallbackUse left side; if zero matches, use right side
( )GroupingOverride operator precedence

The > (fallback) operator is unique: it evaluates the left side first, and only includes the right side's results if the left side matched zero fixtures. This enables graceful degradation across venues with different group inventories. For example, strobes > front_wash prefers strobes but falls back to front wash if no strobes group exists in the venue.

How Selection Reaches the Graph

Fixture selection is not a node in the pattern graph. Instead, every pattern has a Selection argument -- a selection expression that is set when the pattern is placed on a timeline as an annotation. The resolved fixtures are passed into the graph's execution context and automatically available to Apply nodes and Get Attribute nodes.

Spatial Attributes

The get_attribute node (in src-tauri/src/node_graph/nodes/selection.rs) extracts per-fixture scalar values from a Selection and outputs them as a Signal with N = fixture count, T = 1, C = 1.

AttributeDescription
indexInteger order within the selection (0, 1, 2, ...)
normalized_indexOrder normalized to 0.0-1.0 range
pos_xAbsolute global X position (meters)
pos_yAbsolute global Y position (meters)
pos_zAbsolute global Z position (meters)
rel_xX position relative to selection bounding box (0.0-1.0)
rel_yY position relative to selection bounding box (0.0-1.0)
rel_zZ position relative to selection bounding box (0.0-1.0)
rel_major_spanPosition along the axis with largest physical range
rel_major_countPosition along the axis with most distinct fixture positions
circle_radiusDistance from the selection's centroid
angular_positionAngle on fitted circle via PCA + RANSAC (0.0-1.0)
angular_indexInteger index around fitted circle: fixtures sorted by angle, then assigned 0, 1, 2, ...

rel_major_span vs rel_major_count

The "span" variant picks the axis with the largest physical extent (useful for linear arrangements where fixtures are spread unevenly). The "count" variant picks the axis with the most distinct position values (useful when fixtures are evenly spaced but the physical range is similar across axes).

Circle Fitting Algorithm

File: src-tauri/src/node_graph/circle_fit.rs

For detecting circular fixture arrangements in arbitrary 3D space (used by angular_position and angular_index attributes):

Step 1: Centroid

Compute the mean position of all input points.

Step 2: PCA Plane Fitting

Build the 3x3 covariance matrix of centered points. Extract the two dominant eigenvectors via power iteration with matrix deflation. These two vectors define the best-fit 2D plane containing the points.

Step 3: Project to 2D

Map each 3D point onto the fitted plane using dot products with the two basis vectors. This reduces the problem from 3D circle fitting to 2D.

Step 4: RANSAC Circle Fit

Run 100 iterations of random 3-point sampling:

  1. For each sample of 3 points, compute the circumcenter via the analytic formula
  2. Count inliers (points within 2.5 distance units of the circle)
  3. Keep the fit with the most inliers
  4. Early exit if 90% of points are inliers

Step 5: Kasa Refinement

Apply the Kasa algebraic circle fit on inliers, solving the normal equations via Cramer's rule (3x3 determinant). Recompute inliers with the refined circle.

Step 6: Angular Positions

Compute atan2(v - center_v, u - center_u) for each point's projected 2D coordinates, normalized from [-pi, pi] to [0, 1].

The algorithm is deterministic (fixed seed for the pseudo-random generator) so results are reproducible across graph executions.

On this page