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,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