Skip to content

kinglevel/RayMA

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RayMA

A modular object explorer, search engine, and action toolkit for grandMA3.

The main idea is "Spotlight"/"Raycast" for GrandMA3.

This is a broken example and poorly made plugin purely made by vibe-coding with claude. If anyone wants to pick this up, go ahead and fork it and update it.

This should NOT be used in a show-critical showfile.

Known issues:

  • Elevated UI mode is not implemented well.
  • Prisms and photons framework is in its infancy and their behaviors are broken.
  • Selections needs refinement.
  • Loose-folder modules (prisms/, photons/, user_modules/) do not load when the plugin is imported into a showfile — MA3 only embeds the XML-declared components. This is by design (they are meant as loose "USB-stick" drop-ins); see TODO.md.

Keyboard capture

RayMA grabs the keyboard the same way MA's own patch/editor windows do: the overlay sets OverrideKeybSC="Yes", which suspends MA's keyboard shortcuts (the ShCuts icon turns red) and frees the physical keys so they land in RayMA's capture field. Focus is taken natively with FindBestFocus — no synthetic mouse clicks, and no global KeyboardShortcutsActive toggle to restore. Shortcuts come back automatically the moment the window closes.

Description

  • Version: 8.0
  • Target firmware: grandMA3 2.3.x
  • Runtime: Lua 5.4

RayMA turns the MA3 object tree into a fast, keyboard-driven workspace: fuzzy search across the show file, a tree browser, a property inspector, extensible quick actions, a command line, a calculator, a file browser, a persistent shell terminal, and a full plugin framework for building your own tools.


Screenshots

Home screen

Home screen

Launches straight into an empty search with the module list grouped by family (Navigate / Tools / Selection). Status line shows the live object index count in the showfiles datapoools — here, 2959 objects.

Search

Searching for macros

Typing macr fuzzy-matches across all data pools. Hits are ranked by relevance and show name, type, pool number, datapool, and a short content summary (2 lines, 1 lines). Selected row is highlighted green. Typing anything will match to object type or name.

Object browser

Root browser

Tree navigator starting at Root. Each row shows index, name, class, and children count — press Enter to drill down, Right to inspect properties, Left/Esc to go back up.

BPM Tap

BPM Tap module

Tap-tempo meter with running average and actions to apply the result to a Speed master, reset, or read the current master value. Enter taps, Esc exits.

Cat Facts

Cat Facts module

Toy user module that fetches random cat facts from a public API — included as a live example of async HTTP work from within a RayMA module.

Color Slot Machine

Color Slots module

Three-reel slot machine that spins through a palette of named colors and applies the winner (here Teal) to the current selection. Demonstrates animated, stateful UI inside a user module.

Showfile Stats

Showfile Stats module

Breakdown of a loaded show: total object count and memory use, then per-category rows (Patch — Fixtures / Parameters / Universes; Objects — Groups / Worlds / Filters). Enter drills into any category.

and more...


Installation

The plugin is designed to live at:

<ProgramData>/MALightingTechnology/gma3_library/datapools/plugins/RayMA/
  1. Fork the plugin.
  2. Git clone your repo to the plugin folder to the path above.
  3. In grandMA3, import RayMA.xml via the Plugins pool.
  4. Attach claude to the RayMA Folder
  5. Discuss with claude how to improve and implement features
  6. commit and push updates to your repo.
  7. Create pull requests to this main repo.

No external dependencies. The terminal module will opportunistically use LuaSocket if available, but falls back to io.popen.

Deploy for development

When iterating, use scripts/deploy.py to copy the working tree into the local MA3 onPC plugin DataPool instead of copying files by hand:

python3 scripts/deploy.py            # deploy to the default gma3_library location
python3 scripts/deploy.py --dry-run  # preview what would be copied
MA3_PLUGINS_DIR=/some/path python3 scripts/deploy.py   # override the target dir

