Skip to main content

Selection operations

Most commands you'll write are some flavor of "do X to whatever's selected." A few patterns reduce the boilerplate.

The skeleton​

--!strict
local Types = require(script.Types)

return {
Arguments = { ... },

Run = function(props: Types.Props, arguments: Types.ArgumentResult)
if #props.Selected == 0 then
return props:SendNotification("Select something first")
end

for _, inst in props.Selected do
-- ...do work on inst...
end
end,
}

Three things to internalize:

  • Always early-return on empty selection with a notification. Otherwise Run silently does nothing and the user has no idea why.
  • Iterate props.Selected, not Selection:Get(). The selection can change while Run is on the call stack. props.Selected is the snapshot from execute-time and stays stable.
  • Type-guard before assuming. props.Selected is {Instance}. Check with :IsA("BasePart") (or whatever) before touching type-specific properties.

Filtering selection by type​

Most commands only care about a subset of what could be selected. Pull the filter out:

local function partsIn(list: {Instance}): {BasePart}
local out: {BasePart} = {}
for _, inst in list do
if inst:IsA("BasePart") then
table.insert(out, inst)
end
end
return out
end

return {
Arguments = { ... },

Run = function(props)
local parts = partsIn(props.Selected)
if #parts == 0 then
return props:SendNotification("Select at least one BasePart")
end

for _, part in parts do
-- ...
end
end,
}

Acting on descendants​

If your command should "treat the selection as a tree," walk it with :GetDescendants():

Run = function(props, arguments)
for _, root in props.Selected do
for _, descendant in root:GetDescendants() do
if descendant:IsA("BasePart") then
descendant.Anchored = arguments.Anchor
end
end
end
end,

For huge selections this can be slow. Consider using a paragraph argument with a Lua expression the user can tune, or a boolean "Recursive" toggle.

Replacing the selection after the fact​

A common pattern: a "duplicate" command should leave the new copies selected, not the originals. Update the selection at the end of Run:

Run = function(props)
local copies = {}
for _, source in props.Selected do
local copy = source:Clone()
copy.Parent = source.Parent
table.insert(copies, copy)
end

props:SetSelection(copies)
end,

Framing the camera​

If the command operates somewhere far from the camera (e.g. you spawned new copies offset by 100 studs), focus on the result so the user can see it:

local copies = {}
-- ...spawn copies...

props:SetSelection(copies)
props:FocusView(copies, 4) -- second arg is extra padding in studs

A complete recipe, "scatter"​

A command that scatters cloned copies of the selection randomly within a radius:

--!strict
local Types = require(script.Types)

return {
Arguments = {
Count = {
Type = "number",
Default = 10,
Min = 1,
Max = 200,
},
Radius = {
Type = "number",
Default = 20,
Min = 1,
Step = 0.5,
},
Anchor = {
Type = "switch",
Default = true,
Description = "Anchor each scattered copy",
},
},

Run = function(props: Types.Props, arguments: Types.ArgumentResult)
local source = props.FirstSelected
if not (source and source:IsA("BasePart")) then
return props:SendNotification("Select a single BasePart first")
end

local copies = table.create(arguments.Count)

for i = 1, arguments.Count do
local copy = source:Clone()

local theta = math.random() * math.pi * 2
local r = math.sqrt(math.random()) * arguments.Radius
copy.Position = source.Position
+ Vector3.new(math.cos(theta) * r, 0, math.sin(theta) * r)

copy.Anchored = arguments.Anchor
copy.Parent = source.Parent
copies[i] = copy
end

props:SetSelection(copies)
end,

OnExecuted = function(props, args, _result)
props:SendNotification(
string.format("Scattered %d copies", args.Count)
)
end,
}