AssignHotkey({"\076\101\102\116\083\104\105\102\116","\071"},Support.Call(GroupSelection,"\077\111\100\101\108"))AssignHotkey({"\082\105\103\104\116\083\104\105\102\116";"\071"},Support.Call(GroupSelection,"\077\111\100\101\108"))AssignHotkey({"\076\101\102\116\083\104\105\102\116";"\070"},Support.Call(GroupSelection,"\070\111\108\100\101\114"))AssignHotkey({"\082\105\103\104\116\083\104\105\102\116","\070"},Support.Call(GroupSelection,"\070\111\108\100\101\114"))AssignHotkey({"\076\101\102\116\083\104\105\102\116","\085"},UngroupSelection)AssignHotkey({"\082\105\103\104\116\083\104\105\102\116","\085"},UngroupSelection)function GetPartsFromSelection(g)local o={}for g,B in pairs(g)do if B:IsA("\066\097\115\101\080\097\114\116")then o[#o+1]=B else for g,B in pairs(B:GetDescendants())do if B:IsA("\066\097\115\101\080\097\114\116")then o[#o+1]=B end end end end return o end function IsSelectable(g)for g,o in pairs(g)do if not o or not o.Parent or o:IsA("\066\097\115\101\080\097\114\116")and o.Locked then return false end if not Security.IsItemAllowed(o,Player)then return false end end local o=GetPartsFromSelection(g)if Security.ArePartsViolatingAreas(o,Player,true)then return false end return true end function ExportSelection()if#Selection.Items==0 then return end local g local o=require((Tool:WaitForChild("\085\073")):WaitForChild("\069\120\112\111\114\116\068\105\097\108\111\103"))local B=function()g=Roact.unmount(g)end local u=Roact.createElement(o,{Text="\085\112\108\111\097\100\105\110\103 \115\101\108\101\099\116\105\111\110\046\046\046";OnDismiss=B})g=Roact.mount(u,UI,"\069\120\112\111\114\116\068\105\097\108\111\103");(((((Try(SyncAPI.Invoke,SyncAPI,"\069\120\112\111\114\116",Selection.Items)):Then(function(u)Roact.update(g,Roact.createElement(o,{Text="\089\111\117\114 \099\114\101\097\116\105\111\110\'\115 \073\068\058\060\102\111\110\116 \115\105\122\101\061\"\053\"\062\060\098\114 \047\062\060\047\102\111\110\116\062\n"..("\060\102\111\110\116 \102\097\099\101\061\"\071\111\116\104\097\109\066\108\097\099\107\" \115\105\122\101\061\"\049\056\"\062"..(u.."\060\047\102\111\110\116\062\060\102\111\110\116 \115\105\122\101\061\"\054\"\062\060\098\114 \047\062\060\047\102\111\110\116\062\n\060\102\111\110\116 \102\097\099\101\061\"\071\111\116\104\097\109\" \115\105\122\101\061\"\049\048\"\062\085\115\101 \116\104\101 \099\111\100\101 \097\098\111\118\101 \116\111 \105\109\112\111\114\116 \121\111\117\114 \099\114\101\097\116\105\111\110 \117\115\105\110\103 \116\104\101 \112\108\117\103\105\110 \105\110 \083\116\117\100\105\111\046\060\047\102\111\110\116\062"));OnDismiss=B}))print("\091\066\117\105\108\100\105\110\103 \084\111\111\108\115 \098\121 \070\051\088\093 \085\112\108\111\097\100\101\100 \069\120\112\111\114\116\058",u)end)):Catch("\072\116\116\112 \114\101\113\117\101\115\116\115 \097\114\101 \110\111\116 \101\110\097\098\108\101\100",function()Roact.update(g,Roact.createElement(o,{Text="\080\108\101\097\115\101 \101\110\097\098\108\101 \072\084\084\080 \114\101\113\117\101\115\116\115\046";OnDismiss=B}))end)):Catch("\069\120\112\111\114\116 \102\097\105\108\101\100 \100\117\101 \116\111 \115\101\114\118\101\114-\115\105\100\101 \101\114\114\111\114",function()Roact.update(g,Roact.createElement(o,{Text="\065\110 \101\114\114\111\114 \111\099\099\117\114\114\101\100 \226\128\148 \112\108\101\097\115\101 \116\114\121 \097\103\097\105\110\046",OnDismiss=B}))end)):Catch("\080\111\115\116 \100\097\116\097 \116\111\111 \108\097\114\103\101",function()Roact.update(g,Roact.createElement(o,{Text="\084\114\121 \115\112\108\105\116\116\105\110\103 \117\112 \121\111\117\114 \098\117\105\108\100\046",OnDismiss=B}))end)):Catch(function(u,W,I)Roact.update(g,Roact.createElement(o,{Text="\065\110 \117\110\107\110\111\119\110 \101\114\114\111\114 \111\099\099\117\114\114\101\100 \226\128\148 \112\108\101\097\115\101 \116\114\121 \097\103\097\105\110\046",OnDismiss=B}))warn("\226\157\140 \091\066\117\105\108\100\105\110\103 \084\111\111\108\115 \098\121 \070\051\088\093 \070\097\105\108\101\100 \116\111 \101\120\112\111\114\116 \115\101\108\101\099\116\105\111\110","\n\n\069\114\114\111\114\058\n",u,"\n\n\083\116\097\099\107\058\n",W)end)end

AssignHotkey({ 'LeftShift', 'G' }, Support.Call(GroupSelection, 'Model'))
AssignHotkey({ 'RightShift', 'G' }, Support.Call(GroupSelection, 'Model'))
AssignHotkey({ 'LeftShift', 'F' }, Support.Call(GroupSelection, 'Folder'))
AssignHotkey({ 'RightShift', 'F' }, Support.Call(GroupSelection, 'Folder'))
AssignHotkey({ 'LeftShift', 'U' }, UngroupSelection)
AssignHotkey({ 'RightShift', 'U' }, UngroupSelection)

function GetPartsFromSelection(Selection)
	local Parts = {}

	-- Get parts from selection
	for _, Item in pairs(Selection) do
		if Item:IsA 'BasePart' then
			Parts[#Parts + 1] = Item

		-- Get parts within other items
		else
			for _, Descendant in pairs(Item:GetDescendants()) do
				if Descendant:IsA 'BasePart' then
					Parts[#Parts + 1] = Descendant
				end
			end
		end
	end

	-- Return parts
	return Parts
end

function IsSelectable(Items)
	-- Returns whether `Items` can be selected

	-- Check each item
	for _, Item in pairs(Items) do

		-- Ensure item exists and is not locked
		if (not Item) or (not Item.Parent) or (Item:IsA 'BasePart' and Item.Locked) then
			return false
		end

		-- Ensure item can be modified
		if not Security.IsItemAllowed(Item, Player) then
			return false
		end
	end

	-- Check if parts intruding into private areas
	local Parts = GetPartsFromSelection(Items)
	if Security.ArePartsViolatingAreas(Parts, Player, true) then
		return false
	end

	-- If no checks fail, items are selectable
	return true

end

function ExportSelection()
	-- Exports the selected parts

	-- Make sure that there are items in the selection
	if #Selection.Items == 0 then
		return;
	end;

	-- Start an export dialog
	local DialogHandle
	local DialogComponent = require(Tool:WaitForChild('UI'):WaitForChild('ExportDialog'))
	local DialogDismissCallback = function ()
		DialogHandle = Roact.unmount(DialogHandle)
	end
	local DialogElement = Roact.createElement(DialogComponent, {
		Text = 'Uploading selection...';
		OnDismiss = DialogDismissCallback;
	})
	DialogHandle = Roact.mount(DialogElement, UI, 'ExportDialog')

	-- Send the exporting request to the server
	Try(SyncAPI.Invoke, SyncAPI, 'Export', Selection.Items)

	-- Display creation ID on success
	:Then(function (CreationId)
		Roact.update(DialogHandle, Roact.createElement(DialogComponent, {
			Text = 'Your creation\'s ID:<font size="5"><br /></font>\n' ..
				'<font face="GothamBlack" size="18">' .. CreationId .. '</font><font size="6"><br /></font>\n' ..
				'<font face="Gotham" size="10">Use the code above to import your creation using the plugin in Studio.</font>';
			OnDismiss = DialogDismissCallback;
		}))
		print('[Building Tools by F3X] Uploaded Export:', CreationId);
	end)

	-- Display error messages on failure
	:Catch('Http requests are not enabled', function ()
		Roact.update(DialogHandle, Roact.createElement(DialogComponent, {
			Text = 'Please enable HTTP requests.';
			OnDismiss = DialogDismissCallback;
		}))
	end)
	:Catch('Export failed due to server-side error', function ()
		Roact.update(DialogHandle, Roact.createElement(DialogComponent, {
			Text = 'An error occurred — please try again.';
			OnDismiss = DialogDismissCallback;
		}))
	end)
	:Catch('Post data too large', function ()
		Roact.update(DialogHandle, Roact.createElement(DialogComponent, {
			Text = 'Try splitting up your build.';
			OnDismiss = DialogDismissCallback;
		}))
	end)
	:Catch(function (Error, Stack, Attempt)
		Roact.update(DialogHandle, Roact.createElement(DialogComponent, {
			Text = 'An unknown error occurred — please try again.';
			OnDismiss = DialogDismissCallback;
		}))
		warn('❌ [Building Tools by F3X] Failed to export selection', '\n\nError:\n', Error, '\n\nStack:\n', Stack)
	end)

end;
