Skip to main content

Overview

Groups are fundamental to MA2 plugin operation. Plugins use groups to select fixtures, apply effects, and organize complex operations. This guide covers how plugins interact with the group system.

Understanding Group Selection

Groups in GrandMA2 are collections of fixtures that can be selected together. Plugins use groups as their primary method of fixture selection.

Basic Group Selection

The most common pattern in plugins:
-- Select a single group
cmd('Group '..groupNumber)

-- Select multiple groups
cmd('Group '..group1..' + '..group2)

-- Example from PulseWaveGen
cmd('Group '..rows[grpAct1]..' + '..rows[grpAct2])

Group-Based Operations

From the Color Picker Update plugin:
local groups = {"A", "B", "C", "D", "E", "F", "G"}
local grpStart = 1

for group=grpStart, #groups do
  local presetCurrent = presetStart + ((group-1)* presetWidth)
  
  for start=1,colNb do
    local preset = presetCurrent+start-1
    cmd("Group "..group.." At Gel 1."..colSwatchIndex[start])
    cmd("Store Preset 4."..preset)
    cmd("Label Preset 4."..preset.." \""..groups[group].." "..colSwatchBook[start].."\"") 
  end
  clear()
end
Notice how the plugin iterates through groups numerically but uses letter names for labeling. This is a common pattern for organizing fixtures by position or type.

Reading Group Contents