It mirrors RayMA.xml, RayMA.lua, and the core/ modules/ prisms/ photons/ user_modules/ trees into <ProgramData>/MALightingTechnology/gma3_library/datapools/plugins/RayMA/ (macOS: ~/MALightingTechnology/...), skipping .git/.DS_Store junk. After it runs, reload the show (or restart MA3 onPC) to pick up the changes.


Launching

Invoke the plugin with an optional argument:

Argument Behavior
(none) Open the search home screen (default)
search Force search mode
root Open the browser at Root()
<ModuleName> Switch directly into a module (e.g. browser, cmd, math, terminal, bpm)
<ModuleName>/seg1/seg2/... Open module with a route (passed to onRoute)
#<ModuleName> Open the module in a tabbed elevated (full-workspace) UI
ray <command> Headless: execute a Ray shorthand command without opening the UI
debug Enable debug logging (combinable, e.g. debug root)

Session state (last mode, last query) persists across invocations via GlobalVars.


UI Modes

  • Compact – a floating overlay with a single-line search input, result list, and status bar. Default for keyboard-first workflows.
  • Elevated – a full-screen tabbed workspace holding up to 5 concurrent module tabs, each with its own state. Enter via #<ModuleName> or when opening a module marked elevatedOnly.

Architecture

RayMA.lua              Entry point, Main loop, auto-loaders
core/
  state.lua            Global state singleton, color cache, registries
  helpers.lua          Object introspection helpers (name/class/address/children)
  ui.lua               Compact UI builder and renderer
  keys.lua             Key dispatch and signal wiring
  elevated.lua         Tabbed full-workspace UI
modules/               Built-in modules (XML-registered)
photons/               Value parsers (color / beam / preset / fan)
prisms/                Symgrid layout algorithms (auto-loaded)
user_modules/          Drop-in user modules (auto-loaded, no XML edit)

Prisms, photons, and user modules are discovered at startup by scanning their directories (dir /B on Windows, ls on Unix). Files prefixed with _ load first so they can expose shared utilities.


Built-in Modules

Module Purpose
search Fuzzy search over the data pools (Sequences, Groups, Macros, Presets, Pages, Layouts, Timecodes, Timers, Worlds, Plugins, Filters, MAtricks, Appearances). Type aliases (seq, grp, pre …) and 4 sort modes (relevance, children, type, datapool).
browser Object tree browser — navigate Root, ShowData, data pools, and any MA3 object.
properties Property inspector — all readable properties with type, value, and enum info.
qa Quick Actions — context-sensitive menu (playback, edit, browse, cmdline, info). Extensible via RayMA.registerQAAction.
cmd MA3 command line with visual caret.
math Sandboxed calculator with history, math functions, pi/tau/phi/e, and ans.
help Two-level help — module list → module details, auto-populated from module metadata.
terminal Persistent shell terminal (LuaSocket TCP bridge, io.popen fallback). Windows cmd.exe / Unix bash / MA3 console.
selection Live view of fixtures currently selected in the programmer.
filebrowser Filesystem browser with copy/cut/paste, multi-select, sort, and filter.
ray Shorthand command interpreter — dispatches to photon parsers and prism layouts; maintains history and result cache.

Photons (value parsers)

Photons live in photons/ and are registered on load. Ray uses them to parse shorthand values.

  • color#RRGGBB, #RGB, #RRGGBBAA, or named colors (red, blue, …).
  • beam — dimmer (%), gobo (gobo N), strobe (strobe N), and position (pan/tilt/zoom).
  • preset — pool/preset by name or number, including color 1.5-style references.
  • fan — distributes a value sequence across the selection (red - blue - green), interpolating between stops.

Drop a new .lua file into photons/ to register a new parser.


Prisms (symgrid layouts)

Prisms live in prisms/ and implement fixture grid layout algorithms. Files starting with _ load first as shared utilities.

