Shorthand for game.Selection:Get()[n] in Studio command bar

UserStory:

As a developer, it is currently annoying and time-consuming to access Studio’s current selection in the command bar. For instance, if I select two parts and want to join them together, I’d use something like this:

local s1 = game.Selection:Get()[1]; local s2 = game.Selection:Get()[2]; local w = Instance.new("Weld"); w.Part0 = s1; s.Part1 = s2; w.Parent = s1

About half of that code snippet is dedicated to defining the current selection so I can access it.

A Solution:

Google Chrome faced this same challenge with their Inspector+Console. They opted to define $0 in the console to always point to the selected element in the Inspector:




The above code would become:

local w = Instance.new("Weld"); w.Part0 = $1; w.Part1 = $2; w.Parent = $1

which is substantially easier and more convenient to use than the alternative. The exact syntax doesn’t need to be the same (i.e. ROBLOX could use something like _1, _2, etc instead), but a shorthand for the current selection in Studio within the command bar would be tremendously valuable. Shorthand for every selected item would also be useful.

Use Cases:

  • Join the selection together
  • Parent the first select model to another select location (can’t copy+paste because that moves parts)
  • Iterate through selection to change all green parts to red
  • Any other sort of quick manipulation of the selection
23 Likes

When do you find the need to use Selection and how often would you say you use it?

Primarily for batch editing and debugging. I use it pretty often – maybe even daily – but not for a long duration of time (as in, not for an hour straight, but instead periodically throughout the day). Some specific examples of when I’ve used selection in the command bar this past week:

  • Selected all green parts in game.Selection:Get()[1] (a tree model) so I could play around with their color
  • A part was unintentionally attached to a model, so I looped through JointsService to look for a joint whose Part0 or Part1 matched game.Selection:Get()[1] (the part)
  • Had a number of barrels I tipped over intentionally but decided to stand them back up, so selected them, looped through Game.Selection:Get() (the barrels), and used Get/SetPrimaryPartCFrame to stand them up
  • Decided I wanted to sort small, medium, and large trees into different models, so I selected the “all” model (game.Selection:Get()[1]), then the small, medium, and large models I had just created ([2],[3],[4]), looped through [1] and parented them to either [2], [3], or [4] based on their size
5 Likes

I second this. I use this feature alot for running place updates i.e. changing the properties of every door, etc.

2 Likes

I need thisssssssssssssssssssssssssssss

1 Like

I made a ‘Global Commands’ plugin that adds loads of utility methods to _G and _G.gcmd:

For your use-case you could use this shortcut:

local s1 = _G.sel(1); local s2 = _G.sel(2); local w = Instance.new("Weld"); w.Part0 = s1; s.Part1 = s2; w.Parent = s1

or you could do this:

local sel = _G.sel()
local s1 = sel[1]; local s2 = sel[2]; local w = Instance.new("Weld"); w.Part0 = s1; s.Part1 = s2; w.Parent = s1

This will make everyone (including myself) cringe extremely hard but you could also do this:

-- _G.selEnv() injects the selection into your environment
_G.selEnv() local w = Instance.new("Weld"); w.Part0 = s1; s.Part1 = s2; w.Parent = s1
-- the names are s1, s2, s3 etc. but you can customize the variable pattern using this:
_G.selEnv("variable%s") print(variable1, variable2)

Honestly it’s my messiest plugin and it doesn’t have much documentation, it has some more advanced commands for mirroring parts, rotational symmetry, exact model displacing, and tons of other random stuff. The shortcuts aren’t intuitive and I kinda made it for myself; feel free to try figuring it out though.

Some commands like _G.sel() are added to _G, but some stuff is only in _G.gcmd.

If you want to see what commands are available you can call this:

_G.gcmd.Help()

If you want to try figuring out how to actually use the commands you can use this command to insert the source into your selection:

_G.gcmd.Help2()

It’s super hacky, but it can also be super powerful so I’ll explain a few commands.

_G.displace(CFrame.new(0, 1, 0)) -- moves your selected parts up by 1
-- If you use a number instead of a cframe, it will instead use the cframe of the part in your selection at that position, and also remove that part from the selection.
-- If the number is negative, it will :inverse() the cframe
-- Add '.5' to the end if you don't want to remove the part from your selection.
_G.displace(-1.5) -- moves your selection so that the first part is at CFrame.new()

The awkward ‘+.5’ to keep inputted part is mainly useful for rotational symmetry, when you don’t want to clone the part used for the point of symmetry.

-- The first argument the the center of rotation
--  'Origin' is the first part in my selection so it uses that
-- Use the second argument if you want to rename parts
-- The third argument is how many sides
_G.twirly(1, "%s-%s", 6)

_G.twirly(1.5, "%s-%s", 6) -- This would clone the origin as well

Result:
image

2 Likes

I use a similar plugin which injects a few utility functions into the global environment. I considered adding selection shorthand to _G, but after a bit of thought I decided it really didn’t save that much time/effort. For instance:

local a = game.Selection:Get()[1] local b = game.Selection:Get()[2] doStuff(a,b)
local a = _G.sel[1] local b = _G.sel[2] doStuff(a,b)

It doesn’t really save me from typing the higher-effort keystrokes, so the small number of characters I save only amounts to a fraction of a second since they’re all low-effort.

Here’s my version of it. After an initial call to _G.c(), the s variable acts as a shorthand for the current selection.

local lib = {}

--[==[ Selection
Get nth selection:
	s[n]
Insert object as nth selection:
	s[n] = object
Deselect nth selection:
	s[n] = nil
Call function with each selection:
	s(function(v, i) print(v, i) --[[object, index]] end)
Alternative:
	s[[print(v, i) -- object, index]]
Get selection table:
	s()
Set selection table:
	s{...} -- objects, tables of objects
]==]
local Selection = game:GetService("Selection")
lib.s = setmetatable({}, {
	__index = function(t, k)
		return Selection:Get()[k]
	end,
	__newindex = function(t, k, v)
		local sel = Selection:Get()
		if v == nil then
			table.remove(sel, k)
		else
			table.insert(sel, k, v)
		end
		Selection:Set(sel)
	end,
	__call = function(t, f)
		if type(f) == 'function' then
			for i, v in pairs(Selection:Get()) do
				f(v, i)
			end

		elseif type(f) == 'string' then
			local func, o = loadstring([[return function(v, i) ]]..f..[[ end]])
			if func then
				func = func()
				for i, v in pairs(Selection:Get()) do
					func(v, i)
				end
			else
				print(o)
			end

		elseif type(f) == 'table' then
			local t = {}
			for i, v in pairs(f) do
				if type(v) == 'table' then
					for i, v in pairs(v) do
						t[#t+1] = v
					end
				else
					t[#t+1] = v
				end
			end
			Selection:Set(t)

		elseif type(f) == 'nil' then
			return Selection:Get()
		end
	end,
})

-- load commands into current environment
function _G.c()
	local env = getfenv(2)
	for k, v in pairs(lib) do
		env[k] = v
	end
end
3 Likes

Since the commandline and plugins run in the same VM, you could (ab)use an env exploit and alter the shared global environment. No need for initial call(s), but not really supported

Kind of surprised we still don’t have a (legit) way to edit the commandline environment using plugins.

2 Likes