This commit is contained in:
lx
2026-07-06 14:01:11 -04:00
commit c4f97d729d
16468 changed files with 935321 additions and 0 deletions
@@ -0,0 +1,30 @@
-- luacheck: ignore
globals = {
-- global variables
"game", "workspace", "script", "plugin",
-- global functions
"delay", "getfenv", "setfenv", "settings", "spawn", "tick", "time",
"typeof", "unpack", "UserSettings", "wait", "warn", "version",
-- types
"Axes", "BrickColor", "CFrame", "Color3", "ColorSequence", "ColorSequenceKeypoint",
"Enum", "Faces", "Instance", "NumberRange", "NumberSequence", "NumberSequenceKeypoint",
"PhysicalProperties", "Random", "Ray", "Rect", "Region3", "Region3int16", "TweenInfo",
"UDim", "UDim2", "Vector2", "Vector3", "Vector3int16", "DockWidgetPluginGuiInfo",
-- math library
"math.clamp", "math.noise", "math.sign",
-- debug library
"debug.profilebegin", "debug.profileend",
}
-- fix methods
ignore = {"self", "super"}
-- prevent max line lengths
max_line_length = false
max_code_line_length = false
max_string_line_length = false
max_comment_line_length = false
@@ -0,0 +1,21 @@
local Hat = {}
function Hat.Validate(loadedObjects)
if #loadedObjects ~= 1 then
return false, "InvalidStructure"
end
local instance = loadedObjects[1]
if not instance:IsA("Accoutrement") then
return false, "InvalidStructure"
end
-- TODO: Implement proper asset validation here
local isValid = math.random() > 0.5
local validationResult = isValid and "Success" or "TestingInvalid"
return isValid, validationResult
end
return Hat
@@ -0,0 +1,14 @@
-- TexturePack v1.0
local baseUrl, texturePackId = ...
local RCCTexturePackGenerator = game:GetService("RCCTexturePackGenerator")
RCCTexturePackGenerator:AddProfilingCheckpoint("RCCTexturePackGeneratorScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
local result, requestedUrls = RCCTexturePackGenerator:Process(baseUrl, texturePackId)
RCCTexturePackGenerator:AddProfilingCheckpoint("RCCTexturePackGeneratorFinished")
return result, requestedUrls
@@ -0,0 +1,95 @@
local argumentsTable = ...
-- This flag enables local debugging. Debug mode will do the following:
-- Expects gameserver.txt to contain an assetId instead of assetHash + checksum
-- Provides output information for the result
local FFlagDebugUGCServerValidation = game:DefineFastFlag("DebugUGCServerValidation", false)
local ScriptContext = game:GetService("ScriptContext")
local HttpService = game:GetService("HttpService")
local CorePackages = game:GetService("CorePackages")
local ContentProvider = game:GetService("ContentProvider")
pcall(function() ContentProvider:SetBaseUrl(argumentsTable.BaseUrl) end)
ScriptContext.ScriptsDisabled = true
assert(CorePackages, "CorePackages is not loaded!")
local UGCValidation = require(CorePackages.UGCValidation)
local function parseBaseUrlInformation(baseUrl)
-- keep a copy of the base url (https://www.roblox.com/)
-- append a trailing slash if there isn't one
if baseUrl:sub(#baseUrl) ~= "/" then
baseUrl = baseUrl .. "/"
end
-- parse out scheme (http, https)
local _, schemeEnd = baseUrl:find("://")
-- parse out the prefix (www, kyle, ying, etc.)
local prefixIndex, prefixEnd = baseUrl:find("%.", schemeEnd + 1)
local basePrefix = baseUrl:sub(schemeEnd + 1, prefixIndex - 1)
-- parse out the domain (roblox.com/, sitetest1.robloxlabs.com/, etc.)
local baseDomain = baseUrl:sub(prefixEnd + 1)
return baseUrl, basePrefix, baseDomain
end
local function createAssetGameUrl(baseDomain)
return "https://assetgame." .. baseDomain
end
local _, _, BASE_DOMAIN = parseBaseUrlInformation(argumentsTable.BaseUrl)
local ASSET_GAME = createAssetGameUrl(BASE_DOMAIN)
local ASSET_TYPE_MAP = {
Hat = Enum.AssetType.Hat,
HairAccessory = Enum.AssetType.HairAccessory,
FaceAccessory = Enum.AssetType.FaceAccessory,
NeckAccessory = Enum.AssetType.NeckAccessory,
ShoulderAccessory = Enum.AssetType.ShoulderAccessory,
FrontAccessory = Enum.AssetType.FrontAccessory,
BackAccessory = Enum.AssetType.BackAccessory,
WaistAccessory = Enum.AssetType.WaistAccessory,
}
local function validateAsset(assetInfo)
local assetUrl
if FFlagDebugUGCServerValidation then
assetUrl = ASSET_GAME .. string.format("asset/?id=%s", assetInfo.assetId)
else
assetUrl = ASSET_GAME .. string.format("asset/?marAssetHash=%s&marChecksum=%s", assetInfo.assetHash, assetInfo.checksum)
end
local objects = game:GetObjects(assetUrl)
local assetTypeEnum = assert(ASSET_TYPE_MAP[assetInfo.assetType], "Invalid Asset Type")
local success, reasons = UGCValidation.validate(objects, assetTypeEnum, true)
if success then
return true, "Success"
else
return false, table.concat(reasons, "\n")
end
end
local assetsToValidate = argumentsTable.Assets
local resultData = {}
for i, assetInfo in ipairs(assetsToValidate) do
local isValid, validationResult = validateAsset(assetInfo)
resultData[i] = {
assetHash = assetInfo.assetHash,
isValid = isValid,
validationResult = validationResult
}
end
local resultJson = HttpService:JSONEncode(resultData)
if FFlagDebugUGCServerValidation then
print("DEBUG", "resultData", resultJson)
end
return resultJson
@@ -0,0 +1,377 @@
-- AnimationManifest.txt
-- V 1.0.3
-- Exports multiple frames
-- https://spocktopus.roblox.local/Internals_of_3D_Animated_Thumbnails
local baseUrl, characterAppearanceUrl, animationUrl = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
local BundleLoader = require(ThumbnailGenerator:GetThumbnailModule("BundleLoader"))
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local FRAME_RATE = 60
local originalJointCFramesMap = {}
local originalPartsCFramesMap = {}
local function getOriginalJointCFrames(object)
for _, child in pairs(object:GetChildren()) do
if child:IsA("Motor6D") then
originalJointCFramesMap[child] = child.C1
end
if child:IsA("BasePart") then
originalPartsCFramesMap[child] = child.CFrame
end
getOriginalJointCFrames(child)
end
end
local animationObjects = game:GetObjects(animationUrl)
local animations = {}
local rotateCharacter = false
local function getAnimations(model)
for _, child in pairs(model:GetChildren()) do
if child:IsA("StringValue") and child.Name == "swim" then
rotateCharacter = true
elseif child:IsA("StringValue") and child.Name == "swimidle" then
-- Don't include swimidle if we are thumnailing the swim animation
if child.Parent and child.Parent:FindFirstChild("swim") then
return
end
end
if child:IsA("Animation") then
table.insert(animations, child)
else
getAnimations(child)
end
end
end
for _, animationModel in pairs(animationObjects) do
if animationModel:IsA("Animation") then
table.insert(animations, animationModel)
else
getAnimations(animationModel)
end
end
local bundleId
if #animations == 1 then
local anim = animations[1]
local bundleIdValue = anim:FindFirstChild("ThumbnailBundleId")
if bundleIdValue and bundleIdValue:IsA("NumberValue") then
bundleId = bundleIdValue.Value
end
end
local character
if bundleId then
-- Thumbnail with the loaded bundle
character = BundleLoader.LoadBundleCharacter(baseUrl, bundleId)
ThumbnailGenerator:AddProfilingCheckpoint("BundleCharacterLoaded")
else
-- Use characterAppearanceUrl
local player = game:GetService("Players"):CreateLocalPlayer(0)
player.CharacterAppearance = characterAppearanceUrl
player:LoadCharacterBlocking()
character = player.Character
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
end
local humanoid = character:FindFirstChildOfClass("Humanoid")
getOriginalJointCFrames(character)
local animator = humanoid:FindFirstChild("Animator")
if not animator then
animator = Instance.new("Animator")
animator.Parent = humanoid
end
if rotateCharacter then
local rootPart = character:FindFirstChild("HumanoidRootPart")
if rootPart then
rootPart.CFrame = rootPart.CFrame * CFrame.Angles(math.rad(-90), 0, 0)
end
end
local animationId = ""
local bestWeight = -1
for _, anim in pairs(animations) do
local weight = anim:FindFirstChild("Weight")
if weight then
if weight.Value > bestWeight then
bestWeight = weight.Value
animationId = anim.AnimationId
end
else
animationId = anim.AnimationId
end
end
local KeyframeSequenceProvider = game:GetService("KeyframeSequenceProvider")
local keyframeSequence = KeyframeSequenceProvider:GetKeyframeSequence(animationId)
local animationObj = Instance.new("Animation")
animationObj.AnimationId = KeyframeSequenceProvider:RegisterActiveKeyframeSequence(keyframeSequence)
for _, track in pairs(humanoid:GetPlayingAnimationTracks()) do
track:Stop(0)
end
local track = humanoid:LoadAnimation(animationObj)
track:Play(0)
ThumbnailGenerator:AddProfilingCheckpoint("AnimationsLoaded")
local finished = false
local function stepNextFrame()
local delta = 1/FRAME_RATE
if track.TimePosition + delta > track.Length then
delta = track.Length - track.TimePosition
end
if delta <= 0.005 or track.TimePosition >= track.Length or finished then
return false
end
local preStepTimePosition = track.TimePosition
animator:StepAnimations(delta)
if track.TimePosition <= preStepTimePosition then
finished = true
end
return true
end
local math_floor = math.floor
local math_sin = math.sin
local math_cos = math.cos
local math_min = math.min
local math_max = math.max
local function round(number)
return math_floor(number*10000 + 0.5)/10000
end
local function vector3ToTable(vector)
return {
x = round(vector.X),
y = round(vector.Y),
z = round(vector.Z)
}
end
local animationData = {}
while stepNextFrame() do
local frameAnimationData = {}
for _, obj in pairs(character:GetChildren()) do
if obj:IsA("BasePart") or obj:IsA("Accoutrement") then
local part = obj
if obj:IsA("Accoutrement") then
part = obj:FindFirstChild("Handle")
end
if part and part.Name ~= "HumanoidRootPart" then
local posAndRotation = {}
posAndRotation["Position"] = vector3ToTable(part.Position)
local axis, angle = part.CFrame:toAxisAngle()
local halfAngle = angle/2
posAndRotation["Rotation"] = {
x = round(math_sin(halfAngle)*axis.X),
y = round(math_sin(halfAngle)*axis.Y),
z = round(math_sin(halfAngle)*axis.Z),
w = round(math_cos(halfAngle))
}
frameAnimationData[obj.Name] = posAndRotation
end
end
end
table.insert(animationData, frameAnimationData)
end
ThumbnailGenerator:AddProfilingCheckpoint("AnimationDataCollected")
-- Take note of finishing CFrames for the animation
-- The CameraResult position will be transformed based on this
local animatedPartPositionsMap = {}
for part, _ in pairs(originalPartsCFramesMap) do
animatedPartPositionsMap[part] = part.Position
end
-- Restore original CFrames
for motor, origC1 in pairs(originalJointCFramesMap) do
motor.C1 = origC1
end
for part, cframe in pairs(originalPartsCFramesMap) do
part.Anchored = true
part.CFrame = cframe
end
local partsArray = {}
for _, obj in pairs(character:GetChildren()) do
if obj:IsA("BasePart") or obj:IsA("Accoutrement") then
if obj.Name ~= "HumanoidRootPart" then
table.insert(partsArray, obj)
end
end
end
local string_sub = string.sub
local string_len = string.len
local function strEndsWith(str, val)
return string_sub(str, -string_len(val)) == val
end
local function replaceChar(pos, str, r)
return str:sub(1, pos - 1) ..r.. str:sub(pos + 1)
end
game:GetService("Selection"):Set(partsArray)
local objsStrOutput, requestedUrls = ThumbnailGenerator:Click("SplitObjs", 0, 0, true)
ThumbnailGenerator:AddProfilingCheckpoint("ObjFilesGenerated")
local decodedObjsStrOutput = game:GetService("HttpService"):JSONDecode(objsStrOutput)
local partObjsResult = {}
local textures = {}
local cameraResult = nil
local totalAABB = {
["min"] = {},
["max"] = {}
}
local function addToTotalAABB(partName, partAABB)
local part = character:FindFirstChild(partName)
if part and animatedPartPositionsMap[part] then
local currentPosition = part.Position
local animatedPosition = animatedPartPositionsMap[part]
local offset = animatedPosition - currentPosition
local minJSON = partAABB["min"]
local currentMin = Vector3.new(minJSON.x, minJSON.y, minJSON.z)
currentMin = Vector3.new(currentMin.X, currentMin.Y + offset.Y, currentMin.Z)
partAABB["min"] = vector3ToTable(currentMin)
local maxJSON = partAABB["min"]
local currentMax = Vector3.new(maxJSON.x, maxJSON.y, maxJSON.z)
currentMax = Vector3.new(currentMax.X, currentMax.Y + offset.Y, currentMax.Z)
partAABB["min"] = vector3ToTable(currentMax)
end
for xyzkey, val in pairs(partAABB["min"]) do
if not totalAABB["min"][xyzkey] then
totalAABB["min"][xyzkey] = val
else
totalAABB["min"][xyzkey] = math_min(totalAABB["min"][xyzkey], val)
end
end
for xyzkey, val in pairs(partAABB["max"]) do
if not totalAABB["max"][xyzkey] then
totalAABB["max"][xyzkey] = val
else
totalAABB["max"][xyzkey] = math_max(totalAABB["max"][xyzkey], val)
end
end
end
local function resolveCameraResult(partName, cameraJSON)
local part = character:FindFirstChild(partName)
if part and animatedPartPositionsMap[part] then
local currentPosition = part.Position
local animatedPosition = animatedPartPositionsMap[part]
local offset = animatedPosition - currentPosition
local positionJSON = cameraJSON["position"]
local cameraPosition = Vector3.new(positionJSON.x, positionJSON.y, positionJSON.z)
cameraPosition = cameraPosition + offset
cameraJSON["position"] = vector3ToTable(cameraPosition)
end
if partName == "Head" then
cameraResult = cameraJSON
elseif cameraResult == nil then -- Fallback if Head doesn't exist for some reason
cameraResult = cameraJSON
end
end
-- Process the SplitObjs output to consolidate it for the animation output.
-- The data for common textures is mapped to the same output in the textures table.
-- The Camera and AABB fields are consolidated into global fields.
for key, val in pairs(decodedObjsStrOutput) do
local decodedPartObj = game:GetService("HttpService"):JSONDecode(val)
if decodedPartObj["files"] then
local files = decodedPartObj["files"]
for fileName, fileInfo in pairs(files) do
local fileContent = fileInfo.content
if strEndsWith(fileName, ".png") then
local newFileName = fileName
while textures[newFileName] and textures[newFileName].content ~= fileContent do
local charsFromEnd = string_len("Tex.png")
local replacePos = string_len(newFileName) - charsFromEnd
local newNumber = tostring(tonumber(newFileName:sub(replacePos, replacePos)) + 1)
newFileName = replaceChar(replacePos, newFileName, newNumber)
end
textures[newFileName] = {
content = fileContent
}
files["texture"] = newFileName
files[fileName] = nil
end
end
end
-- Calculate total aabb
if decodedPartObj["AABB"] then
addToTotalAABB(key, decodedPartObj["AABB"])
decodedPartObj["AABB"] = nil
end
-- Resolve overall Camera JSON.
if decodedPartObj["camera"] then
resolveCameraResult(key, decodedPartObj["camera"])
decodedPartObj["camera"] = nil
end
partObjsResult[key] = decodedPartObj
end
-- Special camera position and direction for rotated character
if rotateCharacter then
local rootPart = character:FindFirstChild("HumanoidRootPart")
if rootPart then
local cameraOffset = Vector3.new(6, 5, 7) * .7
local cameraPosition = Vector3.new(rootPart.Position.X, rootPart.Position.Y, rootPart.Position.Z) - cameraOffset
local cameraDirection = (rootPart.Position - cameraPosition).unit
cameraResult["position"] = vector3ToTable(cameraPosition)
cameraResult["direction"] = vector3ToTable(cameraDirection)
end
end
local resultData = {
Frames = animationData,
Camera = cameraResult,
AABB = totalAABB,
PartObjs = partObjsResult,
Textures = textures
}
ThumbnailGenerator:AddProfilingCheckpoint("ResultFinalized")
return game:GetService("HttpService"):JSONEncode(resultData), requestedUrls
@@ -0,0 +1,178 @@
-- AnimationSilhouette.lua
-- Generates a Silhouette of a character doing the animation in the color requested
local assetUrl, baseUrl, x, y, silhouetteColor = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
local BundleLoader = require(ThumbnailGenerator:GetThumbnailModule("BundleLoader"))
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local emoteAnim = game:GetObjects(assetUrl)[1]
local bundleId = 401 -- Default is Alexandra Ninniflip
local bundleIdValue = emoteAnim:FindFirstChild("ThumbnailBundleId")
if bundleIdValue and bundleIdValue:IsA("NumberValue") then
bundleId = bundleIdValue.Value
end
-- Default keyframe to use in thumbnail is middle keyframe
local thumbnailKeyframeNumber
local thumbnailKeyframeValue = emoteAnim:FindFirstChild("ThumbnailKeyframe")
if thumbnailKeyframeValue and thumbnailKeyframeValue:IsA("NumberValue") then
thumbnailKeyframeNumber = thumbnailKeyframeValue.Value
end
local thumbnailZoom = 1
local thumbnailZoomValue = emoteAnim:FindFirstChild("ThumbnailZoom")
if thumbnailZoomValue and thumbnailZoomValue:IsA("NumberValue") then
thumbnailZoom = thumbnailZoomValue.Value
end
local fieldOfView = 20
local fieldOfViewValue = emoteAnim:FindFirstChild("ThumbnailFieldOfView")
if fieldOfViewValue and fieldOfViewValue:IsA("NumberValue") then
fieldOfView = fieldOfViewValue.Value
end
local verticalOffset = 0
local verticalOffsetValue = emoteAnim:FindFirstChild("ThumbnailVerticalOffset")
if verticalOffsetValue and verticalOffsetValue:IsA("NumberValue") then
verticalOffset = verticalOffsetValue.Value
end
local horizontalOffset = 0
local horizontalOffsetValue = emoteAnim:FindFirstChild("ThumbnailHorizontalOffset")
if horizontalOffsetValue and horizontalOffsetValue:IsA("NumberValue") then
horizontalOffset = horizontalOffsetValue.Value
end
local rotationDegrees = 0
local thumbnailRotationValue = emoteAnim:FindFirstChild("ThumbnailCharacterRotation")
if thumbnailRotationValue and thumbnailRotationValue:IsA("NumberValue") then
rotationDegrees = thumbnailRotationValue.Value
end
local bundleCharacter = BundleLoader.LoadBundleCharacter(baseUrl, bundleId)
ThumbnailGenerator:AddProfilingCheckpoint("BundleCharacterLoaded")
local r, g, b = unpack(silhouetteColor:split("/"))
local silhouetteColor3 = Color3.fromRGB(tonumber(r), tonumber(g), tonumber(b))
local overrideColor3Value = emoteAnim:FindFirstChild("ThumbnailSilhouetteColor")
if overrideColor3Value and overrideColor3Value:IsA("Color3Value") then
silhouetteColor3 = overrideColor3Value.Value
end
local KeyframeSequenceProvider = game:GetService("KeyframeSequenceProvider")
local kfs = KeyframeSequenceProvider:GetKeyframeSequence(emoteAnim.AnimationId)
local emoteKeyframes = kfs:GetKeyframes()
ThumbnailGenerator:AddProfilingCheckpoint("KeyframesLoaded")
local function getJointBetween(part0, part1)
for _, obj in pairs(part1:GetChildren()) do
if obj:IsA("Motor6D") and obj.Part0 == part0 then
return obj
end
end
end
local function applyPose(character, poseKeyframe)
local function recurApplyPoses(parentPose, poseObject)
if parentPose then
local joint = getJointBetween(character[parentPose.Name], character[poseObject.Name])
joint.C1 = joint.C1 * poseObject.CFrame:inverse()
end
for _, subPose in pairs(poseObject:GetSubPoses()) do
recurApplyPoses(poseObject, subPose)
end
end
for _, poseObj in pairs(poseKeyframe:GetPoses()) do
recurApplyPoses(nil, poseObj)
end
end
local thumbnailKeyframe
if thumbnailKeyframeNumber then
-- Check that the index provided as the keyframe number is valid
if thumbnailKeyframeNumber > 0 and thumbnailKeyframeNumber <= #emoteKeyframes then
thumbnailKeyframe = emoteKeyframes[thumbnailKeyframeNumber]
else
thumbnailKeyframe = emoteKeyframes[math.ceil(#emoteKeyframes/2)]
end
else
thumbnailKeyframe = emoteKeyframes[math.ceil(#emoteKeyframes/2)]
end
if rotationDegrees ~= 0 then
local rootPose = thumbnailKeyframe:GetPoses()[1]
if rootPose then
local upperTorsoPose = rootPose:GetSubPoses()[1]
if upperTorsoPose then
upperTorsoPose.CFrame = upperTorsoPose.CFrame * CFrame.Angles(0, math.rad(rotationDegrees), 0)
end
end
end
applyPose(bundleCharacter, thumbnailKeyframe)
local function getCameraOffset(fov, extentsSize)
local xSize, ySize, zSize = extentsSize.X, extentsSize.Y, extentsSize.Z
local maxSize = math.sqrt(xSize^2 + ySize^2 + zSize^2)
local fovMultiplier = 1 / math.tan(math.rad(fov) / 2)
local halfSize = maxSize / 2
return halfSize * fovMultiplier
end
local function zoomExtents(model, lookVector, thumbnailCamera)
local modelCFrame = model:GetModelCFrame()
local position = modelCFrame.p
position = position + Vector3.new(horizontalOffset, -verticalOffset, 0)
local extentsSize = model:GetExtentsSize()
local cameraOffset = getCameraOffset(thumbnailCamera.FieldOfView, extentsSize)
local zoomFactor = 1 / thumbnailZoom
cameraOffset = cameraOffset * zoomFactor
local cameraRotation = thumbnailCamera.CFrame - thumbnailCamera.CFrame.p
thumbnailCamera.CFrame = cameraRotation + position + (lookVector * cameraOffset)
end
local function createThumbnailCamera(model)
local modelCFrame = model:GetModelCFrame()
local lookVector = modelCFrame.lookVector
local humanoidRootPart = model:FindFirstChild("HumanoidRootPart")
if humanoidRootPart then
lookVector = humanoidRootPart.CFrame.lookVector
end
local thumbnailCamera = Instance.new("Camera")
thumbnailCamera.Name = "ThumbnailCamera"
thumbnailCamera.Parent = model
thumbnailCamera.FieldOfView = fieldOfView
thumbnailCamera.CFrame = CFrame.new(modelCFrame.p + (lookVector * 5), modelCFrame.p)
thumbnailCamera.Focus = modelCFrame
zoomExtents(model, lookVector, thumbnailCamera)
end
createThumbnailCamera(bundleCharacter)
local result, requestedUrls = game:GetService("ThumbnailGenerator"):ClickSilhouette(x, y, silhouetteColor3)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,31 @@
-- Avatar v1.0.2
-- This is the thumbnail script for R6 avatars. Straight up and down, with the right arm out if they have a gear.
local characterAppearanceUrl, baseUrl, fileExtension, x, y = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
local player = game:GetService("Players"):CreateLocalPlayer(0)
player.CharacterAppearance = characterAppearanceUrl
player:LoadCharacterBlocking()
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
-- Raise up the character's arm if they have gear.
if player.Character then
for _, child in pairs(player.Character:GetChildren()) do
if child:IsA("Tool") then
player.Character.Torso["Right Shoulder"].CurrentAngle = math.rad(90)
break
end
end
end
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,130 @@
-- AvatarAnimation V 1.0.3
-- Creates a thumbnail from the middle Keyframe of an animation.
-- Example arguments:
-- https://www.sitetest3.robloxlabs.com/Asset/AvatarAccoutrements.ashx?AvatarHash=80dcfa39a8e90f690d2be0e56239abbe&AssetIDs=42900214,32357663,32357631,32357619,32357584,32357558&ResolvedAvatarType=R15,
-- http://www.sitetest3.robloxlabs.com/,
-- Png,
-- 600,
-- 600,
-- http://www.sitetest3.robloxlabs.com/Asset/?hash=ca005a9e83ac95bd5068029afdc65496
local characterAppearanceUrl, baseUrl, fileExtension, x, y, animationUrl = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local player = game:GetService("Players"):CreateLocalPlayer(0)
player.CharacterAppearance = characterAppearanceUrl
player:LoadCharacterBlocking()
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
local function getJointBetween(part0, part1)
for _, obj in pairs(part1:GetChildren()) do
if obj:IsA("Motor6D") and obj.Part0 == part0 then
return obj
end
end
end
local function applyR15Pose(character, poseKeyframe)
local function recurApplyPoses(parentPose, poseObject)
if parentPose then
local joint = getJointBetween(character[parentPose.Name], character[poseObject.Name])
joint.C1 = joint.C1 * poseObject.CFrame:inverse()
end
for _, subPose in pairs(poseObject:GetSubPoses()) do
recurApplyPoses(poseObject, subPose)
end
end
for _, poseObj in pairs(poseKeyframe:GetPoses()) do
recurApplyPoses(nil, poseObj)
end
end
local animationObjects = game:GetObjects(animationUrl)
local poseAnimation = nil
local animations = {}
local rotateCharacter = false
local thumbnailCamera = nil
local function getAnimations(model)
for _, child in pairs(model:GetChildren()) do
if child:IsA("Animation") then
if string.lower(model.Name) == "pose" then
poseAnimation = child
else
table.insert(animations, child)
end
else
getAnimations(child)
end
if child:IsA("Camera") and child.Name == "ThumbnailCamera" then
thumbnailCamera = child:Clone()
end
if child:IsA("StringValue") and child.Name == "swim" then
rotateCharacter = true
end
end
end
for _, animationModel in pairs(animationObjects) do
getAnimations(animationModel)
end
local KeyframeSequenceProvider = game:GetService("KeyframeSequenceProvider")
local keyframes = {}
if poseAnimation then
local kfs = KeyframeSequenceProvider:GetKeyframeSequence(poseAnimation.AnimationId)
local animKeyframes = kfs:GetKeyframes()
for _, keyframe in pairs(animKeyframes) do
table.insert(keyframes, keyframe)
end
else
for _, animation in pairs(animations) do
local kfs = KeyframeSequenceProvider:GetKeyframeSequence(animation.AnimationId)
local animKeyframes = kfs:GetKeyframes()
for _, keyframe in pairs(animKeyframes) do
table.insert(keyframes, keyframe)
end
end
end
ThumbnailGenerator:AddProfilingCheckpoint("AnimationsLoaded")
local keyframe = keyframes[math.max(1, math.floor(#keyframes/2))]
applyR15Pose(player.Character, keyframe)
if rotateCharacter then
local rootPart = player.Character:FindFirstChild("HumanoidRootPart")
if rootPart then
rootPart.CFrame = rootPart.CFrame * CFrame.Angles(math.rad(-90), 0, 0)
if not thumbnailCamera then
local camera = Instance.new("Camera")
camera.Name = "ThumbnailCamera"
local rotatedCameraOffset = Vector3.new(6, 5, 7) * .7
camera.CFrame = CFrame.new(Vector3.new(rootPart.Position.X, rootPart.Position.Y, rootPart.Position.Z) - rotatedCameraOffset, rootPart.Position)
camera.Parent = player.Character
end
end
end
if thumbnailCamera then
thumbnailCamera.Parent = player.Character
end
local result, requestedUrls = game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,172 @@
-- Avatar_R15_Action v1.1.1
-- For R6, this generates the normal with/without gear pose. For R15 it positions their body in an action pose.
local baseUrl, characterAppearanceUrl, fileExtension, x, y = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
local player = game:GetService("Players"):CreateLocalPlayer(0)
player.CharacterAppearance = characterAppearanceUrl
player:LoadCharacterBlocking()
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
local poseAnimationId = "http://www.roblox.com/asset/?id=532421348"
local function getJointBetween(part0, part1)
for _, obj in pairs(part1:GetChildren()) do
if obj:IsA("Motor6D") and obj.Part0 == part0 then
return obj
end
end
end
local function applyKeyframe(character, poseKeyframe)
local function recurApplyPoses(parentPose, poseObject)
if parentPose then
local joint = getJointBetween(character[parentPose.Name], character[poseObject.Name])
if joint and poseObject.Weight ~= 0 then
joint.C1 = poseObject.CFrame:inverse() + joint.C1.p
end
end
for _, subPose in pairs(poseObject:GetSubPoses()) do
recurApplyPoses(poseObject, subPose)
end
end
for _, poseObj in pairs(poseKeyframe:GetPoses()) do
recurApplyPoses(nil, poseObj)
end
end
local function applyR15Pose(character)
local poseKeyframSequence = game:GetService("KeyframeSequenceProvider"):GetKeyframeSequence(poseAnimationId)
local poseKeyframe = poseKeyframSequence:GetKeyframes()[1]
applyKeyframe(character, poseKeyframe)
end
local function findAttachmentsRecur(parent, resultTable, returnDictionary)
for _, obj in pairs(parent:GetChildren()) do
if obj:IsA("Attachment") then
if returnDictionary then
resultTable[obj.Name] = obj
else
resultTable[#resultTable + 1] = obj
end
elseif not obj:IsA("Tool") and not obj:IsA("Accoutrement") then -- Leave out tools and accoutrements in the character
findAttachmentsRecur(obj, resultTable, returnDictionary)
end
end
end
local function findAttachmentsInTool(tool)
local attachments = {}
findAttachmentsRecur(tool, attachments, false)
return attachments
end
local function findAttachmentsInCharacter(character)
local attachments = {}
findAttachmentsRecur(character, attachments, true)
return attachments
end
local function weldAttachments(attach1, attach2)
local weld = Instance.new("Weld")
weld.Part0 = attach1.Parent
weld.Part1 = attach2.Parent
weld.C0 = attach1.CFrame
weld.C1 = attach2.CFrame
weld.Parent = attach1.Parent
return weld
end
local function findFirstMatchingAttachment(model, name)
for _, child in pairs(model:GetChildren()) do
if child:IsA("Attachment") and child.Name == name then
return child
elseif not child:IsA("Accoutrement") and not child:IsA("Tool") then
local foundAttachment = findFirstMatchingAttachment(child, name)
if foundAttachment then
return foundAttachment
end
end
end
end
local function doR15ToolPose(character, humanoid, tool)
local characterAttachments = findAttachmentsInCharacter(character)
local toolAttachments = findAttachmentsInTool(tool)
local foundAttachments = false
-- If matching attachments exist in the gear then weld them and do the "action" R15 pose.
-- Otherwise keep the R15 in the T-Pose position and just raise the arm.
for _, attachment in pairs(toolAttachments) do
local matchingAttachment = characterAttachments[attachment.Name]
if matchingAttachment then
foundAttachments = true
weldAttachments(matchingAttachment, attachment)
end
end
if foundAttachments then
tool.Parent = character
applyR15Pose(character)
local toolPose = tool:FindFirstChild("ThumbnailPose")
if toolPose and toolPose:IsA("Keyframe") then
applyKeyframe(character, toolPose)
end
else
tool.Parent = nil
local rightShoulderJoint = getJointBetween(character.UpperTorso, character.RightUpperArm)
if rightShoulderJoint then
rightShoulderJoint.C1 = rightShoulderJoint.C1 * CFrame.new(0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 1, 0):inverse()
end
if tool:FindFirstChild("Handle") then
local attachment = findFirstMatchingAttachment(character, "RightGripAttachment")
if attachment then
tool.Handle.CFrame = attachment.Parent.CFrame * attachment.CFrame * tool.Grip:inverse()
end
end
humanoid:EquipTool(tool)
end
end
local character = player.Character
if character then
local tool = character:FindFirstChildOfClass("Tool")
local humanoid = character:FindFirstChildOfClass("Humanoid")
local animateScript = character:FindFirstChild("Animate")
if animateScript then
local equippedPoseValue = animateScript:FindFirstChild("Pose") or animateScript:FindFirstChild("pose")
if equippedPoseValue then
local poseAnim = equippedPoseValue:FindFirstChildOfClass("Animation")
if poseAnim then
poseAnimationId = poseAnim.AnimationId
end
end
end
if humanoid then
if humanoid.RigType == Enum.HumanoidRigType.R6 then
if tool then
character.Torso["Right Shoulder"].CurrentAngle = math.rad(90)
end
elseif humanoid.RigType == Enum.HumanoidRigType.R15 then
if tool then
doR15ToolPose(character, humanoid, tool)
else
applyR15Pose(character)
end
end
end
end
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,56 @@
-- Avatar_R15_Standard v1.0.2
-- Pose R6 characters in the normal way. For R15, have them in the same pose, and raise their arm up if they have gear.
-- Sample params:
-- baseUrl: "http://www.roblox.com/"
-- characterAppearanceUrl: "http://www.roblox.com/Asset/AvatarAccoutrements.ashx?AvatarHash=98925edb8aa60e39ba8a4f0bf8b71d6f&AssetIDs=3372792,9255011,20418682,68258723,158066137,232503325,244097060,248286896,264611665,376530220,376531012,376531300,376531703,376532000,624157131&ResolvedAvatarType=R15&Height=1&Width=0.75&Head=0.95&Depth=0.88"
-- fileExtension: "Png"
-- x: 1260
-- y: 1260
local baseUrl, characterAppearanceUrl, fileExtension, x, y = ...
local ThumbnailGenerator = game:GetService('ThumbnailGenerator')
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
local player = game:GetService("Players"):CreateLocalPlayer(0)
player.CharacterAppearance = characterAppearanceUrl
player:LoadCharacterBlocking()
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
local function getJointBetween(part0, part1)
for _, obj in pairs(part1:GetChildren()) do
if obj:IsA("Motor6D") and obj.Part0 == part0 then
return obj
end
end
end
local function doR15ToolPose(rig)
local rightShoulderJoint = getJointBetween(rig.UpperTorso, rig.RightUpperArm)
if rightShoulderJoint then
rightShoulderJoint.C1 = rightShoulderJoint.C1 * CFrame.new(0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 1, 0):inverse()
end
end
-- Raise right arm up to hold gear.
local character = player.Character
if character then
if character:FindFirstChildOfClass("Tool") then
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
if humanoid.RigType == Enum.HumanoidRigType.R6 then
character.Torso['Right Shoulder'].CurrentAngle = math.rad(90)
elseif humanoid.RigType == Enum.HumanoidRigType.R15 then
doR15ToolPose(character)
end
end
end
end
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,273 @@
-- BodyPart v1.0.6
-- See http://wiki.roblox.com/index.php?title=R15_Compatibility_Guide#Package_Parts for details on how body parts work with R15
local assetUrl, baseUrl, fileExtension, x, y, R6RigUrl, customUrl = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
local DFFlagBodyPartFocusInThreeDThumbnails = settings():GetFFlag("BodyPartFocusInThreeDThumbnails")
local CreateExtentsMinMax
local MannequinUtility
local ScaleUtility
if DFFlagBodyPartFocusInThreeDThumbnails then
CreateExtentsMinMax = require(ThumbnailGenerator:GetThumbnailModule("CreateExtentsMinMax"))
MannequinUtility = require(ThumbnailGenerator:GetThumbnailModule("MannequinUtility"))
ScaleUtility = require(ThumbnailGenerator:GetThumbnailModule("ScaleUtility"))
end
pcall(function() game:GetService('ContentProvider'):SetBaseUrl(baseUrl) end)
game:GetService('ScriptContext').ScriptsDisabled = true
local objects = game:GetObjects(assetUrl)
ThumbnailGenerator:AddProfilingCheckpoint("BodyPartLoaded")
local useR15 = false
local useR15NewNames = false
local bodyPartProportion = "Classic"
local floatMax = math.huge
if DFFlagBodyPartFocusInThreeDThumbnails then
for _, object in pairs(objects) do
if object:IsA("Folder") then
if object.Name == "R15" then
useR15 = true
elseif object.Name == "R15ArtistIntent" then
useR15 = true
useR15NewNames = true
end
end
end
bodyPartProportion = ScaleUtility.GetObjectsScaleType(objects)
else
for _, object in pairs(objects) do
if object:IsA("Folder") and (object.Name == "R15" or object.Name == "R15ArtistIntent") then
useR15 = true
-- Check to see if there are native scale parts in this object
local partScaleType = object:FindFirstChild("AvatarPartScaleType", true)
if partScaleType then
bodyPartProportion = partScaleType.Value
end
break
end
end
end
local mannequin
if DFFlagBodyPartFocusInThreeDThumbnails then
if useR15 then
mannequin = MannequinUtility.LoadMannequinForScaleType(bodyPartProportion)
else
mannequin = MannequinUtility.LoadR6Mannequin()
end
else
local R15RigUrl = "http://www.roblox.com/asset/?id=516159357"
for _, object in pairs(objects) do
if object:IsA("Folder") and object.Name == "R15ArtistIntent" then
useR15NewNames = true
if bodyPartProportion == "Classic" then
R15RigUrl = "http://www.roblox.com/asset/?id=1664543044"
else
R15RigUrl = "http://www.roblox.com/asset/?id=2337256345"
end
break
end
end
if useR15 then
mannequin = game:GetObjects(R15RigUrl)[1]
else
mannequin = game:GetObjects(R6RigUrl)[1]
end
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
mannequin.Parent = workspace
end
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
game:GetObjects(customUrl)[1].Parent = mannequin
ThumbnailGenerator:AddProfilingCheckpoint("CustomUrlLoaded")
local function addFolderChildren(folder, focusPartNamesOut, focusPartsOut)
for _, child in pairs(folder:GetChildren()) do
local existingBodyPart = mannequin:FindFirstChild(child.Name)
if existingBodyPart then
existingBodyPart:Destroy()
end
child.Parent = mannequin
table.insert(focusPartNamesOut, child.name)
table.insert(focusPartsOut, child)
end
end
local r15FolderName = "R15"
if (useR15 and useR15NewNames) then
r15FolderName = "R15ArtistIntent"
end
local focusParts = {}
local focusPartNames = {}
for _, object in pairs(objects) do
if useR15 and object:IsA("Folder") and object.Name == r15FolderName then
addFolderChildren(object, focusPartNames, focusParts)
elseif not useR15 and object:IsA("Folder") and object.Name == "R6" then
addFolderChildren(object, focusPartNames, focusParts)
elseif not (object:IsA("Folder") and string.find(object.Name, "R15")) then -- There will now be MULTIPLE R15 Folders. Ignore the ones we didn't search for.
object.Parent = mannequin
end
end
local function buildJoint(parentAttachment, partForJointAttachment)
local jointName = parentAttachment.Name:gsub("RigAttachment", "")
local motor = partForJointAttachment.Parent:FindFirstChild(jointName)
if not motor then
motor = Instance.new("Motor6D")
end
motor.Name = jointName
motor.Part0 = parentAttachment.Parent
motor.Part1 = partForJointAttachment.Parent
motor.C0 = parentAttachment.CFrame
motor.C1 = partForJointAttachment.CFrame
motor.Parent = partForJointAttachment.Parent
end
-- Builds an R15 rig from the attachments in the parts
local function buildRigFromAttachments(currentPart, lastPart)
local validSiblings = {}
for _, sibling in pairs(currentPart.Parent:GetChildren()) do
-- Don't find matching attachment in the current part being processed.
-- Don't visit the last part visited again, this would cause an infinite loop.
if sibling:IsA("BasePart") and sibling ~= currentPart and sibling ~= lastPart then
table.insert(validSiblings, sibling)
end
end
local function processRigAttachment(attachment)
for _, sibling in pairs(validSiblings) do
local matchingAttachment = sibling:FindFirstChild(attachment.Name)
if matchingAttachment then
buildJoint(attachment, matchingAttachment)
buildRigFromAttachments(matchingAttachment.Parent, currentPart)
end
end
end
for _, object in pairs(currentPart:GetChildren()) do
if object:IsA("Attachment") and string.find(object.Name, "RigAttachment") then
processRigAttachment(object)
end
end
end
if useR15 then
if DFFlagBodyPartFocusInThreeDThumbnails then
local humanoid = mannequin:FindFirstChild("Humanoid")
if humanoid then
ScaleUtility.CreateProportionScaleValues(humanoid, bodyPartProportion)
humanoid:BuildRigFromAttachments()
end
else
-- Build R15 rig
local humanoidRootPart = mannequin:WaitForChild("HumanoidRootPart")
humanoidRootPart.CFrame = CFrame.new(Vector3.new(0, 5, 0)) * CFrame.Angles(0, math.pi, 0)
humanoidRootPart.Anchored = true
buildRigFromAttachments(humanoidRootPart)
local humanoid = mannequin:WaitForChild("Humanoid")
if humanoid then
local typeObject = humanoid:FindFirstChild("BodyTypeScale")
if typeObject == nil then
typeObject = Instance.new("NumberValue")
typeObject.Name = "BodyTypeScale"
typeObject.Value = 0
typeObject.Parent = humanoid
end
local proportionObject = humanoid:FindFirstChild("BodyProportionScale")
if proportionObject == nil then
proportionObject = Instance.new("NumberValue")
proportionObject.Name = "BodyProportionScale"
proportionObject.Value = 0
proportionObject.Parent = humanoid
end
if bodyPartProportion == "ProportionsNormal" then
typeObject.Value = 1
proportionObject.Value = 0
elseif bodyPartProportion == "ProportionsSlender" then
typeObject.Value = 1
proportionObject.Value = 1
end
end
end
end
local function addToBounds(cornerPosition, focusExtentsOut)
focusExtentsOut["minx"] = math.min(focusExtentsOut["minx"], cornerPosition.x)
focusExtentsOut["miny"] = math.min(focusExtentsOut["miny"], cornerPosition.y)
focusExtentsOut["minz"] = math.min(focusExtentsOut["minz"], cornerPosition.z)
focusExtentsOut["maxx"] = math.max(focusExtentsOut["maxx"], cornerPosition.x)
focusExtentsOut["maxy"] = math.max(focusExtentsOut["maxy"], cornerPosition.y)
focusExtentsOut["maxz"] = math.max(focusExtentsOut["maxz"], cornerPosition.z)
end
local function addCornerToBounds(partCFrame, cornerSelect, halfPartSize, focusExtentsOut)
local cornerPositionLocal = cornerSelect * halfPartSize
local cornerPositionWorld = partCFrame * cornerPositionLocal
addToBounds(cornerPositionWorld, focusExtentsOut)
end
local extentsMinMax
local shouldCrop = false
if DFFlagBodyPartFocusInThreeDThumbnails then
extentsMinMax = CreateExtentsMinMax(focusParts)
shouldCrop = #focusParts > 0
else
local focusOnExtents = { minx = floatMax, miny = floatMax, minz = floatMax, maxx = -floatMax, maxy = -floatMax, maxz = -floatMax }
local FFlagThumbnailSupportFocusOnPart = settings():GetFFlag("ThumbnailSupportFocusOnPart")
if FFlagThumbnailSupportFocusOnPart and string.lower(fileExtension) == "png" then
-- expand focusOnExtents to bound all the part(s) in the focusPartNames table
if #focusPartNames > 0 then
for _, focusPartName in pairs(focusPartNames) do
local focusPart = mannequin:FindFirstChild(focusPartName, --[[recursive = ]] true)
if focusPart then
local partPosition = focusPart.Position
local partRotation = focusPart.Rotation
local halfPartSize = focusPart.Size / 2.0
local partCFrame = CFrame.Angles(math.rad(partRotation.x), math.rad(partRotation.y), math.rad(partRotation.z)) + partPosition
addCornerToBounds(partCFrame, Vector3.new( 1, 1, 1), halfPartSize, focusOnExtents)
addCornerToBounds(partCFrame, Vector3.new( 1, 1,-1), halfPartSize, focusOnExtents)
addCornerToBounds(partCFrame, Vector3.new( 1,-1, 1), halfPartSize, focusOnExtents)
addCornerToBounds(partCFrame, Vector3.new( 1,-1,-1), halfPartSize, focusOnExtents)
addCornerToBounds(partCFrame, Vector3.new(-1, 1, 1), halfPartSize, focusOnExtents)
addCornerToBounds(partCFrame, Vector3.new(-1, 1,-1), halfPartSize, focusOnExtents)
addCornerToBounds(partCFrame, Vector3.new(-1,-1, 1), halfPartSize, focusOnExtents)
addCornerToBounds(partCFrame, Vector3.new(-1,-1,-1), halfPartSize, focusOnExtents)
shouldCrop = true
end
end
end
end
extentsMinMax = {
Vector3.new(focusOnExtents["minx"], focusOnExtents["miny"], focusOnExtents["minz"]),
Vector3.new(focusOnExtents["maxx"], focusOnExtents["maxy"], focusOnExtents["maxz"])
}
end
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop = ]] shouldCrop, extentsMinMax)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,75 @@
-- Closeup v1.0.3
-- Used for avatar closeup
local baseUrl, characterAppearanceUrl, fileExtension, x, y, quadratic, baseHatZoom, maxHatZoom, cameraOffsetX, cameraOffsetY = ...
local FFlagOnlyCheckHeadAccessoryInHeadShot = game:DefineFastFlag("OnlyCheckHeadAccessoryInHeadShot", false)
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService('ContentProvider'):SetBaseUrl(baseUrl) end)
game:GetService('ScriptContext').ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local player = game:GetService("Players"):CreateLocalPlayer(0)
player.CharacterAppearance = characterAppearanceUrl
player:LoadCharacterBlocking()
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
local headAttachments = {}
if FFlagOnlyCheckHeadAccessoryInHeadShot then
if player.Character:FindFirstChild("Head") then
for _,child in pairs(player.Character.Head:GetChildren()) do
if child:IsA("Attachment") then
headAttachments[child.Name] = true
end
end
end
end
local maxDimension = 0
if player.Character then
-- Remove gear
for _, child in pairs(player.Character:GetChildren()) do
if child:IsA("Tool") then
child:Destroy()
elseif child:IsA("Accoutrement") then
local handle = child:FindFirstChild("Handle")
if handle then
local attachment = handle:FindFirstChildWhichIsA("Attachment")
--legacy hat does not have attachment in it and should be considered when zoom out camera
if not FFlagOnlyCheckHeadAccessoryInHeadShot or not attachment or headAttachments[attachment.Name] then
local size = handle.Size / 2 + handle.Position - player.Character.Head.Position
local xy = Vector2.new(size.x, size.y)
if xy.magnitude > maxDimension then
maxDimension = xy.magnitude
end
end
end
end
end
-- Setup Camera
local maxHatOffset = 0.5 -- Maximum amount to move camera upward to accomodate large hats
maxDimension = math.min(1, maxDimension / 3) -- Confine maxdimension to specific bounds
if quadratic then
maxDimension = maxDimension * maxDimension -- Zoom out on quadratic interpolation
end
local viewOffset = player.Character.Head.CFrame * CFrame.new(cameraOffsetX, cameraOffsetY + maxHatOffset * maxDimension, 0.1) -- View vector offset from head
local positionOffset = player.Character.Head.CFrame + (CFrame.Angles(0, -math.pi / 16, 0).lookVector.unit * 3) -- Position vector offset from head
local camera = Instance.new("Camera", player.Character)
camera.Name = "ThumbnailCamera"
camera.CameraType = Enum.CameraType.Scriptable
camera.CoordinateFrame = CFrame.new(positionOffset.p, viewOffset.p)
camera.FieldOfView = baseHatZoom + (maxHatZoom - baseHatZoom) * maxDimension
end
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,28 @@
-- Decal v1.0.2
-- Used for faces and decals
local assetUrl, fileExtension, x, y, baseUrl = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService('ScriptContext').ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local decal = game:GetObjects(assetUrl)[1]
ThumbnailGenerator:AddProfilingCheckpoint("DecalLoaded")
local image, requestedUrls
local success = pcall(function()
image, requestedUrls = ThumbnailGenerator:ClickTexture(decal.Texture, fileExtension, x, y)
end)
ThumbnailGenerator:AddProfilingCheckpoint("TextureGenerated")
if success then
return image, requestedUrls
end
-- if we fail return the hourglass, since we're probably in moderation.
return "/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAX8P/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABBQJ//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwF//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPwF//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAGPwJ//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPyF//9oADAMBAAIAAwAAABAf/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPxB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPxB//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPxB//9k=", {decal.Texture}
@@ -0,0 +1,21 @@
-- Gear v1.0.3
local assetUrl, fileExtension, x, y, baseUrl = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
for _, object in pairs(game:GetObjects(assetUrl)) do
object.Parent = workspace
end
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop =]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,73 @@
-- Hat v1.1.0
local assetUrl, fileExtension, x, y, baseUrl = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
local DFFlagHatThumbnailMannequins = settings():GetFFlag("HatThumbnailMannequins")
-- Modules
local CreateExtentsMinMax
local MannequinUtility
local ScaleUtility
if DFFlagHatThumbnailMannequins then
CreateExtentsMinMax = require(ThumbnailGenerator:GetThumbnailModule("CreateExtentsMinMax"))
MannequinUtility = require(ThumbnailGenerator:GetThumbnailModule("MannequinUtility"))
ScaleUtility = require(ThumbnailGenerator:GetThumbnailModule("ScaleUtility"))
end
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local accoutrement = game:GetObjects(assetUrl)[1]
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
local handle
if DFFlagHatThumbnailMannequins then
local accoutrementScaleType = ScaleUtility.GetScaleTypeForAccessory(accoutrement)
local mannequin = MannequinUtility.LoadMannequinForScaleType(accoutrementScaleType)
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
-- Rotate mannequin for back accessories
handle = accoutrement:FindFirstChild("Handle")
if handle and handle:FindFirstChild("BodyBackAttachment") then
MannequinUtility.RotateMannequin(mannequin, CFrame.Angles(0, math.pi, 0))
end
-- Scale mannequin based on accoutrement scale type
local humanoid = mannequin:FindFirstChild("Humanoid")
if humanoid then
ScaleUtility.CreateProportionScaleValues(humanoid, accoutrementScaleType)
humanoid:BuildRigFromAttachments()
end
accoutrement.Parent = mannequin
else
accoutrement.Parent = workspace
end
local focusParts = {}
local extentsMinMax
if DFFlagHatThumbnailMannequins then
if handle then
focusParts[#focusParts + 1] = handle
local connectedParts = handle:GetConnectedParts()
for _, part in pairs(connectedParts) do
focusParts[#focusParts + 1] = part
end
end
extentsMinMax = CreateExtentsMinMax(focusParts)
end
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop =]] true, extentsMinMax)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,143 @@
-- Head v1.2.0
local assetUrl, fileExtension, x, y, baseUrl, mannequinId = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
local DFFlagHeadThumbnailMannequins = settings():GetFFlag("HeadThumbnailMannequins")
-- Modules
local CreateExtentsMinMax
local MannequinUtility
local ScaleUtility
if DFFlagHeadThumbnailMannequins then
CreateExtentsMinMax = require(ThumbnailGenerator:GetThumbnailModule("CreateExtentsMinMax"))
MannequinUtility = require(ThumbnailGenerator:GetThumbnailModule("MannequinUtility"))
ScaleUtility = require(ThumbnailGenerator:GetThumbnailModule("ScaleUtility"))
end
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local objects = game:GetObjects(assetUrl)
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
local headScaleType
local mannequin
if DFFlagHeadThumbnailMannequins then
headScaleType = ScaleUtility.GetObjectsScaleType(objects)
mannequin = MannequinUtility.LoadMannequinForScaleType(headScaleType)
else
mannequin = game:GetObjects(baseUrl.. "/asset/?id=" .. tostring(mannequinId))[1]
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
mannequin.Parent = workspace
end
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
local function addFaceDecal(head)
if head:FindFirstChild("face") then
return
end
local face = Instance.new("Decal")
face.Name = "face"
face.Texture = "rbxasset://textures/face.png"
face.Parent = head
end
local function replaceMannequinMeshPartHead(meshPartHead, meshHead)
local newHead = Instance.new("Part")
newHead.Size = meshPartHead.Size
newHead.CFrame = meshPartHead.CFrame
newHead.Color = meshPartHead.Color
newHead.Name = "Head"
addFaceDecal(newHead)
local copiedAttachments = false
for _, child in pairs(meshHead:GetChildren()) do
if child:IsA("Vector3Value") and string.find(child.Name, "Attachment") then
copiedAttachments = true
local newAttachment = Instance.new("Attachment")
newAttachment.Name = child.Name
newAttachment.Position = child.Value
newAttachment.Parent = newHead
end
end
if not copiedAttachments then
for _, child in pairs(meshPartHead:GetChildren()) do
child.Parent = newHead
end
end
meshPartHead:Destroy()
newHead.Parent = mannequin
end
local function replaceMannequinHeadWithMeshHead()
for _, obj in pairs(objects) do
if obj:IsA("Folder") and obj.Name == "R15ArtistIntent" then
local head = obj.Head
addFaceDecal(head)
mannequin.Head:Destroy()
head.Parent = mannequin
end
end
end
local headObject = objects[1]
if headObject:IsA("Folder") then
replaceMannequinHeadWithMeshHead()
else
if DFFlagHeadThumbnailMannequins then
if mannequin.Head:IsA("MeshPart") then
replaceMannequinMeshPartHead(mannequin.Head, headObject)
end
else
mannequin.Head.BrickColor = BrickColor.Gray()
end
if mannequin.Head:FindFirstChild("Mesh") then
mannequin.Head.Mesh:Destroy()
end
headObject.Parent = mannequin.Head
end
if DFFlagHeadThumbnailMannequins then
-- Scale mannequin based on the scale type of the Head
local humanoid = mannequin:FindFirstChild("Humanoid")
if humanoid then
ScaleUtility.CreateProportionScaleValues(humanoid, headScaleType)
humanoid:BuildRigFromAttachments()
end
else
for _, child in pairs(mannequin:GetChildren()) do
if child:IsA("BasePart") and child.Name ~= "Head" then
child:Destroy()
end
end
end
local shouldCrop = false
local extentsMinMax
if DFFlagHeadThumbnailMannequins then
local focusParts = {
mannequin:FindFirstChild("Head")
}
shouldCrop = #focusParts > 0
extentsMinMax = CreateExtentsMinMax(focusParts)
end
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, shouldCrop, extentsMinMax)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,37 @@
imageId, baseUrl, fileExtension, x, y = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
local ContentProvider = game:GetService("ContentProvider")
game:GetService('StarterGui'):SetCoreGuiEnabled(Enum.CoreGuiType.All, false);
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local IsSuccess, actualImageId = pcall(function()
local Asset = game:GetObjects(baseUrl.."/asset/?id="..tostring(imageId))[1]
if Asset:IsA("Decal") then
return Asset.Texture
end
for _, child in pairs(Asset:GetDescendants()) do
if child:IsA("Decal") then
return child.Texture
end
end
end)
local AssetURL
if not IsSuccess then
actualImageId = imageId
AssetURL = baseUrl.."/asset/?id="..tostring(imageId)
else
AssetURL = actualImageId
end
local success, result = pcall(function()
return ThumbnailGenerator:ClickTexture(AssetURL, fileExtension, x, y)
end)
if not success then
result = "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAQAAABecRxxAAAEnklEQVR42u3UMQEAAAjDMOZf9JCAABIJPZp2gKdiAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYABiAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAAIoABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYABiAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAABgAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAYABiAAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAGAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABwW1Dy/i5f+lAuAAAAAElFTkSuQmCC" -- 1x1 transparent pixel
end
return result
@@ -0,0 +1,22 @@
-- Mesh v1.0.2
local assetUrl, fileExtension, x, y, baseUrl = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local part = Instance.new("Part")
part.Parent = workspace
local specialMesh = Instance.new("SpecialMesh")
specialMesh.MeshId = assetUrl
specialMesh.Parent = part
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop = ]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,47 @@
-- MeshPart v1.0.2
local assetUrl, fileExtension, x, y, baseUrl = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
game:DefineFastFlag("OnlyAllowMeshParts", false)
for _, object in pairs(game:GetObjects(assetUrl)) do
if game:GetFastFlag("OnlyAllowMeshParts") then
if object:IsA("MeshPart") and #object:GetChildren() == 0 then
pcall(function() object.Parent = workspace end)
break
end
else
if object:IsA("Sky") then
local resultValues = nil
local success = pcall(function() resultValues = {ThumbnailGenerator:ClickTexture(object.SkyboxFt, fileExtension, x, y)} end)
if success then
return unpack(resultValues)
else
object.Parent = game:GetService("Lighting")
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] false)
end
elseif object:IsA("LuaSourceContainer") then
return ThumbnailGenerator:ClickTexture(baseUrl.. "Thumbs/Script.png", fileExtension, x, y)
elseif object:IsA("SpecialMesh") then
local part = Instance.new("Part")
part.Parent = workspace
object.Parent = part
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
else
pcall(function() object.Parent = workspace end)
end
end
end
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,39 @@
-- Model v1.0.2
local assetUrl, fileExtension, x, y, baseUrl = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
for _, object in pairs(game:GetObjects(assetUrl)) do
if object:IsA("Sky") then
local resultValues = nil
local success = pcall(function() resultValues = {ThumbnailGenerator:ClickTexture(object.SkyboxFt, fileExtension, x, y)} end)
if success then
return unpack(resultValues)
else
object.Parent = game:GetService("Lighting")
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] false)
end
elseif object:IsA("LuaSourceContainer") then
return ThumbnailGenerator:ClickTexture(baseUrl.. "/static/img/LuaThumbnail.png", fileExtension, x, y)
elseif object:IsA("SpecialMesh") then
local part = Instance.new("Part")
part.Parent = workspace
object.Parent = part
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
else
pcall(function() object.Parent = workspace end)
end
end
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,361 @@
-- Package v1.1.7
-- See http://wiki.roblox.com/index.php?title=R15_Compatibility_Guide#Package_Parts for details on how body parts work with R15
local assetUrls, baseUrl, fileExtension, x, y, R6RigUrl, customTextureUrls = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local function split(str, delim)
local results = {}
local lastMatchEnd = 0
local matchStart, matchEnd = string.find(str, delim, --[[init = ]] 1, --[[plain = ]] true)
while matchStart and matchEnd do
if matchStart - lastMatchEnd > 1 then
table.insert(results, string.sub(str, lastMatchEnd + 1, matchStart - 1))
end
lastMatchEnd = matchEnd
matchStart, matchEnd = string.find(str, delim, --[[init = ]] lastMatchEnd + 1, --[[plain = ]] true)
end
if string.len(str) - lastMatchEnd > 1 then
table.insert(results, string.sub(str, lastMatchEnd + 1))
end
return results
end
local R15ArtistIntentAssets = {}
local R15Assets = {}
local R6Assets = {}
local bothAssets = {}
local useR15ArtistIntent = false
local useR15 = true
local poseAnimationId = nil
local poseValueFound = false
local function processR15Anim(animFolder)
local function processStrValue(strValue)
local animation = strValue:FindFirstChildOfClass("Animation")
if not animation then
return
end
-- By default the pose animation will be used for the thumbnail
-- If the pose animation doesn't exist then the idle will be used, otherwise the first animation will be used.
if string.lower(strValue.Name) == "pose" then
poseValueFound = true
poseAnimationId = animation.AnimationId
elseif not poseValueFound and string.lower(strValue.Name) == "idle" then
poseAnimationId = animation.AnimationId
elseif not poseAnimationId then
poseAnimationId = animation.AnimationId
end
end
for _, obj in pairs(animFolder:GetChildren()) do
if obj:IsA("StringValue") then
processStrValue(obj)
end
end
end
local assetUrlsList = split(assetUrls, ";")
for _, assetUrl in pairs(assetUrlsList) do
local currObjects = game:GetObjects(assetUrl)
for _, object in pairs(currObjects) do
if object:IsA("Folder") and object.Name == "R15ArtistIntent" then
for _, child in pairs(object:GetChildren()) do
table.insert(R15ArtistIntentAssets, child)
end
elseif object:IsA("Folder") and object.Name == "R15Fixed" then -- luacheck: ignore
-- Do nothing. We just don't want this to be dumped in bothAssets
elseif object:IsA("Folder") and object.Name == "R15" then
for _, child in pairs(object:GetChildren()) do
table.insert(R15Assets, child)
end
elseif object:IsA("Folder") and object.Name == "R6" then
for _, child in pairs(object:GetChildren()) do
table.insert(R6Assets, child)
end
elseif object:IsA("CharacterMesh") then
-- Legacy body part format using a CharacterMesh
table.insert(R6Assets, object)
elseif object:IsA("Folder") and object.Name == "R15Anim" then
processR15Anim(object)
else
table.insert(bothAssets, object)
end
end
end
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
-- if the package doesn't contain animations, use this pose.
poseAnimationId = poseAnimationId or "http://www.roblox.com/asset/?id=532421348"
-- Only use R6 if we found body parts that are only compatible with R15
if #R6Assets ~= 0 and #R15Assets == 0 and #R15ArtistIntentAssets == 0 then
useR15 = false
end
local R15RigUrl = "http://www.roblox.com/asset/?id=516159357"
if useR15 and #R15ArtistIntentAssets > 0 then
useR15ArtistIntent = true
R15RigUrl = "http://www.roblox.com/asset/?id=1664543044"
end
local mannequin
if useR15 then
mannequin = game:GetObjects(R15RigUrl)[1]
if (useR15ArtistIntent) then
for _, obj in pairs(R15ArtistIntentAssets) do
table.insert(bothAssets, obj)
end
else
for _,obj in pairs(R15Assets) do
table.insert(bothAssets, obj)
end
end
else
mannequin = game:GetObjects(R6RigUrl)[1]
for _,obj in pairs(R6Assets) do
table.insert(bothAssets, obj)
end
end
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
mannequin.Parent = workspace
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
local tool = nil
local accoutrements = {}
for _, currObject in pairs(bothAssets) do
if currObject:IsA("BasePart") then
local existingBodyPart = mannequin:FindFirstChild(currObject.Name)
if existingBodyPart ~= nil then
existingBodyPart:Destroy()
end
end
if currObject:IsA("Tool") then
if useR15 then
tool = currObject
else
mannequin.Torso["Right Shoulder"].CurrentAngle = math.rad(90)
currObject.Parent = mannequin
end
elseif currObject:IsA("DataModelMesh") then
local headMesh = mannequin.Head:FindFirstChild("Mesh")
if headMesh then
headMesh:Destroy()
end
currObject.Parent = mannequin.Head
elseif currObject:IsA("Decal") then
local face = mannequin.Head:FindFirstChild("face")
if face then
face:Destroy()
end
currObject.Parent = mannequin.Head
elseif currObject:IsA("Accoutrement") then
table.insert(accoutrements, currObject)
else
currObject.Parent = mannequin
end
end
local textureUrls = split(customTextureUrls, ";")
for _, url in pairs(textureUrls) do
local obj = game:GetObjects(url)[1]
if obj:IsA("Shirt") then
-- Don't add a texture Shirt if package already has a Shirt
if not mannequin:FindFirstChildOfClass("Shirt") then
obj.Parent = mannequin
end
elseif obj:IsA("Pants") then
-- Don't add a texture Pants if package already has a Pants
if not mannequin:FindFirstChildOfClass("Pants") then
obj.Parent = mannequin
end
else
obj.Parent = mannequin
end
end
ThumbnailGenerator:AddProfilingCheckpoint("CustomUrlsLoaded")
local function buildJoint(parentAttachment, partForJointAttachment)
local jointName = parentAttachment.Name:gsub("RigAttachment", "")
local motor = partForJointAttachment.Parent:FindFirstChild(jointName)
if not motor then
motor = Instance.new("Motor6D")
end
motor.Name = jointName
motor.Part0 = parentAttachment.Parent
motor.Part1 = partForJointAttachment.Parent
motor.C0 = parentAttachment.CFrame
motor.C1 = partForJointAttachment.CFrame
motor.Parent = partForJointAttachment.Parent
end
-- Builds an R15 rig from the attachments in the parts
local function buildRigFromAttachments(currentPart, lastPart)
local validSiblings = {}
for _, sibling in pairs(currentPart.Parent:GetChildren()) do
-- Don't find matching attachment in the current part being processed.
-- Don't visit the last part visited again, this would cause an infinite loop.
if sibling:IsA("BasePart") and sibling ~= currentPart and sibling ~= lastPart then
table.insert(validSiblings, sibling)
end
end
local function processRigAttachment(attachment)
for _, sibling in pairs(validSiblings) do
local matchingAttachment = sibling:FindFirstChild(attachment.Name)
if matchingAttachment then
buildJoint(attachment, matchingAttachment)
buildRigFromAttachments(matchingAttachment.Parent, currentPart)
end
end
end
for _, object in pairs(currentPart:GetChildren()) do
if object:IsA("Attachment") and string.find(object.Name, "RigAttachment") then
processRigAttachment(object)
end
end
end
local function getJointBetween(part0, part1)
for _, obj in pairs(part1:GetChildren()) do
if obj:IsA("Motor6D") and obj.Part0 == part0 then
return obj
end
end
end
local function applyR15ToolPose(rig)
local upperTorso = rig:FindFirstChild("UpperTorso")
local rightUpperArm = rig:FindFirstChild("RightUpperArm")
if upperTorso and rightUpperArm then
local rightShoulderJoint = getJointBetween(upperTorso, rightUpperArm)
if rightShoulderJoint then
rightShoulderJoint.C1 = rightShoulderJoint.C1 * CFrame.new(0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 1, 0):inverse()
end
end
end
-- Applies the middle keyframe of a pose to a given character.
local function applyPoseToCharacter(character, poseAnimId)
local poseKeyframSequence = game:GetService("KeyframeSequenceProvider"):GetKeyframeSequence(poseAnimId)
local keyframes = poseKeyframSequence:GetKeyframes()
local poseKeyframe = keyframes[math.max(1, math.floor(#keyframes/2))]
local function recurApplyPoses(parentPose, poseObject)
if parentPose then
local joint = getJointBetween(character[parentPose.Name], character[poseObject.Name])
if joint then
joint.C1 = joint.C1 * poseObject.CFrame:inverse()
end
end
for _, subPose in pairs(poseObject:GetSubPoses()) do
recurApplyPoses(poseObject, subPose)
end
end
for _, poseObj in pairs(poseKeyframe:GetPoses()) do
recurApplyPoses(nil, poseObj)
end
end
if useR15 then
-- Build R15 rig
local humanoidRootPart = mannequin:WaitForChild("HumanoidRootPart")
humanoidRootPart.CFrame = CFrame.new(Vector3.new(0, 5, 0)) * CFrame.Angles(0, math.pi, 0)
humanoidRootPart.Anchored = true
buildRigFromAttachments(humanoidRootPart)
if tool then
applyR15ToolPose(mannequin)
local hand = mannequin:FindFirstChild("RightHand")
local handle = tool:FindFirstChild("Handle")
if hand and handle then
local handGrip = hand:FindFirstChild("RightGripAttachment")
if handGrip then
handle.CFrame = hand.CFrame * handGrip.CFrame * tool.Grip:inverse()
end
end
tool.Parent = mannequin
elseif poseAnimationId then
applyPoseToCharacter(mannequin, poseAnimationId)
end
end
local function findFirstMatchingAttachment(model, name)
for _, child in pairs(model:GetChildren()) do
if child:IsA("Attachment") and child.Name == name then
return child
elseif not child:IsA("Accoutrement") and not child:IsA("Tool") then
local foundAttachment = findFirstMatchingAttachment(child, name)
if foundAttachment then
return foundAttachment
end
end
end
end
for _, accoutrement in pairs(accoutrements) do
local handle = accoutrement:FindFirstChild("Handle")
if handle then
local accoutrementAttachment = handle:FindFirstChildOfClass("Attachment")
local characterAttachment = nil
if accoutrementAttachment then
characterAttachment = findFirstMatchingAttachment(mannequin, accoutrementAttachment.Name)
end
local attachmentPart
if characterAttachment then
attachmentPart = characterAttachment.Parent
else
attachmentPart = mannequin:FindFirstChild("Head")
end
local attachmentCFrame
if characterAttachment then
attachmentCFrame = characterAttachment.CFrame
else
attachmentCFrame = CFrame.new(0, 0.5, 0)
end
local hatCFrame
if accoutrementAttachment then
hatCFrame = accoutrementAttachment.CFrame
else
hatCFrame = accoutrement.AttachmentPoint
end
handle.CFrame = attachmentPart.CFrame * attachmentCFrame * hatCFrame:inverse()
handle.Anchored = true
handle.Parent = mannequin
end
end
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,39 @@
-- Pants v1.0.2
local assetUrl, fileExtension, x, y, baseUrl, mannequinId = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local mannequin = game:GetObjects(baseUrl.. "/asset/?id=" .. tostring(mannequinId))[1]
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
mannequin.Parent = workspace
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
local pants = game:GetObjects(assetUrl)[1]
pants.Parent = mannequin
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
local DFFlagThrowErrorWhenRequestedURLFailed = settings():GetFFlag("ThrowErrorWhenRequestedURLFailed")
if DFFlagThrowErrorWhenRequestedURLFailed then
local ContentProvider = game:GetService("ContentProvider")
local failedRequests = ContentProvider:GetFailedRequests()
if #failedRequests > 0 then
local failedRequestString = "Asset failed to be requested:"
for _,failedString in pairs(failedRequests) do
failedRequestString = failedRequestString.." "..failedString
end
error(failedRequestString)
end
end
return result, requestedUrls
@@ -0,0 +1,28 @@
-- Place v1.0.2
local assetUrl, fileExtension, x, y, baseUrl, universeId = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
if universeId ~= nil then
pcall(function() game:SetUniverseId(universeId) end)
end
game:GetService("UserInputService").MouseIconEnabled = false
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("StarterGui").ShowDevelopmentGui = false
game:Load(assetUrl)
ThumbnailGenerator:AddProfilingCheckpoint("GameLoaded")
-- Do this after again loading the place file to ensure that these values aren't changed when the place file is loaded.
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("StarterGui").ShowDevelopmentGui = false
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] false)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
return result, requestedUrls
@@ -0,0 +1,21 @@
-- Place Validation v1.0.0
assetUrl, baseUrl = ...
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("StarterGui").ShowDevelopmentGui = false
local success, message = pcall(function()
game:Load(assetUrl)
end)
if not success then
return message
end
-- Do this after again loading the place file to ensure that these values aren't changed when the place file is loaded.
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("StarterGui").ShowDevelopmentGui = false
return true
@@ -0,0 +1,39 @@
-- Shirt v1.0.2
local assetUrl, fileExtension, x, y, baseUrl, mannequinId = ...
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
game:GetService("ScriptContext").ScriptsDisabled = true
game:GetService("UserInputService").MouseIconEnabled = false
local mannequin = game:GetObjects(baseUrl.. "/asset/?id=" .. tostring(mannequinId))[1]
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
mannequin.Parent = workspace
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
local shirt = game:GetObjects(assetUrl)[1]
shirt.Parent = mannequin
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
local DFFlagThrowErrorWhenRequestedURLFailed = settings():GetFFlag("ThrowErrorWhenRequestedURLFailed")
if DFFlagThrowErrorWhenRequestedURLFailed then
local ContentProvider = game:GetService("ContentProvider")
local failedRequests = ContentProvider:GetFailedRequests()
if #failedRequests > 0 then
local failedRequestString = "Asset failed to be requested:"
for _,failedString in pairs(failedRequests) do
failedRequestString = failedRequestString.." "..failedString
end
error(failedRequestString)
end
end
return result, requestedUrls
@@ -0,0 +1,128 @@
local BundleLoader = {}
local AssetService = game:GetService("AssetService")
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
local MannequinUtility = require(ThumbnailGenerator:GetThumbnailModule("MannequinUtility"))
local ScaleUtility = require(ThumbnailGenerator:GetThumbnailModule("ScaleUtility"))
local ARTIST_INTENT_FOLDER = "R15ArtistIntent"
local ASSET_URL = "asset/?id="
local function constructAssetUrl(baseUrl, assetId)
return baseUrl ..ASSET_URL.. tostring(assetId)
end
function BundleLoader.LoadBundleAssets(baseUrl, bundleId)
local bundleInfo = AssetService:GetBundleDetailsSync(bundleId)
local contentIdsList = {}
for _, itemInfo in pairs(bundleInfo.Items) do
if itemInfo.Type == "Asset" then
local assetId = itemInfo.Id
local assetUrl = constructAssetUrl(baseUrl, assetId)
contentIdsList[#contentIdsList + 1] = assetUrl
end
end
local objectsList = game:GetObjectsList(contentIdsList)
local results = {}
for _, objects in pairs(objectsList) do
local assetFolder = Instance.new("Folder")
for _, object in pairs(objects) do
object.Parent = assetFolder
end
results[#results + 1] = assetFolder
end
return results
end
local function addPartsToCharacter(character, folder)
for _, part in pairs(folder:GetChildren()) do
local existingPart = character:FindFirstChild(part.Name)
part.Parent = character
if existingPart then
existingPart:Destroy()
end
end
end
local function addMeshHeadToCharacter(character, mesh)
local head = character:FindFirstChild("Head")
if not head then
return
end
local existingMesh = head:FindFirstChild("Mesh")
if existingMesh then
existingMesh:Destroy()
end
for _, child in pairs(mesh:GetChildren()) do
if child:IsA("Vector3Value") and string.find(child.Name, "Attachment") then
local attachment = head:FindFirstChild(child.Name)
if not attachment then
attachment = Instance.new("Attachment")
end
attachment.Name = child.Name
attachment.Position = child.Value
attachment.Parent = head
end
end
mesh.Parent = head
end
local function addFaceToCharacter(character, face)
local head = character:FindFirstChild("Head")
if not head then
return
end
local existingFace = head:FindFirstChild("face")
if existingFace then
existingFace:Destroy()
end
face.Parent = head
end
function BundleLoader.LoadBundleCharacter(baseUrl, bundleId)
local bundleAssets = BundleLoader.LoadBundleAssets(baseUrl, bundleId)
local character = MannequinUtility.LoadR15Mannequin()
local scaleType = ScaleUtility.GetObjectsScaleType(bundleAssets)
for _, loadedAsset in pairs(bundleAssets) do
for _, item in pairs(loadedAsset:GetChildren()) do
if item:IsA("Folder") and item.Name == ARTIST_INTENT_FOLDER then
addPartsToCharacter(character, item)
elseif not item:IsA("Folder") then
if item:IsA("DataModelMesh") then
addMeshHeadToCharacter(character, item)
elseif item:IsA("Decal") then
addFaceToCharacter(character, item)
else
item.Parent = character
end
end
end
end
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
ScaleUtility.CreateProportionScaleValues(humanoid, scaleType)
humanoid:BuildRigFromAttachments()
end
return character
end
return BundleLoader
@@ -0,0 +1,75 @@
-- Utility function for focusing on a selection of parts in a thumbnail
local FLOAT_MAX = math.huge
-- 10% tolerance for meshes that are bigger than the part which contains them
local MESH_SIZE_TOLERANCE_MULTIPLIER = 1.1
local function addToBounds(cornerPosition, focusExtentsOut)
focusExtentsOut["minx"] = math.min(focusExtentsOut["minx"], cornerPosition.x)
focusExtentsOut["miny"] = math.min(focusExtentsOut["miny"], cornerPosition.y)
focusExtentsOut["minz"] = math.min(focusExtentsOut["minz"], cornerPosition.z)
focusExtentsOut["maxx"] = math.max(focusExtentsOut["maxx"], cornerPosition.x)
focusExtentsOut["maxy"] = math.max(focusExtentsOut["maxy"], cornerPosition.y)
focusExtentsOut["maxz"] = math.max(focusExtentsOut["maxz"], cornerPosition.z)
end
local function addCornerToBounds(partCFrame, cornerSelect, halfPartSize, focusExtentsOut)
local cornerPositionLocal = cornerSelect * halfPartSize
local cornerPositionWorld = partCFrame * cornerPositionLocal
addToBounds(cornerPositionWorld, focusExtentsOut)
end
-- Adds a tolerance for meshes in parts
local function getPartSizeBounds(part)
local mesh = part:FindFirstChildWhichIsA("DataModelMesh")
if not mesh then
return part.Size
end
return part.Size * MESH_SIZE_TOLERANCE_MULTIPLIER
end
local CORNERS = {
Vector3.new( 1, 1, 1),
Vector3.new( 1, 1, -1),
Vector3.new( 1, -1, 1),
Vector3.new( 1, -1, -1),
Vector3.new(-1, 1, 1),
Vector3.new(-1, 1, -1),
Vector3.new(-1, -1, 1),
Vector3.new(-1, -1, -1),
}
local function CreateExtentsMinMax(focusParts)
local focusOnExtents = {
minx = FLOAT_MAX,
miny = FLOAT_MAX,
minz = FLOAT_MAX,
maxx = -FLOAT_MAX,
maxy = -FLOAT_MAX,
maxz = -FLOAT_MAX
}
-- Expand focusOnExtents to bound all the parts in the focusParts table
for _, focusPart in ipairs(focusParts) do
if focusPart:IsA("BasePart") then
local partSize = getPartSizeBounds(focusPart)
local halfPartSize = partSize * 0.5
local partCFrame = focusPart.CFrame
for _, corner in ipairs(CORNERS) do
addCornerToBounds(partCFrame, corner, halfPartSize, focusOnExtents)
end
end
end
local extentsMinMax = {
Vector3.new(focusOnExtents["minx"], focusOnExtents["miny"], focusOnExtents["minz"]),
Vector3.new(focusOnExtents["maxx"], focusOnExtents["maxy"], focusOnExtents["maxz"])
}
return extentsMinMax
end
return CreateExtentsMinMax
@@ -0,0 +1,51 @@
-- Utility module for functions related to loading mannequins for thumbnails
local MannequinUtility = {}
local InsertService = game:GetService("InsertService")
local R6_MANNEQUIN_CONTENT_ID = "rbxasset://models/Thumbnails/Mannequins/R6.rbxmx"
local R15_MANNEQUIN_CONTENT_ID = "rbxasset://models/Thumbnails/Mannequins/R15.rbxm"
local RTHRO_MANNEQUIN_CONTENT_ID = "rbxasset://models/Thumbnails/Mannequins/Rthro.rbxm"
local function loadMannequin(contentId)
local mannequin = InsertService:LoadLocalAsset(contentId)
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
mannequin.Parent = workspace
return mannequin
end
function MannequinUtility.LoadR15Mannequin()
return loadMannequin(R15_MANNEQUIN_CONTENT_ID)
end
function MannequinUtility.LoadR6Mannequin()
return loadMannequin(R6_MANNEQUIN_CONTENT_ID)
end
function MannequinUtility.LoadRthroMannequin()
return loadMannequin(RTHRO_MANNEQUIN_CONTENT_ID)
end
function MannequinUtility.LoadMannequinForScaleType(scaleType)
if scaleType == "Classic" then
return MannequinUtility.LoadR15Mannequin()
else
return MannequinUtility.LoadRthroMannequin()
end
end
function MannequinUtility.RotateMannequin(mannequin, cframe)
local humanoidRootPart = mannequin:FindFirstChild("HumanoidRootPart")
if not humanoidRootPart then
return
end
local rootRigAttachment = humanoidRootPart:FindFirstChild("RootRigAttachment")
if rootRigAttachment then
rootRigAttachment.CFrame = rootRigAttachment.CFrame * cframe
end
end
return MannequinUtility
@@ -0,0 +1,63 @@
-- Utility module for managing the scale of mannequins and items in thumbnails
local ScaleUtility = {}
local CLASSIC_SCALE = "Classic"
local RTHRO_NORMAL = "ProportionsNormal"
local RTHRO_SLENDER = "ProportionsSlender"
local function getPartScaleType(part)
local value = part:FindFirstChild("AvatarPartScaleType")
if value then
return value.Value
end
return CLASSIC_SCALE
end
function ScaleUtility.GetScaleTypeForAccessory(accessory)
local handle = accessory:FindFirstChild("Handle")
if not handle then
return CLASSIC_SCALE
end
return getPartScaleType(handle)
end
function ScaleUtility.GetObjectsScaleType(objects)
for _, object in pairs(objects) do
local partScaleType = object:FindFirstChild("AvatarPartScaleType", --[[ recursive = ]] true)
if partScaleType then
return partScaleType.Value
end
end
end
local function getOrCreateScaleValue(humanoid, name, default)
local scaleValue = humanoid:FindFirstChild(name)
if scaleValue then
return scaleValue
end
scaleValue = Instance.new("NumberValue")
scaleValue.Name = name
scaleValue.Value = default
scaleValue.Parent = humanoid
return scaleValue
end
function ScaleUtility.CreateProportionScaleValues(humanoid, scaleType)
local bodyTypeValue = getOrCreateScaleValue(humanoid, "BodyTypeScale", 0)
local bodyProportionValue = getOrCreateScaleValue(humanoid, "BodyProportionScale", 0)
if scaleType == RTHRO_NORMAL then
bodyTypeValue.Value = 1
bodyProportionValue.Value = 0
elseif scaleType == RTHRO_SLENDER then
bodyTypeValue.Value = 1
bodyProportionValue.Value = 1
end
end
return ScaleUtility