Prism Layout
default Bird's-eye (2D top) if 3D data exists, else by type.
2d Top/front/left/right projections with Z-band detection.
auto Ring vs. linear auto-detection per type.
compact Remove gaps while keeping structure.
cylinder Cylindrical UV unwrap (angle → column, height → row).
sphere Spherical UV unwrap (lat/lon).
type One row per fixture type, mirror-aware columns.
fid Group rows by FID prefix.
flip, mirror Flip / mirror the current grid.
_geometry, _grid_utils Shared geometry + grid helpers.

User modules (auto-loaded)

Drop-in modules in user_modules/ are loaded automatically — no XML edit required. Files prefixed with _ (like _template.lua) are skipped by the loader and serve as references.

Module What it does
banner ASCII banner display.
bpm Tap-tempo BPM meter; applies to Speed master.
catfacts Random cat facts.
christmas Seasonal greeting.
colorbucket Color mixing palette.
colorslots 3-reel slot machine that applies the resulting color (with strobe).
countdown Configurable countdown timer.
dmxdip DMX DIP-switch calculator (1–512).
helloworld Minimal example module.
luxcalc Lux calculator (aperture / shutter / ISO / ND).
stats Show-file statistics — object counts and memory usage.
subfixture Subfixture navigator grouped by fixture type.
tcoffset Timecode offset calculator.
timecodeeditor Timeline event editor with its own selection state.

Extending RayMA

Minimal user module

Drop a .lua file into user_modules/ — it's auto-loaded at startup, no XML edit. The module describes itself, draws a list of results into RayMA.T.RESULTS, and handles keys. Here's a complete, runnable example — user_modules/greet.lua:

-- user_modules/greet.lua — a tiny greeting picker
local S = RayMA.getModuleState("greet")    -- state survives mode switches
S.greetings = S.greetings or { "Hello", "Hej", "Bonjour", "Ciao", "你好" }

local mod = {
    name        = "greet",
    family      = "Community",
    description = "Pick a greeting and echo it",
    helpEntries = {
        { "Type",   "Filter greetings" },
        { "Enter",  "Echo the selected greeting" },
        { "Esc",    "Go back" },
    },
}