The getGroup() function extracts all fixtures from a group by exporting and parsing XML:
function getGroup(grpNum)
  gma.cmd('SelectDrive 1') -- select the internal drive
  
  local file = {}
  file.name = 'tempfile_getgroup.xml' 
  file.directory = gma.show.getvar('PATH')..'/'..'importexport'..'/'
  file.fullpath = file.directory..file.name
  
  -- Export group to temporary XML file
  gma.cmd('Export Group ' .. grpNum .. ' "' .. file.name .. '"')
  
  -- Read XML file into table
  local t = {}
  for line in io.lines(file.fullpath) do
    t[#t + 1] = line
  end
  
  -- Clean up temp file
  os.remove(file.fullpath)
  
  -- Parse XML to extract fixture IDs
  local groupList = {}
  for i = 1, #t do
    if t[i]:find('Subfixture ') then
      local indices = {t[i]:find('"%d+"')}
      indices[1], indices[2] = indices[1] + 1, indices [2] - 1
      
      -- Identify as fixture or channel
      local fixture
      if t[i]:find('fix_id') then
        fixture = 'Fixture ' .. tostring(t[i]:sub(indices[1], indices[2]))
      elseif t[i]:find('cha_id') then
        fixture = 'Channel ' .. tostring(t[i]:sub(indices[1], indices[2]))
      end
        
      -- Handle subfixtures
      if t[i]:find('sub_index') then
        local indices = {t[i]:find('"%d+"', indices[2]+2)}
        indices[1], indices[2] = indices[1] + 1, indices[2] - 1
        fixture = fixture .. '.' .. tostring(t[i]:sub(unpack(indices)))
      end
      
      -- Add to list
      groupList[#groupList + 1] = fixture
    end
  end
  
  return groupList
end
This function creates a temporary XML file in the importexport directory. Ensure this directory is accessible and has write permissions.

Using getGroup() Results

From the Color Sweep Update plugin:
-- Collect group numbers from user
local rows = {}
local loopct = 1

while true do  
  local displayMessage
  if loopct == 1 then displayMessage = {'DS Group #'}
  else displayMessage = {'Row '..loopct..' Group #'} end
  
  local t = text(displayMessage[x], endMessage)
  if tonumber(t) then
    rows[loopct] = tonumber(t)
    loopct = loopct + 1
  elseif t == endMessage then
    break
  end
end

-- Generate group arrays
local groups = {} -- groups[group][fixture]
for i = 1, #rows do
  groups[i] = getGroup(rows[i])
end

Group Input Patterns

Single Group Input

From Pulse Generator:
local PG_grp = 0

local function PG_setup()
  PG_grp = text('Enter Group Number', PG_grp)
  -- ... other setup
end

local function PG_create()
  cmd('Group '..PG_grp)
  -- ... operations on group
end

Multiple Group Collection

From Pulse Wave Generator:
local PWG_groups = {}

local function PWG_setup()
  PWG_groups = {}
  
  if PWG_batch or PWG_singleStack then
    local grpCollect = true
    while grpCollect do
      local grpInput = text("Enter Group " .. (#PWG_groups + 1) .. " (empty to finish)", "")
      if grpInput == nil or grpInput == "" then
        grpCollect = false
      else
        table.insert(PWG_groups, grpInput)
      end
    end
  else
    local grpInput = text("Enter Group Number", "0")
    if grpInput and grpInput ~= "" then
      table.insert(PWG_groups, grpInput)
    end
  end
  
  if #PWG_groups == 0 then
    fb("No groups entered, exiting.")
    return false
  end
  
  fb("Collected groups: " .. table.concat(PWG_groups, ", "))
  return true
end
Allow users to enter groups one at a time until they provide empty input. This provides flexibility for working with varying numbers of groups.

Group Operations

Applying Effects to Groups

-- Select group and apply values
cmd('Group '..grp)
cmd('At 100')
cmd('At Gel 1.1') -- Apply color

-- With MAtricks for group manipulation
cmd('Group '..PG_grp)
cmd('MAtricksWings '..PG_wing)
if PG_rnd == 'true' then
  cmd('ShuffleSelection')
end
cmd('MAtricksInterleave '..PG_amount)

Group-Based Preset Storage

From Color Picker Update:
function createPresets()
  for group=grpStart, #groups do
    local presetCurrent = presetStart + ((group-1)* presetWidth)

    for start=1,colNb do
      local preset = presetCurrent+start-1
      cmd("Group "..group.." At Gel 1."..colSwatchIndex[start])
      cmd("Store Preset 4."..preset)
      cmd("Label Preset 4."..preset.." \""..groups[group].." "..colSwatchBook[start].."\"") 
    end
    clear()
  end
end

Group-Based Cue Creation

function createCues()
  fb("--- Creating Cues")
  for group=grpStart, #groups do
    local presetCurrent = presetStart + ((group-1)* presetWidth)
    for start=1, colNb do
      local preset = presetCurrent + start - 1
      local seqCurrent = seqStart + group - 1
      cmd("Group "..group.." At Preset 4."..preset)
      cmd("Store Cue "..start.." Sequence "..seqCurrent)
      cmd("Label Sequence "..seqCurrent.." Cue "..start.." \""..groups[group].." "..colSwatchBook[start].."\"") 
    end
  end
  clear()
end

Advanced Group Patterns

Random Group Selection

From Pulse Wave Generator:
local function PWG_pickRandomAvoid(list, lastPick)
  if #list == 0 then return nil end
  if #list == 1 then return list[1] end
  
  local available = {}
  for _, item in ipairs(list) do
    if item ~= lastPick then
      table.insert(available, item)
    end
  end
  
  if #available == 0 then
    available = list
  end
  
  return available[math.random(1, #available)]
end

local function PWG_getRandomGroups(groupList, count)
  local result = {}
  local lastGrp = nil
  for i = 1, count do
    local newGrp = PWG_pickRandomAvoid(groupList, lastGrp)
    table.insert(result, newGrp)
    lastGrp = newGrp
  end
  return result
end
This pattern ensures variety by avoiding consecutive repetition of the same group.

Wing-Based Group Operations

local function PWG_getWing(direction)
  if direction == "in" or direction == "out" then
    return 2
  else
    return 0
  end
end

-- Apply to group
cmd("Group " .. grp)
local wing = PWG_getWing(direction)
if wing > 0 then
  cmd("MAtricksWings " .. wing)
end

Multiple Group Operations

From Pulse Wave Generator’s single stack mode:
for i, grp in ipairs(PWG_groups) do
  progressStep = progressStep + 1
  if PWG_progressHandle then
    gma.gui.progress.set(PWG_progressHandle, progressStep)
    gma.gui.progress.settext(PWG_progressHandle, "Adding G" .. grp .. " to Full cue")
  end
  
  cmd("Group " .. grp)
  if wing > 0 then
    cmd("MAtricksWings " .. wing)
  end
  cmd("PresetType \"DIMMER\"")
  cmd("At 100")
  PWG_sleep(0.05)
  cmd("Delay " .. delayStr)
  PWG_sleep(0.05)
end

cmd("Store Sequence " .. PWG_seq .. " Cue 1")

Group Organization Strategies

1

Positional Groups

Organize fixtures by physical location:
  • Group 1: Downstage Left
  • Group 2: Downstage Right
  • Group 3: Upstage Center
local groups = {"DSL", "DSR", "USC", "USL", "USR"}
2

Type-Based Groups

Organize by fixture type:
  • Group 10: Moving Heads
  • Group 20: LEDs
  • Group 30: Conventionals
3

Row-Based Groups

For matrix/grid layouts:
-- Each group represents a row
local rows = {1, 2, 3, 4, 5, 6, 7, 8}

-- Operations on rows
for i, row in ipairs(rows) do
  groups[i] = getGroup(rows[i])
end
4

Functional Groups

Based on use case:
  • Groups A-G: Color groups (Color Picker)
  • Groups 1-10: Effect zones
  • Groups 100+: Special/temporary selections

Best Practices

cmd('Group '..grp)
cmd('At 100')
cmd('Store Preset 4.'..preset)
cmd('ClearAll') -- Always clear
This prevents unintended selections from affecting subsequent operations.
if #PWG_groups == 0 then
  fb("No groups entered, exiting.")
  return false
end
Check that groups exist before processing to avoid errors.
-- Good: Clear purpose
local groups = {"A", "B", "C", "D", "E", "F", "G"}
cmd("Label Preset 4."..preset.." \""..groups[group].." "..colSwatchBook[start].."\"") 

-- Result: "A Red", "B Blue", etc.
When reading groups, account for subfixtures:
if t[i]:find('sub_index') then
  local indices = {t[i]:find('"%d+"', indices[2]+2)}
  indices[1], indices[2] = indices[1] + 1, indices[2] - 1
  fixture = fixture .. '.' .. tostring(t[i]:sub(unpack(indices)))
end

Common Group Workflows

Creating Color Palettes Per Group

local groups = {"A", "B", "C", "D", "E", "F", "G"}
local colors = {"White", "Red", "Orange", "Yellow", "Green", "Cyan", "Blue"}
local colorIndices = {1, 2, 3, 4, 6, 8, 10}

for group=1, #groups do
  for col=1, #colors do
    cmd("Group "..group.." At Gel 1."..colorIndices[col])
    cmd("Store Preset 4."..presetNum)
    cmd("Label Preset 4."..presetNum.." \""..groups[group].." "..colors[col].."\"") 
    presetNum = presetNum + 1
  end
end

Building Effects Across Groups

for i, grp in ipairs(groupList) do
  cmd("Group " .. grp)
  cmd("At 100")
  cmd("Delay 0 Thru " .. (i * 0.2)) -- Stagger timing per group
  cmd("Store Sequence "..seqNum.." Cue "..cueNum)
end

Troubleshooting

Group Not Found: If a group number doesn’t exist, commands will fail silently. Always verify groups exist in your showfile before running plugins.
Use gma.feedback() to confirm group operations:
fb("Collected groups: " .. table.concat(PWG_groups, ", "))

Build docs developers (and LLMs) love