
KeylessUniversal

--[[
XANE'S MODEL RECREATOR (SCRIPT/DECONSTRUCTOR PHASE) V3, WRITTEN IN 2024
LAST UPDATED ON APRIL 1ST, 2025
A personal script of mine, released to the public to enable anyone to save models (and other supported instances)
from any Roblox experience (as long as your Roblox app doesn't crash in the process of loading it)! Execute this
script and a small button will appear at the top of your screen. Click/tap on it to open the GUI, then click on
one of the four buttons at the top of the window to scan through that container, making a list of all of the
supported Instances!
Click on an instance's icon to include it in this save (and do that again to deselect it), and use the page
buttons at the bottom of the window to switch between pages of 100 Instances. If you're unsure of which
Instance you're selecting, a box will be drawn around them, and you can use the camera button to focus on that
object, if it supports changing the camera's subject to it. To return to your character, right-click or long
tap that button.
Instances can be selected from one or all of the containers. Switch between them using the top row of buttons,
select anything that you want, give a name to this export using the text box at the bottom-right corner, then
with one click/tap of the save button, your selection will be transformed into JSON files in your executor's
"workspace" folder.
GLOBAL ARRAY (_G) SETTINGS
For those in the know, you can change a couple settings that aren't accessible from the GUI by setting certain
values in the _G dictionary! Here's the complete list of changes you can make:
_G.PageLength (number) - Adjusts the length of each set of Instances which are shown in the GUI. By default,
this is 24, but changing this will make each page longer or shorter. If it's small enough, the whole page fits!
_G.AntiLagInterval (number) - To ensure the progress UI updates instead of the script freezing the client while
it's indexing or saving instances, the recursive iteration function intentionally waits a frame after it checks
every 25th Instance. If you have a weaker device, increase this to improve performance, though it'll come at
the cost of longer indexing/saving times. If on a stronger device, I recommend decreasing this or using 0.
]]--
-- USER CONFIGS
_G.PageLength = 32
_G.AntiLagInterval = 30
_G.SkipPreSaveVerify = false
if not game:IsLoaded() then game.Loaded:Wait() end
task.wait(0.975)
print("Xane's Model Recreator GUI is initializing... (Please work!)")
-- SERVICES
local Players = game:GetService("Players") -- These are the three locations that can be accessed using this script.
local Lighting = game:GetService("Lighting")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage") -- This container stores the four "container imposter" Models for redirection.
local MaterialService = game:GetService("MaterialService")
-- This script's GUI will be hidden by the executor.
local hiddenUI = get_hidden_gui or gethui
if not hiddenUI then error("Your executor doesn't support hiding GUI's, so it's too unsafe to use this script. Sorry!") end
-- REFERENCES (PARTIAL, NOT INCLUDING GUI)
local CoreGui = hiddenUI()
-- STATIC DATA
local DefaultMessage = "Welcome to Xane's Model Recreator v3, the ultimate instance capturer! Please choose a container above, select which instances you want, name it, then save!"
--- This script uses Xane's Recreator API! The boilerplate code below will load it from one of its locations online, trying each in order.
local RecreatorAPIMirrors = {
'https://pastebin.com/raw/DL77J2LY',
'https://raw.githubusercontent.com/Xane123/Roblox-Scripts/main/API_Recreation.luau'
}
-- Prepare our environment; If this executor supports it, use its getgenv() function, falling back on the detectable _G if it isn't available.
local env = getgenv and getgenv() or _G
if env then
if not env.XRecreator then
for i,mirror in RecreatorAPIMirrors do
-- Attempt to load Xane's Recreator API from Pastebin,
local success, errormsg = pcall(function() loadstring(game:HttpGet(mirror))() end)
if success then
print("Loaded Recreator API from source", i, "of", #RecreatorAPIMirrors)
break
else
warn("Recreator API couldn't load from " .. mirror .. ". It's either removed, or isn't available in your region.")
end
end
if env.XRecreator and env.XRecreator.Save then
print("Recreator API has been imported and setup successfully!")
else
game:GetService("Debris"):AddItem(script, 5)
local VisibleError = Instance.new("Hint", game:GetService("CoreGui"))
VisibleError.Text = "Error: Xane's Recreator API couldn't load/initialize! Please load a local copy of that script " ..
"then execute this one again, or try again."
game:GetService("Debris"):AddItem(VisibleError,7)
error("The Recreator API couldn't load or initialize. This script depends on it, so execution cannot continue.")
end
else print("The recreator API is already set up! Its functions will be used by this script.")
end
else
game:GetService("Debris"):AddItem(script, 1)
error("Couldn't find a destination for the recreation APIs! (Your executor is really bad if you see this!)")
end
-- XANE'S RECREATOR API TYPES
-- The format used by the API's internal Instance list. This is used by both the API and this GUI script.
type InternalListEntry = {
Instance : Instance, -- Instance which this entry represents. Its class and name are used when generating the visible list.
Level : number -- Represents the depth this instance was placed at in the hierarchy during the scan. 0 is the container itself.
}
-- List item definition for the currently shown subset of the full list, generated as needed.
type VisibleListEntry = {
CheckboxClickEvent : RBXScriptConnection, -- Connection which lets the user (de)select this item. (This also updates its icon.)
CameraFocusEvent : RBXScriptConnection, -- Click event for a button which makes the user's camera focus on this instance.
CamRevertEvent1 : RBXScriptConnection, -- Right-click event, which brings the camera back to the player.
CamRevertEvent2 : RBXScriptConnection, -- Alternative camera reverting event, for mobile devices (long tap).
Instance : Instance, -- Reference to this instance, used to access its properties if needed.
RowBase : Frame, -- A reference to this row's container Frame.
Checkbox : TextButton, -- The toggle-box found at the left side of this row/entry.
SelectBox : SelectionBox, -- This Instance's SelectionBox, which is created and destroyed as needed upon its selection.
IsSelectAllRow : boolean -- If TRUE, selecting this row will deselect all instances within the selected container.
}
type ClassDefinition = {
ListView : { -- Properties that customize how this instance is displayed in the list (only used in the script).
Icon : string,
CreateTest : boolean,
CanView : string,
},
Props : {string} -- List of properties that should be saved or loaded to/from JSON for this instance.
}
type ModelRecreatorStruct = {
-- FUNCTIONS
Select : (_mode:"set"|"add"|"remove", _list:{Instance}) -> (), -- Updates the API's selection.
MakeList : (_baseInst : Instance) -> (nil), -- Generates a series of list entries, for GUI's that display one.
Save : (_name:string,_rescan:boolean) -> (boolean), -- Saves selection to files. Will fail on invalid filenames.
CustomizeProgressBox:(_message : string, _total : number) -> (), -- Affects the text shown in "saving"/"indexing" GUI (may be unusable).
ActivateAPI : (_use : boolean) -> boolean, -- Requests to use API functions. If in use, this returns FALSE.
SetStatusGui: (_gui:TextLabel) -> (nil), -- Changes GUI that save status is shown in. Set to nil to use default.
OnSaveDone : (_success : boolean) -> (), -- External code ran upon finishing. Used for other scripts' cleanup.
IsInstanceAllowed : (_instance : Instance) -> (boolean), -- Checks if an Instance's type is safe to save. (Model Recreator v3)
-- VARIABLES
Reserved : boolean, -- Set to TRUE after a script calls ActivateAPI().
IgnoreAttrib: string, -- Instances with this attribute won't be scanned.
Selection : {Instance}, -- List of instances marked to be saved.
FullList : {InternalListEntry}, -- List generated by MakeList(), which can be used by scripts' GUIs.
StatusGui : TextLabel, -- Reference to current label that save/index statuses are shown in.
PageLength : number, -- How many instances SHOULD be shown on list pages. (Not used by API.)
AntiLagInterval : number, -- How many instances to parse before waiting, to reduce lag.
ClassData : {ClassDefinition} -- This script's "Roblox class API" (read-onlu).
}
local Recreator : ModelRecreatorStruct = env.XRecreator -- Let's simplify references to the API by just defining a new "shortcut" variable.
if not Recreator.ActivateAPI(true) then -- Attempt to claim the API; If it's in use, the script will stop here.
warn("Another script is using Xane's recreator API! Please close that script or rejoin (if it doesn't have a 'close' button).")
script:Destroy()
return false
end
Recreator.IgnoreAttrib = { "XaneProtectedDoNotShowInRecreatorGui" } -- This script's GUI and SelectionBox instances can't be saved.
if not Recreator.AntiLagInterval or type(Recreator.AntiLagInterval) ~= "number" or
(type(Recreator.AntiLagInterval) == "number" and Recreator.AntiLagInterval <= 0)
then
Recreator.AntiLagInterval = 25
end
--[[
Roblox2Lua
----------
This code was generated using
Deluct's Roblox2Lua plugin.
]]--
--// Instances
local xane_mdlrecreator_gui = Instance.new("ScreenGui")
xane_mdlrecreator_gui.IgnoreGuiInset = false
xane_mdlrecreator_gui.ResetOnSpawn = true
xane_mdlrecreator_gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
xane_mdlrecreator_gui.Name = "XaneMDLRecreatorGUI"
xane_mdlrecreator_gui:SetAttribute("XaneProtectedDoNotShowInRecreatorGui",true) -- Hide this instance from the Recreator script itself.
xane_mdlrecreator_gui.Parent = CoreGui
-- TOP BUTTONS (HIDE & CANCEL BUTTONS)
local toggle_frame = Instance.new("Frame")
toggle_frame.AnchorPoint = Vector2.new(0.5, 0)
toggle_frame.BackgroundTransparency = 1
toggle_frame.BorderSizePixel = 0
toggle_frame.Position = UDim2.new(0.5, 0, 0.0625, 0)
toggle_frame.Size = UDim2.new(0.25, 0, 0.09375, 0)
toggle_frame.SizeConstraint = Enum.SizeConstraint.RelativeYY
toggle_frame.Visible = true
toggle_frame.Name = "ToggleFrame"
toggle_frame.Parent = xane_mdlrecreator_gui
local toggle_list_layout = Instance.new("UIListLayout")
toggle_list_layout.Name = "ToggleBtnList"
toggle_list_layout.FillDirection = Enum.FillDirection.Horizontal
toggle_list_layout.Padding = UDim.new(0.001,0)
toggle_list_layout.Parent = toggle_frame
local toggle_button = Instance.new("TextButton")
toggle_button.Font = Enum.Font.RobotoCondensed
toggle_button.Text = "Save Instances!"
toggle_button.TextColor3 = Color3.new(1, 1, 1)
toggle_button.TextScaled = true
toggle_button.TextSize = 14
toggle_button.TextStrokeTransparency = 0
toggle_button.TextWrapped = true
-- toggle_button.AnchorPoint = Vector2.new(0.5, 0.5)
toggle_button.BackgroundColor3 = Color3.new(0.882353, 0.756863, 0.615686)
toggle_button.BorderSizePixel = 0
-- toggle_button.Position = UDim2.new(0.5, 0, 0.5, 0)
toggle_button.Size = UDim2.fromScale(0.5, 1)
toggle_button.LayoutOrder = 1
toggle_button.Visible = true
toggle_button.Name = "ToggleButton"
toggle_button.Parent = toggle_frame
local clear_button = Instance.new("TextButton")
clear_button.Font = Enum.Font.RobotoCondensed
clear_button.Text = "Clear Selection"
clear_button.TextColor3 = Color3.new(1, 1, 1)
clear_button.TextScaled = true
clear_button.TextSize = 14
clear_button.TextStrokeTransparency = 0
clear_button.TextWrapped = true
-- clear_button.AnchorPoint = Vector2.new(0.5, 0.5)
clear_button.BackgroundColor3 = Color3.new(0.882353, 0.623529, 0.686275)
clear_button.BorderSizePixel = 0
-- clear_button.Position = UDim2.new(0.5, 0, 0.5, 0)
clear_button.Size = UDim2.fromScale(0.5, 1)
clear_button.LayoutOrder = 2
clear_button.Visible = false
clear_button.Name = "ClearButton"
clear_button.Parent = toggle_frame
local close_button = Instance.new("TextButton")
close_button.Font = Enum.Font.RobotoCondensed
close_button.Text = "End Script..."
close_button.TextColor3 = Color3.new(1, 1, 1)
close_button.TextScaled = true
close_button.TextSize = 14
close_button.TextStrokeTransparency = 0
close_button.TextWrapped = true
close_button.BackgroundColor3 = Color3.new(0.75, 0.4, 0.625)
close_button.BorderSizePixel = 0
close_button.Size = UDim2.fromScale(0.5, 1)
close_button.LayoutOrder = 3
close_button.Visible = true -- The close button is only visible when the main UI is hidden.
close_button.Name = "CloseButton"
close_button.Parent = toggle_frame
local uicorner = Instance.new("UICorner")
uicorner.CornerRadius = UDim.new(0.125, 0)
uicorner.Parent = toggle_button
-- THE MAIN WINDOW
local main_frame = Instance.new("Frame")
main_frame.AnchorPoint = Vector2.one / 2
main_frame.BackgroundColor3 = Color3.new(0.219608, 0.321569, 0.627451)
main_frame.BackgroundTransparency = 0.25
main_frame.BorderColor3 = Color3.new(0, 0, 0)
main_frame.BorderSizePixel = 0
main_frame.Position = UDim2.fromScale(0.5, 1.75) -- This Frame starts off-screen, revealed by clicking the toggle button at the top of the screen.
main_frame.Size = UDim2.fromScale(0.75, 0.725)
main_frame.Visible = true
main_frame.ZIndex = 2
main_frame.Name = "MainFrame"
main_frame.Parent = xane_mdlrecreator_gui
local uicorner_2 = Instance.new("UICorner")
uicorner_2.CornerRadius = UDim.new(0.025, 0)
uicorner_2.Parent = main_frame
--- STATUS MESSAGE
local message = Instance.new("TextLabel")
message.Font = Enum.Font.Gotham
message.Text = DefaultMessage
message.TextColor3 = Color3.new(1, 1, 1)
message.TextScaled = true
message.TextSize = 14
message.TextStrokeTransparency = 0
message.TextWrapped = true
message.AutomaticSize = Enum.AutomaticSize.Y
message.BackgroundColor3 = Color3.new(1, 1, 1)
message.BackgroundTransparency = 1
message.BorderColor3 = Color3.new(0, 0, 0)
message.BorderSizePixel = 0
message.LayoutOrder = 2
message.Size = UDim2.new(1, 0, 0.0625, 0)
message.Visible = true
message.Name = "Message"
message.Parent = main_frame
local uitext_size_constraint = Instance.new("UITextSizeConstraint")
uitext_size_constraint.MaxTextSize = 24
uitext_size_constraint.MinTextSize = 8
uitext_size_constraint.Parent = message
local top_bar = Instance.new("Frame")
top_bar.AnchorPoint = Vector2.xAxis / 2
top_bar.BackgroundTransparency = 1
top_bar.BorderSizePixel = 0
top_bar.LayoutOrder = 1
top_bar.Position = UDim2.new(0.5, 0, 0, 4)
top_bar.Size = UDim2.new(1,-8, 0.094,0)
top_bar.Visible = true
top_bar.Name = "TopBar"
top_bar.Parent = main_frame
local topbar_layout = Instance.new("UIListLayout")
topbar_layout.HorizontalFlex = Enum.UIFlexAlignment.None
topbar_layout.Padding = UDim.new(0, 4)
topbar_layout.FillDirection = Enum.FillDirection.Horizontal
topbar_layout.HorizontalAlignment = Enum.HorizontalAlignment.Right
topbar_layout.SortOrder = Enum.SortOrder.LayoutOrder
topbar_layout.Parent = top_bar
-- CONTAINER BUTTONS
local container1 = Instance.new("TextButton")
container1.Font = Enum.Font.RobotoCondensed
container1.Text = "🌎Workspace"
container1.TextColor3 = Color3.new(1, 1, 1)
container1.TextScaled = true
container1.TextSize = 14
container1.TextStrokeTransparency = 0
container1.TextWrapped = true
container1.Modal = true
container1.AutomaticSize = Enum.AutomaticSize.X
container1.BackgroundColor3 = Color3.new(0.596078, 0.921569, 0.921569)
container1.BorderColor3 = Color3.new(0, 0, 0)
container1.BorderSizePixel = 0
container1.LayoutOrder = 1
container1.Size = UDim2.fromScale(0.125,1)
container1.SizeConstraint = Enum.SizeConstraint.RelativeXY
container1.Visible = true
container1.Name = "Container1"
container1.Parent = top_bar
local uitext_size_constraint_2 = Instance.new("UITextSizeConstraint")
uitext_size_constraint_2.MaxTextSize = 24
uitext_size_constraint_2.MinTextSize = 6
uitext_size_constraint_2.Parent = container1
local container4 = Instance.new("TextButton")
container4.Font = Enum.Font.RobotoCondensed
container4.Text = "📦ReplicatedStorage"
container4.TextColor3 = Color3.new(1, 1, 1)
container4.TextScaled = true
container4.TextSize = 14
container4.TextStrokeTransparency = 0
container4.TextWrapped = true
container4.Modal = true
container4.AutomaticSize = Enum.AutomaticSize.X
container4.BackgroundColor3 = Color3.new(0.596078, 0.921569, 0.921569)
container4.BorderColor3 = Color3.new(0, 0, 0)
container4.BorderSizePixel = 0
container4.LayoutOrder = 4
container4.Size = UDim2.fromScale(0.125,1)
container4.SizeConstraint = Enum.SizeConstraint.RelativeXY
container4.Visible = true
container4.Name = "Container4"
container4.Parent = top_bar
local uitext_size_constraint_3 = Instance.new("UITextSizeConstraint")
uitext_size_constraint_3.MaxTextSize = 24
uitext_size_constraint_3.MinTextSize = 6
uitext_size_constraint_3.Parent = container4
local container3 = Instance.new("TextButton")
container3.Font = Enum.Font.RobotoCondensed
container3.Text = "🌟Lighting"
container3.TextColor3 = Color3.new(1, 1, 1)
container3.TextScaled = true
container3.TextSize = 14
container3.TextStrokeTransparency = 0
container3.TextWrapped = true
container3.Modal = true
container3.AutomaticSize = Enum.AutomaticSize.X
container3.BackgroundColor3 = Color3.new(0.596078, 0.921569, 0.921569)
container3.BorderColor3 = Color3.new(0, 0, 0)
container3.BorderSizePixel = 0
container3.LayoutOrder = 3
container3.Size = UDim2.fromScale(0.125,1)
container3.SizeConstraint = Enum.SizeConstraint.RelativeXY
container3.Visible = true
container3.Name = "Container3"
container3.Parent = top_bar
local uitext_size_constraint_4 = Instance.new("UITextSizeConstraint")
uitext_size_constraint_4.MaxTextSize = 24
uitext_size_constraint_4.MinTextSize = 6
uitext_size_constraint_4.Parent = container3
local container2 = Instance.new("TextButton")
container2.Font = Enum.Font.RobotoCondensed
container2.Text = "👥Players"
container2.TextColor3 = Color3.new(1, 1, 1)
container2.TextScaled = true
container2.TextSize = 14
container2.TextStrokeTransparency = 0
container2.TextWrapped = true
container2.Modal = true
container2.AutomaticSize = Enum.AutomaticSize.X
container2.BackgroundColor3 = Color3.new(0.596078, 0.921569, 0.921569)
container2.BorderColor3 = Color3.new(0, 0, 0)
container2.BorderSizePixel = 0
container2.LayoutOrder = 2
container2.Size = UDim2.fromScale(0.125,1)
container2.SizeConstraint = Enum.SizeConstraint.RelativeXY
container2.Visible = true
container2.Name = "Container2"
container2.Parent = top_bar
local container5 = Instance.new("TextButton")
container5.Font = Enum.Font.RobotoCondensed
container5.Text = "🕺🏼Characters"
container5.TextColor3 = Color3.new(1, 1, 1)
container5.TextScaled = true
container5.TextSize = 14
container5.TextStrokeTransparency = 0
container5.TextWrapped = true
container5.Modal = true
container5.AutomaticSize = Enum.AutomaticSize.X
container5.BackgroundColor3 = Color3.new(0.596078, 0.921569, 0.921569)
container5.BorderColor3 = Color3.new(0, 0, 0)
container5.BorderSizePixel = 0
container5.LayoutOrder = 0
container5.Size = UDim2.fromScale(0.1,1)
container5.SizeConstraint = Enum.SizeConstraint.RelativeXY
container5.Visible = true
container5.Name = "Container5"
container5.Parent = top_bar
local uitext_size_constraint_5 = Instance.new("UITextSizeConstraint")
uitext_size_constraint_5.MaxTextSize = 24
uitext_size_constraint_5.MinTextSize = 6
uitext_size_constraint_5.Parent = container2
local uilist_layout_2 = Instance.new("UIListLayout")
uilist_layout_2.Padding = UDim.new(0, 4)
uilist_layout_2.VerticalFlex = Enum.UIFlexAlignment.Fill
uilist_layout_2.HorizontalAlignment = Enum.HorizontalAlignment.Center
uilist_layout_2.SortOrder = Enum.SortOrder.LayoutOrder
uilist_layout_2.Parent = main_frame
local instance_list = Instance.new("ScrollingFrame")
instance_list.AutomaticCanvasSize = Enum.AutomaticSize.Y
instance_list.CanvasSize = UDim2.new(0, 0, 0, 0)
instance_list.BackgroundColor3 = Color3.new(1, 1, 1)
instance_list.BackgroundTransparency = 1
instance_list.BorderColor3 = Color3.new(0, 0, 0)
instance_list.BorderSizePixel = 0
instance_list.LayoutOrder = 3
instance_list.Size = UDim2.new(1, 0, 0.675, 0)
instance_list.Visible = true
instance_list.Name = "InstanceList"
instance_list.Parent = main_frame
local uilist_layout_3 = Instance.new("UIListLayout")
uilist_layout_3.Padding = UDim.new(0, 8)
uilist_layout_3.HorizontalAlignment = Enum.HorizontalAlignment.Center
uilist_layout_3.SortOrder = Enum.SortOrder.LayoutOrder
uilist_layout_3.Parent = instance_list
-- "EXPLORER" ROW TEMPLATE
local template_entry = Instance.new("Frame")
template_entry.BackgroundColor3 = Color3.new(1, 1, 1)
template_entry.BackgroundTransparency = 0.875
template_entry.BorderColor3 = Color3.new(0, 0, 0)
template_entry.BorderSizePixel = 0
template_entry.Size = UDim2.new(1, 0, 0.0925, 0)
template_entry.Visible = false
template_entry.Name = "TemplateEntry"
template_entry.Parent = instance_list
local checkbox_button = Instance.new("TextButton")
checkbox_button.Font = Enum.Font.SourceSans
checkbox_button.Text = "❗"
checkbox_button.TextColor3 = Color3.new(0, 0, 0)
checkbox_button.TextScaled = true
checkbox_button.TextSize = 14
checkbox_button.TextWrapped = true
checkbox_button.Modal = true
checkbox_button.BackgroundColor3 = Color3.new(1, 1, 1)
checkbox_button.BackgroundTransparency = 1
checkbox_button.BorderColor3 = Color3.new(0, 0, 0)
checkbox_button.BorderSizePixel = 0
checkbox_button.LayoutOrder = 2
checkbox_button.Size = UDim2.new(1, 0, 1, 0)
checkbox_button.SizeConstraint = Enum.SizeConstraint.RelativeYY
checkbox_button.Visible = true
checkbox_button.Name = "SelectButton"
checkbox_button.Parent = template_entry
local uilist_layout_4 = Instance.new("UIListLayout")
uilist_layout_4.HorizontalFlex = Enum.UIFlexAlignment.None
uilist_layout_4.FillDirection = Enum.FillDirection.Horizontal
uilist_layout_4.SortOrder = Enum.SortOrder.LayoutOrder
uilist_layout_4.Parent = template_entry
local indent = Instance.new("Frame")
indent.BackgroundColor3 = Color3.new(1, 1, 1)
indent.BackgroundTransparency = 1
indent.BorderSizePixel = 0
indent.LayoutOrder = 1
indent.Size = UDim2.fromScale(0.01825, 1)
indent.Visible = false
indent.Name = "Indent"
indent.Parent = template_entry
local indent_outline = Instance.new("UIStroke")
indent_outline.Color = Color3.new(0.825,0.933,1)
indent_outline.Transparency = 0.725
indent_outline.Thickness = 1
indent_outline.Parent = indent
local inst_name = Instance.new("TextLabel")
inst_name.Font = Enum.Font.RobotoCondensed
inst_name.Text = "Template Row"
inst_name.TextColor3 = Color3.new(1, 1, 1)
inst_name.TextScaled = true
inst_name.TextSize = 14
inst_name.TextStrokeTransparency = 0
inst_name.TextWrapped = true
inst_name.TextXAlignment = Enum.TextXAlignment.Left
inst_name.BackgroundColor3 = Color3.new(1, 1, 1)
inst_name.BackgroundTransparency = 1
inst_name.BorderColor3 = Color3.new(0, 0, 0)
inst_name.BorderSizePixel = 0
inst_name.LayoutOrder = 3
inst_name.Size = UDim2.fromScale(0.75, 1)
inst_name.Visible = true
inst_name.Name = "InstName"
inst_name.Parent = template_entry
local uitext_size_constraint_6 = Instance.new("UITextSizeConstraint")
uitext_size_constraint_6.MaxTextSize = 28
uitext_size_constraint_6.MinTextSize = 6
uitext_size_constraint_6.Parent = inst_name
local cam_action_btn = Instance.new("TextButton")
cam_action_btn.Font = Enum.Font.SourceSans
cam_action_btn.Text = "📸"
cam_action_btn.TextColor3 = Color3.new(0, 0, 0)
cam_action_btn.TextScaled = true
cam_action_btn.TextSize = 14
cam_action_btn.TextWrapped = true
cam_action_btn.Modal = true
cam_action_btn.BackgroundColor3 = Color3.new(1, 1, 1)
cam_action_btn.BackgroundTransparency = 1
cam_action_btn.BorderColor3 = Color3.new(0, 0, 0)
cam_action_btn.BorderSizePixel = 0
cam_action_btn.LayoutOrder = 4
cam_action_btn.Size = UDim2.new(1, 0, 1, 0)
cam_action_btn.SizeConstraint = Enum.SizeConstraint.RelativeYY
cam_action_btn.Visible = true
cam_action_btn.Name = "CamActionButton"
cam_action_btn.Parent = template_entry
local cam_revert_btn = Instance.new("TextButton")
cam_revert_btn.Font = Enum.Font.SourceSans
cam_revert_btn.Text = "🔁"
cam_revert_btn.TextColor3 = Color3.new(0, 0, 0)
cam_revert_btn.TextTransparency = 1 -- This button was going to be used, but now its functions are handled by the "camera" button to its left.
cam_revert_btn.TextScaled = true
cam_revert_btn.TextSize = 14
cam_revert_btn.TextWrapped = true
cam_revert_btn.Modal = true
cam_revert_btn.BackgroundColor3 = Color3.new(1, 1, 1)
cam_revert_btn.BackgroundTransparency = 1
cam_revert_btn.BorderColor3 = Color3.new(0, 0, 0)
cam_revert_btn.BorderSizePixel = 0
cam_revert_btn.LayoutOrder = 5
cam_revert_btn.Size = UDim2.new(1, 0, 1, 0)
cam_revert_btn.SizeConstraint = Enum.SizeConstraint.RelativeYY
cam_revert_btn.Visible = true
cam_revert_btn.Name = "CamRevertButton"
cam_revert_btn.Parent = template_entry
-- BOTTOM BAR (PAGINATION BUTTONS, NAME ENTRY, AND SAVE BUTTON)
local bot_bar = Instance.new("Frame")
bot_bar.AnchorPoint = Vector2.new(0.5,1)
bot_bar.BackgroundTransparency = 1
bot_bar.BorderSizePixel = 0
bot_bar.LayoutOrder = 4
bot_bar.Position = UDim2.new(0.5, 0, 0, -4)
bot_bar.Size = UDim2.new(1,-8, 0.094,0)
bot_bar.Visible = true
bot_bar.Name = "BottomBar"
bot_bar.Parent = main_frame
local botbar_layout = Instance.new("UIListLayout")
botbar_layout.HorizontalFlex = Enum.UIFlexAlignment.None
botbar_layout.Padding = UDim.new(0, 4)
botbar_layout.FillDirection = Enum.FillDirection.Horizontal
botbar_layout.HorizontalAlignment = Enum.HorizontalAlignment.Right
botbar_layout.SortOrder = Enum.SortOrder.LayoutOrder
botbar_layout.Parent = bot_bar
local prevPageButton = Instance.new("TextButton")
prevPageButton.Font = Enum.Font.RobotoCondensed
prevPageButton.Text = "◀ Prev"
prevPageButton.TextColor3 = Color3.new(1, 1, 1)
prevPageButton.TextScaled = true
prevPageButton.TextSize = 14
prevPageButton.TextStrokeTransparency = 0
prevPageButton.TextWrapped = true
prevPageButton.Modal = true
prevPageButton.AutomaticSize = Enum.AutomaticSize.X
prevPageButton.BackgroundColor3 = Color3.new(0.596078, 0.921569, 0.921569)
prevPageButton.BorderColor3 = Color3.new(0, 0, 0)
prevPageButton.BorderSizePixel = 0
prevPageButton.LayoutOrder = 1
prevPageButton.Size = UDim2.fromScale(0.1125,1)
prevPageButton.SizeConstraint = Enum.SizeConstraint.RelativeXY
prevPageButton.Visible = true
prevPageButton.Name = "PrevPage"
prevPageButton.Parent = bot_bar
local prevPageBtnSizeConstraint = Instance.new("UITextSizeConstraint")
prevPageBtnSizeConstraint.MaxTextSize = 24
prevPageBtnSizeConstraint.MinTextSize = 6
prevPageBtnSizeConstraint.Parent = prevPageButton
local nextPageButton = Instance.new("TextButton")
nextPageButton.Font = Enum.Font.RobotoCondensed
nextPageButton.Text = "Next ▶"
nextPageButton.TextColor3 = Color3.new(1, 1, 1)
nextPageButton.TextScaled = true
nextPageButton.TextSize = 14
nextPageButton.TextStrokeTransparency = 0
nextPageButton.TextWrapped = true
nextPageButton.Modal = true
nextPageButton.AutomaticSize = Enum.AutomaticSize.X
nextPageButton.BackgroundColor3 = Color3.new(0.596078, 0.921569, 0.921569)
nextPageButton.BorderColor3 = Color3.new(0, 0, 0)
nextPageButton.BorderSizePixel = 0
nextPageButton.LayoutOrder = 2
nextPageButton.Size = UDim2.fromScale(0.1125,1)
nextPageButton.SizeConstraint = Enum.SizeConstraint.RelativeXY
nextPageButton.Visible = true
nextPageButton.Name = "NextPage"
nextPageButton.Parent = bot_bar
local nextPageBtnSizeConstraint = Instance.new("UITextSizeConstraint")
nextPageBtnSizeConstraint.MaxTextSize = 24
nextPageBtnSizeConstraint.MinTextSize = 6
nextPageBtnSizeConstraint.Parent = nextPageButton
-- SEARCH BOX (added feature)
local search_box = Instance.new("TextBox")
search_box.Font = Enum.Font.Ubuntu
search_box.PlaceholderColor3 = Color3.new(0.4, 0.4, 0.4)
search_box.PlaceholderText = "🔍 Search instances..."
search_box.Text = ""
search_box.TextColor3 = Color3.new(0.1, 0.1, 0.2)
search_box.TextScaled = true
search_box.TextSize = 14
search_box.TextWrapped = true
search_box.ClearTextOnFocus = false
search_box.BackgroundColor3 = Color3.new(0.88, 0.93, 1)
search_box.BorderColor3 = Color3.new(0, 0, 0)
search_box.BorderSizePixel = 0
search_box.LayoutOrder = 0 -- Place it before prev/next page buttons
search_box.Size = UDim2.fromScale(0.2, 1)
search_box.SizeConstraint = Enum.SizeConstraint.RelativeXY
search_box.Visible = true
search_box.ZIndex = 2
search_box.Name = "SearchBox"
search_box.Parent = bot_bar
local uicorner_search = Instance.new("UICorner")
uicorner_search.CornerRadius = UDim.new(0.225, 0)
uicorner_search.Parent = search_box
local uitextsize_search = Instance.new("UITextSizeConstraint")
uitextsize_search.MaxTextSize = 20
uitextsize_search.MinTextSize = 6
uitextsize_search.Parent = search_box
local PoseCharCheckbox = Instance.new("TextButton")
PoseCharCheckbox.Font = Enum.Font.RobotoCondensed
PoseCharCheckbox.Text = "💃🏼Ignore characters"
PoseCharCheckbox.TextColor3 = Color3.new(1, 1, 1)
PoseCharCheckbox.TextScaled = true
PoseCharCheckbox.TextSize = 14
PoseCharCheckbox.TextStrokeTransparency = 0
PoseCharCheckbox.TextWrapped = true
PoseCharCheckbox.Modal = true
PoseCharCheckbox.AutomaticSize = Enum.AutomaticSize.X
PoseCharCheckbox.BackgroundColor3 = Color3.new(0.596078, 0.921569, 0.921569)
PoseCharCheckbox.BorderColor3 = Color3.new(0, 0, 0)
PoseCharCheckbox.BorderSizePixel = 0
PoseCharCheckbox.LayoutOrder = 3
PoseCharCheckbox.Size = UDim2.fromScale(0.2,1)
PoseCharCheckbox.SizeConstraint = Enum.SizeConstraint.RelativeXY
PoseCharCheckbox.Visible = true
PoseCharCheckbox.Name = "CBox_PoseChars"
PoseCharCheckbox.Parent = bot_bar
local pose_chars_size_constraint = Instance.new("UITextSizeConstraint")
pose_chars_size_constraint.MaxTextSize = 24
pose_chars_size_constraint.MinTextSize = 6
pose_chars_size_constraint.Parent = PoseCharCheckbox
-- JSON MODEL NAME ENTRY FIELD/TEXTBOX
local filename_box = Instance.new("TextBox")
filename_box.Font = Enum.Font.Ubuntu
filename_box.PlaceholderColor3 = Color3.new(0.239216, 0.392157, 0.290196)
filename_box.PlaceholderText = "What should this be called?"
filename_box.Text = ""
filename_box.TextColor3 = Color3.new(0.317647, 0.231373, 0.490196)
filename_box.TextScaled = true
filename_box.TextSize = 14
filename_box.TextStrokeColor3 = Color3.new(0.317647, 0.231373, 0.490196)
filename_box.TextStrokeTransparency = 0.5
filename_box.TextWrapped = true
filename_box.BackgroundColor3 = Color3.new(0.654902, 0.788235, 0.980392)
filename_box.BorderColor3 = Color3.new(0, 0, 0)
filename_box.BorderSizePixel = 0
filename_box.LayoutOrder = 6
filename_box.Position = UDim2.new(0, 4, 0, 4)
filename_box.Size = UDim2.fromScale(0.175,1)
filename_box.SizeConstraint = Enum.SizeConstraint.RelativeXY
filename_box.Visible = true
filename_box.ZIndex = 2
filename_box.Name = "FilenameBox"
filename_box.Parent = bot_bar
local uicorner_3 = Instance.new("UICorner")
uicorner_3.CornerRadius = UDim.new(0.22499999403953552, 0)
uicorner_3.Parent = filename_box
local label = Instance.new("TextLabel")
label.Font = Enum.Font.FredokaOne
label.Text = "JSON filename:"
label.TextColor3 = Color3.new(0.811765, 1, 0.431373)
label.TextScaled = true
label.TextSize = 14
label.TextStrokeColor3 = Color3.new(0.266667, 0.364706, 0.411765)
label.TextStrokeTransparency = 0
label.TextWrapped = true
label.TextXAlignment = Enum.TextXAlignment.Left
label.AnchorPoint = Vector2.new(0.5, 0)
label.BackgroundColor3 = Color3.new(1, 1, 1)
label.BackgroundTransparency = 1
label.BorderColor3 = Color3.new(0, 0, 0)
label.BorderSizePixel = 0
label.Position = UDim2.new(0.5, 4, -0.300000012, 0)
label.Size = UDim2.new(1, 0, 0.532999992, 0)
label.Visible = true
label.ZIndex = 2
label.Name = "Label"
label.Parent = filename_box
-- THE RH-ESQUE "3D" SAVE BUTTON
local save_button = Instance.new("TextButton")
save_button.Font = Enum.Font.Cartoon
save_button.Text = ""
save_button.TextColor3 = Color3.new(1, 1, 1)
save_button.TextScaled = true
save_button.TextSize = 14
save_button.TextWrapped = true
save_button.AnchorPoint = Vector2.new(1, 0)
save_button.BorderSizePixel = 0
save_button.LayoutOrder = 7
save_button.Position = UDim2.fromScale(0.125,1)
save_button.Size = UDim2.fromScale(0.125,1)
save_button.SizeConstraint = Enum.SizeConstraint.RelativeXY
save_button.Visible = true
save_button.Name = "SaveButton"
save_button.Parent = bot_bar
local uicorner_4 = Instance.new("UICorner")
uicorner_4.CornerRadius = UDim.new(0.125, 0)
uicorner_4.Parent = save_button
local button_top = Instance.new("TextLabel")
button_top.Font = Enum.Font.Cartoon
button_top.Text = "Save"
button_top.TextColor3 = Color3.new(1, 1, 1)
button_top.TextScaled = true
button_top.TextSize = 14
button_top.TextStrokeTransparency = 0
button_top.TextWrapped = true
button_top.AnchorPoint = Vector2.new(0.5, 1)
button_top.BackgroundColor3 = Color3.new(0.647059, 1, 0.678431)
button_top.BorderColor3 = Color3.new(0, 0, 0)
button_top.BorderSizePixel = 0
button_top.Position = UDim2.new(0.5, 0, 0.875, 0)
button_top.Selectable = true
button_top.Size = UDim2.new(1, 0, 1, 0)
button_top.Visible = true
button_top.ZIndex = 2
button_top.Name = "FakeTop"
button_top.Parent = save_button
local uicorner_5 = Instance.new("UICorner")
uicorner_5.CornerRadius = UDim.new(0.125, 0)
uicorner_5.Parent = button_top
local button_outline = Instance.new("UIStroke")
button_outline.ApplyStrokeMode = Enum.ApplyStrokeMode.Border
button_outline.Color = Color3.new(0.388235, 0.705882, 0.627451)
button_outline.Thickness = 2
button_outline.Parent = button_top
local uigradient = Instance.new("UIGradient")
uigradient.Color = ColorSequence.new({ColorSequenceKeypoint.new(0, Color3.new(0.752941, 0.752941, 0.752941)), ColorSequenceKeypoint.new(0.125, Color3.new(1, 1, 1)), ColorSequenceKeypoint.new(0.875, Color3.new(1, 1, 1)), ColorSequenceKeypoint.new(1, Color3.new(0.752941, 0.752941, 0.752941))})
uigradient.Parent = save_button
-- PROGRESS BOX (USED FOR INDEXING AND SAVING)
local progress_ui = Instance.new("TextLabel")
progress_ui.Font = Enum.Font.RobotoCondensed
progress_ui.Text = "Indexing instance\u{000D}\u{000A}56 / 203"
progress_ui.TextColor3 = Color3.new(1, 1, 1)
progress_ui.TextScaled = true
progress_ui.TextStrokeTransparency = 0
progress_ui.TextWrapped = true
progress_ui.AnchorPoint = Vector2.one / 2
progress_ui.BackgroundColor3 = Color3.new(0.161, 0.353, 0.224)
progress_ui.BackgroundTransparency = 0.25
progress_ui.BorderColor3 = Color3.new(0, 0, 0)
progress_ui.BorderSizePixel = 0
progress_ui.Position = UDim2.fromScale(0.5, 0.5)
progress_ui.Size = UDim2.fromScale(0.425, 0.225)
progress_ui.Visible = false
progress_ui.ZIndex = 3
progress_ui.Name = "ProgressUI"
progress_ui.Parent = xane_mdlrecreator_gui
local ui_aspectratio_progress = Instance.new("UIAspectRatioConstraint")
ui_aspectratio_progress.AspectRatio = 3.5
ui_aspectratio_progress.Parent = progress_ui
local uitextsize_progress = Instance.new("UITextSizeConstraint")
uitextsize_progress.MaxTextSize = 50
uitextsize_progress.MinTextSize = 10
uitextsize_progress.Parent = progress_ui
local uicorner_progress = Instance.new("UICorner")
uicorner_progress.CornerRadius = UDim.new(0.125, 0)
uicorner_progress.Parent = progress_ui
local template_selbox = Instance.new("SelectionBox") -- A box that appears around any selected instances that support rendering it around them.
template_selbox.Name = "XaneSelBoxTemplate"
template_selbox.LineThickness = 0.03125
template_selbox.Transparency = 0.25
template_selbox.SurfaceTransparency = 0.925
template_selbox:SetAttribute("XaneProtectedDoNotShowInRecreatorGui",true) -- Hide this instance from the Recreator script itself.
template_selbox.Parent = xane_mdlrecreator_gui
print("UI created!")
-- DATA FOR THE ANIMATED SAVE BUTTON
local Position_Raised = UDim2.fromScale(0.5,0.875)
local Position_Pressed = UDim2.fromScale(0.5,1)
local BtnColor_Ready = Color3.fromRGB(160,255,224) -- Save button colors.
local BtnColor_Caution = Color3.fromRGB(212,255,204) -- Used if the selected file already exists.
local BtnColor_Disabled = Color3.fromRGB(126,128,130) -- Used until a valid model and filename are entered.
local RowOpacityNormal = 0.875
local RowOpacitySelected = 0.75
-- An array of dictionary entries which keeps all of the events and references needed by each instance shown in the main UI list.
-- As creating too many rows will lag the client when the list updates (scrolling, generating, etc), this array should be limited to
-- 100 or so entries at any time.
-- The full list (which has less data in each entry) is InternalList below.
local ListData : {VisibleListEntry} = {}
-- VARIABLES (SECOND SET)
local GUIShown = true -- This is set to TRUE when the main window is visible, used by the toggle button at the top of the screen.
local IsBusy = false -- Marks the main window as busy. This disables all buttons, so the script can execute code in peace.
local Offset = 0 -- The current "page" of instances that are being shown to the user.
local CurrentContainer = nil
local function ChangeButtonState(_release : boolean, _color : Color3, _text : string)
if _text then button_top.Text = _text end
if _color then
button_top.BackgroundColor3 = _color
local btnH, btnS, btnV = _color:ToHSV()
button_outline.Color = Color3.fromHSV(btnH, btnS+(btnS/4), btnV-(btnV/6))
save_button.BackgroundColor3 = Color3.fromHSV(btnH, btnS+(btnS/3), btnV-(btnV/4))
end
-- If the button is already in the same state as this command would change it to, stop the function here.
if _release == save_button.Active then return nil
else
save_button.Active = _release -- Immediately update whether the user can click on this button before playing the animation.
button_top:TweenPosition(
_release and Position_Raised or Position_Pressed,
Enum.EasingDirection.InOut,
Enum.EasingStyle.Quad,
0.125,
true
)
return nil
end
end
-- Initially disable the save button.
ChangeButtonState(false, BtnColor_Disabled, "Can't save")
local function CheckSavePrerequisites()
if filename_box.Text:len() > 0 and -- Make sure the TextBox isn't empty and the filename doesn't contain invalid characters.
not filename_box.Text:find("/") and
not filename_box.Text:find("\"") and
not filename_box.Text:find("\\") and
not filename_box.Text:find(":") and
not filename_box.Text:find("*") and
not filename_box.Text:find("?") and
#Recreator.Selection > 0 -- Ensure the player has at least one Instance selected before letting them export.
then
local FileStatus = nil
pcall(function()
FileStatus = readfile(filename_box.Text .. "_header.json")
end)
if FileStatus then ChangeButtonState(true, BtnColor_Caution, "Overwrite")
else ChangeButtonState(true, BtnColor_Ready, "Save!")
end
else ChangeButtonState(false, BtnColor_Disabled, "Can't save")
end
clear_button.Visible = #Recreator.Selection > 0 -- A second button will appear at the top of the screen to deselect everything if anything's marked.
message.Text = DefaultMessage
end
-- Disconnects all events within the list shown in the main window, then removes all of its rows.
local function ClearVisibleList()
if #ListData > 0 then
for i,entry in ListData do
-- Disconnect all of the button events' connections before destroying this row.
if entry.CheckboxClickEvent then
entry.CheckboxClickEvent:Disconnect()
ListData[i].CheckboxClickEvent = nil
end
if entry.CameraFocusEvent then
entry.CameraFocusEvent:Disconnect()
ListData[i].CameraFocusEvent = nil
end
if entry.CamRevertEvent1 then
entry.CamRevertEvent1:Disconnect()
ListData[i].CamRevertEvent1 = nil
end
if entry.CamRevertEvent2 then
entry.CamRevertEvent2:Disconnect()
ListData[i].CamRevertEvent2 = nil
end
if entry.SelectBox then
entry.SelectBox:Destroy()
entry.SelectBox = nil
end
entry.Checkbox:Destroy()
entry.Checkbox = nil
entry.RowBase:Destroy()
entry.RowBase = nil
end
table.clear(ListData) -- Remove all of the now-useless entries from the array.
end
end
-- Visually updates a given row to make it apppear to be (de)selected.
local function UpdateRowVisualState(_entry : InstanceListEntry, _select : boolean)
if _select then
_entry.RowBase.BackgroundTransparency = RowOpacitySelected
_entry.Checkbox.Text = "✅"
else
_entry.RowBase.BackgroundTransparency = RowOpacityNormal
_entry.Checkbox.Text = Recreator.ClassData[_entry.Instance.ClassName].ListView.Icon -- Revert this checkbox's icon to the class icon.
end
end
-- An iffy function that does what ApplyChildAction()'s "list" mode used to do, only now in a for loop. It destroys the
-- current "visible list" then rebuilds it using data from the full, pre-generated internal instance list.
local function RedrawVisibleList()
if #ListData > 0 then
ClearVisibleList()
end
for i = (Recreator.PageLength*Offset)+1, (Recreator.PageLength*Offset)+Recreator.PageLength-1 do
-- Make sure we haven't reached the end of the list yet. If we have, stop creating entries now.
if Recreator.FullList[i] then
-- Make sure this Instance isn't a "bad actor" which could cause a softlock or wouldn't assist this export.
if not Recreator.IsInstanceAllowed(Recreator.FullList[i].Instance) then continue end
-- If execution reaches this point, this Instance is supported, so let's add it to our temporary list!
local NewEntry : InstanceListEntry = {
Instance = Recreator.FullList[i].Instance,
RowBase = template_entry:Clone(),
Checkbox = nil,
CheckboxClickEvent = nil,
CameraFocusEvent = nil,
CamRevertEvent1 = nil,
CamRevertEvent2 = nil
}
NewEntry.RowBase.Name = "Listing_" .. i -- Name each row's Frame after its ordering, just to make it easier for those sifting through UI instances.
NewEntry.Checkbox = NewEntry.RowBase:WaitForChild("SelectButton")
local temp_instName = NewEntry.RowBase:WaitForChild("InstName")
-- Before continuing, determine how big a row should be in pixels rather than scale. Since executors are behind the times,
-- they unfortyunately don't support Roblox's new CSS-esque flexible UI containers, so there isn't an easy way to make the
-- "instance name" label fill the space the icon on the left side and camera button on the right side exactly. It's dumb!
-- TODO: I just cannot get rows to be just long enough to squeeze the camera button onto the right edge of each row, regardless
-- of indentation. For some reason, with or without a delay, AbsoluteSize is always 0, 0! I'm sorry for this awful alt. sizing...
temp_instName.Size = UDim2.fromScale(1 - (indent.Size.X.Scale * Recreator.FullList[i].Level) - 0.0825,1)
NewEntry.RowBase:WaitForChild("CamActionButton").AnchorPoint = Vector2.xAxis
--[[
task.wait()
print("Row X scale was calculated as", 1 - (indent.Size.X.Scale * InternalList[i].Level))
print("Just the subtracted value is", indent.Size.X.Scale * InternalList[i].Level)
print("Absolute size is", temp_instName.AbsoluteSize)
print("Final X size is somehow", temp_instName.AbsoluteSize.X-temp_instName.AbsoluteSize.Y)
temp_instName.Size = UDim2.fromOffset( -- Take the size determined above and subtract one square icon's size from it.
temp_instName.AbsoluteSize.X-temp_instName.AbsoluteSize.Y,
temp_instName.AbsoluteSize.Y
)
]]--
temp_instName.Text = Recreator.FullList[i].Instance.Name
local temp_newIndent = NewEntry.RowBase:WaitForChild("Indent") -- Hold a reference to the original indent, which will be duplicated based on this instance's "level".
temp_newIndent.Visible = Recreator.FullList[i].Level > 0
if Recreator.FullList[i].Level > 1 then -- If this Instance is at the second level or deeper, duplicate the indent as needed, forming a grid for clarity left of the list.
for i = 2, Recreator.FullList[i].Level do
local temp_additionalIndent = temp_newIndent:Clone()
temp_additionalIndent.Parent = temp_newIndent.Parent
end
end
NewEntry.Checkbox.Text = Recreator.ClassData[Recreator.FullList[i].Instance.ClassName].ListView.Icon
-- If this object has been previously selected, highlight it now.
if table.find(Recreator.Selection, Recreator.FullList[i].Instance) then
UpdateRowVisualState(NewEntry, true)
end
NewEntry.RowBase.Visible = true
-- Add functionality to the the two buttons found on the ends of this row.
local temp_camButton : TextButton = NewEntry.RowBase:WaitForChild("CamActionButton")
temp_camButton.Visible = Recreator.ClassData[Recreator.FullList[i].Instance.ClassName].ListView.CanView ~= "no"
if temp_camButton.Visible then
NewEntry.CameraFocusEvent = temp_camButton.MouseButton1Click:Connect(function()
if Recreator.ClassData[Recreator.FullList[i].Instance.ClassName].ListView.CanView == "parent" then
local temp_ancestor = Recreator.FullList[i].Instance:FindFirstAncestorWhichIsA("BasePart")
if temp_ancestor then
workspace.CurrentCamera.CameraSubject = temp_ancestor
end
elseif Recreator.ClassData[Recreator.FullList[i].Instance.ClassName].ListView.CanView == "child" then
local temp_child = Recreator.FullList[i].Instance:FindFirstChildWhichIsA("BasePart")
if temp_child then
workspace.CurrentCamera.CameraSubject = Recreator.FullList[i].Instance.Parent
else
-- Hide the camera button if nothing camera-compatible is found within this instance.
NewEntry.RowBase:FindFirstChild("CamActionButton").Visible = false
end
else
workspace.CurrentCamera.CameraSubject = Recreator.FullList[i].Instance
end
end)
NewEntry.CamRevertEvent1 = temp_camButton.MouseButton2Click:Connect(function()
workspace.CurrentCamera.CameraSubject = Players.LocalPlayer.Character:WaitForChild("Humanoid")
end)
NewEntry.CamRevertEvent2 = temp_camButton.TouchLongPress:Connect(function()
workspace.CurrentCamera.CameraSubject = Players.LocalPlayer.Character:WaitForChild("Humanoid")
end)
end
NewEntry.CheckboxClickEvent = NewEntry.Checkbox.MouseButton1Click:Connect(function()
-- If this Instance is currently selected, remove it from the list and undo changes to this row on the list.
local SelectionIndex = table.find(Recreator.Selection, Recreator.FullList[i].Instance)
if SelectionIndex then -- TODO: This code block relies on the selection list using debug IDs instead of proper references!
Recreator.Select("remove", {Recreator.FullList[i].Instance})
-- table.remove(Recreator.Selection, SelectionIndex)
UpdateRowVisualState(NewEntry, false)
-- If this Instance is visually selected in-world, remove the box that's around it.
if NewEntry.SelectBox then
NewEntry.SelectBox:Destroy()
NewEntry.SelectBox = nil -- Make sure the other part of this mini-function knows that the box no longer exists.
end
else
-- table.insert(Recreator.Selection, Recreator.FullList[i].Instance)
Recreator.Select("add", {Recreator.FullList[i].Instance})
UpdateRowVisualState(NewEntry, true)
if Recreator.FullList[i].Level <= 0 then -- If the container itself was just selected, deselect anything within it.
local SelectionDelQueue = {} -- Keep track of the array indices that'll be removed after this for loop.
local Cleared = false -- The selection array will be endlessly checked until this flag is set to TRUE.
while not Cleared do
for i1,_inst1 : Instance in Recreator.Selection do -- For each selected Instance...
if _inst1 and _inst1:IsDescendantOf(Recreator.FullList[i].Instance) then
table.remove(Recreator.Selection, i1) -- The table.remove() function messes up arrays' orders, so let's start over...
break
elseif i1 >= #Recreator.Selection and not Cleared then
Cleared = true -- Without this action, the while loop would force this for loop to completely restart.
end
end
end
Cleared = false
RedrawVisibleList() -- Update the list, in case the loop above deselected any Instances.
end
-- Also, create a SelectionBox for this instance, to make it easier to tell what has been selected in messy games (like Royale High).
-- Only do this for child instances within the workspace, and obviously, don't draw a box around the workspace itself.
if Recreator.FullList[i].Level > 0 and Recreator.FullList[i].Instance:IsDescendantOf(workspace) and not NewEntry.SelectBox then
NewEntry.SelectBox = template_selbox:Clone()
NewEntry.SelectBox.Color3 = Color3.fromHSV(math.random(), 0.125+(math.random()/5), 1-(math.random()/16)) -- Use a random color.
NewEntry.SelectBox.SurfaceColor3 = NewEntry.SelectBox.Color3
NewEntry.SelectBox.Adornee = Recreator.FullList[i].Instance
NewEntry.SelectBox.Parent = Recreator.FullList[i].Instance
end
end
CheckSavePrerequisites() -- If the user's entered a filename and has at least one instance selected, allow them to start exporting things!
end)
NewEntry.RowBase.Parent = instance_list
table.insert(ListData, NewEntry)
else break
end
end
end
-- Slides the main window on/off the screen. Normally, this is prevented when anything important is happening, but _force will bypass restrictions.
local function ToggleUI(_force : boolean)
if not IsBusy or _force then
close_button.Visible = GUIShown -- Show the close button when the user closes the main UI.
if GUIShown then
IsBusy = true
if #Recreator.Selection <= 0 then toggle_button.Text = "Open Save GUI..."
else toggle_button.Text = "Select Instances... (" .. tostring(#Recreator.Selection) .. ")"
end
toggle_button.BackgroundColor3 = BtnColor_Ready
main_frame:TweenPosition(UDim2.fromScale(0.5, 2.0), Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, 0.333, true)
task.wait(0.125)
IsBusy = false
else
IsBusy = true
toggle_button.Text = "Hide GUI"
toggle_button.BackgroundColor3 = BtnColor_Caution
main_frame:TweenPosition(UDim2.fromScale(0.5, 0.55), Enum.EasingDirection.Out, Enum.EasingStyle.Back, 0.35, true)
task.wait(0.35)
IsBusy = false
end
GUIShown = not GUIShown
end
end
toggle_button.MouseButton1Click:Connect(function() ToggleUI(false) end)
ToggleUI(false)
local function ChangeListSource(_base : Instance)
if not IsBusy then
IsBusy = true -- Disable all interface buttons until the list has been updated. (Hopefully, no errors occur!)
ToggleUI(false)
if #ListData > 0 then ClearVisibleList() end
if #Recreator.FullList > 0 then table.clear(Recreator.FullList) end
-- Set up and show the "indexing progress" UI, which will be updated by ApplyChildAction(), so the user isn't left in the dark.
progress_ui.Visible = true -- For now, make the UI pop up rather than sliding in like the other frames would.
Recreator.MakeList(_base) -- Generate the full list of Instances within the new container.
table.insert(Recreator.FullList, 1, { -- Slip a fake entry for the container itself into the start of the list.
Instance = _base,
Level = 0
})
progress_ui.Visible = false -- Hide the progress UI; We're about to display the list that was just generated.
Offset = 0 -- Always start from the beginning of a container's Instance list.
RedrawVisibleList()
task.wait(0.425)
ToggleUI(false)
IsBusy = false
end
end
container1.MouseButton1Click:Connect(function() ChangeListSource(workspace) end)
container2.MouseButton1Click:Connect(function() ChangeListSource(Players) end)
container3.MouseButton1Click:Connect(function() ChangeListSource(Lighting) end)
container4.MouseButton1Click:Connect(function() ChangeListSource(ReplicatedStorage) end)
-- "Container 5" is a special case; Rather than indexing every instance within a container, it specifically tries to find each player's
-- character model and indexes ITS contents. If playing Royale High, its EquippedStorage folder is checked for and also indexed.
local function IndexCharacters()
if not IsBusy then
IsBusy = true -- Disable all interface buttons until the list has been updated. (Hopefully, no errors occur!)
ToggleUI(false)
-- Clear the full and visible lists if they're already occupied by another container button's instances.
if #ListData > 0 then ClearVisibleList() end
if #Recreator.FullList > 0 then table.clear(Recreator.FullList) end
for _,player : Player in pairs(Players:GetPlayers()) do
local PlayerChar = player.Character -- Try to access this player's character model.
if not PlayerChar then -- If it hasn't been created yet, retry a couple of times, then give up.
local CharTries = 10
local success = false
while CharTries > 0 do
success, _ = pcall(function()
PlayerChar = player.Character
end)
if success then break end -- Did we find it? Let's move on!
task.wait(0.25)
CharTries -= 1
end
if not PlayerChar then continue end -- If their character is still inaccessible, just move on to the next player.
end
-- If execution reaches this point, we have a player character to index!
-- Unlike every other "mode", we won't be listing every instance within character models to save time; A player could leave the
-- server within a minute, after all! Just index the character models directly.
table.insert(Recreator.FullList, {
Instance = PlayerChar,
Level = 1
})
-- progress_ui.Visible = false -- Hide the progress UI; We're about to display the list that was just generated.
end
-- Check for a folder named "EquippedStorage", which Royale High places clothes that players are wearing inside. This folder should be
-- listed first, as the user probably doesn't want to save characters with missing body parts or anything weird-looking.
local RH_EquippedStorage = workspace:FindFirstChild("EquippedStorage")
if RH_EquippedStorage then
table.insert(Recreator.FullList, 1, {
Instance = RH_EquippedStorage,
Level = 1
})
end
Offset = 0 -- Always start from the beginning of a container's Instance list.
RedrawVisibleList()
task.wait(0.425)
ToggleUI(false)
IsBusy = false
end
end
container5.MouseButton1Click:Connect(function() IndexCharacters(false) end)
prevPageButton.MouseButton1Click:Connect(function()
-- If advancing to the next page would go beyond the full Instance list's bounds, ignore this action. Otherwise, update the visible list.
if not IsBusy and Offset > 0 then
IsBusy = true -- Prevent other actions from occurring until the list has been finalized.
Offset -= 1
RedrawVisibleList()
instance_list.CanvasPosition = Vector2.yAxis * instance_list.AbsoluteCanvasSize.Y -- Jump to the end of the visible list.
IsBusy = false -- It is now safe to close the main window and export.
end
end)
nextPageButton.MouseButton1Click:Connect(function()
-- If advancing to the next page would go beyond the full Instance list's bounds, ignore this action. Otherwise, update the visible list.
if not IsBusy and ((Offset*Recreator.PageLength)+1)+Recreator.PageLength <= #Recreator.FullList then
IsBusy = true -- Prevent other actions from occurring until the list has been finalized.
Offset += 1
RedrawVisibleList()
instance_list.CanvasPosition = Vector2.zero -- Jump to the top of the visible list.
IsBusy = false -- It is now safe to close the main window and export.
end
end)
-- If the clear button is visible, tappping it will deselect everything then redraw whatever page the window was showing.
clear_button.MouseButton1Click:Connect(function()
Recreator.Select("set", {}) -- Clear the current selection.
ClearVisibleList()
RedrawVisibleList()
CheckSavePrerequisites()
end)
-- If the user just finished using the model name text box, validate their selection and name to determine if they're allowed to save it now.
local LastSafeName = ""
filename_box.FocusLost:Connect(function()
if not IsBusy then -- As long as saving or something important isn't happening now, save the current file name to a variable. It's used below.
LastSafeName = filename_box.Text
CheckSavePrerequisites()
else -- If something is happening, don't let the user change the save's name!
filename_box.Text = LastSafeName
end
end)
-- Unfreezes frozen char. parts, shows the GUI, and unsets the "busy" flag, restoring the ability to select instances again.
-- If _success is set, this function also deselects everything and redraws the list.
local function PostSaveCleanup(_success)
if _success then
RedrawVisibleList()
Recreator.Select("set", {})
end
if not GUIShown then
ToggleUI(false)
task.wait(0.25)
ChangeButtonState(true)
message.Text = "Done! Transfer the new JSON files in your workspace folder to your PC, " ..
"then paste each of them into the Studio plugin's text fields, adding more as needed." ..
"Make sure the 'header' goes first, followed by each piece IN ORDER!"
IsBusy = false
end
end
Recreator.OnSaveDone = function(_success : boolean)
PostSaveCleanup(_success)
end
save_button.MouseButton1Click:Connect(function()
if not IsBusy and save_button.Active then
if GUIShown then ToggleUI(false) end
IsBusy = true
progress_ui.Text = "Preparing to save..."
progress_ui.Visible = true
Recreator.Save(filename_box.Text)
progress_ui.Visible = false
IsBusy = false
end
end)
close_button.MouseButton1Click:Connect(function()
if not IsBusy then
Recreator.ActivateAPI(false)
table.clear(ListData)
xane_mdlrecreator_gui:Destroy()
print("Xane's Model Recreator has been closed. Thank you for using this awesome script!")
script:Destroy()
end
end)
Recreator.SetStatusGui(progress_ui)
-- SEARCH FEATURE
-- Keeps a copy of the full list so search can restore it when the query is cleared.
-- RedrawVisibleList() reads from Recreator.FullList, so we just swap its contents
-- temporarily — no existing function needs to be changed.
local SearchFullListCache : {any} = {} -- Stores the original FullList while a search is active.
local SearchActive = false
local function ApplySearch(_query : string)
if IsBusy then return end
_query = _query:lower():gsub("^%s+", ""):gsub("%s+$", "") -- trim + lowercase
if _query == "" then
-- Restore original list if we had stashed it.
if SearchActive and #SearchFullListCache > 0 then
table.clear(Recreator.FullList)
for _, entry in SearchFullListCache do
table.insert(Recreator.FullList, entry)
end
table.clear(SearchFullListCache)
SearchActive = false
end
else
-- Stash the original list only once (on the first non-empty search).
if not SearchActive then
table.clear(SearchFullListCache)
for _, entry in Recreator.FullList do
table.insert(SearchFullListCache, entry)
end
SearchActive = true
end
-- Build a filtered copy from the cache so repeated typing works correctly.
local filtered = {}
for _, entry in SearchFullListCache do
local instName = entry.Instance.Name:lower()
local className = entry.Instance.ClassName:lower()
if instName:find(_query, 1, true) or className:find(_query, 1, true) then
table.insert(filtered, entry)
end
end
table.clear(Recreator.FullList)
for _, entry in filtered do
table.insert(Recreator.FullList, entry)
end
end
-- Reset to page 0 and redraw with the (filtered or restored) list.
Offset = 0
ClearVisibleList()
RedrawVisibleList()
-- Update the status message to give feedback.
if SearchActive and _query ~= "" then
message.Text = "Search: \"" .. _query .. "\" — " .. #Recreator.FullList .. " result(s) found."
else
message.Text = DefaultMessage
end
end
search_box:GetPropertyChangedSignal("Text"):Connect(function()
-- Only run a search when a container has actually been loaded.
if #Recreator.FullList > 0 or SearchActive then
ApplySearch(search_box.Text)
end
end)No comments yet for this script.