local function rebuild(query)
    local T, q = RayMA.T, (query or ""):lower()
    T.RESULTS = {}
    for i, g in ipairs(S.greetings) do
        if q == "" or g:lower():find(q, 1, true) then
            T.RESULTS[#T.RESULTS + 1] = { name = g, type = "Greeting", no = i }
        end
    end
end

function mod.onEnter()
    RayMA.T.QUERY, RayMA.T.CARET, RayMA.T.CURSOR = "", 0, 1
    rebuild("")
    RayMA.UI.refresh()
end

function mod.renderRow(entry)  return string.format("  #%-2d  %s", entry.no, entry.name)  end
function mod.getStatusText()    return #RayMA.T.RESULTS .. " greetings  |  Enter: echo  |  Esc: back"  end
function mod.onChar(c)          RayMA.H.defaultOnChar(c); rebuild(RayMA.T.QUERY); RayMA.UI.refresh()  end

function mod.onKey(keyCode)
    local KC = RayMA.H.KC
    if keyCode == KC.ESCAPE then RayMA.goBack(); return end
    if RayMA.H.handleListKey(keyCode) then return end
    if RayMA.H.handleTextKey(keyCode) then rebuild(RayMA.T.QUERY); RayMA.UI.refresh(); return end
    if keyCode == KC.ENTER then
        local e = RayMA.T.RESULTS[RayMA.T.CURSOR]
        if e then Echo("[greet] " .. e.name) end
    end
end

RayMA.registerModule(mod)

-- Optional: let users type "greet" to jump in, or launch with arg "greet"
RayMA.registerCommand({
    name = "Greet", command = "greet", _module = "greet",
    description = mod.description,
    handler = function() RayMA.switchMode("greet") end,
})

Key pieces:

  • RayMA.getModuleState(name) — persistent table, survives onEnter/onExit cycles.
  • RayMA.T — session state: QUERY, CARET, CURSOR, OFFSET, RESULTS (entries the UI renders).
  • RayMA.H.defaultOnChar, handleListKey, handleTextKey, KC — input plumbing so you don't reimplement text editing and arrow-key navigation.
  • RayMA.UI.refresh() — redraw after a state change.
  • Optional hooks: onExit, onTick (called ~5×/s, useful for async work), onRoute(segments) (route-style args), onPluginClose, getSearchDisplay, onClick.
  • Set elevatedOnly = true on the module to force it to open in a tabbed workspace.

See user_modules/_template.lua for the full hook list and user_modules/helloworld.lua for a slightly more featured version.

Add a Quick Action:

RayMA.registerQAAction({ ... })

Add a Ray command hint:

RayMA.registerRayHints({ ... })

Prisms and photons are added by dropping a .lua file into the matching directory (no XML edit needed).

Minimal prism

A prism implements a symgrid layout algorithm. It gets a list of fixtures with position + FID data, decides a row/column for each, and applies the resulting grid. Drop this into prisms/diagonal.lua:

-- prisms/diagonal.lua — place fixtures on the main diagonal
local P = RayMA.Prism

RayMA.registerPrism({
    name        = "diagonal",
    description = "One fixture per row on the diagonal",
    modes       = { "diagonal" },     -- exposes "ray symgrid diagonal"
    execute = function(fixtures, mode)
        -- Assign each fixture its own row and column
        local rows = {}
        table.sort(fixtures, function(a, b) return a.fid < b.fid end)
        for i, f in ipairs(fixtures) do
            f.row = i - 1
            f.col = i - 1
            rows[i] = { f }
        end
        local layout = P.buildLayout(rows)
        local ok = P.applyGrid(layout)
        return ok, P.symResultMsg(rows, layout, "diagonal")
    end,
})

Fixture fields (populated by Ray before execute is called): fid, posX, posY, posZ, plus whatever you set on col / row.

Useful RayMA.Prism helpers: mapToGridIndices(fixtures, colKey, rowKey), centerAlignRows(rows), mirrorAssignCols(group), buildLayout(rows), applyGrid(layout), symResultMsg(rows, layout, label). See prisms/_grid_utils.lua and prisms/_geometry.lua for the full API.

Each entry in modes gets an auto-registered Ray hint (symgrid <mode>) so it shows up in the command palette.

Minimal photon

A photon is a value parser + applier. It attaches helper functions to the shared RayMA.Photon namespace and registers Ray hints so users can discover it. Drop this into photons/kelvin.lua:

-- photons/kelvin.lua — apply a color temperature in Kelvin
local PH = RayMA.Photon

local function parseKelvin(str)
    local k = str:match("^(%d+)[kK]$")   -- matches "3200k", "5600K"
    return k and tonumber(k) or nil
end

local function applyKelvin(k)
    Cmd(string.format('Attribute "ColorRGB_R" At %d', k < 4000 and 100 or 80))
    Cmd(string.format('Attribute "ColorRGB_G" At %d', 80))
    Cmd(string.format('Attribute "ColorRGB_B" At %d', k < 4000 and 40 or 100))
    return true, string.format("Colour temperature %dK", k)
end

-- Expose so other photons / the ray module can reuse them
PH.parseKelvin = parseKelvin
PH.applyKelvin = applyKelvin

-- Discoverability: show up in the Ray command list
RayMA.registerRayHints({
    { cmd = "3200k", desc = "Tungsten white",   family = "Photon" },
    { cmd = "5600k", desc = "Daylight white",   family = "Photon" },
})

Photons don't have a dedicated registerPhoton call — they're just Lua files that publish functions onto RayMA.Photon and register Ray hints. The Ray module (modules/ray.lua) decides which photon handles a given input, so for deeper integration (e.g. making your parser participate in fan interpolation), wire it into the dispatch path in modules/ray.lua.

Files starting with _ (e.g. _geometry.lua) load first and are treated as shared utilities.


Debug mode

Launch with debug in the argument to enable RayMA.dbg(...) trace output to the MA3 command line. Combinable: debug root, debug search, etc.


License

No license file is currently shipped with this plugin.

About

A broken spotlight plugin for grandma3

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors